/*------------------------------------------------------------------
  WashTrack — semantic design tokens
  -----------------------------------------------------------------
  Loaded AFTER the SkyDash theme, so these declarations win at equal
  specificity without touching the 31,000-line compiled stylesheet.

  WHY THIS FILE EXISTS
  The theme's SCSS source (public/scss) no longer builds the CSS that
  ships: it still defines $primary as SkyDash purple #E85A16, while the
  compiled stylesheet carried the old brand orange in 150 places (now blue #E85A16).
  Recompiling would revert the brand and break the design freeze, so the
  palette is centralised here instead. See docs/DECISIONS.md D7.

  RULE: no hex value in a Blade file, ever. Reference a token.
-------------------------------------------------------------------*/

:root {
    /* Brand — lifted from the compiled theme so nothing shifts visually */
    --st-brand: #E85A16;
    --st-brand-dark: #1A1A1A;

    /* Semantic status palette — the theme's own values, now named */
    --st-success: #57B657;
    --st-info: #248AFD;
    --st-warning: #FFC100;
    --st-danger: #FF4747;
    --st-muted: #a3a4a5;

    /* Surfaces */
    --st-body-bg: #f3f6fb;
    --st-card-bg: #ffffff;
    --st-border: #e5eaf3;

    /* Text */
    --st-text: #0f172a;
    --st-text-muted: #64748b;

    /* Form controls — one geometry for every input, select and textarea.
       See the "Form controls" block below for why these exist. */
    --st-field-h: 2.75rem;            /* 44px — the touch-target floor */
    --st-field-h-sm: 2.25rem;         /* 36px — dense filter bars */
    --st-field-pad-y: 0.5rem;
    --st-field-pad-x: 0.875rem;
    --st-field-radius: 0.5rem;        /* matches the card radius */
    --st-field-border: #d9e1e4;       /* calmer than the theme's #CED4DA */
    --st-field-border-hover: #b9c6ca;
    --st-field-bg: #ffffff;
    --st-field-bg-readonly: #f6f9fa;
    --st-field-placeholder: #9aa4a9;
    --st-field-ring: rgba(232, 90, 22, 0.18);
    --st-field-ring-danger: rgba(255, 71, 71, 0.18);

    /* Claim strip — the success palette at surface strength. Derived from
       --st-success rather than invented, so a tenant recolouring success
       recolours the claim with it. */
    --st-success-surface: rgba(87, 182, 87, 0.08);
    --st-success-rule: rgba(87, 182, 87, 0.25);
    --st-success-dark: #4aa14a;

    /* Chart series — replaces the hardcoded hex in the dashboard legends */
    --st-chart-1: #E85A16;
    --st-chart-2: #1A1A1A;
    --st-chart-3: #57B657;
    --st-chart-4: #248AFD;
    --st-chart-5: #FFC100;
    --st-chart-6: #a3a4a5;
}

/*------------------------------------------------------------------
  Badges
  The theme renders badges as outline pills (coloured text + border on a
  transparent ground). That look is preserved exactly — only the source
  of the colour changes.
-------------------------------------------------------------------*/

.badge-primary {
    color: var(--st-brand);
    border: 1px solid var(--st-brand);
}

.badge-secondary,
.badge-muted {
    color: var(--st-muted);
    border: 1px solid var(--st-muted);
}

.badge-success {
    color: var(--st-success);
    border: 1px solid var(--st-success);
}

.badge-info {
    color: var(--st-info);
    border: 1px solid var(--st-info);
}

.badge-warning {
    color: var(--st-warning);
    border: 1px solid var(--st-warning);
}

.badge-danger {
    color: var(--st-danger);
    border: 1px solid var(--st-danger);
}

/*------------------------------------------------------------------
  Destructive actions
  Delete confirmations and row delete buttons were styled btn-primary
  throughout the prototype, which reads as "proceed" on an irreversible
  action. They are danger now.
-------------------------------------------------------------------*/

.btn-danger,
.btn-danger:focus {
    background: var(--st-danger);
    border-color: var(--st-danger);
    color: #fff;
}

.btn-danger:hover {
    background: #e63c3c;
    border-color: #e63c3c;
    color: #fff;
}

