/* =============================================================================
   RumiNest360 — components (Phase 16A)

   Two halves:

   1. BOOTSTRAP RETHEME. Bootstrap 5.3.3 resolves most of its appearance through
      custom properties, and tokens.css has already re-pointed those. What is
      left here are the places where Bootstrap compiled a LITERAL value — every
      button variant, the focus ring, a checked control, the light table head —
      which no root variable can reach. Each block below exists for that reason
      and for no other.

   2. OUR OWN COMPONENTS. Classes the Razor components in Components/Shared use.
      Named for what they are, never for what they look like.

   Bootstrap itself is untouched on disk, and still 5.3.3.
   ========================================================================== */

/* =============================================================================
   1a. Buttons

   Bootstrap writes each variant's colours as literals inside .btn-<variant>,
   so a variant has to be restated to be rethemed. Only the variants the
   application actually uses are listed, plus Vuexy's tinted "label" buttons.

   Every fill that carries white text is a measured shade, not the brand hue:
   primary #267c81 (4.9:1), success/paid, warning/due, danger/over — see the
   money semantics in tokens.css. The soft coloured shadow is Vuexy's.
   ========================================================================== */
.btn {
    --bs-btn-padding-x: 1.25rem;
    --bs-btn-padding-y: 0.5rem;
    --bs-btn-font-size: var(--sm-text);
    --bs-btn-font-weight: 500;
    --bs-btn-line-height: 1.4;
    --bs-btn-border-radius: var(--sm-r-sm);
    --bs-btn-focus-box-shadow: 0 0 0 3px var(--sm-focus-halo);
    --bs-btn-active-shadow: none;
    --bs-btn-disabled-opacity: 0.55;
    letter-spacing: var(--sm-track-tight);
    transition: background-color var(--sm-fast) var(--sm-ease),
                border-color var(--sm-fast) var(--sm-ease),
                color var(--sm-fast) var(--sm-ease),
                box-shadow var(--sm-fast) var(--sm-ease);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sm-s-2);
    white-space: nowrap;
}

.btn-sm {
    --bs-btn-padding-x: 0.875rem;
    --bs-btn-padding-y: 0.3125rem;
    --bs-btn-font-size: var(--sm-text-sm);
    --bs-btn-border-radius: var(--sm-r-xs);
}

.btn-lg {
    --bs-btn-padding-x: 1.625rem;
    --bs-btn-padding-y: 0.6875rem;
    --bs-btn-font-size: var(--sm-text-xl);
    --bs-btn-border-radius: var(--sm-r-lg);
}

/* A button is a target before it is a decoration: 38px normal, 32px small, and
   44px on a touch screen (see the narrow-screen block in shell.css). */
.btn:not(.btn-sm):not(.btn-link) { min-height: 38px; }
.btn-sm:not(.btn-link) { min-height: 32px; }

.btn:focus-visible {
    outline: 2px solid var(--sm-focus);
    outline-offset: 1px;
    box-shadow: 0 0 0 3px var(--sm-focus-halo);
}

.btn-primary {
    --bs-btn-bg: var(--sm-primary-600);
    --bs-btn-border-color: var(--sm-primary-600);
    --bs-btn-hover-bg: var(--sm-primary-700);
    --bs-btn-hover-border-color: var(--sm-primary-700);
    --bs-btn-active-bg: var(--sm-primary-700);
    --bs-btn-active-border-color: var(--sm-primary-700);
    --bs-btn-disabled-bg: var(--sm-primary-600);
    --bs-btn-disabled-border-color: var(--sm-primary-600);
    --bs-btn-focus-shadow-rgb: var(--sm-primary-rgb);
    box-shadow: var(--sm-shadow-primary);
}

.btn-primary:disabled,
.btn-primary.disabled {
    box-shadow: none;
}

.btn-outline-primary {
    --bs-btn-color: var(--sm-primary-600);
    --bs-btn-border-color: var(--sm-primary);
    --bs-btn-hover-bg: var(--sm-primary-tint);
    --bs-btn-hover-border-color: var(--sm-primary);
    --bs-btn-hover-color: var(--sm-primary-ink);
    --bs-btn-active-bg: var(--sm-primary-tint-2);
    --bs-btn-active-border-color: var(--sm-primary);
    --bs-btn-active-color: var(--sm-primary-ink);
    --bs-btn-disabled-color: var(--sm-ink-4);
    --bs-btn-disabled-border-color: var(--sm-line-solid);
    --bs-btn-focus-shadow-rgb: var(--sm-primary-rgb);
}

/* The application's everyday secondary action: an ink button with a hairline,
   which reads as "the other choice" rather than "unavailable". */
.btn-outline-secondary {
    --bs-btn-color: var(--sm-ink-2);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--sm-neutral-border);
    --bs-btn-hover-bg: var(--sm-neutral-tint);
    --bs-btn-hover-border-color: var(--sm-line-strong);
    --bs-btn-hover-color: var(--sm-ink);
    --bs-btn-active-bg: var(--sm-neutral-tint);
    --bs-btn-active-border-color: var(--sm-line-strong);
    --bs-btn-active-color: var(--sm-ink);
    --bs-btn-disabled-color: var(--sm-ink-4);
    --bs-btn-disabled-border-color: var(--sm-line-solid);
    --bs-btn-focus-shadow-rgb: 109, 107, 119;
}

/* Written only on the Cancel and Close buttons of dialogs, so it takes Vuexy's
   tinted grey "label" look rather than a solid grey block. ink-2 on this tint
   is 5.6:1. */
.btn-secondary {
    --bs-btn-color: var(--sm-ink-2);
    --bs-btn-bg: #ebebed;
    --bs-btn-border-color: #ebebed;
    --bs-btn-hover-color: var(--sm-ink);
    --bs-btn-hover-bg: #dfdfe2;
    --bs-btn-hover-border-color: #dfdfe2;
    --bs-btn-active-color: var(--sm-ink);
    --bs-btn-active-bg: #dfdfe2;
    --bs-btn-active-border-color: #dfdfe2;
    --bs-btn-disabled-color: var(--sm-ink-3);
    --bs-btn-disabled-bg: #ebebed;
    --bs-btn-disabled-border-color: #ebebed;
    --bs-btn-focus-shadow-rgb: 109, 107, 119;
}

.btn-danger {
    --bs-btn-bg: var(--sm-over);
    --bs-btn-border-color: var(--sm-over);
    --bs-btn-hover-bg: var(--sm-over-ink);
    --bs-btn-hover-border-color: var(--sm-over-ink);
    --bs-btn-active-bg: var(--sm-over-ink);
    --bs-btn-active-border-color: var(--sm-over-ink);
    --bs-btn-disabled-bg: var(--sm-over);
    --bs-btn-disabled-border-color: var(--sm-over);
    --bs-btn-focus-shadow-rgb: 208, 38, 43;
    box-shadow: 0 0.125rem 0.375rem rgba(255, 76, 81, 0.3);
}

.btn-outline-danger {
    --bs-btn-color: var(--sm-over);
    --bs-btn-border-color: var(--sm-danger);
    --bs-btn-hover-bg: var(--sm-over-tint);
    --bs-btn-hover-border-color: var(--sm-danger);
    --bs-btn-hover-color: var(--sm-over-ink);
    --bs-btn-active-bg: var(--sm-over-tint);
    --bs-btn-active-border-color: var(--sm-over);
    --bs-btn-active-color: var(--sm-over-ink);
    --bs-btn-disabled-color: var(--sm-ink-4);
    --bs-btn-disabled-border-color: var(--sm-line-solid);
    --bs-btn-focus-shadow-rgb: 208, 38, 43;
}

