/* ==========================================================================
   paychain.forms.css — the MIS form layer
   --------------------------------------------------------------------------
   LOADS LAST, after paychain.bridge.css. Forms are the one thing in this
   product people TYPE INTO rather than read, and four sheets had an opinion
   about them. One place now decides, and it decides last.

   WHAT THIS REPLACED, and why the forms looked raw:

     1  A BARE <input> HAD NO STYLING AT ALL. Nothing in any sheet targeted a
        plain `input` element — every rule needed .form-control or .bc-input.
        The newer screens (.mis-label, used by Long Distance) write bare inputs,
        so they fell straight through to the browser default: grey bevel,
        system font, native spinners. That is the "Edit route" modal.

     2  `.form-control` CARRIED min-height:72px ON THE BASE CLASS. Meant for
        textareas, applied to every text input on 99 views. Every field on
        those screens was twice the height it should be.

     3  `.btn-close` DREW AN X WITH PSEUDO-ELEMENTS AND NEVER HID ITS TEXT.
        The markup is <button class="btn-close">Close</button> on 71 views, so
        the word "Close" and the drawn × rendered on top of each other. Real
        Bootstrap hides the label; the compat layer forgot to.

   THE MODEL, and it is deliberately small:

        .field            one label + control + help/error     (the only unit)
        .field__label     always ABOVE the control, always visible
        .field__help      quiet, below
        .field__error     replaces help when invalid
        .field-row        two or three fields side by side, collapsing at 640px
        .fieldset         a titled GROUP of fields with air around it

   Everything else — .mis-label, .form-control, .form-floating, .bc-input,
   .form-select, .input-group and bare elements — is mapped ONTO that model
   rather than restyled separately, so a screen written in any of the five
   idioms comes out the same shape with no view edited.

   TOP LABELS, NOT FLOATING. Materio's .form-floating is degraded to a top
   label on the 16 views that use it. Floating labels test worse on completion
   time, halve their own contrast at the moment they matter, collide with
   placeholders, and confuse autofill and screen readers. They also cannot hold
   "Company registration number" without truncating.
   ========================================================================== */


/* ==========================================================================
   1 · THE CONTROL
   ==========================================================================
   One declaration for every typed control in the product, whatever class it
   carries and whether it carries one at all.

   The bare-element selectors are scoped to form contexts rather than applied
   globally: a global `input {}` would catch the OTP boxes, the table filter
   chips and anything a page builds by hand. Inside a <form>, a .mis-label, a
   .field or a modal body, a bare input IS a form field and should look like
   one — that assumption holds across all 181 views.
   ========================================================================== */
:where(
    :is(form, .mis-label, .mis-form, .field, .modal-body, .card-body, .bce-filterrow__field, .bce-toolbar)
    :is(
      input[type="text"], input[type="email"], input[type="password"],
      input[type="search"], input[type="tel"], input[type="url"],
      input[type="number"], input[type="date"], input[type="time"],
      input[type="datetime-local"], input[type="month"], input[type="week"],
      input:not([type]),
      select, textarea
    ):not(.bc-otp input):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"])
  ),
.form-control,
.form-select,
.bc-input,
.bc-select,
.bc-textarea {
  /* 44px is the CI's minimum touch target and this product is used on phones
     at a taxi rank. The old 36px was under it on every screen. */
  /* :where(), NOT :is(), for the context prefix — and this is load-bearing.

     :is() takes the specificity of its most specific argument, which pushed
     this whole selector to roughly (0,3,2). Every rule AFTER it that tried to
     refine one control type — `.form-select { padding-right: 40px }` at
     (0,1,0) — silently lost. The chevron then drew at the right edge of a
     select whose text ran all the way under it, which is exactly what a person
     sees as "the dropdown text is overlapping".

     The ENTIRE descendant compound goes inside ONE :where(), not just the
     context prefix. The first attempt wrapped only the prefix and left the
     `:not(...)` chain outside it — and a `:not()` also carries the specificity
     of its argument, so the rule was still (0,5,1) and the select chevron was
     still losing. A :where() that does not enclose the whole compound buys
     nothing.

     At zero specificity the later, narrower rules win the way they read as
     though they should, and anything added below can refine a control without
     having to think about specificity at all. The explicit class selectors
     below stay OUTSIDE the :where() so they keep their natural (0,1,0) and
     still beat the upstream sheets. */
  min-height: 44px;
  width: 100%;
  padding: 10px 13px;
  font: inherit;
  font-size: var(--bc-fs-base, 1rem);
  line-height: 1.45;
  color: var(--bc-text-strong);
  background-color: var(--bc-bg-surface);
  /* background-image is NOT reset here. It was, and it silently beat the select
     chevron three rules down: this selector's :is() context prefix pushes it to
     roughly (0,3,2) while `.form-select` alone is (0,1,0), so the later, more
     specific-looking rule lost. background-color rather than the shorthand is
     what keeps them independent, and nothing upstream sets an image on an input
     that needs clearing. */
  border: 1px solid var(--bc-border-strong);
  border-radius: var(--bc-radius, 8px);   /* the CI's control radius */
  box-shadow: none;
  appearance: none;
  -webkit-appearance: none;
  transition: border-color var(--bc-motion-micro, 150ms) var(--bc-ease-out, ease),
              box-shadow   var(--bc-motion-micro, 150ms) var(--bc-ease-out, ease),
              background-color var(--bc-motion-micro, 150ms) var(--bc-ease-out, ease);
}

/* THE 72px BUG. `.form-control{min-height:72px}` was written for textareas and
   landed on the base class. Textareas get the tall treatment by being
   textareas; nothing else should. */
textarea,
textarea.form-control,
.bc-textarea {
  min-height: 96px;
  padding-top: 10px;
  resize: vertical;
}

/* A <select> is not a text field and should not pretend to be: it gets a
   chevron drawn in the token colour, and room for it. Inline SVG rather than a
   sprite because a background-image cannot reference <use>. */