.btn-outline-danger {
    background: transparent;
    border: 1px solid var(--st-danger);
    color: var(--st-danger);
}

.btn-outline-danger:hover {
    background: var(--st-danger);
    color: #fff;
}

.modal-header.bg-danger {
    background: var(--st-danger) !important;
}

/*------------------------------------------------------------------
  Status text and dots — for places where a badge is too heavy
-------------------------------------------------------------------*/

.text-status-success { color: var(--st-success) !important; }
.text-status-warning { color: var(--st-warning) !important; }
.text-status-danger  { color: var(--st-danger)  !important; }
.text-status-info    { color: var(--st-info)    !important; }
.text-status-muted   { color: var(--st-muted)   !important; }

.status-dot {
    display: inline-block;
    width: .5rem;
    height: .5rem;
    border-radius: 50%;
    margin-inline-end: .4rem;
    vertical-align: middle;
}

.status-dot-success { background: var(--st-success); }
.status-dot-warning { background: var(--st-warning); }
.status-dot-danger  { background: var(--st-danger); }
.status-dot-info    { background: var(--st-info); }
.status-dot-muted   { background: var(--st-muted); }

/*------------------------------------------------------------------
  Empty states — the prototype had none, so every table that ran out of
  rows simply showed nothing.
-------------------------------------------------------------------*/

.st-empty-state {
    padding: 3rem 1rem;
    text-align: center;
    color: var(--st-text-muted);
}

.st-empty-state > .mdi {
    font-size: 3rem;
    line-height: 1;
    color: #ffff;
    display: block;
    margin-bottom: .75rem;
}

.st-empty-state-title {
    font-weight: 600;
    color: var(--st-text);
    margin-bottom: .25rem;
}

/*------------------------------------------------------------------
  Locked feature (brief §8.3)
  Modules the tenant's plan does not include are shown locked, never as
  a broken link.
-------------------------------------------------------------------*/

.nav-item .nav-link.is-locked,
.st-locked {
    opacity: .55;
    cursor: not-allowed;
}

.st-lock-icon {
    font-size: .8rem;
    margin-inline-start: .35rem;
    color: var(--st-muted);
}

/*------------------------------------------------------------------
  Impersonation banner (DECISIONS.md D14)
  Deliberately loud and unmissable: acting as another user must never
  be something you forget you are doing.
-------------------------------------------------------------------*/

.st-impersonation-banner {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 1080;
    background: var(--st-warning);
    color: #1f1f1f;
    padding: .5rem 1rem;
    font-weight: 600;
    text-align: center;
    box-shadow: 0 2px 6px rgba(0, 0, 0, .2);
}

body.has-impersonation-banner { padding-block-start: 2.5rem; }

/*------------------------------------------------------------------
  RTL (brief §7)
  The layout sets dir="rtl" on <html>; these correct the handful of
  places the theme hardcodes a side.
-------------------------------------------------------------------*/

[dir="rtl"] .navbar .navbar-brand-wrapper { text-align: right; }
[dir="rtl"] .sidebar .nav .nav-item .nav-link .menu-icon { margin-right: 0; margin-left: 1rem; }
[dir="rtl"] .sidebar .nav .nav-item .nav-link .menu-arrow { margin-left: 0; margin-right: auto; }
[dir="rtl"] .float-end { float: left !important; }
[dir="rtl"] .text-sm-left { text-align: right !important; }
[dir="rtl"] .me-2 { margin-right: 0 !important; margin-left: .5rem !important; }
[dir="rtl"] .ms-2 { margin-left: 0 !important; margin-right: .5rem !important; }

/* Bootstrap paints the select chevron on the right via background-position,
   which lands on top of the text once the flow is mirrored. */
[dir="rtl"] .form-select {
    background-position: left .75rem center;
    padding-right: .75rem;
    padding-left: 2.25rem;
}

/* The search input group's icon sits on the wrong side of the field in RTL. */
[dir="rtl"] .input-group > .input-group-text:first-child {
    border-radius: 0 .25rem .25rem 0;
}
[dir="rtl"] .input-group > .form-control:last-child {
    border-radius: .25rem 0 0 .25rem;
}