.btn-success {
    --bs-btn-bg: var(--sm-paid);
    --bs-btn-border-color: var(--sm-paid);
    --bs-btn-hover-bg: var(--sm-paid-ink);
    --bs-btn-hover-border-color: var(--sm-paid-ink);
    --bs-btn-active-bg: var(--sm-paid-ink);
    --bs-btn-active-border-color: var(--sm-paid-ink);
    --bs-btn-disabled-bg: var(--sm-paid);
    --bs-btn-disabled-border-color: var(--sm-paid);
    --bs-btn-focus-shadow-rgb: 23, 128, 70;
    box-shadow: 0 0.125rem 0.375rem rgba(40, 199, 111, 0.3);
}

.btn-outline-success {
    --bs-btn-color: var(--sm-paid);
    --bs-btn-border-color: var(--sm-success);
    --bs-btn-hover-bg: var(--sm-paid-tint);
    --bs-btn-hover-border-color: var(--sm-success);
    --bs-btn-hover-color: var(--sm-paid-ink);
    --bs-btn-active-bg: var(--sm-paid-tint);
    --bs-btn-active-border-color: var(--sm-paid);
    --bs-btn-active-color: var(--sm-paid-ink);
    --bs-btn-focus-shadow-rgb: 23, 128, 70;
}

.btn-warning {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--sm-due);
    --bs-btn-border-color: var(--sm-due);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--sm-due-ink);
    --bs-btn-hover-border-color: var(--sm-due-ink);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--sm-due-ink);
    --bs-btn-active-border-color: var(--sm-due-ink);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--sm-due);
    --bs-btn-disabled-border-color: var(--sm-due);
    --bs-btn-focus-shadow-rgb: 167, 90, 0;
    box-shadow: 0 0.125rem 0.375rem rgba(255, 159, 67, 0.3);
}

.btn-outline-warning {
    --bs-btn-color: var(--sm-due-ink);
    --bs-btn-border-color: var(--sm-warning);
    --bs-btn-hover-bg: var(--sm-due-tint);
    --bs-btn-hover-border-color: var(--sm-warning);
    --bs-btn-hover-color: var(--sm-due-ink);
    --bs-btn-active-bg: var(--sm-due-tint);
    --bs-btn-active-border-color: var(--sm-due);
    --bs-btn-active-color: var(--sm-due-ink);
    --bs-btn-focus-shadow-rgb: 167, 90, 0;
}

/* Used on the subscription banner, over a tinted panel. */
.btn-outline-dark {
    --bs-btn-color: var(--sm-ink);
    --bs-btn-border-color: var(--sm-line-strong);
    --bs-btn-hover-bg: var(--sm-ink);
    --bs-btn-hover-border-color: var(--sm-ink);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--sm-ink);
    --bs-btn-active-border-color: var(--sm-ink);
    --bs-btn-active-color: #fff;
    --bs-btn-focus-shadow-rgb: var(--sm-ink-rgb);
}

.btn-link {
    --bs-btn-color: var(--sm-primary-600);
    --bs-btn-hover-color: var(--sm-primary-700);
    --bs-btn-font-weight: 500;
    text-decoration: none;
}