:where(form, .mis-label, .field, .modal-body, .card-body, .bce-filterrow__field, .bce-toolbar) select:not([multiple]),
.form-select,
.bc-select {
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236E6A63' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  background-size: 16px;
  cursor: pointer;
}
select[multiple], select[size]:not([size="1"]) { background-image: none; padding-right: 13px; }

/* ── States ──────────────────────────────────────────────────────────────
   Hover is a hint, focus is an event. The focus ring is a box-shadow so it
   follows the border radius, and it is never removed — the CI requires a
   visible keyboard focus on every control, and `outline:0` with nothing to
   replace it is how that requirement gets quietly dropped. */
:where(form, .mis-label, .field, .modal-body, .card-body, .bce-filterrow__field, .bce-toolbar) :is(input, select, textarea):hover:not(:disabled):not([readonly]),
.form-control:hover:not(:disabled):not([readonly]),
.form-select:hover:not(:disabled),
.bc-input:hover:not(:disabled):not([readonly]) {
  border-color: var(--bc-grey-400);
}

:where(form, .mis-label, .field, .modal-body, .card-body, .bce-filterrow__field, .bce-toolbar) :is(input, select, textarea):focus,
:where(form, .mis-label, .field, .modal-body, .card-body, .bce-filterrow__field, .bce-toolbar) :is(input, select, textarea):focus-visible,
.form-control:focus, .form-select:focus, .bc-input:focus, .bc-select:focus, .bc-textarea:focus {
  outline: none;
  border-color: var(--bc-accent-500);
  box-shadow: var(--bc-focus-ring);
  background-color: var(--bc-bg-surface);
}

:where(form, .mis-label, .field, .modal-body, .card-body, .bce-filterrow__field, .bce-toolbar) :is(input, select, textarea):disabled,
.form-control:disabled, .form-select:disabled, .bc-input:disabled,
.form-control[readonly], .bc-input[readonly] {
  background-color: var(--bc-grey-50);
  color: var(--bc-text-muted);
  border-color: var(--bc-border);
  cursor: not-allowed;
}
/* readonly is not disabled: the value is still selectable and copyable, which
   is the entire point of a readonly reference or account number. */
.form-control[readonly], .bc-input[readonly] { cursor: text; }

::placeholder { color: var(--bc-text-subtle); opacity: 1; }


/* ==========================================================================
   2 · NUMBERS AND MONEY
   ==========================================================================
   The native number spinner is two 8px hit targets stacked on top of each
   other. On a phone they are unhittable; with a trackpad they fire on an
   accidental scroll over a focused field, which on a FARE field silently
   changes what a passenger is charged. Removed everywhere — the keyboard is
   the input method for a number, and inputmode already tells the phone which
   one to show.
   ========================================================================== */
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Money and quantities read right-aligned in tabular figures, so the decimal
   points line up in a column and a mistyped order of magnitude is visible as a
   shape rather than something you have to read. */
input[type="number"],
input[inputmode="decimal"],
input[inputmode="numeric"],
.field--money input,
input.mis-money {
  font-family: var(--bc-font-money);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  text-align: right;
  letter-spacing: -.01em;
}
/* A currency prefix sits INSIDE the field, so the control is still one
   rectangle. Add .field--money to the .field / .mis-label.

   It shares a grid cell with the input rather than being absolutely positioned
   against the field. The first version anchored it to the bottom of the field,
   which put it inside the control only when the input happened to be the last
   element — the moment a field carried help text underneath, the R slid down
   and landed on top of the help. Sharing the cell is correct whatever else the
   field contains. */
.field--money > .field__prefix,
.field--money > :is(input, .form-control, .bc-input) {
  grid-row: var(--field-control-row, 2);
  grid-column: 1;
}
.field--money > .field__prefix {
  z-index: 1;
  justify-self: start;
  align-self: center;
  padding-left: 13px;
  font-family: var(--bc-font-money);
  font-size: var(--bc-fs-base);
  color: var(--bc-text-muted);
  pointer-events: none;
}
.field--money > :is(input, .form-control, .bc-input) { padding-left: 34px; }
/* The control is on row 2 when a label precedes it, which is every real case;
   a field built without a label sets --field-control-row: 1 on itself. */


/* ==========================================================================
   3 · THE FIELD — label, control, help, error
   ==========================================================================
   .mis-label and .field are the same object and are styled together, so the
   three views already written in .mis-label need no edit.
   ========================================================================== */
.field, .mis-label {
  display: grid;
  gap: var(--bc-s-2, 8px);
  /* The label's own text size, NOT the help text's. .mis-label used to set
     the whole block to --bc-fs-xs and then push the label back up, which made
     every inherited element in a field tiny by default. */
  font-size: var(--bc-fs-base, 1rem);
  min-width: 0;
}

.field__label,
.mis-label > span:first-child,
.form-label,
label.bc-label {
  display: block;
  font-size: var(--bc-fs-sm, .875rem);
  font-weight: 600;
  line-height: 1.35;
  color: var(--bc-text-strong);

  /* The label owns its own gap to the control. This used to be 0, on the theory
     that section 13's column `gap` supplied it — but that gap only ever reached
     columns inside `form .row`, so every stacked field and every `<form class="row">`
     (where the form IS the row) rendered its label flush against the input.
     Spacing belongs here, where it applies everywhere; section 13 zeroes it in the
     one place that supplies its own. */
  margin: 0 0 var(--bc-s-2, 8px);
}

/* Required marker. The asterisk is decoration — the accessible name comes from
   the control's own `required` attribute — so it is hidden from the screen
   reader rather than read out as "star" on every field. */
.field__label .req,
.mis-label .text-danger,
.form-label .text-danger,
abbr[title="required"] {
  color: var(--bc-danger);
  font-weight: 700;
  text-decoration: none;
  margin-left: 2px;
}
/* The legend that makes the asterisk mean something. Put it once at the top of
   a long form; short forms do not need it. */
.form-legend { font-size: var(--bc-fs-xs); color: var(--bc-text-muted); margin: 0 0 var(--bc-s-4); }
.form-legend .req { color: var(--bc-danger); font-weight: 700; }