/* ------------------------------------------------------------------ checkboxes
   THE TICK WAS INVISIBLE ON EVERY CHECKBOX IN THE APP.

   The purchased theme ships its own checkbox: it sets `opacity: 0` on any
   input inside `.form-check .form-check-label` (style.css ~23470) and expects
   a sibling `<span class="input-helper">` to draw the box and the tick with
   ::before / ::after.

   This app writes plain Bootstrap 5 markup and has no `.input-helper`
   anywhere — none of its checkboxes carry one. So the native control was
   hidden and nothing drew a replacement: clicking still toggled the value,
   but the box never looked checked, which reads as a dead control.

   Rather than add a helper span to every checkbox in the app, the real
   control is put back on screen. The theme already reserves a 1.75rem gutter
   on `.form-check-label` for its custom box, so the native one is placed in
   exactly that space and the row is not re-laid out.

   Bootstrap's own `:checked` rules are untouched, so the tick SVG and the
   filled background come back with it. */
.form-check .form-check-label input.form-check-input {
    position: absolute;
    top: 0;
    left: 0;
    width: 18px;
    height: 18px;
    margin: 0;
    opacity: 1;
    filter: none;
}

/* The theme's mirrored versions of these key off a `.rtl` BODY CLASS that this
   app never sets — it marks direction with dir="rtl" on <html> — so under
   Arabic the gutter and the box both stayed on the left, and the box landed on
   top of the label text. Restated here against the attribute actually used. */
[dir="rtl"] .form-check .form-check-label {
    margin-left: 0;
    margin-right: 1.75rem;
}

[dir="rtl"] .form-check .form-check-label input.form-check-input {
    left: auto;
    right: 0;
}

/* Bootstrap's checked state is its own blue (#0d6efd). Left alone the tick
   would be Bootstrap's own off-brand blue — and the theme's
   custom checkbox, the thing being replaced here, was brand-coloured. */
.form-check-input:checked {
    background-color: var(--st-brand);
    border-color: var(--st-brand);
}

.form-check-input:focus {
    border-color: var(--st-brand);
    box-shadow: 0 0 0 0.2rem rgba(232, 90, 22, 0.25);
}

/*------------------------------------------------------------------
  Form controls
  -----------------------------------------------------------------
  THE THEME GAVE EVERY KIND OF FIELD A DIFFERENT SHAPE. Measured on the
  branch form before this block existed:

      text input        57px tall,  border #CED4DA,  line-height 14px
      file input        52px tall,  border #CED4DA,  line-height 14px
      select2 dropdown  34px tall,  border #CCCCCC,  line-height 20px

  So a text box and the dropdown beside it in the same row differed by
  23px, in three different greys. That mismatch — not the colours — is
  what made the forms look unfinished.

  Two other faults came out of the same measurement:

  1. LINE-HEIGHT 14px ON 14px TEXT. Zero leading. In a textarea the lines
     physically touch and descenders (g, y, p) clip against the row
     below. It is the worst thing here, and it was invisible in a
     one-line input — which is presumably where it was checked.

  2. 18px OF VERTICAL PADDING doing the work line-height should have done
     — a 57px box holding 14px of text, so the field looked bloated and
     the text still sat wrong inside it.

  Everything below is one shape: a 44px control (the touch-target floor
  for a screen used on a tablet at a wash bay), 1.5 line-height, one
  border, one radius, and a brand-coloured focus ring in place of
  Bootstrap blue.

  Only colour, border and shadow are transitioned. Never height or
  padding: that would reflow the row on every focus.
-------------------------------------------------------------------*/

.form-control,
.form-select,
.select2-container--default .select2-selection--single {
    min-height: var(--st-field-h);
    padding: var(--st-field-pad-y) var(--st-field-pad-x);
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--st-text);
    background-color: var(--st-field-bg);
    border: 1px solid var(--st-field-border);
    border-radius: var(--st-field-radius);
    box-shadow: none;
    transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out, background-color .15s ease-in-out;
}

/* A textarea is the one control that must NOT be pinned to a single row. */
textarea.form-control {
    min-height: calc(var(--st-field-h) * 2);
    line-height: 1.6;
}