.btn-link:hover {
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Vuexy's tinted "label" buttons: the 16% wash of a colour with its measured
   ink on top. Quieter than a solid button and louder than an outline — the
   right weight for a row action or a secondary action in a card header. */
.btn-label-primary,
.btn-label-secondary,
.btn-label-success,
.btn-label-warning,
.btn-label-danger,
.btn-label-info {
    --bs-btn-color: var(--sm-label-ink);
    --bs-btn-bg: var(--sm-label-tint);
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: var(--sm-label-ink);
    --bs-btn-hover-bg: var(--sm-label-hover);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: var(--sm-label-ink);
    --bs-btn-active-bg: var(--sm-label-hover);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-color: var(--sm-label-ink);
    --bs-btn-disabled-bg: var(--sm-label-tint);
    --bs-btn-disabled-border-color: transparent;
}

.btn-label-primary { --sm-label-ink: var(--sm-primary-ink); --sm-label-tint: var(--sm-primary-tint); --sm-label-hover: var(--sm-primary-tint-2); }
.btn-label-secondary { --sm-label-ink: var(--sm-ink-2); --sm-label-tint: #ebebed; --sm-label-hover: #dfdfe2; }
.btn-label-success { --sm-label-ink: var(--sm-paid-ink); --sm-label-tint: var(--sm-paid-tint); --sm-label-hover: #c8f0da; }
.btn-label-warning { --sm-label-ink: var(--sm-due-ink); --sm-label-tint: var(--sm-due-tint); --sm-label-hover: #ffe4ca; }
.btn-label-danger { --sm-label-ink: var(--sm-over-ink); --sm-label-tint: var(--sm-over-tint); --sm-label-hover: #ffcfd1; }
.btn-label-info { --sm-label-ink: var(--sm-credit-ink); --sm-label-tint: var(--sm-credit-tint); --sm-label-hover: #c1d9fa; }

/* A square button holding only an icon. Used for row actions and in the shell. */
.sm-icon-btn {
    width: 34px;
    height: 34px;
    min-height: 34px;
    padding: 0;
    flex: none;
    border-radius: var(--sm-r-sm);
}

.sm-icon-btn.btn-sm {
    width: 30px;
    height: 30px;
    min-height: 30px;
}

/* =============================================================================
   1b. Forms

   .form-control:focus and .form-select:focus carry a literal #86b7fe border and
   a literal rgba(13,110,253,.25) ring, so both have to be restated.
   ========================================================================== */
/* The control's edge is #d1d0d4 — Vuexy's input border, and 1.5:1 against white,
   which is enough for an edge that a label and a placeholder already identify.
   On focus it turns primary and lifts on a soft primary shadow, as Vuexy's do. */
.form-control,
.form-select {
    font-size: var(--sm-text);
    line-height: 1.4;
    padding: 0.5rem 0.875rem;
    border-radius: var(--sm-r-sm);
    border-color: #d1d0d4;
    color: var(--sm-ink);
    background-color: var(--sm-surface);
    transition: border-color var(--sm-fast) var(--sm-ease),
                box-shadow var(--sm-fast) var(--sm-ease);
}

.form-select {
    padding-right: 2.5rem;
    /* The chevron, in our ink rather than Bootstrap's near-black. */
    --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236d6b77' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.6' d='m3 6 5 5 5-5'/%3e%3c/svg%3e");
    background-size: 15px 12px;
}

.form-control:hover:not(:disabled):not([readonly]),
.form-select:hover:not(:disabled) {
    border-color: var(--sm-ink-4);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--sm-primary);
    box-shadow: var(--sm-shadow-primary);
    outline: 0;
}

/* A placeholder is text, so it takes an ink that clears 4.5:1, not the decorative one. */
.form-control::placeholder {
    color: var(--sm-ink-3);
}

.form-control:disabled,
.form-select:disabled {
    background-color: var(--sm-surface-sunken);
    color: var(--sm-ink-3);
}

.form-control-sm,
.form-select-sm {
    font-size: var(--sm-text-md);
    padding: 0.3125rem 0.625rem;
    border-radius: var(--sm-r-xs);
}

.form-control-sm { min-height: 32px; }
.form-select-sm { min-height: 32px; padding-right: 2rem; }

.form-control:not(.form-control-sm),
.form-select:not(.form-select-sm) {
    min-height: 38px;
}

textarea.form-control {
    min-height: 76px;
    line-height: 1.5;
}

.form-label {
    font-size: var(--sm-text-sm);
    font-weight: 500;
    color: var(--sm-ink);
    margin-bottom: 0.25rem;
}

.form-text {
    font-size: var(--sm-text-sm);
    color: var(--sm-ink-3);
}

.col-form-label {
    font-size: var(--sm-text-md);
    font-weight: 500;
}

/* The control itself must show that it failed, not only the message under it. */
.form-control.is-invalid,
.form-select.is-invalid,
.form-control.invalid,
.form-select.invalid {
    border-color: var(--sm-over);
}

.form-control.is-invalid:focus,
.form-select.is-invalid:focus,
.form-control.invalid:focus,
.form-select.invalid:focus {
    box-shadow: 0 0.125rem 0.375rem rgba(255, 76, 81, 0.3);
}

/* Blazor's own validation classes, which EditForm puts on the input. */
.form-control.modified.valid,
.form-select.modified.valid {
    border-color: var(--sm-paid-border);
}

.validation-message,
.invalid-feedback {
    font-size: var(--sm-text-sm);
    color: var(--sm-over);
}

/* The box's edge is the muted ink (5.2:1), well over the 3:1 a control boundary
   needs; Vuexy draws it the same colour. */
.form-check-input {
    width: 1.125em;
    height: 1.125em;
    margin-top: 0.18em;
    border: 1.5px solid var(--sm-ink-3);
    border-radius: var(--sm-r-xs);
}

.form-check-input:checked,
.form-check-input:indeterminate {
    background-color: var(--sm-primary-600);
    border-color: var(--sm-primary-600);
    box-shadow: var(--sm-shadow-primary);
}

.form-check-input:focus {
    border-color: var(--sm-primary);
    box-shadow: 0 0 0 3px var(--sm-focus-halo);
}

.form-switch .form-check-input {
    width: 2.1em;
    border-radius: var(--sm-r-pill);
}

.form-check-input[type="radio"] { border-radius: 50%; }

.form-check-label {
    font-size: var(--sm-text);
    color: var(--sm-ink);
}

/* Vuexy's input groups are one white field: the add-on shares the control's
   background and edge, and the whole group lights up when the control is focused. */
.input-group-text {
    font-size: var(--sm-text);
    background-color: var(--sm-surface);
    border-color: #d1d0d4;
    color: var(--sm-ink-3);
    border-radius: var(--sm-r-sm);
}

.input-group:focus-within {
    border-radius: var(--sm-r-sm);
    box-shadow: var(--sm-shadow-primary);
}

.input-group:focus-within > .input-group-text,
.input-group:focus-within > .form-control,
.input-group:focus-within > .form-select {
    border-color: var(--sm-primary);
    box-shadow: none;
}

fieldset legend,
legend.form-label {
    font-size: var(--sm-text-md);
    font-weight: 600;
    color: var(--sm-ink);
}

/* =============================================================================
   1c. Cards

   Vuexy's card: no border at all, the soft card shadow on every card whether
   or not the page wrote "shadow-sm", 24px of room, and a header that is a title
   rather than a ruled-off strip. A header still gets a hairline beneath it when
   what follows is a list or a table, so a row never seems to float under it.
   ========================================================================== */
.card {
    --bs-card-spacer-y: 1.5rem;
    --bs-card-spacer-x: 1.5rem;
    --bs-card-cap-padding-y: 1.25rem;
    --bs-card-cap-padding-x: 1.5rem;
    --bs-card-cap-bg: transparent;
    --bs-card-border-width: 0;
    --bs-card-border-color: transparent;
    --bs-card-border-radius: var(--sm-r);
    --bs-card-inner-border-radius: var(--sm-r);
    --bs-card-box-shadow: var(--sm-shadow-2);
    --bs-card-title-spacer-y: 0.5rem;
    --bs-card-title-color: var(--sm-ink);
    box-shadow: var(--sm-shadow-2);
}

/* A card drawn with a border utility on purpose (a choice, a selected option)
   keeps its edge. */
.card.border,
.card[class*="border-"] {
    --bs-card-border-width: 1px;
}

.card > .card-header {
    border-bottom: 0;
    font-size: var(--sm-text-xl);
    font-weight: 500;
    color: var(--sm-ink);
    letter-spacing: var(--sm-track-tight);
    line-height: 1.37;
}

/* The header's padding already separates it from the body; Vuexy pulls the body
   up rather than doubling the gap. */
.card > .card-header + .card-body {
    padding-top: 0.25rem;
}

.card > .card-header + .table-responsive,
.card > .card-header + .table,
.card > .card-header + .list-group,
.card > .card-header + .card-body.p-0 {
    border-top: 1px solid var(--sm-line);
}

.card > .card-footer {
    border-top: 1px solid var(--sm-line);
    background-color: transparent;
    padding: 1rem 1.5rem;
}

.card-title {
    font-size: var(--sm-text-xl);
    font-weight: 500;
    color: var(--sm-ink);
}

.card-subtitle {
    font-size: var(--sm-text-sm);
    color: var(--sm-ink-3);
}

/* A table that fills the body of a card should meet the card's corners. */
.card > .card-body.p-0 > .table:last-child,
.card > .table:last-child,
.card > .table-responsive:last-child > .table {
    margin-bottom: 0;
}

/* =============================================================================
   1d. Tables
   ========================================================================== */
.table {
    --bs-table-border-color: var(--sm-line);
    --bs-table-hover-bg: rgba(var(--sm-shade-rgb), 0.04);
    --bs-table-hover-color: var(--sm-ink-2);
    --bs-table-striped-bg: rgba(var(--sm-shade-rgb), 0.03);
    font-size: var(--sm-text);
    margin-bottom: 0;
    border-color: var(--sm-line);
}

.table > :not(caption) > * > * {
    padding: 0.8125rem 1.25rem;
    background-color: transparent;
    box-shadow: none;
}

.table-sm > :not(caption) > * > * {
    padding: 0.5625rem 1rem;
}

/* The head: Vuexy's uppercase label row in the heading ink, on white, ruled top
   and bottom. thead.table-light is written on most of the application's tables,
   and .table-light compiles literal greys, so it is restated here. */
.table > thead > tr > th,
.table > thead.table-light > tr > th,
.table thead.table-light {
    --bs-table-bg: var(--sm-surface);
    --bs-table-color: var(--sm-ink);
    --bs-table-border-color: var(--sm-line);
}

.table > thead > tr > th {
    background-color: var(--sm-surface);
    color: var(--sm-ink);
    font-size: var(--sm-text-sm);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    white-space: nowrap;
    border-top: 1px solid var(--sm-line);
    border-bottom: 1px solid var(--sm-line);
    padding-top: 0.875rem;
    padding-bottom: 0.875rem;
    vertical-align: middle;
}

.table > tbody > tr {
    transition: background-color var(--sm-fast) var(--sm-ease);
}

.table > tbody > tr > td {
    vertical-align: middle;
    color: var(--sm-ink-2);
}

/* The first column usually names the row; it takes the heading ink, as in Vuexy. */
.table > tbody > tr > td:first-child {
    color: var(--sm-ink);
}

/* Every list in the application gets the hover, as every Vuexy table does. Set
   as a background because the cell rule above clears Bootstrap's own state
   layer (an inset box-shadow). */
.table > tbody > tr:hover > * {
    background-color: rgba(var(--sm-shade-rgb), 0.04);
}

.table > tbody > tr:last-child > td {
    border-bottom-width: 0;
}

/* A totals row: ruled off from the rows it adds up, and given its own tint. */
.table > tfoot > tr > td,
.table > tfoot > tr > th {
    background-color: var(--sm-surface-sunken);
    border-top: 1px solid var(--sm-line-strong);
    color: var(--sm-ink);
    font-weight: 500;
}

/* Money and quantity columns line up by default wherever a cell is right
   aligned, so no page has to remember to ask for tabular figures. */
.table td.text-end,
.table th.text-end,
.table td.text-nowrap,
.sm-num-col {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* A wide table scrolls inside its own box. Giving the box a focus ring and a
   name makes it reachable and announced for a keyboard or screen-reader user. */
/* It scrolls sideways only. Under border-collapse the head's top rule can leave the
   table a pixel taller than its box, which would otherwise draw a vertical
   scrollbar for one pixel. */
.table-responsive {
    position: relative;
    overflow-y: hidden;
}

/* position: relative above also makes the box the containing block for anything
   absolutely positioned inside it — a .visually-hidden label in a row action — so
   such an element is clipped with the table instead of escaping the box and widening
   the page (found on Outstanding at 1024px). */

.table-responsive:focus-visible {
    outline: 2px solid var(--sm-focus);
    outline-offset: -2px;
}

/* -----------------------------------------------------------------------------
   The table card: title, toolbar, table, footer
   -------------------------------------------------------------------------- */
.sm-table-head {
    padding: var(--sm-s-5) var(--sm-s-6);
}

.sm-table-head-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sm-s-3);
}

.sm-table-title {
    font-size: var(--sm-text-xl);
    font-weight: 500;
    color: var(--sm-ink);
    margin: 0;
}

.sm-table-head-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sm-s-2);
}

.sm-table-head-row + .sm-toolbar {
    margin-top: var(--sm-s-3);
}

/* A filter row that sits above its own table rather than inside it. The same idea as
   .sm-toolbar, for the lists whose filters are their own block: a surface of its own,
   so the controls read as one group acting on what follows rather than as loose inputs
   floating on the page background. */
.sm-filters {
    padding: var(--sm-s-5) var(--sm-s-6);
    margin-bottom: var(--sm-s-6);
    background-color: var(--sm-surface);
    border: 0;
    border-radius: var(--sm-r);
    box-shadow: var(--sm-shadow-2);
    align-items: flex-end;
}

/* Bootstrap's row uses negative margins; a card must not hang past the cards
   around it, so they are cancelled and the padding does the spacing. */
.sm-filters {
    margin-left: 0;
    margin-right: 0;
    margin-top: 0;
}

.sm-filters > [class*="col-"] {
    padding-top: 0;
}

/* Phase 21D: a filter row directly above its list joins it, as Vuexy's datatable
   card holds its filters — one card, the filters in its top section, ruled off
   from the table. Each half's shadow is clipped at the seam so no line of shadow
   falls between them. */
.sm-filters:has(+ .sm-table-card) {
    margin-bottom: 0;
    border-bottom: 1px solid var(--sm-line);
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    clip-path: inset(-24px -24px 0 -24px);
}

.sm-filters + .sm-table-card {
    --bs-card-border-radius: 0 0 var(--sm-r) var(--sm-r);
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    clip-path: inset(0 -24px -24px -24px);
}

/* Row actions: Vuexy's quiet text-and-icon buttons rather than outlined boxes in
   every row. Only inside a table body; the same classes elsewhere are unchanged. */
.table > tbody .btn-sm.btn-outline-secondary,
.table > tbody .btn-sm.btn-outline-primary {
    --bs-btn-color: var(--sm-ink-2);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: var(--sm-primary-ink);
    --bs-btn-hover-bg: var(--sm-primary-tint);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: var(--sm-primary-ink);
    --bs-btn-active-bg: var(--sm-primary-tint-2);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-border-color: transparent;
}

.table > tbody .btn-sm.btn-outline-danger {
    --bs-btn-color: var(--sm-over);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: transparent;
    --bs-btn-hover-color: var(--sm-over-ink);
    --bs-btn-hover-bg: var(--sm-over-tint);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-bg: var(--sm-over-tint);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-border-color: transparent;
}

/* A person's name beside their initials, in a list (Vuexy's user cell). */
.sm-person {
    display: flex;
    align-items: center;
    gap: var(--sm-s-3);
    min-width: 0;
}

.sm-person-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.sm-person-sub {
    font-size: var(--sm-text-sm);
    font-weight: 400;
    color: var(--sm-ink-3);
}

/* Search and filters, attached to the table they act on rather than floating above it. */
.sm-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sm-s-3);
}