/* Optional marker, for forms where MOST fields are required and the exception
   is worth naming. Never use both conventions in one form. */
.field__label .opt { color: var(--bc-text-subtle); font-weight: 400; margin-left: 6px; font-size: var(--bc-fs-xs); }

.field__help,
.form-text,
.mis-label > .form-text {
  display: block;
  font-size: var(--bc-fs-xs, .8125rem);
  line-height: 1.5;
  color: var(--bc-text-muted);
  /* Same reasoning as the label above: 4px here, matching Bootstrap's own
     .form-text, rather than relying on a column gap that most forms never get. */
  margin: 4px 0 0;
}

/* ── Validation ──────────────────────────────────────────────────────────
   Server-rendered (asp-validation-for emits .field-validation-error) and
   client-side (.is-invalid / :user-invalid) both land here.

   :user-invalid, not :invalid — :invalid matches an empty required field the
   moment the page loads, so a fresh form would open covered in red before
   anyone has typed anything. :user-invalid waits until the person has actually
   interacted. It is supported everywhere this product runs; browsers without
   it simply get the server-rendered state, which is the one that matters. */
.field__error,
.field-validation-error,
.invalid-feedback,
.bc-error {
  display: block;
  font-size: var(--bc-fs-xs);
  line-height: 1.5;
  font-weight: 500;
  color: var(--bc-danger-fg);
  margin: 0;
}
/* Never colour alone — the CI is explicit about that, and a red border is
   invisible to a red-green colourblind operator. The message carries a mark. */
.field__error:not(:empty)::before,
.field-validation-error:not(:empty)::before,
.bc-error:not(:empty)::before {
  content: "!";
  display: inline-grid; place-items: center;
  width: 14px; height: 14px; margin-right: 6px;
  border-radius: 50%;
  background: var(--bc-danger); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 1;
  vertical-align: -2px;
}

.is-invalid,
.input-validation-error,
:where(form, .field, .mis-label) :is(input, select, textarea):user-invalid {
  border-color: var(--bc-danger) !important;
  background-color: var(--bc-danger-bg);
}
.is-invalid:focus,
.input-validation-error:focus,
:where(form, .field, .mis-label) :is(input, select, textarea):user-invalid:focus {
  box-shadow: 0 0 0 3px rgba(181, 58, 58, .26) !important;
}

/* A field carrying an error hides its help text — two lines of small print
   under one control, one of them red, is where people stop reading. */
.field:has(.field__error:not(:empty)) .field__help,
.mis-label:has(.field-validation-error:not(:empty)) .form-text { display: none; }

/* Validation summary at the top of a form. */
.validation-summary-errors {
  border: 1px solid var(--bc-danger);
  background: var(--bc-danger-bg);
  color: var(--bc-danger-fg);
  border-radius: var(--bc-radius-lg, 12px);
  padding: var(--bc-s-4) var(--bc-s-5);
  margin-bottom: var(--bc-s-5);
  font-size: var(--bc-fs-sm);
}
.validation-summary-errors ul { margin: 0; padding-left: 1.1rem; }
.validation-summary-errors li + li { margin-top: 4px; }
.validation-summary-valid { display: none; }


/* ==========================================================================
   4 · GROUPS AND RHYTHM
   ==========================================================================
   The spacing rule for this product, stated once: 16px INSIDE a group, 32px
   BETWEEN groups. Mike's standing note on this — related things together, real
   air between them, professional over dense — and it is the single thing that
   separated the old modals from looking considered.
   ========================================================================== */
.mis-form { display: grid; gap: var(--bc-s-8, 32px); max-width: 780px; }
.mis-form__section, .fieldset { display: grid; gap: var(--bc-s-4, 16px); border: 0; padding: 0; margin: 0; }
.mis-form__section > h2,
.fieldset > legend,
.fieldset__title {
  font-family: var(--bc-font-display);
  font-size: var(--bc-fs-h3, 1rem);
  font-weight: 600;
  color: var(--bc-text-strong);
  margin: 0 0 calc(var(--bc-s-2) * -1) 0;   /* the gap below supplies the rest */
  padding: 0; float: none; width: auto;
}
.fieldset__hint { font-size: var(--bc-fs-xs); color: var(--bc-text-muted); margin: 0; }

/* Fields side by side. Collapses to one column at 640px — two 44px controls on
   a 390px phone is how you get a form nobody completes. */