/* The chevron needs room, and the theme padding no longer applies. */
.form-select {
    padding-right: 2.25rem;
    background-position: right .875rem center;
}

[dir="rtl"] .form-select {
    padding-right: var(--st-field-pad-x);
    padding-left: 2.25rem;
    background-position: left .875rem center;
}

/* ---------------------------------------------------------------- states */

/* `:not(:focus)` is load-bearing, not tidiness. These selectors carry three
   pseudo-classes and so outrank the plain `.form-control:focus` below — a
   focused field with the cursor still resting on it had its brand border
   painted back to hover grey, leaving the ring floating on a grey box. */
.form-control:hover:not(:focus):not(:disabled):not([readonly]),
.form-select:hover:not(:focus):not(:disabled) {
    border-color: var(--st-field-border-hover);
}

/* A visible ring, in the brand colour rather than Bootstrap blue — the
   same mismatch the checkboxes had. Never `outline: 0` with nothing to
   replace it: keyboard users lose their place entirely. */
.form-control:focus,
.form-select:focus,
.select2-container--default.select2-container--focus .select2-selection--single,
.select2-container--default.select2-container--open .select2-selection--single {
    border-color: var(--st-brand);
    box-shadow: 0 0 0 3px var(--st-field-ring);
    outline: 0;
}

/* Placeholders are not labels. They read as secondary so they are never
   mistaken for a value the field already holds. */
.form-control::placeholder {
    color: var(--st-field-placeholder);
    opacity: 1;
}

/* Disabled and read-only must not look alike: one is unavailable, the
   other is simply not this user's to change. */
.form-control:disabled,
.form-select:disabled {
    background-color: var(--st-field-bg-readonly);
    color: var(--st-text-muted);
    opacity: .6;
    cursor: not-allowed;
}

.form-control[readonly]:not(:disabled) {
    background-color: var(--st-field-bg-readonly);
    color: var(--st-text);
    opacity: 1;
    cursor: default;
}

.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--st-danger);
}

.form-control.is-invalid:focus,
.form-select.is-invalid:focus {
    border-color: var(--st-danger);
    box-shadow: 0 0 0 3px var(--st-field-ring-danger);
}

/* ---------------------------------------------------------------- groups */

/* The addon has to share the field height and radius or the join shows. */
.input-group-text {
    min-height: var(--st-field-h);
    padding: var(--st-field-pad-y) var(--st-field-pad-x);
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--st-text-muted);
    background-color: var(--st-field-bg-readonly);
    border: 1px solid var(--st-field-border);
    border-radius: var(--st-field-radius);
}