.sm-toolbar-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
    flex: 1 1 180px;
    max-width: 280px;
}

.sm-toolbar-field--wide { flex-basis: 260px; max-width: 380px; }
.sm-toolbar-field--narrow { flex-basis: 140px; max-width: 200px; }

.sm-toolbar-label {
    font-size: var(--sm-text-sm);
    font-weight: 500;
    color: var(--sm-ink-3);
}

/* A check that sits on the filter line rather than under it. */
.sm-toolbar-check {
    display: flex;
    align-items: center;
    gap: var(--sm-s-2);
    min-height: 38px;
    flex: 0 1 auto;
}

.sm-toolbar-spacer { margin-left: auto; }

/* A search field with its glyph inside. */
.sm-search {
    position: relative;
    width: 100%;
}

.sm-search .form-control { padding-left: 2.25rem; }

.sm-search .sm-icon {
    position: absolute;
    left: 0.6875rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--sm-ink-3);
    pointer-events: none;
}

/* Row actions: icons on a narrow screen, icons with words where there is room. */
.sm-row-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--sm-s-1);
    white-space: nowrap;
}

/* A word that appears beside its icon only where the table has room for it. Below
   that the icon carries the action and the button's aria-label carries its name, so
   nothing is lost to a screen reader. */
.sm-label-md { display: none; }

@media (min-width: 1200px) {
    .sm-label-md { display: inline; }
}

/* -----------------------------------------------------------------------------
   Loading skeleton
   -------------------------------------------------------------------------- */
.sm-skeleton {
    padding: var(--sm-s-2) 0;
}

.sm-skeleton-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sm-s-4);
    padding: 0.8125rem var(--sm-s-5);
    border-bottom: 1px solid var(--sm-line);
}

.sm-skeleton-row:last-child { border-bottom: 0; }

.sm-skeleton-bar {
    display: block;
    height: 10px;
    border-radius: var(--sm-r-pill);
    background: linear-gradient(90deg,
        var(--sm-neutral-tint) 0%, var(--sm-surface-sunken) 50%, var(--sm-neutral-tint) 100%);
    background-size: 200% 100%;
    animation: sm-shimmer 1.4s var(--sm-ease) infinite;
}

.sm-skeleton-bar--short { width: 68px; flex: none; }

@keyframes sm-shimmer {
    from { background-position: 200% 0; }
    to { background-position: -200% 0; }
}

/* -----------------------------------------------------------------------------
   The same list as cards, for a phone

   Shown only below the md breakpoint, and only for the lists that supply one;
   everything else keeps the horizontal scroll box, which is fine for a table
   nobody reads on a phone.
   -------------------------------------------------------------------------- */
.sm-cardlist {
    display: none;
    list-style: none;
    margin: 0;
    padding: 0;
}

.sm-cardlist-item {
    padding: var(--sm-s-4) var(--sm-s-4);
    border-bottom: 1px solid var(--sm-line);
}

.sm-cardlist-item:last-child { border-bottom: 0; }

.sm-cardlist-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sm-s-3);
}

.sm-cardlist-title {
    font-weight: 600;
    color: var(--sm-ink);
    min-width: 0;
    overflow-wrap: anywhere;
}

.sm-cardlist-meta {
    font-size: var(--sm-text-sm);
    color: var(--sm-ink-3);
    margin-top: 2px;
}

.sm-cardlist-figures {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sm-s-2) var(--sm-s-5);
    margin-top: var(--sm-s-3);
}

.sm-cardlist-figure {
    min-width: 0;
}

.sm-cardlist-figure .sm-info-label { display: block; }

.sm-cardlist-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sm-s-2);
    margin-top: var(--sm-s-3);
}

@media (max-width: 767.98px) {
    .sm-cardlist { display: block; }

    /* Where a card list exists, the table itself steps aside. */
    .table-responsive.sm-table-wide { display: none; }
}

/* =============================================================================
   1e. Badges

   Vuexy's "label" badges: the 16% wash of a colour, its measured ink on top, no
   ring. Every .text-bg-* in the application (bill, payment, complaint, salary,
   invoice and subscription statuses) is remapped here, so the statuses change
   appearance without a single display helper being touched. Bootstrap writes
   these with !important, so these must too.
   ========================================================================== */