.field-row { display: grid; gap: var(--bc-s-4, 16px); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.field-row--tight { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
@media (max-width: 640px) { .field-row, .field-row--tight { grid-template-columns: 1fr; } }

/* Bootstrap's .row inside a form body gets the same rhythm, so the ~99 legacy
   views pick the spacing up without being touched. */
.modal-body .row > [class*="col-"],
.mis-form .row > [class*="col-"] { margin-bottom: 0; }
.modal-body .row { row-gap: var(--bc-s-4); }

/* Consecutive fields written without a wrapper — the legacy idiom. */
.modal-body > .mis-label + .mis-label,
.modal-body > .field + .field,
.modal-body > .mis-label + .row,
.modal-body > .row + .mis-label { margin-top: var(--bc-s-5, 20px); }


/* ==========================================================================
   5 · CHOICE CONTROLS
   ==========================================================================
   A checkbox is a control with a label, not a label with a control: the whole
   row is the hit target, and the box is drawn rather than native so it takes
   the accent token and the same focus ring as everything else.
   ========================================================================== */
.field-check, .form-check, .bc-check {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: var(--bc-s-3, 12px);
  align-items: start;
  min-height: 44px;                  /* the ROW is the touch target */
  align-content: center;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font-size: var(--bc-fs-sm);
  color: var(--bc-text-body);
}
.field-check input[type="checkbox"], .field-check input[type="radio"],
.form-check input[type="checkbox"], .form-check input[type="radio"],
.bc-check input[type="checkbox"], .bc-check input[type="radio"],
.form-check-input {
  appearance: none; -webkit-appearance: none;
  width: 20px; height: 20px; min-height: 0;
  margin: 0; padding: 0;
  border: 1.5px solid var(--bc-border-strong);
  border-radius: 5px;
  background: var(--bc-bg-surface);
  display: grid; place-content: center;
  cursor: pointer;
  transition: background-color var(--bc-motion-micro, 150ms), border-color var(--bc-motion-micro, 150ms);
}
.field-check input[type="radio"], .form-check input[type="radio"], .form-check-input[type="radio"] { border-radius: 50%; }

.form-check-input:checked,
.field-check input:checked, .form-check input:checked, .bc-check input:checked {
  background-color: var(--bc-accent-500);
  border-color: var(--bc-accent-500);
}
/* The tick is drawn in ink-on-accent, so it stays readable on a gold house
   accent and on a dark domain accent alike. */
.form-check-input[type="checkbox"]:checked::before,
.field-check input[type="checkbox"]:checked::before,
.bc-check input[type="checkbox"]:checked::before {
  content: "";
  width: 11px; height: 11px;
  background-color: var(--cp-ink-on-accent);
  clip-path: polygon(14% 45%, 0 60%, 39% 100%, 100% 22%, 84% 8%, 38% 68%);
}
.form-check-input[type="radio"]:checked::before,
.field-check input[type="radio"]:checked::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  background-color: var(--cp-ink-on-accent);
}
.form-check-input:focus-visible,
.field-check input:focus-visible, .form-check input:focus-visible, .bc-check input:focus-visible {
  outline: none; box-shadow: var(--bc-focus-ring);
}
.form-check-label, .field-check > span, .bc-check > span { cursor: pointer; line-height: 1.45; padding-top: 1px; }

/* Bootstrap's switch. */
.form-switch .form-check-input {
  width: 38px; border-radius: 999px; place-content: center start; padding: 2px;
}
.form-switch .form-check-input::before {
  content: ""; width: 16px; height: 16px; border-radius: 50%;
  background: var(--bc-grey-400); clip-path: none;
  transition: transform var(--bc-motion-micro, 150ms) var(--bc-ease-out, ease), background-color var(--bc-motion-micro, 150ms);
}
.form-switch .form-check-input:checked::before { background: var(--cp-ink-on-accent); transform: translateX(18px); }


/* ==========================================================================
   6 · INPUT GROUPS AND FLOATING LABELS
   ========================================================================== */
.input-group { display: flex; align-items: stretch; width: 100%; }
.input-group > :is(.form-control, .form-select, input, select) { flex: 1 1 auto; width: 1%; min-width: 0; }
.input-group > :not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -1px; }
.input-group > :not(:last-child)  { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.input-group-text {
  display: flex; align-items: center;
  padding: 0 13px;
  background: var(--bc-grey-50);
  border: 1px solid var(--bc-border-strong);
  border-radius: var(--bc-radius, 8px);
  color: var(--bc-text-muted);
  font-size: var(--bc-fs-sm);
  white-space: nowrap;
}
/* The focused member of a group must sit above its neighbours or the ring is
   clipped by the next control's border. */
.input-group > :focus, .input-group > :focus-within { z-index: 2; position: relative; }
/* Bootstrap's merged variant collapses the seam between field and adornment. */
.input-group-merge .form-control { border-right: 0; }
.input-group-merge .input-group-text { border-left: 0; background: var(--bc-bg-surface); }

/* FLOATING LABELS ARE RETIRED. Rather than edit the 16 views that use
   .form-floating, the pattern is degraded here into the same top-label field
   everyone else gets: the wrapper becomes the grid, the absolutely-positioned
   label returns to the flow above the control, and the padding Materio added
   to make room for it comes back off. Those views now match the rest of the
   product without being touched. */
.form-floating {
  position: static;
  display: grid;
  gap: var(--bc-s-2, 8px);
  margin-bottom: 0;
}
.form-floating > label {
  position: static;
  order: -1;                                   /* Materio puts the label AFTER the input */
  top: auto; left: auto;
  padding: 0;
  pointer-events: auto;
  transform: none;
  font-size: var(--bc-fs-sm);
  font-weight: 600;
  color: var(--bc-text-strong);
  opacity: 1;
  height: auto; width: auto;
  line-height: 1.35;
}
.form-floating > .form-control,
.form-floating > .form-select,
.form-floating > input {
  height: auto;
  min-height: 44px;
  padding: 10px 13px;
  line-height: 1.45;
}
.form-floating > .form-select { padding-right: 40px; }
/* Placeholders on a floating-label control were only ever there to trigger the
   float. Now that the label is permanent they are noise sitting in the field. */
.form-floating > .form-control::placeholder { color: transparent; }
.form-floating > .form-control:focus::placeholder { color: var(--bc-text-subtle); }


/* ==========================================================================
   7 · THE CLOSE BUTTON — the overlapping X
   ==========================================================================
   The markup across 71 views is:

       <button class="btn-close" aria-label="Close">Close</button>

   Real Bootstrap hides that label behind a background-image. The compat layer
   drew the × with ::before/::after and never hid the text, so the word "Close"
   and the glyph stacked on top of each other on every modal in the product.

   Fixed HERE rather than in the 71 views, and fixed by collapsing the text
   rather than `font-size:0`, which some browsers still reserve line-box space
   for. The accessible name survives: it comes from aria-label, and where a
   view forgot one, ::after supplies it below.
   ========================================================================== */
.btn-close {
  position: relative;
  display: grid;
  place-items: center;
  width: 36px; height: 36px;          /* was 30px — under the 44px guidance, but
                                         this sits in a 56px header row whose own
                                         padding carries the rest of the target */
  flex: none;
  padding: 0;
  border: 0;
  border-radius: var(--bc-radius, 8px);
  background: transparent;
  color: var(--bc-text-muted);
  cursor: pointer;
  /* Collapse the label. The text stays in the DOM for the rare view with no
     aria-label, and stays reachable to assistive tech, but occupies nothing. */
  font-size: 0;
  line-height: 0;
  text-indent: -9999px;
  overflow: hidden;
  white-space: nowrap;
  transition: background-color var(--bc-motion-micro, 150ms), color var(--bc-motion-micro, 150ms);
}
/* The × itself, drawn as two strokes so it inherits colour and needs no asset. */
.btn-close::before,
.btn-close::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 15px; height: 1.75px;
  border-radius: 2px;
  background: currentColor;
  text-indent: 0;
}
.btn-close::before { transform: translate(-50%, -50%) rotate(45deg); }
.btn-close::after  { transform: translate(-50%, -50%) rotate(-45deg); }