.input-group > :not(:first-child):not(.dropdown-menu) {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

.input-group > :not(:last-child):not(.dropdown-menu) {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

[dir="rtl"] .input-group > :not(:first-child):not(.dropdown-menu) {
    border-radius: var(--st-field-radius) 0 0 var(--st-field-radius);
}

[dir="rtl"] .input-group > :not(:last-child):not(.dropdown-menu) {
    border-radius: 0 var(--st-field-radius) var(--st-field-radius) 0;
}

/* The focused field has to sit above its neighbour or the ring is clipped. */
.input-group > .form-control:focus,
.input-group > .form-select:focus {
    z-index: 3;
}

/* ---------------------------------------------------------------- select2 */

/* select2 draws its own box, and it was the 34px outlier next to a 44px
   input.

   THE THEME NAME MATTERS. speedtrack.js:150 initialises with
   `{ theme: 'bootstrap' }`, so the wrapper is `.select2-container--bootstrap`
   — NOT the `--default` every select2 snippet online targets. Styling
   `--default` here matched nothing at all. Both are listed so a dropdown
   initialised either way lands on the same shape.

   `height: auto` is required, not merely tidy: select2 ships
   `.select2-container .select2-selection--single { height: 28px }` as an
   explicit height, and min-height alone leaves the box short. */
body .select2-container--bootstrap .select2-selection--single,
body .select2-container--default .select2-selection--single {
    display: flex;
    align-items: center;
    height: auto;
    min-height: var(--st-field-h);
    padding: var(--st-field-pad-y) var(--st-field-pad-x);
    font-size: 0.875rem;
    border: 1px solid var(--st-field-border);
    border-radius: var(--st-field-radius);
    background-color: var(--st-field-bg);
    box-shadow: none;
}

body .select2-container--bootstrap.select2-container--focus .select2-selection--single,
body .select2-container--bootstrap.select2-container--open .select2-selection--single,
body .select2-container--default.select2-container--focus .select2-selection--single,
body .select2-container--default.select2-container--open .select2-selection--single {
    border-color: var(--st-brand);
    box-shadow: 0 0 0 3px var(--st-field-ring);
}

body .select2-container--bootstrap .select2-selection--single .select2-selection__rendered,
body .select2-container--default .select2-selection--single .select2-selection__rendered {
    padding-left: 0;
    padding-right: 1.25rem;
    line-height: 1.5;
    color: var(--st-text);
}

[dir="rtl"] body .select2-container--bootstrap .select2-selection--single .select2-selection__rendered,
[dir="rtl"] body .select2-container--default .select2-selection--single .select2-selection__rendered {
    padding-right: 0;
    padding-left: 1.25rem;
}

body .select2-container--bootstrap .select2-selection--single .select2-selection__arrow,
body .select2-container--default .select2-selection--single .select2-selection__arrow {
    height: 100%;
    top: 0;
    right: .5rem;
}

[dir="rtl"] body .select2-container--bootstrap .select2-selection--single .select2-selection__arrow,
[dir="rtl"] body .select2-container--default .select2-selection--single .select2-selection__arrow {
    right: auto;
    left: .5rem;
}

body .select2-container--bootstrap .select2-selection--single .select2-selection__placeholder,
body .select2-container--default .select2-selection--single .select2-selection__placeholder {
    color: var(--st-field-placeholder);
}

.select2-dropdown {
    border-color: var(--st-field-border);
    border-radius: var(--st-field-radius);
}

body .select2-container--bootstrap .select2-results__option--highlighted[aria-selected],
body .select2-container--default .select2-results__option--highlighted[aria-selected] {
    background-color: var(--st-brand);
}

/* ---------------------------------------------------------------- small */

/* The dense variant used in filter bars. Still 36px, which stays clickable
   without pretending a filter row is a primary touch target. */
.form-control-sm,
.form-select-sm {
    min-height: var(--st-field-h-sm);
    padding: .3125rem .625rem;
    font-size: 0.8125rem;
    line-height: 1.5;
    border-radius: .375rem;
}

.form-select-sm {
    padding-right: 2rem;
    background-position: right .625rem center;
}

[dir="rtl"] .form-select-sm {
    padding-right: .625rem;
    padding-left: 2rem;
    background-position: left .625rem center;
}

/* ---------------------------------------------------------------- labels */

.form-group > label,
.form-label {
    display: inline-block;
    margin-bottom: .375rem;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--st-text);
}

/* Helper text belongs under the field it explains, close enough to read as
   attached to it rather than floating above the next one. */
.form-text,
.form-group > small.text-muted {
    display: block;
    margin-top: .375rem;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--st-text-muted);
}

/* ---------------------------------------------------------------- mobile */

/* iOS ZOOMS THE WHOLE PAGE when a focused field carries text under 16px,
   and this app is used on a tablet at the bay. 16px on small screens stops
   the viewport lurching on every tap. */