.badge {
    --bs-badge-padding-x: 0.625rem;
    --bs-badge-padding-y: 0.3125rem;
    --bs-badge-font-size: var(--sm-text-sm);
    --bs-badge-font-weight: 500;
    --bs-badge-border-radius: var(--sm-r-xs);
    letter-spacing: 0;
    line-height: 1.15;
    vertical-align: middle;
}

.badge.rounded-pill {
    --bs-badge-padding-x: 0.75rem;
}

.text-bg-primary {
    color: var(--sm-primary-ink) !important;
    background-color: var(--sm-primary-tint) !important;
}

.text-bg-success {
    color: var(--sm-paid-ink) !important;
    background-color: var(--sm-paid-tint) !important;
}

.text-bg-warning {
    color: var(--sm-due-ink) !important;
    background-color: var(--sm-due-tint) !important;
}

.text-bg-danger {
    color: var(--sm-over-ink) !important;
    background-color: var(--sm-over-tint) !important;
}

.text-bg-info {
    color: var(--sm-credit-ink) !important;
    background-color: var(--sm-credit-tint) !important;
}

/* ink-2 on #ebebed is 5.6:1. */
.text-bg-secondary {
    color: var(--sm-ink-2) !important;
    background-color: #ebebed !important;
}

.text-bg-light {
    color: var(--sm-ink-2) !important;
    background-color: var(--sm-neutral-tint) !important;
}

/* Vuexy's "label-dark": the heading ink's wash with the ink on it (8.0:1). */
.text-bg-dark {
    color: var(--sm-ink) !important;
    background-color: rgba(var(--sm-ink-rgb), 0.12) !important;
}

/* "text-bg-light border" is the display helpers' fallback badge; keep its edge
   quiet. */
.badge.text-bg-light.border {
    border-color: var(--sm-neutral-border) !important;
}

/* A tiny filled dot before a status word, as Vuexy's lists use. Opt-in. */
.sm-dot {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background-color: currentColor;
    vertical-align: middle;
}

/* =============================================================================
   1f. Alerts

   Vuexy's: a tinted panel, no border and no accent bar, the colour's ink for the
   text. The tints and inks come through Bootstrap's own --bs-*-bg-subtle and
   --bs-*-text-emphasis, which tokens.css points at the money semantics.
   ========================================================================== */
.alert {
    --bs-alert-padding-x: 1rem;
    --bs-alert-padding-y: 0.75rem;
    --bs-alert-border-radius: var(--sm-r-sm);
    --bs-alert-border-color: transparent;
    font-size: var(--sm-text);
}

.alert-secondary {
    --bs-alert-color: var(--sm-ink-2);
    --bs-alert-bg: #ebebed;
    --bs-alert-border-color: transparent;
}

.alert-light {
    --bs-alert-color: var(--sm-ink-2);
    --bs-alert-bg: var(--sm-neutral-tint);
    --bs-alert-border-color: transparent;
}

.alert .alert-link,
.alert a:not(.btn) {
    color: inherit;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.alert-heading {
    color: inherit;
    font-weight: 500;
}

/* An icon in a tinted square at the alert's leading edge — Vuexy's alert
   pattern. Opt-in: an alert written with one gets it, the rest are unchanged. */
.alert .sm-alert-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.875rem;
    height: 1.875rem;
    flex: none;
    border-radius: var(--sm-r-sm);
    background-color: rgba(255, 255, 255, 0.55);
}

/* =============================================================================
   1g. Progress
   ========================================================================== */
.progress,
.progress-stacked {
    --bs-progress-height: 6px;
    --bs-progress-bg: rgba(var(--sm-primary-rgb), 0.16);
    --bs-progress-bar-bg: var(--sm-primary);
    --bs-progress-border-radius: var(--sm-r-pill);
    --bs-progress-box-shadow: none;
}

.progress-bar {
    border-radius: var(--sm-r-pill);
}

.progress-bar.bg-success { background-color: var(--sm-success) !important; }
.progress-bar.bg-warning { background-color: var(--sm-warning) !important; }
.progress-bar.bg-danger { background-color: var(--sm-danger) !important; }
.progress-bar.bg-info { background-color: var(--sm-info) !important; }

/* =============================================================================
   1h. List group
   ========================================================================== */
.list-group {
    --bs-list-group-border-color: var(--sm-line);
    --bs-list-group-item-padding-x: 1.25rem;
    --bs-list-group-item-padding-y: 0.875rem;
    --bs-list-group-action-hover-bg: var(--sm-surface-sunken);
    --bs-list-group-active-bg: var(--sm-primary);
    --bs-list-group-active-border-color: var(--sm-primary);
}

.list-group-item {
    font-size: var(--sm-text);
}

/* =============================================================================
   1i. Modal

   ModalHost renders the dialog from component state rather than through
   Bootstrap's JS, which the application does not load. These rules only change
   how it looks; nothing here drives whether it is open.
   ========================================================================== */
/* Vuexy's dialog: no rules between header, body and footer, 24px of room, and a
   greyed rather than darkened page behind it. */
.modal {
    --bs-modal-border-radius: var(--sm-r-lg);
    --bs-modal-inner-border-radius: var(--sm-r-lg);
    --bs-modal-box-shadow: 0 0.375rem 1.5rem rgba(var(--sm-shade-rgb), 0.2);
    --bs-modal-border-color: transparent;
    --bs-modal-header-border-color: transparent;
    --bs-modal-footer-border-color: transparent;
    --bs-modal-padding: 1.5rem;
    --bs-modal-header-padding: 1.5rem 1.5rem 0.75rem;
    --bs-modal-width: 540px;
}

.modal-content {
    box-shadow: var(--bs-modal-box-shadow);
}

.modal-title {
    font-size: var(--sm-text-xl);
    font-weight: 500;
    color: var(--sm-ink);
}

.modal-body {
    padding-top: 0.5rem;
}

.modal-footer {
    padding: 0.5rem 1.5rem 1.5rem;
    gap: var(--sm-s-1);
    background-color: var(--sm-surface);
    border-bottom-left-radius: var(--bs-modal-inner-border-radius);
    border-bottom-right-radius: var(--bs-modal-inner-border-radius);
}

.modal-backdrop {
    --bs-backdrop-bg: #97959e;
    --bs-backdrop-opacity: 0.5;
}

.btn-close:focus {
    box-shadow: 0 0 0 3px var(--sm-focus-halo);
}

/* =============================================================================
   1j. Toast

   The toast host keeps its "toast-container" root — the layout tests look for
   exactly that element — and its chips are ours. Toasts stack top-right, just
   under the top bar, and hide themselves; the bar along the bottom counts down
   the time left (its duration is set inline from ToastService).
   ========================================================================== */
.sm-toast-stack {
    position: fixed;
    top: calc(var(--sm-topbar-gap) + var(--sm-topbar-h) + 0.75rem);
    right: 1rem;
    z-index: 1080;
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    width: 360px;
    max-width: calc(100vw - 2rem);
    pointer-events: none;
}

.toast {
    --bs-toast-border-radius: var(--sm-r-lg);
    --bs-toast-box-shadow: var(--sm-shadow-3);
    --bs-toast-font-size: var(--sm-text);
    --bs-toast-border-width: 1px;
    --bs-toast-max-width: 100%;
}

.sm-toast {
    position: relative;
    width: 100%;
    overflow: hidden;
    pointer-events: auto;
    background-color: var(--sm-surface);
    border: 0;
    border-radius: var(--sm-r-lg);
    box-shadow: var(--sm-shadow-3);
    color: var(--sm-ink);
    animation: sm-toast-in 260ms cubic-bezier(0.16, 1, 0.3, 1);
}