.btn-close:hover { background: var(--bc-grey-100); color: var(--bc-text-strong); opacity: 1; }
.btn-close:focus-visible { outline: none; box-shadow: var(--bc-focus-ring); color: var(--bc-text-strong); }


/* ==========================================================================
   8 · THE MODAL SHELL
   ==========================================================================
   Most forms in this product are IN a modal, so the dialog's own spacing is
   part of the form's spacing. Same rule as section 4: air between groups.
   ========================================================================== */
.modal-content {
  border: 1px solid var(--bc-border);
  border-radius: var(--bc-radius-xl, 16px);
  background: var(--bc-bg-surface);
  box-shadow: var(--bc-shadow-lg);
  overflow: hidden;                    /* so the header/footer fills respect the radius */
}

.modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bc-s-4);
  padding: var(--bc-s-5, 20px) var(--bc-s-6, 24px);
  border-bottom: 1px solid var(--bc-border);
  background: var(--bc-bg-surface);
}
.modal-header > :is(h1, h2, h3, h4, h5, .modal-title) {
  font-family: var(--bc-font-display);
  font-size: var(--bc-fs-h2, 1.125rem);
  font-weight: 600;
  line-height: 1.3;
  color: var(--bc-text-strong);
  margin: 0;
  min-width: 0;
}
/* A one-line description under the dialog title, where the title alone does not
   say what the dialog will do. */
.modal-header .modal-sub { font-size: var(--bc-fs-sm); color: var(--bc-text-muted); margin: 4px 0 0; font-weight: 400; }

.modal-body {
  padding: var(--bc-s-6, 24px);
  display: grid;
  gap: var(--bc-s-5, 20px);            /* THE fix for cramped modals: every direct
                                          child of a body is a group, and groups
                                          get real space between them */
  overflow-y: auto;
}

.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--bc-s-3, 12px);
  padding: var(--bc-s-5, 20px) var(--bc-s-6, 24px);
  border-top: 1px solid var(--bc-border);
  background: var(--bc-grey-25);       /* a quiet shelf, so the actions read as a
                                          bar rather than as more form */
}
/* Buttons in a dialog footer are decisions, not table actions: full scale, and
   the confirming one last, where the eye finishes. */
.modal-footer .btn,
.modal-footer .bc-btn { height: 44px; min-height: 44px; padding-inline: 20px; font-size: var(--bc-fs-sm); }
.modal-footer .btn-sm { height: 44px; min-height: 44px; font-size: var(--bc-fs-sm); padding-inline: 20px; }
/* A destructive action moves to the left, away from the safe default, so the
   two are not adjacent under a moving cursor. */
.modal-footer .btn-danger, .modal-footer .bc-btn--danger { margin-right: auto; }

@media (max-width: 560px) {
  .modal-body { padding: var(--bc-s-5, 20px); gap: var(--bc-s-4); }
  .modal-header, .modal-footer { padding-inline: var(--bc-s-5, 20px); }
  /* Stacked, full-width, confirm on top — the thumb reaches the bottom of the
     screen first and that is where Cancel should be. */
  .modal-footer { flex-direction: column-reverse; align-items: stretch; }
  .modal-footer .btn, .modal-footer .bc-btn { width: 100%; margin-right: 0; }
}


/* ==========================================================================
   9 · CONTROL ROWS — filter bars and toolbars
   ==========================================================================
   `.bce-toolbar` / `.bce-filterrow` hold a run of labelled controls followed by
   bare buttons: Show / From / To / Rows, then Apply and Reset.

   Two things were wrong and they compound. Each labelled field is a stacked
   block — caption, gap, control — so it stands ~65px tall, while a bare button
   is 32px. The row centred them, which left the buttons floating above the
   controls' baseline and below their caption line: aligned to nothing. And
   `.btn-sm` pinned those buttons to 32px next to 44px controls, so even level
   they would not have matched.

   Align to the BOTTOM, not the centre. In a row of stacked fields the control
   baseline is the line the eye reads along, and a button that acts on those
   controls belongs on it. Then give the buttons the controls' own height so
   the row is one band rather than three.
   ========================================================================== */
.bce-toolbar,
.bce-filterrow {
  align-items: end;
  row-gap: var(--bc-s-4, 16px);
}

/* The caption sits above its control with the same rhythm as .field. */
.bce-filterrow__field {
  display: grid;
  gap: var(--bc-s-2, 8px);
  font-size: var(--bc-fs-sm, .875rem);
  font-weight: 600;
  color: var(--bc-text-strong);
  min-width: 0;
}
.bce-filterrow__field :is(input, select, textarea) { font-weight: 400; }

/* Buttons standing in a control row match the controls they act on. This is
   the same rule the bridge already applies to `.bc-row > .bc-btn`; the toolbar
   variant was simply never covered. */
:is(.bce-toolbar, .bce-filterrow) > :is(.btn, .bc-btn, button[type="submit"], a.btn),
:is(.bce-toolbar, .bce-filterrow) > .btn-group > .btn {
  height: 44px;
  min-height: 44px;
  padding-inline: 18px;
  font-size: var(--bc-fs-sm, .875rem);
  flex: none;
}

/* A toolbar that carries only buttons — no labelled fields — has no control
   baseline to sit on, so it centres as before. */
.bce-toolbar--meta { align-items: center; }