@media (max-width: 767.98px) {
    .form-control,
    .form-select,
    .form-control-sm,
    .form-select-sm,
    .select2-container--default .select2-selection--single {
        font-size: 1rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .form-control,
    .form-select,
    .select2-container--default .select2-selection--single {
        transition: none;
    }
}

/*------------------------------------------------------------------
  Form controls — the two corrections the filter bars exposed
-------------------------------------------------------------------*/

/* A NATIVE <select> WAS NOT GETTING THE SHARED BORDER. The theme styles
   selects with `border: 0` plus an `outline` standing in for one, so
   `border-top-color` fell through to `currentColor` and the box read
   #858585 — a different grey again, right next to a corrected input.

   The tag is included in the selector on purpose: `select.form-select`
   outranks the plain `.form-select` the theme uses, without !important. */
select.form-select,
select.form-select-sm {
    border: 1px solid var(--st-field-border);
    outline: none;
    color: var(--st-text);
}

select.form-select:hover:not(:focus):not(:disabled),
select.form-select-sm:hover:not(:focus):not(:disabled) {
    border-color: var(--st-field-border-hover);
}

select.form-select:focus,
select.form-select-sm:focus {
    border-color: var(--st-brand);
    outline: none;
    box-shadow: 0 0 0 3px var(--st-field-ring);
}

/* AN ADDON WAS STRETCHING A SMALL FIELD BACK TO FULL HEIGHT. The search
   boxes in the filter bars sit in an .input-group with a magnifier addon;
   the addon carried the 44px floor, so the row ignored the -sm class on
   the input beside it and the bar sat taller than its own buttons.
   The addon now follows whatever size it is grouped with. */
.input-group:has(.form-control-sm) .input-group-text,
.input-group:has(.form-select-sm) .input-group-text {
    min-height: var(--st-field-h-sm);
    padding: .3125rem .625rem;
    font-size: 0.8125rem;
}

.input-group:has(.form-control-sm) > .form-control-sm,
.input-group:has(.form-select-sm) > .form-select-sm {
    min-height: var(--st-field-h-sm);
}

/*------------------------------------------------------------------
  Card bodies — contain the floated header controls
  -----------------------------------------------------------------
  `x-page-card` puts its action button and its `toolbar` slot BEFORE the
  <h4>, both floated right:

      <div class="card-body">
        <a class="float-end btn ...">        {{-- action --}}
        {{ $toolbar ?? '' }}                 {{-- often float-end too --}}
        <h4 class="card-title">…
        {{ $slot }}

  A float is out of normal flow, so it does not contribute to its
  parent's height. When the card body's remaining content is shorter
  than the floated control, the float spills past the bottom of the card
  and lands on top of whatever is laid out next — the neighbouring card
  in the same row, or the row below it. Nothing in the card body cleared
  them, so this was luck rather than design on every one of the ~30
  screens that use the component.

  A ::after clearfix rather than `overflow: hidden`: overflow would clip
  the select2 and datepicker popups that legitimately escape a card.
-------------------------------------------------------------------*/

.card > .card-body::after {
    content: "";
    display: block;
    clear: both;
}

/*------------------------------------------------------------------
  Label/value tables  (.st-kv-table)
  -----------------------------------------------------------------
  WHY THE SUBSCRIPTION CARD SPILLED OVER THE USAGE CARD.

  style.css:25784 sets `white-space: nowrap` on EVERY table cell in the
  app:

      .table th, .table td { white-space: nowrap; line-height: 1; … }

  That is defensible for wide data tables inside `.table-responsive`,
  where a row is meant to stay on one line and scroll. It is wrong for a
  two-column label/value table in a narrow card, because free text in the
  value column then cannot wrap: its min-content width becomes the whole
  sentence on one line. The subscription card's "Custom adjustment"
  reason is up to 500 characters of prose, so the table grew far wider
  than its col-lg-5 card, overflowed, and painted across the Usage card
  beside it — clipping the reason mid-word into the bargain.

  Three things are needed, and all three matter:

    - `white-space: normal` so the prose can wrap at all;
    - `table-layout: fixed` plus a label width, so the value column is
      bounded by the card rather than by its own content — without this,
      auto layout can still widen the table to fit a long single word;
    - `overflow-wrap: break-word` for the case the reason contains a URL
      or reference number with no spaces to break at.

  `line-height` is restored to 1.5 because these cells now hold wrapped
  sentences, and the theme's `line-height: 1` makes wrapped lines touch.
-------------------------------------------------------------------*/

.st-kv-table {
    table-layout: fixed;
    width: 100%;
}

.st-kv-table > tbody > tr > th,
.st-kv-table > tbody > tr > td {
    white-space: normal;
    overflow-wrap: break-word;
    line-height: 1.5;
    vertical-align: top;
    padding: .5rem .75rem;
}

/* The label column is the predictable one, so it carries the fixed share
   and the value column takes whatever the card has left. */
.st-kv-table > tbody > tr > th {
    width: 40%;
    font-weight: 500;
}

/* Flush with the card's own padding: an indented first column in a
   two-column table reads as an accident. */
.st-kv-table > tbody > tr > th:first-child {
    padding-left: 0;
}

.st-kv-table > tbody > tr > td:last-child {
    padding-right: 0;
}

[dir="rtl"] .st-kv-table > tbody > tr > th:first-child {
    padding-left: .75rem;
    padding-right: 0;
}

[dir="rtl"] .st-kv-table > tbody > tr > td:last-child {
    padding-right: .75rem;
    padding-left: 0;
}

/*------------------------------------------------------------------
  Loading feedback
  -----------------------------------------------------------------
  WHY A BAR AND A BUTTON STATE, NOT A FULL-SCREEN OVERLAY.

  Every screen here is server-rendered, so a click means a round trip
  and a blank pause. Without feedback the operator assumes the click
  missed and presses again — which on this app posts a second wash, a
  second package sale, a second punch. Preventing that double submit is
  the point; the animation is only how it is communicated.

  A dimming overlay was rejected: it hides the form the operator is
  mid-way through reading, and on a fast local network it would flash
  for 80ms on every click, which reads as a glitch rather than progress.
  A 3px bar at the top of the viewport says the same thing without
  taking the screen away.
-------------------------------------------------------------------*/

.st-progress {
    position: fixed;
    inset-block-start: 0;
    inset-inline-start: 0;
    z-index: 2000;               /* above the fixed navbar, below modals */
    height: 3px;
    width: 0;
    background: var(--st-brand);
    box-shadow: 0 0 8px rgba(232, 90, 22, .6);
    opacity: 0;
    pointer-events: none;
    transition: width .2s ease-out, opacity .2s ease-out;
}

/* Creeps toward 90% and waits there. It must never reach 100% on its
   own: a bar that completes while the page is still loading tells the
   operator it is done when it is not. The navigation itself ends it. */
.st-progress.is-active {
    opacity: 1;
    width: 90%;
    transition: width 8s cubic-bezier(.1, .8, .2, 1), opacity .15s ease-in;
}

.st-progress.is-done {
    width: 100%;
    opacity: 0;
    transition: width .15s ease-out, opacity .3s ease-out .1s;
}

/* ---------------------------------------------------------------- buttons */

/* The button keeps its size while busy. Swapping the label for a bare
   spinner makes the button jump to a different width, which moves
   whatever sits beside it — so the label stays and the spinner is
   added in front of it. */
.btn.is-busy {
    pointer-events: none;
    cursor: progress;
    opacity: .75;
}

.btn.is-busy > .st-spinner {
    display: inline-block;
    width: .85em;
    height: .85em;
    margin-inline-end: .4rem;
    vertical-align: -.1em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: st-spin .6s linear infinite;
}

/* An icon and a spinner in the same button read as two competing
   states, so the icon steps aside while it is working. */
.btn.is-busy > .mdi {
    display: none;
}

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

/* A whole panel waiting on a fetch — used by the sell screen's phone
   lookup, where the answer replaces a block rather than the page. */
.st-loading {
    position: relative;
    min-height: 3rem;
    pointer-events: none;
}

.st-loading::after {
    content: "";
    position: absolute;
    inset-block-start: .75rem;
    inset-inline-start: 50%;
    width: 1.4rem;
    height: 1.4rem;
    margin-inline-start: -.7rem;
    border: 2px solid var(--st-brand);
    border-right-color: transparent;
    border-radius: 50%;
    animation: st-spin .6s linear infinite;
}

/* MOTION IS NOT THE MESSAGE. With reduced motion the bar still appears
   and the button still locks — only the spinning stops, so the meaning
   survives for someone who cannot tolerate the animation. */
@media (prefers-reduced-motion: reduce) {
    .st-progress,
    .st-progress.is-active,
    .st-progress.is-done {
        transition: opacity .1s linear;
    }

    .st-progress.is-active { width: 100%; }

    .btn.is-busy > .st-spinner,
    .st-loading::after {
        animation: none;
    }
}

/*------------------------------------------------------------------
  Navbar togglers
  -----------------------------------------------------------------
  The theme gives .navbar-toggler `padding: 0`, which left the mobile
  burger at 35x22 — under the 44x44 minimum for a touch target, and it
  is the ONLY way to reach navigation below lg. Padding it out rather
  than growing the icon keeps the glyph the same size as the rest of
  the bar while making the hit area what a thumb needs.
-------------------------------------------------------------------*/

.navbar .navbar-menu-wrapper .navbar-toggler {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.75rem;
    min-height: 2.75rem;
}

/*------------------------------------------------------------------
  Card scanner stage
  -----------------------------------------------------------------
  html5-qrcode builds its own <video> (and a canvas it decodes from)
  inside #scanReader and sizes them itself, so this only frames what
  the library puts there: rounded, clipped, and dark behind, so the
  panel does not flash white in the moment before the camera opens.
-------------------------------------------------------------------*/

.st-scan-stage {
    position: relative;
    width: 100%;
    min-height: 12rem;
    max-height: 65vh;
    overflow: hidden;
    border-radius: var(--st-radius, 0.5rem);
    background: var(--st-scan-bg, #1b1c1f);
}

#scanReader video,
#scanReader canvas {
    width: 100% !important;
    height: auto !important;
    display: block;
}


/*------------------------------------------------------------------
  Claim strip — a loyalty reward the customer can take right now
  -----------------------------------------------------------------
  Lives on Enter Car, where the counter has seconds to notice that the
  wash in front of them is already paid for. Everything else on that
  screen is a calm white card, so this one deliberately is not: a
  success-tinted ground, a rule down the leading edge, and a button
  large enough to be hit with a thumb.

  IT BORROWS SUCCESS RATHER THAN THE BRAND. Blue is the colour of
  every primary action on the page; reusing it here would make the
  claim compete with "Record a wash" instead of standing apart from it.

  Logical properties throughout — this screen runs in Arabic too, and a
  rule pinned to the left edge would land on the wrong side in RTL.
-------------------------------------------------------------------*/

.st-claim {
    border: 1px solid var(--st-success);
    border-inline-start-width: 4px;
    border-radius: var(--st-field-radius);
    background: var(--st-success-surface);
    padding: 1rem 1.15rem;
}

.st-claim-head {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    margin-block-end: .85rem;
}

.st-claim-icon {
    font-size: 1.75rem;
    line-height: 1;
    color: var(--st-success);
}

.st-claim-title {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--st-text);
}