.sm-toast--success { --sm-alert-accent: var(--sm-paid); --sm-alert-tint: var(--sm-paid-tint); --sm-alert-border: var(--sm-paid-border); }
.sm-toast--danger { --sm-alert-accent: var(--sm-over); --sm-alert-tint: var(--sm-over-tint); --sm-alert-border: var(--sm-over-border); }
.sm-toast--warning { --sm-alert-accent: var(--sm-due); --sm-alert-tint: var(--sm-due-tint); --sm-alert-border: var(--sm-due-border); }

.sm-toast-body {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.875rem 0.75rem 1rem 0.875rem;
}

/* Vuexy's tinted icon square rather than a solid disc. */
.sm-toast .sm-toast-icon {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--sm-r-sm);
    background-color: var(--sm-alert-tint, var(--sm-neutral-tint));
    color: var(--sm-alert-accent, var(--sm-ink-3));
}

.sm-toast-text {
    flex: 1 1 auto;
    min-width: 0;
    padding-top: 0.0625rem;
}

.sm-toast-title {
    font-weight: 500;
    line-height: 1.3;
    color: var(--sm-ink);
}

.sm-toast-message {
    margin-top: 0.125rem;
    line-height: 1.4;
    color: var(--sm-ink-2);
    overflow-wrap: anywhere;
}

.sm-toast-close {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    margin: -0.125rem -0.125rem 0 0;
    padding: 0;
    border: 0;
    border-radius: var(--sm-r-xs);
    background: transparent;
    color: var(--sm-ink-3);
}

.sm-toast-close:hover {
    background-color: var(--sm-line-solid);
    color: var(--sm-ink);
}

.sm-toast-close:focus-visible {
    outline: 2px solid var(--sm-alert-accent, var(--sm-ink-3));
    outline-offset: 1px;
}

.sm-toast-timer {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 3px;
    background-color: var(--sm-alert-accent, var(--sm-ink-3));
    transform-origin: left center;
    animation-name: sm-toast-timer;
    animation-timing-function: linear;
    animation-fill-mode: forwards;
}

@keyframes sm-toast-in {
    from { opacity: 0; transform: translateX(24px) scale(0.98); }
    to { opacity: 1; transform: none; }
}

@keyframes sm-toast-in-down {
    from { opacity: 0; transform: translateY(-16px); }
    to { opacity: 1; transform: none; }
}

@keyframes sm-toast-timer {
    from { transform: scaleX(1); }
    to { transform: scaleX(0); }
}

/* Phones: full width across the top. */
@media (max-width: 575.98px) {
    .sm-toast-stack {
        left: 0.75rem;
        right: 0.75rem;
        width: auto;
        max-width: none;
    }

    .sm-toast { animation-name: sm-toast-in-down; }
}

@media (prefers-reduced-motion: reduce) {
    .sm-toast { animation: none; }
    .sm-toast-timer { display: none; }
}

/* =============================================================================
   1k. Tabs
   ========================================================================== */
.nav-tabs {
    --bs-nav-tabs-border-color: var(--sm-line);
    --bs-nav-tabs-link-active-color: var(--sm-primary-600);
    --bs-nav-tabs-link-active-bg: transparent;
    --bs-nav-tabs-link-active-border-color: transparent;
    --bs-nav-tabs-link-hover-border-color: transparent;
    --bs-nav-link-color: var(--sm-ink-2);
    --bs-nav-link-hover-color: var(--sm-ink);
    --bs-nav-link-font-size: var(--sm-text);
    --bs-nav-link-font-weight: 500;
    gap: var(--sm-s-1);
}

.nav-tabs .nav-link {
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    padding: 0.5rem 0.875rem;
}

.nav-tabs .nav-link:hover {
    border-bottom-color: var(--sm-line-strong);
}

.nav-tabs .nav-link.active {
    border-bottom-color: var(--sm-primary);
    font-weight: 600;
}

/* -----------------------------------------------------------------------------
   Our own tabs (TabStrip)
   -------------------------------------------------------------------------- */
.sm-tabs {
    border-bottom: 1px solid var(--sm-line);
    margin-bottom: var(--sm-s-5);
    overflow-x: auto;
    overscroll-behavior-x: contain;
}

.sm-tablist {
    display: flex;
    gap: var(--sm-s-1);
    min-width: max-content;
}

.sm-tab {
    display: inline-flex;
    align-items: center;
    gap: var(--sm-s-2);
    padding: 0.625rem 0.875rem;
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--sm-ink-2);
    font-size: var(--sm-text);
    font-weight: 500;
    white-space: nowrap;
    transition: color var(--sm-fast) var(--sm-ease),
                border-color var(--sm-fast) var(--sm-ease);
}

.sm-tab:hover {
    color: var(--sm-ink);
    border-bottom-color: var(--sm-line-strong);
}

.sm-tab:focus-visible {
    outline: 2px solid var(--sm-focus);
    outline-offset: -2px;
}

.sm-tab.is-selected {
    color: var(--sm-primary-600);
    border-bottom-color: var(--sm-primary);
    font-weight: 600;
}

.sm-tab .sm-icon { color: var(--sm-ink-3); }

.sm-tab.is-selected .sm-icon { color: var(--sm-primary-600); }

.sm-tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    padding: 0 6px;
    height: 20px;
    border-radius: var(--sm-r-pill);
    background-color: var(--sm-neutral-tint);
    color: var(--sm-ink-2);
    font-size: var(--sm-text-xs);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.sm-tab.is-selected .sm-tab-count {
    background-color: var(--sm-primary-tint);
    color: var(--sm-primary-ink);
}

/* -----------------------------------------------------------------------------
   Pills: the active one is a primary fill on its soft shadow, as Vuexy's are.
   -------------------------------------------------------------------------- */
.nav-pills {
    --bs-nav-pills-border-radius: var(--sm-r-sm);
    --bs-nav-pills-link-active-color: #fff;
    --bs-nav-pills-link-active-bg: var(--sm-primary-600);
    --bs-nav-link-color: var(--sm-ink-2);
    --bs-nav-link-hover-color: var(--sm-primary-600);
    --bs-nav-link-font-weight: 500;
    --bs-nav-link-padding-x: 1.25rem;
    --bs-nav-link-padding-y: 0.5rem;
    gap: var(--sm-s-1);
}

.nav-pills .nav-link.active {
    box-shadow: var(--sm-shadow-primary);
}

/* =============================================================================
   1m. Pagination

   Vuexy's rounded squares on a faint tint, the current page a primary fill.
   -------------------------------------------------------------------------- */
.pagination {
    --bs-pagination-padding-x: 0.5rem;
    --bs-pagination-padding-y: 0.375rem;
    --bs-pagination-font-size: var(--sm-text-md);
    --bs-pagination-color: var(--sm-ink-2);
    --bs-pagination-bg: rgba(var(--sm-shade-rgb), 0.06);
    --bs-pagination-border-width: 0;
    --bs-pagination-border-radius: var(--sm-r-sm);
    --bs-pagination-hover-color: var(--sm-primary-ink);
    --bs-pagination-hover-bg: var(--sm-primary-tint);
    --bs-pagination-focus-color: var(--sm-primary-ink);
    --bs-pagination-focus-bg: var(--sm-primary-tint);
    --bs-pagination-focus-box-shadow: 0 0 0 3px var(--sm-focus-halo);
    --bs-pagination-active-color: #fff;
    --bs-pagination-active-bg: var(--sm-primary-600);
    --bs-pagination-disabled-color: var(--sm-ink-4);
    --bs-pagination-disabled-bg: rgba(var(--sm-shade-rgb), 0.04);
    gap: 0.25rem;
}

.pagination .page-link {
    min-width: 2.25rem;
    text-align: center;
    border-radius: var(--sm-r-sm) !important;
}

.pagination .page-item.active .page-link {
    box-shadow: var(--sm-shadow-primary);
}

/* =============================================================================
   1n. Dropdown (Bootstrap's markup, for any page that uses it; the shell's own
   menus are .sm-menu below)
   ========================================================================== */