/* ==========================================================================
   10 · A LINK STYLED AS A BUTTON IS A BUTTON
   ==========================================================================
   `enterprise.components.css` sets `.theme-enterprise a { color: var(--bc-text-accent) }`
   at (0,1,1), which beats every button variant's own `color` at (0,1,0). So
   `<a class="btn btn-secondary">` came out in the ACCENT colour while the
   `<button class="btn btn-secondary">` beside it came out in ink — two controls
   with identical classes, rendered differently because one is an anchor. On a
   white-labelled domain that made Reset show up in the domain's brand colour
   next to a black Apply.

   Fixed here rather than in enterprise.components.css, which is vendored from
   the BCDF pack and would lose the edit on the next pull. Each variant is
   restated at (0,1,1) so it ties on specificity and wins on source order.
   ========================================================================== */
a.btn, a.bc-btn { text-decoration: none; }
a.btn:hover, a.bc-btn:hover { text-decoration: none; }

a.btn-primary, a.bc-btn--primary          { color: var(--cp-ink-on-accent); }
a.btn-secondary, a.btn-outline-secondary,
a.bc-btn--secondary, a.bc-btn--ghost      { color: var(--bc-brand-600); }
a.btn-success, a.btn-danger, a.btn-dark,
a.btn-navy, a.bc-btn--danger              { color: #fff; }
a.btn-outline-primary                     { color: var(--bc-text-accent); }
a.btn-outline-danger                      { color: var(--bc-danger-fg); }
a.btn-text, a.btn-link                    { color: var(--bc-text-accent); }


/* ==========================================================================
   11 · NUMERALS
   ==========================================================================
   Money, rates, counts and timestamps are set in INTER with tabular figures,
   not in a monospace face.

   IBM Plex Mono was carrying them, and its zero has a dot through it. In a
   column reading "R 0,00" that dot is the first thing anyone notices, and it
   is not what the CI asked for: the guidelines name Inter as the primary
   family and ask for "tabular figures for money, rates, counts and
   timestamps" — a monospace face was my addition, not theirs.

   Inter's tabular figures do the actual job. Every digit occupies the same
   advance width, so decimal points line up down a column and a mistyped order
   of magnitude still reads as a shape. `tnum` is what delivers that; a
   monospace family was only ever a blunt way of getting the same thing, and it
   brought a second typeface and a dotted zero with it.

   `slnum`/`zero` are switched OFF explicitly so no fallback face can
   reintroduce a slashed or dotted zero.
   ========================================================================== */
:root, .theme-enterprise {
  --bc-font-money: "Inter", Arial, "Nimbus Sans", sans-serif;
  --bc-font-mono:  "Inter", Arial, "Nimbus Sans", sans-serif;
}

.bc-money, .mis-money, .text-monospace, .font-monospace,
td.bc-num, .bc-num, .bce-money, .bce-kpi__value,
input[type="number"], input[inputmode="decimal"], input[inputmode="numeric"],
.field--money input, .field--money .field__prefix {
  font-family: var(--bc-font-money);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1, "zero" 0, "ss01" 0;
  letter-spacing: 0;
}

/* Table cells holding figures get tabular numerals whether or not the view
   remembered to mark them up — a column of rands that does not line up is the
   single most common complaint about a finance table, and it should not depend
   on 99 views having used the right class. */
.bc-table td, .bce-table td, .table td { font-variant-numeric: tabular-nums; }

/* The MONOSPACE COMPENSATION HAS TO COME OFF WITH THE MONOSPACE FACE.

   `.bc-money` and `.bce-money` carried `word-spacing: -.22em`, and it was
   correct while they were set in IBM Plex Mono: en-ZA groups thousands with a
   no-break space, which in a monospace face is a full character wide, so
   "R 106 200,00" opened a visible gap mid-number. In Inter that space is
   already a normal word gap, and -.22em now pulls "106" and "200" into each
   other — the exact fault it was added to fix, in the opposite direction.

   Two sheets set it, one of them vendored, so it is reset here rather than
   edited in both. The letter-spacing goes too: it was closing up a monospace
   advance width that no longer exists. */
.bc-money, .bce-money, .mis-money, .bc-num, .bce-kpi__value,
.field--money input, input[type="number"] {
  word-spacing: normal;
  letter-spacing: 0;
}

/* TRUE identifiers — an audit reference, a transaction id, a wallet GUID —
   keep a monospace face, because there the job is telling 0 from O and 1 from
   l rather than lining up a decimal point. They are deliberately NOT money. */
.bc-ref, .mis-ref, code, kbd, samp, pre {
  font-family: ui-monospace, "Cascadia Mono", "JetBrains Mono", Consolas, monospace;
  font-feature-settings: "zero" 0;
}


/* ==========================================================================
   12 · TABS
   ==========================================================================
   Lives in this sheet, not mis-legacy.css, for the same reason everything else
   here does: it has to load last. `.nav-tabs` is styled in three upstream
   sheets and `li.nav-item` collides with the SIDEBAR's `.nav-item` from
   finance.css, so anything written earlier is arguing with one of them.

   THE ALIGNMENT BUG. The markup is:

       <li class="nav-item"><a class="nav-link">Overview</a></li>
       <li class="nav-item"><a class="nav-link">Payment Points <span class="badge">6</span></a></li>

   A badge is an inline-block taller than the text beside it, so it grows the
   line box of the link that carries one. "Payment Points" then sits centred in
   a tall box while "Overview" sits at the top of a short one — the captions
   end up on two different baselines, a few pixels apart, which reads as
   sloppy rather than as a bug anyone can name.

   Making each link a flex row with a shared min-height fixes it at the source:
   the badge no longer participates in a line box at all, so it cannot move the
   text, and every tab is the same height whether it carries a count or not.
   ========================================================================== */
.nav-tabs {
  display: flex;
  align-items: stretch;      /* every tab the same height … */
  flex-wrap: wrap;
  gap: var(--bc-s-6, 24px);
  padding: 0;
  margin: 0 0 var(--bc-s-6, 24px);
  border-bottom: 1px solid var(--bc-border);
  list-style: none;
}
.nav-tabs > li.nav-item {
  /* Neutralise the sidebar `.nav-item` that finance.css applies to every
     element carrying the class — width:100%, its own padding and a hover fill
     have no business on a tab. */
  display: flex;
  width: auto;
  margin: 0;
  padding: 0;
  background: none;
  border-radius: 0;
  box-shadow: none;
}

.nav-tabs .nav-link {
  display: inline-flex;
  align-items: center;       /* … and the caption centred within it */
  gap: var(--bc-s-2, 8px);
  min-height: 44px;          /* the CI's touch target — a tab is a control */
  padding: 0 0 10px;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--bc-text-muted);
  font-size: var(--bc-fs-sm, .875rem);
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  position: relative;
  transition: color var(--bc-motion-micro, 150ms) var(--bc-ease-out, ease);
}
.nav-tabs .nav-link:hover { color: var(--bc-text-strong); background: none; }