.st-claim-sub {
    font-size: .8125rem;
    color: var(--st-text-muted);
}

.st-claim-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* One reward per row, the button always at the trailing edge so a
   counter claiming the second of three does not have to hunt for it. */
.st-claim-item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding-block: .65rem;
    border-block-start: 1px solid var(--st-success-rule);
}

.st-claim-item:first-child {
    border-block-start: 0;
    padding-block-start: 0;
}

.st-claim-reward {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .5rem;
    min-width: 0;
}

.st-claim-name {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--st-text);
}

/* The price in points sits beside the name, not under it: at a till the
   two are read as one fact — "free wash, ten points". */
.st-claim-cost {
    font-size: .8125rem;
    font-weight: 600;
    color: var(--st-success);
    white-space: nowrap;
}

.st-claim-note {
    flex-basis: 100%;
    font-size: .8125rem;
    color: var(--st-text-muted);
}

/* Solid, not the theme's outline pill. This is the one button on the
   screen that spends something the customer owns, and it has to look
   like the answer to the question the panel just asked. */
.st-claim-btn.btn.btn-success {
    background: var(--st-success);
    border-color: var(--st-success);
    color: var(--st-card-bg);
    min-height: var(--st-field-h);
    padding-inline: 1.35rem;
    font-weight: 600;
}

.st-claim-btn.btn.btn-success:hover,
.st-claim-btn.btn.btn-success:focus {
    background: var(--st-success-dark);
    border-color: var(--st-success-dark);
    color: var(--st-card-bg);
}

/* How close the next reward is. Short and thin — it is a hint under a
   heading, not a chart. */
.st-claim-progress {
    height: .5rem;
    background: var(--st-border);
}

.st-claim-progress .progress-bar {
    background: var(--st-success);
}