.dropdown-menu {
    --bs-dropdown-border-width: 0;
    --bs-dropdown-border-radius: var(--sm-r-sm);
    --bs-dropdown-box-shadow: var(--sm-shadow-3);
    --bs-dropdown-padding-y: 0.5rem;
    --bs-dropdown-font-size: var(--sm-text);
    --bs-dropdown-link-color: var(--sm-ink-2);
    --bs-dropdown-link-hover-color: var(--sm-ink);
    --bs-dropdown-link-hover-bg: rgba(var(--sm-shade-rgb), 0.06);
    --bs-dropdown-link-active-color: var(--sm-primary-ink);
    --bs-dropdown-link-active-bg: var(--sm-primary-tint);
    --bs-dropdown-item-padding-x: 1rem;
    --bs-dropdown-item-padding-y: 0.5rem;
    box-shadow: var(--bs-dropdown-box-shadow);
}

.dropdown-item {
    border-radius: var(--sm-r-sm);
    margin: 0 0.5rem;
    width: calc(100% - 1rem);
}

/* =============================================================================
   1l. Spinner
   ========================================================================== */
.spinner-border {
    --bs-spinner-border-width: 0.18em;
}

.spinner-border-sm {
    --bs-spinner-width: 1rem;
    --bs-spinner-height: 1rem;
    --bs-spinner-border-width: 0.15em;
}

/* =============================================================================
   2. Our own components
   ========================================================================== */

/* -----------------------------------------------------------------------------
   Icon

   Inline SVG, drawn in the current text colour, sized by its own font size so
   it matches whatever it sits beside. Stroke-based, 24-unit grid.
   -------------------------------------------------------------------------- */
.sm-icon {
    width: 1.125em;
    height: 1.125em;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    vertical-align: -0.18em;
    overflow: visible;
}

.sm-icon-sm { width: 1em; height: 1em; stroke-width: 2; }
.sm-icon-lg { width: 1.375em; height: 1.375em; stroke-width: 1.6; }
.sm-icon-xl { width: 2rem; height: 2rem; stroke-width: 1.5; }

/* Vuexy's "avatar-initial": an icon in a rounded square on the 16% wash of its
   colour, drawn in the colour at full strength. Decorative — the figure or the
   words beside it carry the meaning — so the bright hues are used as they are. */
.sm-icon-tile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex: none;
    border-radius: var(--sm-r-sm);
    background-color: var(--sm-primary-tint);
    color: var(--sm-primary);
    font-size: 1.125rem;
}

.sm-icon-tile--paid { background-color: var(--sm-paid-tint); color: var(--sm-success); }
.sm-icon-tile--due { background-color: var(--sm-due-tint); color: var(--sm-warning); }
.sm-icon-tile--over { background-color: var(--sm-over-tint); color: var(--sm-danger); }
.sm-icon-tile--credit { background-color: var(--sm-credit-tint); color: var(--sm-info); }
.sm-icon-tile--neutral { background-color: #ebebed; color: var(--sm-secondary); }

/* -----------------------------------------------------------------------------
   Page header
   -------------------------------------------------------------------------- */
.sm-page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sm-s-3);
    margin-bottom: var(--sm-s-6);
}

.sm-page-title-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sm-s-3);
    min-width: 0;
}

.sm-page-title {
    font-size: var(--sm-title);
    font-weight: 500;
    line-height: 1.33;
    letter-spacing: 0;
    color: var(--sm-ink);
    margin: 0;
    overflow-wrap: anywhere;
}

.sm-page-status {
    display: inline-flex;
    align-items: center;
    transform: translateY(1px);
}

/* A line of facts under a detail page's title: a date, a property, a reference. */
.sm-page-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sm-s-2) var(--sm-s-4);
    margin-top: var(--sm-s-2);
    font-size: var(--sm-text-md);
    color: var(--sm-ink-2);
}

.sm-page-meta > * {
    display: inline-flex;
    align-items: center;
    gap: var(--sm-s-2);
    min-width: 0;
}

.sm-page-meta .sm-icon { color: var(--sm-ink-3); }

.sm-page-subtitle {
    font-size: var(--sm-text-md);
    color: var(--sm-ink-2);
    margin-top: 0.25rem;
    max-width: 78ch;
}

.sm-page-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sm-s-2);
}

/* -----------------------------------------------------------------------------
   Stat card
   -------------------------------------------------------------------------- */
.sm-stat {
    display: flex;
    align-items: flex-start;
    gap: var(--sm-s-3);
    height: 100%;
    padding: var(--sm-s-6);
    background-color: var(--sm-surface);
    border: 0;
    border-radius: var(--sm-r);
    box-shadow: var(--sm-shadow-2);
}

.sm-stat-body { min-width: 0; }

/* Vuexy writes a stat's label as plain body text above the figure. */
.sm-stat-label {
    font-size: var(--sm-text);
    font-weight: 400;
    color: var(--sm-ink-2);
    line-height: 1.4;
}

.sm-stat-value {
    font-size: 1.5rem;
    font-weight: 500;
    line-height: 1.2;
    letter-spacing: -0.01em;
    color: var(--sm-ink);
    margin-top: 0.3125rem;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    overflow-wrap: anywhere;
}

.sm-stat-hint {
    font-size: var(--sm-text-sm);
    color: var(--sm-ink-3);
    margin-top: 0.25rem;
}

/* -----------------------------------------------------------------------------
   Empty state
   -------------------------------------------------------------------------- */
.sm-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--sm-s-8) var(--sm-s-5);
    border: 1px dashed var(--sm-line-strong);
    border-radius: var(--sm-r);
    background-color: var(--sm-surface);
}

.sm-empty-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border-radius: var(--sm-r-sm);
    background-color: var(--sm-primary-tint);
    color: var(--sm-primary);
    margin-bottom: var(--sm-s-3);
}

.sm-empty-title {
    font-size: var(--sm-text-lg);
    font-weight: 500;
    color: var(--sm-ink);
}

.sm-empty-text {
    font-size: var(--sm-text-md);
    color: var(--sm-ink-2);
    margin-top: 0.25rem;
    max-width: 52ch;
}

/* -----------------------------------------------------------------------------
   Loading
   -------------------------------------------------------------------------- */
.sm-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sm-s-2);
    padding: var(--sm-s-7) var(--sm-s-4);
    color: var(--sm-ink-2);
    font-size: var(--sm-text-md);
}

/* -----------------------------------------------------------------------------
   Pager
   -------------------------------------------------------------------------- */
.sm-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sm-s-2);
}

.sm-pager-status {
    font-size: var(--sm-text-sm);
    color: var(--sm-ink-3);
    font-variant-numeric: tabular-nums;
}

/* -----------------------------------------------------------------------------
   Avatar
   -------------------------------------------------------------------------- */
/* Vuexy's initials avatar: the primary wash with the primary ink on it (5.1:1). */
.sm-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex: none;
    border-radius: 50%;
    background-color: var(--sm-primary-tint);
    color: var(--sm-primary-ink);
    font-size: var(--sm-text-sm);
    font-weight: 500;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    user-select: none;
}

.sm-avatar--sm { width: 32px; height: 32px; font-size: var(--sm-text-xs); }
.sm-avatar--lg { width: 44px; height: 44px; font-size: var(--sm-text); }

/* The profile card's initials: a rounded square, as Vuexy's user view draws its photo. */
.sm-avatar--xl {
    width: 100px;
    height: 100px;
    border-radius: var(--sm-r-sm);
    font-size: 2rem;
}

/* -----------------------------------------------------------------------------
   Menu (our own dropdown)

   Bootstrap's dropdown JS is not loaded. This is opened by site.js toggling
   aria-expanded on the trigger, so the open state lives in one place and is the
   same thing a screen reader is told.
   -------------------------------------------------------------------------- */