/* The active rule is drawn as a pseudo-element flush with the container's
   border, not as a box-shadow inset — an inset shadow sits INSIDE the link's
   padding box and so floated a couple of pixels above the divider it is meant
   to sit on. */
.nav-tabs .nav-link.active {
  color: var(--bc-text-strong);
  background: none;
  box-shadow: none;
}
.nav-tabs .nav-link.active::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;   /* -1px covers the container's own border */
  height: 2px;
  background: var(--bc-accent-500);
  border-radius: 2px 2px 0 0;
}
.nav-tabs .nav-link:focus-visible {
  outline: none;
  box-shadow: var(--bc-focus-ring);
  border-radius: var(--bc-radius-sm, 6px);
}

/* ── The count ───────────────────────────────────────────────────────────
   ONE treatment for all of them. The views use bg-label-primary on one tab,
   bg-label-info on the next and bg-label-success on the third — three colours
   for the same thing, a count, which invites the reader to look for a meaning
   that is not there. Worse on a white-labelled domain: whichever of the three
   happens to match the domain accent lights up for no reason.

   Fixed height and line-height so it cannot grow the row it sits in, which is
   the other half of the alignment fix. */
.nav-tabs .nav-link .badge,
.nav-tabs .nav-link .bc-badge {
  display: inline-grid;
  place-items: center;
  min-width: 22px;
  height: 22px;
  padding: 0 7px;
  margin: 0;
  border-radius: 999px;
  background: var(--bc-grey-100) !important;
  color: var(--bc-text-muted) !important;
  font-size: var(--bc-fs-xs, .8125rem);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
/* The active tab's count follows the tab, so the pair reads as one thing. */
.nav-tabs .nav-link.active .badge,
.nav-tabs .nav-link.active .bc-badge {
  background: var(--bc-accent-50) !important;
  color: var(--bc-text-accent) !important;
}
/* A zero is not news. Kept legible, but it stops competing with counts that
   actually have something in them. */
.nav-tabs .nav-link .badge:not(.badge--keep) { font-variant-numeric: tabular-nums; }

@media (max-width: 640px) {
  .nav-tabs { gap: var(--bc-s-4, 16px); overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; }
  .nav-tabs::-webkit-scrollbar { display: none; }
}


/* ==========================================================================
   13 · FORM ROWS WHERE THE COLUMNS DO NOT MATCH
   ==========================================================================
   The commonest layout in this product, and it has never lined up:

       .row
         .col-sm-4   label + input + help        ("Amount (R)" … "Available: R 1 676,16")
         .col-sm-4   label + input               ("Wallet PIN")
         .col-sm-4   button only                 ("Transfer Now")

   Three columns, three different internal structures, and a flex row that
   aligns none of them. The labels land on different baselines, the inputs
   land on different baselines, and the button floats below both. Every
   column is individually correct, which is why it survives review — you have
   to see them side by side to notice.

   WHY NOT subgrid, which is the textbook answer: it needs `.row` to become a
   real grid, and `.row` here is Bootstrap's flex 12-column system driven by
   ~40 different col-* variants across 99 views. Rewriting that to make three
   fields line up would be a large change with a long tail of broken layouts.

   So the fix works WITH the flex grid instead: make every form column a
   column-flex box, and reserve the label row in the columns that have no
   label. One line of empty space where the caption would have been is all it
   takes — the controls below it then start at the same y, and the button with
   them. Same principle as the CSS `visibility: hidden` spacer, minus the
   markup.
   ========================================================================== */
:is(form, .mis-form, .modal-body) .row > [class*="col-"],
:is(form, .mis-form, .modal-body).row > [class*="col-"] {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  min-width: 0;
  /* The COLUMN owns the label-to-control spacing, not the label. That is what
     lets a reserved caption row and a real one occupy identical space: the
     spacer below is the label's height and nothing more, and this gap supplies
     the rest for both. The first version put a margin on the spacer instead
     and left the button 8px low — the label carries no margin, so the spacer
     was one gap taller than the thing it stood in for. */
  gap: var(--bc-s-2, 8px);
}
:is(form, .mis-form, .modal-body) .row,
:is(form, .mis-form, .modal-body).row { align-items: flex-start; }

/* Inside those flex columns the gap above supplies the label-to-control spacing,
   so the label's own margin is dropped here — otherwise the two stack and the
   reserved caption row below no longer matches the height of a real label. */
:is(form, .mis-form, .modal-body) .row > [class*="col-"] > :is(label, .form-label, .field__label, .mis-label),
:is(form, .mis-form, .modal-body).row > [class*="col-"] > :is(label, .form-label, .field__label, .mis-label) {
  margin-bottom: 0;
}

/* The reserved caption row. 1.35 line-height on the .875rem label plus the
   8px gap the field uses — the same numbers section 3 sets, so a real label
   and a reserved one occupy identical space.

   :has() carries this. A column that already has a label of any of the four
   idioms in this codebase is left alone. */
:is(form, .mis-form, .modal-body) .row > [class*="col-"]:not(
    :has(> label, > .form-label, > .field__label, > .mis-label, > .form-floating)
  )::before,
:is(form, .mis-form, .modal-body).row > [class*="col-"]:not(
    :has(> label, > .form-label, > .field__label, > .mis-label, > .form-floating)
  )::before {
  content: "";
  display: block;
  /* Exactly one line of the label's own type — same font-size and line-height
     section 3 sets on .form-label / .field__label. No margin: the column's gap
     handles that for a real label and a reserved one alike. */
  height: calc(var(--bc-fs-sm, .875rem) * 1.35);
  flex: none;
}

/* A column holding nothing but an action: the button takes the control's own
   height so the row reads as one band, and it stops stretching to whatever
   the tallest neighbour happens to be. */
:is(form, .mis-form, .modal-body) .row > [class*="col-"] > .btn,
:is(form, .mis-form, .modal-body) .row > [class*="col-"] > .bc-btn,
:is(form, .mis-form, .modal-body).row > [class*="col-"] > .btn,
:is(form, .mis-form, .modal-body).row > [class*="col-"] > .bc-btn {
  height: 44px;
  min-height: 44px;
  flex: none;
  align-self: stretch;
}

/* Help text hangs BELOW the row's shared control line rather than pushing its
   own column up, so a field with a hint and one without still line up. The
   column's gap spaces it — a margin here would double that. */
:is(form, .mis-form, .modal-body) .row > [class*="col-"] > :is(.form-text, .field__help),
:is(form, .mis-form, .modal-body).row > [class*="col-"] > :is(.form-text, .field__help) {
  margin: 0;
}

/* The same treatment for a bare flex row of fields that is not Bootstrap's
   grid — the `.d-flex` / `.gap-*` idiom a few of the newer screens use. */
:is(form, .mis-form, .modal-body) .d-flex.gap-2 > .mis-label,
:is(form, .mis-form, .modal-body) .d-flex.gap-3 > .mis-label { align-self: flex-start; }


/* ==========================================================================
   13 · CONTROL + BUTTON ROWS, AND SENTENCE ALERTS   (2026-09-24)
   --------------------------------------------------------------------------
   Mike, 24 Sep: "where there is a button right next to a textbox or form
   control it does not align — it needs to align perfectly."

   Measured on Domain/Details → Wallet App Registration, with this exact CSS
   stack: the URL field was 44px (the §2 control height) and its Copy button
   32px. `.input-group` asks its children to stretch, but `.btn-sm` (bridge,
   height:32px) and `.btn` (compat, height:36px) set an explicit HEIGHT, and an
   explicit height beats align-items:stretch. So every input-group button sat
   short of its field, hanging off the top edge — 38 views use input groups.

   The same happens outside input groups wherever a view puts a field and a
   button side by side in a flex row (UserAdmin "Update", Payroll "Add wallet",
   the list filters). Those rows centre their children, so a 32px button sat
   6px inside a 44px field on both sides.

   Rule: a button that shares a row with a form control IS a control, and
   takes the control height. `.btn-sm` inside such a row is overridden on
   purpose — "small" was chosen to look tidy next to the old 30px inputs, and
   those inputs no longer exist.
   ========================================================================== */
.input-group > :is(.btn, .bc-btn, a.btn, button) {
  height: auto;
  min-height: 44px;
  align-self: stretch;
  flex: 0 0 auto;
  padding-block: 0;
  padding-inline: 16px;
  font-size: var(--bc-fs-sm, .875rem);
  white-space: nowrap;
  box-shadow: none;
}
/* A field and a button laid side by side without an input-group. The :has()
   limits this to rows that really do hold a text-like control, so ordinary
   button bars keep their own sizes. */
:is(.d-flex, .d-inline-flex, .hstack):has(> :is(.form-control, .form-select, .bc-input, .bc-select,
    input:not([type="checkbox"], [type="radio"], [type="hidden"], [type="range"], [type="color"], [type="file"]),
    select)) > :is(.btn, .bc-btn, a.btn, button) {
  height: 44px;
  min-height: 44px;
  padding-block: 0;
  flex: none;
  white-space: nowrap;
}

/* ALERTS THAT ARE ONE SENTENCE.
   The base .alert is a flex row (compat, mis-legacy). That suits the
   structured form — an icon followed by a <div> of content — but most MIS
   alerts are a sentence: icon, text, a <span>, an <a href>Edit</a>, more text.
   In a flex row every one of those becomes its own column, so "Set one in",
   the URL, "Edit" and "to brand them." stood side by side as four ragged
   columns. .cp-note (bridge §3.6) patched one instance; this is the general
   fix, and it leaves the structured form (any block child) alone.

   A sentence alert flows as ordinary text. Its leading icon is lifted into the
   left padding: absolutely positioned with `top` left at auto, so it keeps its
   static position on the FIRST line whatever the alert's padding is, and every
   wrapped line aligns under the words rather than under the icon. Being out of
   flow, it also stops the whitespace after it from indenting the first line. */
.alert:not(:has(> :is(div, p, ul, ol, dl, table, form, h1, h2, h3, h4, h5, h6, .d-flex, .row))) {
  display: block;
  line-height: 1.55;
}
.alert:has(> i[class*="ri-"]:first-child):not(:has(> :is(div, p, ul, ol, dl, table, form, h1, h2, h3, h4, h5, h6, .d-flex, .row))) {
  position: relative;
  padding-left: calc(14px + 1.75em) !important;
}
.alert:has(> i[class*="ri-"]:first-child):not(:has(> :is(div, p, ul, ol, dl, table, form, h1, h2, h3, h4, h5, h6, .d-flex, .row))) > i[class*="ri-"]:first-child {
  position: absolute;
  left: 14px;
  margin: 0 !important;
  line-height: inherit;
}
/* A URL or code inside a sentence alert breaks where it must, inside the flow,
   rather than forcing its own column. */
.alert :is(.font-monospace, code, .bc-ref) { overflow-wrap: anywhere; word-break: normal; }