.sm-menu-wrap {
    position: relative;
}

.sm-menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: var(--sm-z-menu);
    min-width: 240px;
    padding: var(--sm-s-2);
    background-color: var(--sm-surface);
    border: 0;
    border-radius: var(--sm-r-sm);
    box-shadow: var(--sm-shadow-3);
    display: none;
}

[data-menu-toggle][aria-expanded="true"] + .sm-menu {
    display: block;
    animation: sm-menu-in var(--sm-fast) var(--sm-ease);
}

@keyframes sm-menu-in {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: none; }
}

/* Vuexy's account header: the avatar beside the name and role, ruled off. */
.sm-menu-head {
    display: flex;
    align-items: center;
    gap: var(--sm-s-3);
    padding: var(--sm-s-2) var(--sm-s-3) var(--sm-s-3);
    border-bottom: 1px solid var(--sm-line);
    margin-bottom: var(--sm-s-2);
}

.sm-menu-head-text {
    min-width: 0;
}

.sm-menu-name {
    font-size: var(--sm-text);
    font-weight: 500;
    color: var(--sm-ink);
    overflow-wrap: anywhere;
}

.sm-menu-meta {
    font-size: var(--sm-text-sm);
    color: var(--sm-ink-3);
    margin-top: 0.125rem;
}

.sm-menu-item {
    display: flex;
    align-items: center;
    gap: var(--sm-s-3);
    width: 100%;
    padding: 0.5rem var(--sm-s-3);
    border: 0;
    border-radius: var(--sm-r-sm);
    background: none;
    color: var(--sm-ink);
    font-size: var(--sm-text);
    text-align: left;
    text-decoration: none;
    transition: background-color var(--sm-fast) var(--sm-ease);
}

.sm-menu-item:hover,
.sm-menu-item:focus-visible {
    background-color: rgba(var(--sm-shade-rgb), 0.06);
    color: var(--sm-ink);
    text-decoration: none;
}

.sm-menu-item .sm-icon { color: var(--sm-ink-2); }

/* Sign out is Vuexy's full-width red button at the foot of the menu. White on
   the danger fill is 5.3:1. */
.sm-menu-item--danger {
    justify-content: center;
    margin-top: var(--sm-s-1);
    padding: 0.4375rem var(--sm-s-3);
    background-color: var(--sm-over);
    color: #fff;
    font-size: var(--sm-text-sm);
    font-weight: 500;
    box-shadow: 0 0.125rem 0.375rem rgba(255, 76, 81, 0.3);
}

.sm-menu-item--danger .sm-icon { color: #fff; }

.sm-menu-item--danger:hover,
.sm-menu-item--danger:focus-visible {
    background-color: var(--sm-over-ink);
    color: #fff;
}

.sm-menu-divider {
    height: 1px;
    margin: var(--sm-s-2) 0;
    background-color: var(--sm-line);
}

/* -----------------------------------------------------------------------------
   Skip link
   -------------------------------------------------------------------------- */
.sm-skip-link {
    position: absolute;
    left: var(--sm-s-4);
    top: -100px;
    z-index: 2000;
    padding: 0.625rem var(--sm-s-4);
    background-color: var(--sm-surface);
    color: var(--sm-primary-600);
    font-size: var(--sm-text-md);
    font-weight: 600;
    border: 1px solid var(--sm-primary-border);
    border-radius: var(--sm-r-sm);
    box-shadow: var(--sm-shadow-2);
    transition: top var(--sm-fast) var(--sm-ease);
}

.sm-skip-link:focus {
    top: var(--sm-s-3);
    text-decoration: none;
}

/* -----------------------------------------------------------------------------
   Label / value pairs
   -------------------------------------------------------------------------- */
.sm-info-label {
    font-size: var(--sm-text-sm);
    font-weight: 600;
    letter-spacing: var(--sm-track-label);
    text-transform: uppercase;
    color: var(--sm-ink-3);
}

.sm-info-value {
    font-size: var(--sm-text);
    font-weight: 500;
    color: var(--sm-ink);
    margin-top: 0.125rem;
    overflow-wrap: anywhere;
}

/* -----------------------------------------------------------------------------
   Label/value pairs written as a Bootstrap grid list

   Several detail pages state their facts as <dl class="row"> with a dt/dd pair
   per fact. That markup is correct and there is no reason to rewrite it; what it
   lacked was the typography the rest of the application gives a label. This gives
   those blocks the same voice as InfoList without touching a single page.
   -------------------------------------------------------------------------- */
.card-body > dl.row > dt,
.card-body > dl.row > dd {
    margin-bottom: var(--sm-s-3);
}

.card-body > dl.row > dt {
    font-size: var(--sm-text-sm);
    font-weight: 600;
    letter-spacing: var(--sm-track-label);
    text-transform: uppercase;
    color: var(--sm-ink-3);
    line-height: 1.5;
}

.card-body > dl.row > dd {
    font-size: var(--sm-text);
    color: var(--sm-ink);
    overflow-wrap: anywhere;
}

.card-body > dl.row > dd:last-of-type,
.card-body > dl.row > dt:last-of-type {
    margin-bottom: 0;
}

/* -----------------------------------------------------------------------------
   The two utilities the design system needs and Bootstrap 5.3 does not have
   -------------------------------------------------------------------------- */

/* A flex or grid child that must be allowed to be narrower than its contents,
   so a long name or a wide table shrinks rather than pushing the layout open. */
.min-width-0 {
    min-width: 0;
}

/* Set by site.js while a dialog is open, so the page behind it does not scroll
   under the pointer. */
body.sm-modal-open {
    overflow: hidden;
}

/* A list that fits on one page has no pager; its footer would otherwise stand as an
   empty band under the table. */
.sm-table-card > .card-footer:not(:has(*)) {
    display: none;
}

/* ---------------------------------------------------------------------------
   Phase M1: the connection panel (App.razor). Blazor sets a
   components-reconnect-* class on it; without one it is not shown. A card at the
   foot of the screen rather than a dialog, so the page stays readable while the
   connection comes back, which on a phone is usually a second or two.
   --------------------------------------------------------------------------- */
.sm-reconnect {
    display: none;
    position: fixed;
    inset: auto 0 calc(var(--sm-s-5) + env(safe-area-inset-bottom, 0px)) 0;
    z-index: 1090; /* above dialogs and toasts: nothing on the page works until it is gone */
    justify-content: center;
    padding: 0 var(--sm-s-4);
    pointer-events: none;
}

.sm-reconnect.components-reconnect-show,
.sm-reconnect.components-reconnect-failed,
.sm-reconnect.components-reconnect-rejected {
    display: flex;
}

.sm-reconnect-card {
    display: flex;
    align-items: center;
    gap: var(--sm-s-3);
    padding: var(--sm-s-3) var(--sm-s-4);
    border-radius: var(--sm-r);
    background: var(--sm-surface);
    box-shadow: var(--sm-shadow-3);
    color: var(--sm-ink);
    pointer-events: auto;
}

.sm-reconnect-spinner {
    width: 1rem;
    height: 1rem;
    border: 2px solid rgba(var(--sm-primary-rgb), 0.25);
    border-top-color: var(--sm-primary);
    border-radius: 50%;
    animation: sm-reconnect-spin 0.8s linear infinite;
}

.sm-reconnect-when-show,
.sm-reconnect-when-failed,
.sm-reconnect-when-rejected {
    display: none;
}

.components-reconnect-show .sm-reconnect-when-show,
.components-reconnect-failed .sm-reconnect-when-failed,
.components-reconnect-rejected .sm-reconnect-when-rejected {
    display: inline-block;
}

.components-reconnect-failed .sm-reconnect-spinner {
    display: none;
}

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

@media (prefers-reduced-motion: reduce) {
    .sm-reconnect-spinner { animation-duration: 2.4s; }
}
