/* Solis.BackOffice.Portal — shared UI components (the "solis-*" design system's building blocks).

   Buttons, form fields, alerts, pills, cards and the small composites built from them (OTP boxes,
   password strength meter, QR frame, setup key). Nothing here knows which screen it is on.

   These lived in solis-auth.css until they didn't: the lock overlay, the notifications page and the
   account pages all reuse them, so every one of those screens was pulling in an auth-named
   stylesheet to get a button. Screen-specific layout stays in its own file (solis-auth.css,
   solis-shell.css, solis-user-account.css, solis-notifications.css) — this file is only the parts that
   are genuinely shared.

   Depends on solis-tokens.css for every --solis-* custom property; load that first.

   HOW TO VARY A COMPONENT FOR ONE SCREEN: don't. Override it from that screen's own stylesheet.
   If the auth screens want a taller button, or the notifications toolbar wants a different field
   height, that rule belongs in solis-auth.css / solis-notifications.css — scoped under a selector
   that screen owns — not as a new branch or a widened rule in here. Both layouts load this file
   before the per-screen ones, so the screen's rule wins on order without needing !important or a
   contrived selector.

   The point is that a change made for one screen can never reach the others. A variant only earns
   a place in this file once more than one screen needs it, and then it comes in as a named
   modifier (--lg, --outline) rather than by loosening what the base class already means. */

/* The `hidden` attribute must actually hide.

   Any class that sets `display` outranks the UA stylesheet's `[hidden] { display: none }`, so an
   element toggled by the attribute stays on screen while every script and test reports it hidden.
   This has cost real time four separate times — a dialog icon that kept its column, a field error
   that never went away, a drawer field revealed before it should be, a drawer footer showing under
   a read-only panel — and each was fixed with one more per-class rule until this one.

   !important is warranted here in a way it is not for the variant overrides described above:
   `hidden` is a state, not a style, and nothing should be able to out-specify it. */
[hidden] {
  display: none !important;
}

.solis-wordmark {
  height: 20px;
  width: auto;
  color: var(--solis-text-on-accent);
}

.solis-field {
  display: flex;
  flex-direction: column;
}

.solis-field__row {
  display: flex;
  justify-content: space-between;
  margin-bottom: var(--solis-space-xs);
}

.solis-field__label {
  font-size: var(--solis-fs-sm);
  font-weight: 600;
  color: var(--solis-text-secondary);
  display: block;
  margin-bottom: var(--solis-space-xs);
}

.solis-field__row .solis-field__label {
  margin-bottom: 0;
}

/* The "(optional)" beside a field label. Parenthesised and spaced, because as bare muted text it
   ran straight on from the label — "Note for the approver optional" reads as one phrase.
   Shared: this existed twice, byte-identical, as .solis-acct-optional and .solis-messages__optional,
   and a third copy was about to be written for a third drawer. */
/* ---- Outcome ----
   What an event in a timeline came to, as a word in its meaning's ink: every row reads the same quiet way, and a
   failure stands out by its colour, not by a pill no one acts on. */
.solis-outcome {
  display: inline-flex;
  align-items: center;
  gap: var(--solis-space-2xs);
  font-weight: 600;
  white-space: nowrap;
}

.solis-outcome i { font-size: var(--solis-fs-md); }
.solis-outcome--success { color: var(--solis-success-ink); }
.solis-outcome--danger { color: var(--solis-danger-ink); }
.solis-outcome--warn { color: var(--solis-warn-ink); }
.solis-outcome--neutral { color: var(--solis-text-muted); }

/* ---- Select ----
   The native control with a list drawn over it, and a search box once the list is long. The native
   select stays the value and is kept in the DOM, hidden, so a form reads it as it always did. */
.solis-select { position: relative; }

.solis-select > select[data-solis-select] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.solis-select__trigger {
  display: flex;
  align-items: center;
  gap: var(--solis-space-sm);
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  font-size: var(--solis-fs-md);
  color: var(--solis-text-primary);
}

.solis-select__label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Nothing chosen yet reads as a prompt, not as an answer. */
.solis-select.is-empty .solis-select__label { color: var(--solis-text-muted); }

.solis-select__cv { color: var(--solis-text-muted); font-size: var(--solis-fs-lg); }

.solis-select__panel {
  position: absolute;
  z-index: 40;
  left: 0;
  right: 0;
  top: calc(100% + 5px);
  display: none;
  background: var(--solis-surface);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  box-shadow: var(--solis-shadow-pop);
  overflow: hidden;
}

.solis-select.is-open .solis-select__panel { display: block; }

/* Set on open when the window has no room below — see place() in solisSelect.ts. */
.solis-select.is-up .solis-select__panel {
  top: auto;
  bottom: calc(100% + 5px);
}

/* Inset and rounded, off the panel's edge: the panel clips anything drawn outside an option, and
   a square ring against its rounded corner read as cut off. */
.solis-select__option:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: -2px;
}

.solis-select__searchwrap {
  display: flex;
  align-items: center;
  gap: var(--solis-space-sm);
  margin: var(--solis-space-sm);
  padding: var(--solis-space-sm) var(--solis-space-md);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-sm);
  background: var(--solis-surface-muted);
  color: var(--solis-text-muted);
}

.solis-select__searchwrap:focus-within {
  border-color: var(--solis-accent-500);
  background: var(--solis-surface);
}

/* lint-allow: outline-none  (.solis-select__searchwrap:focus-within shows focus above) */
.solis-select__search {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: none;
  font: inherit;
  font-size: var(--solis-fs-md);
  color: var(--solis-text-primary);
}

.solis-select__search::placeholder { color: var(--solis-text-muted); }

.solis-select__list {
  display: grid;
  gap: 1px;
  max-height: 16rem;
  overflow-y: auto;
  padding: var(--solis-space-2xs);
}

.solis-select__option {
  display: grid;
  width: 100%;
  gap: 2px;
  padding: var(--solis-space-sm) var(--solis-space-md);
  border: 0;
  border-radius: var(--solis-radius-sm);
  background: none;
  text-align: left;
  cursor: pointer;
  font: inherit;
  /* A button does not inherit colour — it takes the browser's own, which is black in dark mode. */
  color: var(--solis-text-primary);
}

.solis-select__option:hover { background: var(--solis-surface-muted); }

/* accent-600, not 700: 600 has a dark-mode value and 700 does not, so 700 on the dark tint was
   near-black on brown. */
.solis-select__option.is-on {
  background: var(--solis-accent-50);
  color: var(--solis-accent-600);
  font-weight: 600;
}

.solis-select__option-text { font-size: var(--solis-fs-md); }
.solis-select__option-hint { font-size: var(--solis-fs-xs); color: var(--solis-text-muted); }

.solis-select__empty {
  padding: var(--solis-space-md);
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
}

.solis-field--invalid .solis-select__trigger { border-color: var(--solis-danger-500); }

/* ---- Multi-pick ----
   Several picks from a known list (widgets/multiPick.ts): the chips and a typing field in one box that looks and
   focuses as an input does, over the select's own panel and options. A chip is the shared filter chip. */
.solis-multipick__box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--solis-space-2xs);
  padding-block: var(--solis-space-sm);
  cursor: text;
}

.solis-multipick__box:focus-within {
  border-color: transparent;
  outline: var(--solis-focus-ring);
  outline-offset: 0;
}

.solis-multipick__chips { display: contents; }

/* lint-allow: outline-none  (.solis-multipick__box:focus-within shows focus around the whole field) */
.solis-multipick__input {
  flex: 1;
  min-width: 8rem;
  padding: var(--solis-space-2xs) 0;
  border: 0;
  outline: none;
  background: none;
  font: inherit;
  color: var(--solis-text-primary);
}

.solis-multipick__input::placeholder { color: var(--solis-text-muted); }

/* ---- Phone ----
   A country and a national number, side by side. The number is text with a digits-only filter, not
   type=number: a spinner on a phone number is nonsense, and the browser's own numeric parsing drops
   a leading zero. */
.solis-phone {
  display: flex;
  gap: var(--solis-space-sm);
  align-items: start;
}

.solis-phone__country { flex: 0 0 7rem; }
.solis-phone__number { flex: 1; min-width: 0; }

@media (max-width: 560px) {
  .solis-phone { flex-wrap: wrap; }
  .solis-phone__country { flex: 1 1 100%; }
}

/* A phone number shown to staff (Partials/_PhoneLink): a tel: link that reads as the text around
   it — never the browser's blue. Live only without a mouse, so a desktop keeps it as text to select
   and copy rather than opening a calling app. */
.solis-tel {
  color: inherit;
  text-decoration: none;
}

@media (hover: hover) and (pointer: fine) {
  .solis-tel {
    pointer-events: none;
    cursor: text;
  }
}

/* ---- File drop ----
   A drop target that is also a file input: the label wraps a hidden input, so a click anywhere on
   it opens the picker and a drop writes into the same input. Shared — the first upload screen built
   it, and the next one should not build a second. */
.solis-drop {
  display: grid;
  place-items: center;
  gap: var(--solis-space-xs);
  padding: var(--solis-space-xl) var(--solis-space-lg);
  border: 1px dashed var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface-muted);
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.solis-drop:hover,
.solis-drop:focus-within { border-color: var(--solis-accent-500); }

/* While a file is over it. The dashed edge going solid is what says "let go here". */
.solis-drop.is-over {
  border-style: solid;
  border-color: var(--solis-accent-500);
  background: var(--solis-accent-50);
}

/* Present for the form to read, never seen: the label around it is the control. */
.solis-drop__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.solis-drop__empty {
  display: grid;
  justify-items: center;
  gap: var(--solis-space-2xs);
  color: var(--solis-text-muted);
}

.solis-drop__empty .ti { font-size: 1.75rem; }

.solis-drop__lead {
  font-size: var(--solis-fs-md);
  color: var(--solis-text-secondary);
}

.solis-drop__link { color: var(--solis-accent-500); font-weight: 600; }

.solis-drop__hint { font-size: var(--solis-fs-xs); }

/* Once something is picked, the target becomes a row about that file. */
.solis-drop__file {
  display: flex;
  align-items: center;
  gap: var(--solis-space-md);
  width: 100%;
  text-align: left;
}

.solis-drop__icon { font-size: 1.5rem; color: var(--solis-text-muted); }

.solis-drop__meta { display: grid; min-width: 0; flex: 1; }

.solis-drop__name {
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-primary);
  overflow-wrap: anywhere;
}

.solis-drop__size { font-size: var(--solis-fs-xs); color: var(--solis-text-muted); }

.solis-drop__clear {
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--solis-text-muted);
  padding: var(--solis-space-xs);
  border-radius: var(--solis-radius-sm);
  line-height: 0;
}

.solis-drop__clear:hover { background: var(--solis-surface); color: var(--solis-danger-500); }

/* The invalid state the shared field helper sets, on the target rather than an input nobody sees. */
.solis-field--invalid .solis-drop { border-color: var(--solis-danger-500); }

/* A stack of fields that is not inside a drawer — the drawer's own group (.solis-drawer__group)
   is the same idea, and this is the page-level one. A screen wanting columns lays them out itself. */
.solis-fieldset {
  display: grid;
  gap: var(--solis-space-md);
}

/* A named group of fields inside a form card. The rule above each one does the separating, so the
   groups read apart without a card each. */
.solis-field-group + .solis-field-group {
  margin-top: var(--solis-space-xl);
  padding-top: var(--solis-space-lg);
  border-top: 1px solid var(--solis-border);
}

.solis-field-group__title {
  display: flex;
  align-items: center;
  gap: var(--solis-space-xs);
  margin: 0 0 var(--solis-space-md);
  font-size: var(--solis-fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--solis-text-muted);
}

/* A group that stacks its blocks (a field, a note or a second fieldset after the first): one gap apart, as a
   fieldset's own fields are, where they would otherwise touch. */
.solis-field-group--stack > :not(.solis-field-group__title) + * { margin-top: var(--solis-space-md); }

/* The mark on a required field's label. Used where a form is mostly required, and labelling the
   exceptions would mean labelling nearly every field. A form may use one or the other, never both. */
.solis-field__req {
  color: var(--solis-danger-500);
  margin-left: 0.15em;
  font-weight: 600;
}

.solis-field__optional {
  margin-left: var(--solis-space-xs);
  font-weight: 500;
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
  text-transform: none;
  letter-spacing: 0;
}

.solis-field__optional::before { content: "("; }
.solis-field__optional::after { content: ")"; }

.solis-field__link {
  font-size: var(--solis-fs-sm);
  font-weight: 600;
  color: var(--solis-accent-500);
  text-decoration: none;
}

.solis-field__link:hover {
  color: var(--solis-accent-600);
}

.solis-input {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  padding: var(--solis-space-md) var(--solis-space-lg);
  font-size: var(--solis-fs-md);
  font-family: inherit;
  color: var(--solis-text-primary);
  background: var(--solis-surface);
}

/* A field that sits in a row of small buttons, at their height. */
.solis-input--sm {
  padding: var(--solis-space-2xs) var(--solis-space-md);
  font-size: var(--solis-fs-sm);
  line-height: 1.25;
}

/* Showing a value the staff member can read but not change: inert, rather than like a field they
   have failed to focus. */
.solis-input:disabled {
  background: var(--solis-surface-muted);
  color: var(--solis-text-muted);
  cursor: not-allowed;
}

/* A field whose value the service stores upper case, shown that way as it is typed so what the
   staff member sees is what gets saved. The value itself is left as typed; the service folds it. */
.solis-input--upper {
  text-transform: uppercase;
}

/* One ring, not two. The resting border is what says "you can type here", but on focus the ring
   says it better and the border becomes a second edge inside it — recolouring it to the accent
   only made the doubling one colour instead of two. Transparent rather than removed so the box
   does not shift, and the field's own background fills the space it leaves, which puts the ring
   flush against the fill. */
.solis-input:focus-visible {
  border-color: transparent;
  outline: var(--solis-focus-ring);
  outline-offset: 0;
}

/* Checkboxes and radios take the ring outside the box rather than on its edge. Element selectors
   rather than a class: these are bare inputs in several screens, and a class would only be
   remembered on the next one. */
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: 2px;
}

/* The native control paints its chevron flush to the border and ignores padding-right. Replacing it
   with a background arrow is the only way to give it the same inset as every other field. Lives here
   rather than on the screen that first needed it — it styles .solis-input, so every screen with a
   select was always going to want it. */
select.solis-input {
  appearance: none;
  padding-right: var(--solis-space-7xl);
  /* On a phone the longest option runs under the chevron and is cut mid-word; a select does not
     ellipsize on its own. */
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 15px;
}

/* Grows with what is typed rather than scrolling inside a fixed box. */
textarea.solis-input {
  resize: vertical;
  min-height: 74px;
  line-height: 1.55;
  font-family: inherit;
}

.solis-field--invalid .solis-input {
  border-color: var(--solis-danger-border);
}

/* ---- Password show/hide toggle ---- */

/* Wraps just the <input>, not the whole .solis-field — the label row/error message above and
   below stay outside so this doesn't affect their layout. */

.solis-field__password-wrap {
  position: relative;
}

.solis-field__password-wrap .solis-input {
  padding-right: var(--solis-space-8xl);
}

.solis-field__toggle-visibility {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  border: none;
  background: none;
  color: var(--solis-text-muted);
  border-radius: var(--solis-radius-md);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.solis-field__toggle-visibility:hover {
  color: var(--solis-text-secondary);
  background: var(--solis-surface-muted);
}

.solis-field__toggle-visibility i {
  font-size: var(--solis-fs-xl);
}

/* ---- OTP digit boxes (2FA verify, MFA enroll confirm) ---- */

.solis-otp-boxes {
  display: flex;
  gap: var(--solis-space-md);
}

.solis-otp-box {
  width: 100%;
  aspect-ratio: 1;
  text-align: center;
  font-size: var(--solis-fs-3xl);
  font-weight: 600;
  font-family: inherit;
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  color: var(--solis-text-primary);
  background: var(--solis-surface);
}

/* The 2FA screen's stand-in for a busy submit button: the boxes are the control, so they are what
   goes quiet while the code is checked. Not `disabled` — that would blur the box the last keystroke
   left focus in, and the screen has nowhere else to put it. */
.solis-otp-boxes[aria-busy="true"] .solis-otp-box {
  opacity: 0.55;
  pointer-events: none;
}

.solis-otp-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--solis-space-sm);
  margin: var(--solis-space-md) 0 0;
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-secondary);
}

.solis-otp-status[hidden] {
  display: none;
}

.solis-otp-status__spinner {
  width: 20px;
  height: 20px;
}

/* The border goes transparent under the ring, exactly as .solis-input does. Left grey it shows
   through at the offset, so a focused box reads as two concentric edges rather than one. */
.solis-otp-box:focus-visible {
  border-color: transparent;
  outline: var(--solis-focus-ring);
  outline-offset: 0;
}

/* ---- MFA enroll manual setup key ---- */

.solis-setup-key {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--solis-space-md);
  padding: var(--solis-space-lg) var(--solis-space-xl);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface);
}

.solis-setup-key__label {
  font-size: var(--solis-fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--solis-text-secondary);
}

.solis-setup-key__value {
  font-size: var(--solis-fs-md);
  font-weight: 600;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.05em;
  margin-top: var(--solis-space-3xs);
  word-break: break-all;
}

.solis-setup-key__copy {
  flex: none;
  color: var(--solis-accent-500);
  font-size: var(--solis-fs-2xl);
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--solis-space-2xs);
}

/* ---- Password strength meter + live checklist (ChangeExpiredPassword) ---- */

.solis-strength-bars {
  display: flex;
  gap: var(--solis-space-2xs);
  margin-top: var(--solis-space-sm);
}

.solis-strength-bar {
  height: 4px;
  flex: 1;
  border-radius: var(--solis-radius-xs);
  background: var(--solis-border);
}

.solis-strength-label {
  font-size: var(--solis-fs-xs);
  font-weight: 600;
  margin-top: var(--solis-space-2xs);
  color: var(--solis-text-muted);
}

.solis-checklist {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-xs);
}

.solis-checklist__item {
  display: flex;
  align-items: center;
  gap: var(--solis-space-sm);
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
}

.solis-checklist__item .ti {
  color: var(--solis-text-muted);
}

.solis-checklist__item[data-met="true"] {
  color: var(--solis-text-secondary);
}

.solis-checklist__item[data-met="true"] .ti {
  color: var(--solis-success-500);
}

/* ---- QR code frame (both authenticator set-up pages) — widgets/qrCanvas.ts fills it ----
   As wide as the card allows up to 280px, so a phone held at arm's length can read it. The frame
   is the code's own paper: the code draws its four-module quiet zone, and any spare width left by
   whole-pixel modules is more of the same white. */

.solis-qr-code {
  width: min(280px, 100%);
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-lg, var(--solis-radius-md));
  /* lint-allow: colour-literal-css  (a QR code is dark on white in either theme, so it can scan) */
  background: #fff;
  margin: 0 auto;
  box-sizing: border-box;
  overflow: hidden;
}

.solis-qr-code canvas {
  display: block;
  max-width: 100%;
}

/* On a phone the card is the limit: the code takes all of it. */
@media (max-width: 640px) {
  .solis-qr-code {
    width: 100%;
  }
}

/* A quiet tinted note attached to the field, not a line of red shouting under it. The rule down
   the leading edge ties it to the control above; the tint carries the severity without the text
   having to be alarm-coloured, which keeps it readable at the size it is actually set in. */
/* A line under a field saying what its value means, where the label cannot. */
.solis-field__hint {
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
  margin-top: var(--solis-space-2xs);
}

.solis-field__error {
  display: flex;
  align-items: flex-start;
  gap: var(--solis-space-xs);
  font-size: var(--solis-fs-xs);
  line-height: 1.45;
  color: var(--solis-text-primary);
  background: color-mix(in oklch, var(--solis-danger-500) 8%, transparent);
  border-left: 2px solid var(--solis-danger-500);
  border-radius: 0 var(--solis-radius-sm) var(--solis-radius-sm) 0;
  padding: var(--solis-space-xs) var(--solis-space-md);
  margin-top: var(--solis-space-xs);
}

.solis-field__error .ti {
  font-size: var(--solis-fs-md);
  color: var(--solis-danger-500);
  flex: none;
  /* Optically centred on the first line rather than on the block, which a multi-line message
     would otherwise leave it floating in the middle of. */
  margin-top: 1px;
}

/* The same note in green, for a field a service has checked and passed — a username that is free.
   Built on the error's rules so the two read as one part, a yes where the other is a no. */
.solis-field__ok {
  display: flex;
  align-items: flex-start;
  gap: var(--solis-space-xs);
  font-size: var(--solis-fs-xs);
  line-height: 1.45;
  color: var(--solis-text-primary);
  background: color-mix(in oklch, var(--solis-success-500) 10%, transparent);
  border-left: 2px solid var(--solis-success-500);
  border-radius: 0 var(--solis-radius-sm) var(--solis-radius-sm) 0;
  padding: var(--solis-space-xs) var(--solis-space-md);
  margin-top: var(--solis-space-xs);
}

.solis-field__ok .ti {
  font-size: var(--solis-fs-md);
  color: var(--solis-success-500);
  flex: none;
  margin-top: 1px;
}

/* The same note in amber, for a value that is allowed but worth a second look before sending — an
   amount past the account's available balance. It warns; it never holds the form back. */
.solis-field__warn {
  display: flex;
  align-items: flex-start;
  gap: var(--solis-space-xs);
  font-size: var(--solis-fs-xs);
  line-height: 1.45;
  color: var(--solis-text-primary);
  background: color-mix(in oklch, var(--solis-warn-500) 12%, transparent);
  border-left: 2px solid var(--solis-warn-500);
  border-radius: 0 var(--solis-radius-sm) var(--solis-radius-sm) 0;
  padding: var(--solis-space-xs) var(--solis-space-md);
  margin-top: var(--solis-space-xs);
}

.solis-field__warn .ti {
  font-size: var(--solis-fs-md);
  color: var(--solis-warn-500);
  flex: none;
  margin-top: 1px;
}
/* ---- Buttons ----

   Three levels, in descending weight: --primary (solid accent) for the one action a screen exists
   to perform; --outline (accent border and text, filling solid on hover) for a standalone
   secondary action; --ghost (neutral, quiet hover) for the passive half of a pair.

   The --outline / --ghost split is deliberate and worth keeping. An accent-outlined button fills
   with accent and turns its text white on hover — which is exactly what --primary looks like at
   rest. Used on a Cancel sitting beside a Save, the secondary's hover state becomes
   indistinguishable from the primary's resting state, at the precise moment of clicking. So
   Cancel-shaped buttons take --ghost and stay neutral; --outline is for buttons with no primary
   next to them (Edit profile, Change password, Mark all read, Retry). */

.solis-btn {
  /* Every variant has the same 1px box, so a filled button is as tall as the outline one beside it: 34px, the height of
     the table search and the selects. Outline and ghost colour their border; the rest keep it transparent. */
  border: 1px solid transparent;
  border-radius: var(--solis-radius-md);
  /* ~36px tall, matching the explicit height of the inputs and selects it sits beside in a
     toolbar. The old 12px/16px came out 5-6px taller than those controls, which the notifications
     toolbar had to override away with a height of its own. */
  padding: var(--solis-space-sm) var(--solis-space-xl);
  font-size: var(--solis-fs-md);
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--solis-space-xs);
  text-decoration: none;
  box-sizing: border-box;
  font-family: inherit;
  transition: color 0.12s ease, border-color 0.12s ease, background-color 0.12s ease;
}

/* The sign-in / verify / enrol CTAs, which are full-width and the only thing on the screen worth
   pressing. They keep the roomier size the whole system used before it was tightened for in-app
   toolbars, where buttons sit among other controls rather than carrying the page. */
.solis-btn--lg {
  padding: var(--solis-space-lg) var(--solis-space-2xl);
}

/* A button in a card's header, beside its title: small enough that the header keeps the height of
   one with a title alone. */
.solis-btn--sm {
  padding: var(--solis-space-2xs) var(--solis-space-md);
  font-size: var(--solis-fs-sm);
  gap: var(--solis-space-2xs);
}

.solis-btn--primary {
  background: var(--solis-accent-500);
  color: var(--solis-text-on-accent);
}

.solis-btn--primary:hover:not(:disabled) {
  background: var(--solis-accent-600);
}

.solis-btn--primary:disabled {
  opacity: 0.75;
  cursor: default;
}

/* ---- Busy: an action working in place ----

   Any .solis-btn, not just the primary one: the same state has to read on the outline and danger
   buttons that submit drawers. Pointer-events rather than `disabled` — see core/busy.ts on why the
   element must keep focus — so the caller still owns its own re-entry guard. */
.solis-btn[aria-busy="true"] {
  opacity: 0.75;
  cursor: default;
  pointer-events: none;
}

/* ---- The one spinner ----

   The brand mark, not a drawn ring: it is what the full-screen loader has always shown, and reusing
   it everywhere means every wait in the app is recognisably the same wait. The SVG animates its own
   rays, so nothing here rotates it.

   The mark is orange, which is also what a filled accent button is painted, so on those it vanished
   into its own background. --solis-spinner-tint is how a ground says the mark has to go flat: unset
   it keeps its colours, and a variant whose fill would swallow them sets it to invert. Anything that
   is transparent or a surface — outline, ghost, drawers, dialogs, both themes — keeps the colour,
   because the mark is bright enough to read on a dark surface as well as a light one. */
.solis-spinner {
  width: 17px;
  height: 17px;
  background: url("/images/brand/solis-spinner.svg") center / contain no-repeat;
  filter: var(--solis-spinner-tint, none);
  flex: none;
}

.solis-btn__spinner {
  width: 17px;
  height: 17px;
  background: url("/images/brand/solis-spinner.svg") center / contain no-repeat;
  filter: var(--solis-spinner-tint, none);
  display: none;
  flex: none;
}

/* Flattened to the button's own text colour, which every filled variant sets to on-accent white.
   The shape and its ray pulse survive — the part that reads as Solis — and only the colour goes,
   which that background was destroying anyway. */
.solis-btn--primary,
.solis-btn--danger,
.solis-btn--success {
  --solis-spinner-tint: brightness(0) invert(1);
}

.solis-btn[aria-busy="true"] .solis-btn__spinner {
  display: inline-block;
}



/* A label wrapped for core/busy.ts spaces its icon and text as the button itself does, however the
   markup is written: without it the gap was whatever whitespace the view happened to leave, and
   none where the icon and text sat on one line (Catalog Cache's Refresh Now). */
.solis-btn__label {
  display: inline-flex;
  align-items: center;
  gap: var(--solis-space-xs);
}

/* Everything the button normally shows steps aside for the busy label, without being removed — see
   core/busy.ts on why the original nodes have to survive the round trip. */
.solis-btn[aria-busy="true"] > *:not(.solis-btn__busy):not(.solis-btn__spinner) {
  display: none;
}

/* The link-styled buttons (Resend code) are their own variant and carry no .solis-btn, so the rule
   above never reaches them. Same state, no spinner: at link weight a ring beside the text is more
   noise than signal, and the label already changes to say what is happening. */
.solis-btn--link[aria-busy="true"] {
  opacity: 0.7;
  pointer-events: none;
}

/* Buttons that carry no spinner span still need somewhere for one, so the ::before stands in.
   Skipped where a real .solis-btn__spinner exists, or the button would show two. */
.solis-btn[aria-busy="true"]:not(:has(.solis-btn__spinner))::before {
  content: "";
  width: 17px;
  height: 17px;
  background: url("/images/brand/solis-spinner.svg") center / contain no-repeat;
  filter: var(--solis-spinner-tint, none);
  flex: none;
}


/* Fills the width it is given, for a button that is the only thing on its row — a card's action,
   or a control stacked with another in a narrow column. */
.solis-btn--full { width: 100%; justify-content: center; }

.solis-btn--outline {
  background: transparent;
  color: var(--solis-accent-600);
  border: 1px solid var(--solis-accent-500);
}

.solis-btn--outline:hover:not(:disabled),
.solis-btn--outline:focus-visible {
  background: var(--solis-accent-500);
  border-color: var(--solis-accent-500);
  color: var(--solis-text-on-accent);
}

.solis-btn--outline:disabled {
  opacity: 0.55;
  cursor: default;
}

/* Cancel/secondary action — first used by SolisDialog's confirm() (see solis-dialog.css). Stays
   neutral by design; see the section comment above for why this one doesn't take the accent. */
.solis-btn--ghost {
  background: transparent;
  color: var(--solis-text-primary);
  border: 1px solid var(--solis-border);
}

.solis-btn--ghost:hover:not(:disabled),
.solis-btn--ghost:focus-visible {
  background: var(--solis-surface-muted);
  border-color: var(--solis-text-muted);
}

/* The filled variants had no focus style at all, so every one of them fell back to the browser's
   own ring — a blue outline in the middle of an orange design system. Most visible on a dialog,
   which focuses its primary button the moment it opens, so a confirm always showed one.
   Offset outward rather than inset: on a solid fill an inset ring disappears into the button. */
.solis-btn--primary:focus-visible,
.solis-btn--danger:focus-visible,
.solis-btn--success:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: var(--solis-focus-ring-offset);
}

/* The catch-all, for every variant without a rule of its own — --link and --lg had none, so they
   still showed the browser's blue ring the filled variants were fixed for. */
.solis-btn:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: var(--solis-focus-ring-offset);
}

/* Every link, so one that carries no component class of its own still gets the accent ring rather
   than the browser's blue. An element selector rather than a class: this is the floor, and anything
   with its own rule overrides it on specificity. */
a:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: var(--solis-focus-ring-offset);
}

/* Icon-only controls inside a component, same reason. */
.solis-setup-key__copy:focus-visible,
.solis-field__toggle-visibility:focus-visible,
.solis-seg :is(button, a):focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: 1px;
  border-radius: var(--solis-radius-md);
}

/* Accent-on-accent has nothing to separate the ring from the fill it surrounds, so the primary
   button gets the page behind it as a gap instead. */
.solis-btn--primary:focus-visible {
  box-shadow: 0 0 0 2px var(--solis-surface);
}

/* Both outline variants were inheriting the enabled look entirely — a disabled button was
   indistinguishable from a live one until you clicked it and nothing happened. */
.solis-btn--ghost:disabled {
  opacity: 0.55;
  cursor: default;
}

/* accent-600 is a mid-dark orange: readable on a white card, roughly 2:1 on the dark surface. The
   resting text/border lighten in dark mode; the hover fill stays accent-500 with white on top,
   which clears AA in both themes. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .solis-btn--outline {
    color: var(--solis-accent-ink);
    border-color: var(--solis-accent-400);
  }

  :root:not([data-theme="light"]) .solis-btn--outline:hover:not(:disabled),
  :root:not([data-theme="light"]) .solis-btn--outline:focus-visible {
    color: var(--solis-text-on-accent);
    border-color: var(--solis-accent-500);
  }
}

:root[data-theme="dark"] .solis-btn--outline {
  color: var(--solis-accent-ink);
  border-color: var(--solis-accent-400);
}

:root[data-theme="dark"] .solis-btn--outline:hover:not(:disabled),
:root[data-theme="dark"] .solis-btn--outline:focus-visible {
  color: var(--solis-text-on-accent);
  border-color: var(--solis-accent-500);
}

/* A destructive outline in a drawer's footer (Cancel Request, Delete Draft, Cancel Mandate): the danger ink on a faint
   danger tint, so it reads apart from an accent outline beside it, in light mode too. The ink token carries the dark
   lift. Placed after the explicit dark block so that one cannot override it. A confirm dialog's button takes the same classes and stays a solid red, so the rule is the footer's alone. */
.solis-drawer__foot .solis-btn--outline.solis-btn--danger {
  background: var(--solis-danger-wash);
  color: var(--solis-danger-ink);
  border-color: var(--solis-danger-500);
}

.solis-drawer__foot .solis-btn--outline.solis-btn--danger:hover:not(:disabled),
.solis-drawer__foot .solis-btn--outline.solis-btn--danger:focus-visible {
  background: var(--solis-danger-500);
  color: var(--solis-text-on-accent);
  border-color: var(--solis-danger-500);
  filter: none;
}

.solis-btn--link {
  background: none;
  border: none;
  color: var(--solis-accent-500);
  font-size: var(--solis-fs-md);
  font-weight: 600;
  cursor: pointer;
  padding: 0;
  text-align: center;
  font-family: inherit;
}

.solis-btn--link:hover:not(:disabled) {
  color: var(--solis-accent-600);
}

/* The same style on an <a> keeps the browser's underline unless told otherwise; no link here has one. */
a.solis-btn--link,
a.solis-btn--link:hover { text-decoration: none; }

/* Reads as a countdown rather than a dead control — the label is doing the explaining ("Resend
   code in 24s"), so this only has to stop it looking pressable. */
.solis-btn--link:disabled {
  color: var(--solis-text-muted);
  cursor: default;
}

/* Variant-matched actions — SolisDialog.alert()'s single button picks one of these to match its
   icon (error/success), instead of every alert defaulting to the same brand-primary button
   regardless of severity. confirm()'s Confirm/Cancel stay --primary/--ghost, unaffected. */
.solis-btn--danger {
  background: var(--solis-danger-500);
  color: var(--solis-text-on-accent);
}

.solis-btn--danger:hover:not(:disabled) {
  filter: brightness(1.08);
}

/* The filled danger variant had neither of these, so a disabled destructive button looked fully
   live and still brightened under the cursor — the most dangerous button on a page reading as the
   most available one.
   Dropped to the muted surface rather than dimmed like --primary: the red fill is itself the signal
   "destructive, and available to press", and taking 25% off a saturated red does not remove it —
   at a phone width the dimmed version still read as live. An unavailable action should not be
   advertising danger; the tooltip carries why it cannot be pressed. */
.solis-btn--danger:disabled {
  background: var(--solis-surface-muted);
  color: var(--solis-text-muted);
  border: 1px solid var(--solis-border);
  cursor: default;
}

/* A button that sends something into a status wears that status's tint, as the pill does, with the
   hue's ink for text so it reads in both themes: Return Request violet, Reject Request rose. */
.solis-btn--violet {
  background: var(--solis-ic-violet-bg);
  border: 1px solid var(--solis-ic-violet);
  color: var(--solis-violet-ink);
}

.solis-btn--rose {
  background: var(--solis-ic-rose-bg);
  border: 1px solid var(--solis-ic-rose);
  color: var(--solis-rose-ink);
}

.solis-btn--violet:hover:not(:disabled),
.solis-btn--rose:hover:not(:disabled) { filter: brightness(1.08); }

/* Muted when unavailable, as --danger is: the status tint says "press to send it there", which a
   Decline waiting on its reason must not. */
.solis-btn--violet:disabled,
.solis-btn--rose:disabled {
  background: var(--solis-surface-muted);
  color: var(--solis-text-muted);
  border: 1px solid var(--solis-border);
  cursor: default;
}

.solis-btn--success {
  background: var(--solis-success-500);
  color: var(--solis-text-on-accent);
}

.solis-btn--success:hover {
  filter: brightness(1.08);
}

/* Branded loading overlay ("Signing in…" etc.) moved to solis-loader-overlay.css — shared across
   the app, not auth-specific, paired with ts/behaviors/loaderOverlay.ts. See ui-patterns.md's
   "branded loader with cycling status text" for the design discussion. */

/* ---- Alert banners (lockout / conflict) ---- */

.solis-alert {
  display: flex;
  gap: var(--solis-space-md);
  align-items: flex-start;
  padding: var(--solis-space-lg) var(--solis-space-xl);
  border-radius: var(--solis-radius-md);
  background: var(--solis-danger-bg);
  border: 1px solid var(--solis-danger-border);
}

.solis-alert__icon {
  color: var(--solis-danger-500);
  font-size: var(--solis-fs-2xl);
  flex: none;
  margin-top: var(--solis-space-3xs);
}

.solis-alert__title {
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-primary);
  margin: 0;
}

.solis-alert__body {
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-secondary);
  margin-top: var(--solis-space-3xs);
}

.solis-alert__action {
  margin-top: var(--solis-space-sm);
  text-align: left;
}

/* Danger is the default (every existing alert on this app's auth screens is a rejection/lockout);
   success and warn are the two variants, both chosen through _Alert's Tone. */

.solis-alert--success {
  background: var(--solis-success-bg);
  border-color: var(--solis-success-border);
}

.solis-alert--success .solis-alert__icon {
  color: var(--solis-success-500);
}

/* A caution — something went through but needs a look. */
.solis-alert--warn {
  background: var(--solis-warn-bg);
  border-color: var(--solis-warn-border);
}

.solis-alert--warn .solis-alert__icon {
  color: var(--solis-warn-500);
}

/* ---- Cards ---- */

.solis-card {
  background: var(--solis-surface);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-lg);
  box-shadow: var(--solis-shadow-card);
  padding: var(--solis-space-4xl);
}

.solis-card__title {
  font-size: var(--solis-fs-lg);
  font-weight: 700;
  color: var(--solis-text-primary);
  margin: 0 0 var(--solis-space-xl);
}

.solis-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--solis-space-lg);
  margin-bottom: var(--solis-space-lg);
}

.solis-card__header .solis-card__title {
  margin: 0;
}

/* Sized and coloured down from the title it sits with: the icon marks the card, it does not
   compete with the word next to it for the reading. */
/* An icon font renders visibly smaller than text at the same size, so matching the title's own
   font-size leaves the glyph looking undersized next to it. One step up reads as level. */
.solis-card__title i {
  margin-right: var(--solis-space-sm);
  font-size: var(--solis-fs-2xl);
  color: var(--solis-ic-accent);
  vertical-align: -3px;
}

/* A card whose heading is a bar across the top rather than text inside the padding. The card gives
   up its own padding for it — a rule that stops short of the card's edges reads as an underline
   under the title, not as a division of the card — so every region inside supplies its own. */
.solis-card--sectioned {
  padding: 0;
}

.solis-card--sectioned .solis-card__header {
  padding: var(--solis-space-2xl) var(--solis-space-4xl);
  border-bottom: 1px solid var(--solis-border);
  margin-bottom: 0;
}

/* A header over a tab strip leaves the rule to the strip, whose own rule closes the pair. */
.solis-card--sectioned .solis-card__header:has(+ .solis-tabs) { border-bottom: 0; padding-bottom: var(--solis-space-sm); }

.solis-card--sectioned .solis-card__body {
  padding: var(--solis-space-3xl) var(--solis-space-4xl);
}

/* A card with something after the body supplies that region's bottom padding itself, so the two
   do not stack into a double gap. */
.solis-card--sectioned .solis-card__body:not(:last-child) {
  padding-bottom: var(--solis-space-xl);
}

.solis-card__header-actions {
  display: flex;
  align-items: center;
  gap: var(--solis-space-lg);
}

.solis-card__count {
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
}

/* ---- Pills ---- */

.solis-pill {
  display: inline-block;
  padding: var(--solis-space-3xs) var(--solis-space-md);
  border-radius: var(--solis-radius-pill);
  font-size: var(--solis-fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  /* A status is one token, not a phrase to break: "Pending approval" wrapped over two lines read as
     two separate values and made its row 13px taller than every other row in the table. A column
     too narrow for its widest status is a column to widen, not a pill to fold. */
  white-space: nowrap;
}

/* Pill text is 11px bold — small text by WCAG, so it needs 4.5:1, not the 3:1 that large text
   gets. Each variant is its hue's tint with that hue's -ink text from solis-tokens.css, which
   carries the dark-mode lift, so no variant restates itself per theme. What each colour means is in
   the repo CLAUDE.md. */
/* No borders except grey's. The semantic -border tokens sit at very different contrasts against
   their own tints, so bordering them made one pill look outlined and the next flat. Grey keeps one
   because its tint is barely off the surface. */
.solis-pill--success {
  background: var(--solis-success-bg);
  color: var(--solis-success-ink);
}

/* Identity tags only (segment, product, source), never a status: orange sits too close to amber
   and is the brand's selection colour. */
.solis-pill--accent {
  background: var(--solis-accent-50);
  color: var(--solis-accent-ink);
}

.solis-pill--warn {
  background: var(--solis-warn-bg);
  color: var(--solis-warn-ink);
}

.solis-pill--info {
  background: var(--solis-info-bg);
  color: var(--solis-info-ink);
}

.solis-pill--danger {
  background: var(--solis-danger-bg);
  color: var(--solis-danger-ink);
}

.solis-pill--neutral {
  background: var(--solis-surface-muted);
  color: var(--solis-text-muted);
  border: 1px solid var(--solis-border);
}

.solis-pill--violet {
  background: var(--solis-ic-violet-bg);
  color: var(--solis-violet-ink);
}

.solis-pill--teal {
  background: var(--solis-ic-teal-bg);
  color: var(--solis-teal-ink);
}

.solis-pill--rose {
  background: var(--solis-ic-rose-bg);
  color: var(--solis-rose-ink);
}

/* Stone: a draft, put together and not yet handed to anyone. */
.solis-pill--stone {
  background: var(--solis-ic-stone-bg);
  color: var(--solis-stone-ink);
}

/* The one solid pill: a second neutral that a tint could not tell apart from grey. Its text is the
   surface colour, so it inverts with the theme on its own. */
.solis-pill--slate {
  background: var(--solis-ic-slate);
  color: var(--solis-surface);
}

/* A fact that qualifies a row without being a status: an icon and a word in muted text beside the
   row's pill, so the pill stays the one coloured signal. */
.solis-marker {
  display: inline-flex;
  align-items: center;
  gap: var(--solis-space-2xs);
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
}

.solis-marker i {
  font-size: var(--solis-fs-sm);
}

/* A marker that follows a value in the same cell. */
.solis-marker--after {
  margin-left: var(--solis-space-sm);
}

/* Something on the row that needs fixing before the record can go on (a draft's open issue): the word in warning ink,
   its reason in the tooltip. Still a marker, so the row's pill stays its one status. */
.solis-marker--fix {
  color: var(--solis-warn-ink);
}

/* ============================================================================
   Tabs — underline-style tab strip. Generic: pair a `.solis-tabs` row of
   `.solis-tab` buttons (data-solis-tab="key") with `.solis-tab-panel`s
   (data-solis-tab-panel="key"); behaviors/tabs.ts toggles `.solis-tab--active`
   and each panel's `hidden`. A tab with no data-solis-tab (e.g. a deferred one)
   is inert; add `disabled` to grey it out. First promoted here for the customer
   detail screen; meant to be the shared tab primitive going forward.
   ============================================================================ */
/* One row at every width. A strip that does not fit scrolls sideways and a tab never stacks: a
   wrapped strip stops reading as one selector, since the active underline then sits on its own row
   with unrelated tabs above and below it. A single scrolling row keeps the underline meaningful,
   holds its height whatever the label length or the reader's font-size setting, and matches the
   swipeable tab strip every mobile app already teaches. Where the whole strip fits there is
   nothing to scroll and no scrollbar shows. */
.solis-tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--solis-space-3xs);
  /* The rule under the strip is an inset shadow, not a border: a scrolling box clips whatever a
     child draws past its padding edge, so the active tab's 2px underline could no longer overlap a
     border and was cut to one. Drawn inside, the underline sits on top of the rule. */
  box-shadow: inset 0 -1px 0 var(--solis-border);
  padding: 0 var(--solis-space-xs);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.solis-tabs::-webkit-scrollbar {
  display: none;
}

/* Fades the trailing edge so it reads as scrollable rather than clipped. behaviors/tabs.ts sets
   .is-at-end when there is nothing further to reach (including a strip that fits), otherwise the
   last tab looks permanently cut off. */
.solis-tabs:not(.is-at-end) {
  /* lint-allow: colour-literal-css  (a mask reads only its alpha, never a colour) */
  mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent 100%);
}

.solis-tab {
  appearance: none;
  background: none;
  border: none;
  font-family: inherit;
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-muted);
  /* 12px a side: at 16 the strip read as loose words rather than one selector. */
  padding: var(--solis-space-xl) var(--solis-space-lg);
  cursor: pointer;
  white-space: nowrap;
  scroll-snap-align: start;
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--solis-space-xs);
  border-bottom: 2px solid transparent;
}

.solis-tab:hover:not(:disabled) {
  color: var(--solis-text-secondary);
}

.solis-tab--active {
  color: var(--solis-accent-ink);
  border-bottom-color: var(--solis-accent-500);
}

.solis-tab:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.solis-tab__count {
  font-size: var(--solis-fs-xs);
  font-weight: 700;
  color: var(--solis-text-muted);
}

.solis-tab--active .solis-tab__count {
  color: var(--solis-accent-ink);
}

.solis-tab-panel[hidden] {
  display: none;
}

/* ============================================================================
   Field grid — a labelled read-only value grid (definition-list style). Each
   `.solis-field-item` is a label (uppercase, optional leading icon) over a
   value. `.solis-field-value--empty` dims a "—" placeholder; `--mono` for true
   identifiers. Rendered by Partials/_Field.cshtml.
   ============================================================================ */
.solis-fields {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--solis-space-3xl) var(--solis-space-6xl);
}

/* A grid cell gives way to its column, so a long unbroken value (an email) wraps instead of running into the next. */
.solis-fields > * { min-width: 0; }

.solis-field-label {
  font-size: var(--solis-fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--solis-text-muted);
  margin-bottom: var(--solis-space-2xs);
  display: flex;
  align-items: center;
  gap: var(--solis-space-xs);
}

.solis-field-label i {
  font-size: var(--solis-fs-lg);
  color: var(--solis-text-muted);
  flex: none;
}

/* 15px, not 14: these grids are the read-only body of every detail screen, and at 14 the value sat
   too close to its own 10.5px label to separate at a glance. Raised here rather than per screen so
   the customer and account details stay the same size as each other. */
.solis-field-value {
  font-size: var(--solis-fs-xl);
  font-weight: 500;
  color: var(--solis-text-primary);
  overflow-wrap: anywhere;
}

/* Mono runs wider at the same nominal size, so it is held a touch below the sans value to keep the
   two optically level where a grid mixes them. */
.solis-field-value--mono {
  font-family: var(--solis-mono);
  font-size: var(--solis-fs-lg);
}

/* A move on the value itself (regenerate a secret): a row-action button after it, pulled back by the height it
   has over the text so the row doesn't grow. */
.solis-field-value__act {
  margin: -4px 0 -4px var(--solis-space-2xs);
  vertical-align: middle;
}

/* A value that is itself the exception — a negative balance, which only OFFICE and LOAN holders are
   permitted to carry. Sits on the plain card surface rather than a tint, so it takes the danger ink,
   which carries its own dark-mode lift, instead of --solis-danger-500: that token is tuned to be
   readable on white and goes muddy against the dark surface. */
.solis-field-value--danger,
.solis-stat__value--danger {
  color: var(--solis-danger-ink);
}

.solis-field-value--empty {
  color: var(--solis-text-muted);
  font-weight: 400;
}

/* Section header inside a panel — an uppercase label with a trailing hairline rule. */
.solis-section-title {
  margin: 0 0 var(--solis-space-xl);
  font-size: var(--solis-fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--solis-text-muted);
  display: flex;
  align-items: center;
  gap: var(--solis-space-md);
}

.solis-section-title i {
  font-size: var(--solis-fs-xl);
  color: var(--solis-text-secondary);
  flex: none;
}

.solis-section-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--solis-border);
}

.solis-section + .solis-section {
  margin-top: var(--solis-space-5xl);
}

/* ---- a fold ----
   A <details> whose <summary> is its heading: a record page's section, or an approval thread tucked
   under the one that matters. It opens without script. The shared headings are display:flex, which
   drops the browser's disclosure marker, so the fold draws its own. */
.solis-fold > summary {
  cursor: pointer;
  list-style: none;
}

.solis-fold > summary::marker,
.solis-fold > summary::-webkit-details-marker { content: ""; display: none; }

/* After the heading's words, before its rule: in front of them it pushed a section's icon out of line
   with the field icons below. */
.solis-fold > summary::before {
  order: 1;
  content: "\25B8";
  font-size: var(--solis-fs-xs);
  line-height: 1;
  transition: transform 120ms ease;
}

.solis-fold[open] > summary::before { transform: rotate(90deg); }

.solis-fold > summary::after { order: 2; }

.solis-fold > summary:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: var(--solis-focus-ring-offset);
}

/* Closed, the heading is all there is, so it keeps no gap for a body that isn't showing. */
.solis-fold:not([open]) > summary.solis-section-title { margin-bottom: 0; }

@media (prefers-reduced-motion: reduce) {
  .solis-fold > summary::before { transition: none; }
}

@media (max-width: 1199px) {
  .solis-fields {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 640px) {
  .solis-fields {
    grid-template-columns: 1fr;
  }
}

/* ============================================================================
   Stat tile — a summary-strip card: a colored icon chip + label on top, a large
   value, and a meta line. Hue set by a `--<color>` modifier (accent/blue/green/
   violet) mapping to the --solis-ic-* tokens. Rendered by Partials/_StatCard.cshtml.
   Meant as the shared dashboard/summary primitive.
   ============================================================================ */
.solis-stat {
  padding: var(--solis-space-2xl) var(--solis-space-3xl);
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-3xs);
}

.solis-stat__top {
  display: flex;
  align-items: center;
  gap: var(--solis-space-md);
  margin-bottom: var(--solis-space-md);
}

.solis-stat__ic {
  width: 34px;
  height: 34px;
  border-radius: var(--solis-radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  font-size: var(--solis-fs-2xl);
}

.solis-stat__ic--accent { background: var(--solis-ic-accent-bg); color: var(--solis-ic-accent); }
.solis-stat__ic--blue { background: var(--solis-ic-blue-bg); color: var(--solis-ic-blue); }
.solis-stat__ic--green { background: var(--solis-ic-green-bg); color: var(--solis-ic-green); }
.solis-stat__ic--violet { background: var(--solis-ic-violet-bg); color: var(--solis-ic-violet); }

/* The semantic pair, for a tile whose figure means money out or money in. Distinct from the four
   --solis-ic-* hues above, which exist to tell several things of the SAME kind apart: these two say
   which way it went, and they are the same tokens a ledger's debit and credit columns use — the
   mapping should be learned once, not once per component. */
.solis-stat__ic--danger { background: var(--solis-danger-bg); color: var(--solis-danger-500); }
.solis-stat__ic--success { background: var(--solis-success-bg); color: var(--solis-success-500); }

.solis-stat__label {
  font-size: var(--solis-fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--solis-text-muted);
}

.solis-stat__value {
  font-size: var(--solis-fs-5xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.solis-stat__value--sm {
  font-size: var(--solis-fs-3xl);
}

.solis-stat__value small {
  font-size: var(--solis-fs-sm);
  font-weight: 600;
  color: var(--solis-text-muted);
}

.solis-stat__meta {
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
  margin-top: var(--solis-space-3xs);
}

/* The change on the previous period: its colour says whether the move is good news, not which way
   it went, so a fall in failures reads green. */
.solis-stat__meta:has(.solis-stat__change) {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--solis-space-2xs) var(--solis-space-xs);
}

.solis-stat__change {
  display: inline-flex;
  align-items: center;
  gap: var(--solis-space-3xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* Ink only, no tint: a filled chip in every tile set a second block of colour beside the tile's own
   icon hue, and the success green read as one more tile colour. */
.solis-stat__change--good { color: var(--solis-success-ink); }
.solis-stat__change--bad { color: var(--solis-danger-ink); }

/* A trend, not a chart: no axis and no values, only the shape of the figure's recent run. */
.solis-stat__spark {
  width: 100%;
  height: 28px;
  margin: var(--solis-space-2xs) 0 var(--solis-space-3xs);
  overflow: visible;
}

.solis-stat__spark polyline {
  fill: none;
  stroke: var(--solis-text-muted);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* The trend in its tile's hue. */
.solis-stat__spark--accent polyline { stroke: var(--solis-ic-accent); }
.solis-stat__spark--blue polyline { stroke: var(--solis-ic-blue); }
.solis-stat__spark--green polyline { stroke: var(--solis-ic-green); }
.solis-stat__spark--violet polyline { stroke: var(--solis-ic-violet); }
.solis-stat__spark--teal polyline { stroke: var(--solis-ic-teal); }
.solis-stat__spark--rose polyline { stroke: var(--solis-ic-rose); }
.solis-stat__spark--slate polyline { stroke: var(--solis-ic-slate); }

.solis-stat__ic--teal { background: var(--solis-ic-teal-bg); color: var(--solis-ic-teal); }
.solis-stat__ic--rose { background: var(--solis-ic-rose-bg); color: var(--solis-ic-rose); }
.solis-stat__ic--slate { background: var(--solis-ic-slate-bg); color: var(--solis-ic-slate); }
.solis-stat__ic--warn { background: var(--solis-warn-bg); color: var(--solis-warn-ink); }

/* ============================================================================
   Empty state — a centered dashed panel for "nothing here yet". Rendered by
   Partials/_EmptyState.cshtml.
   ============================================================================ */
.solis-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--solis-space-xs);
  padding: var(--solis-space-8xl) var(--solis-space-4xl);
  border: 1px dashed var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface-muted);
}

.solis-empty-state__ic {
  width: 46px;
  height: 46px;
  border-radius: var(--solis-radius-lg);
  background: var(--solis-surface);
  border: 1px solid var(--solis-border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--solis-text-muted);
  margin-bottom: var(--solis-space-2xs);
  font-size: var(--solis-fs-4xl);
}

.solis-empty-state__title {
  font-size: var(--solis-fs-lg);
  font-weight: 600;
  color: var(--solis-text-primary);
}

.solis-empty-state__text {
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
  max-width: 360px;
}

/* ---- segmented switcher ---- */

/* One-of-N choice rendered as a filled bar, for a small fixed set where the options are worth
   showing at once rather than hiding behind a dropdown. Promoted here from solis-customers.css so
   the Leads status filter uses the same control the customer search does rather than a near-copy. */
.solis-seg {
  display: flex;
  width: 100%;
  background: var(--solis-surface-muted);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  padding: var(--solis-space-2xs);
  margin-bottom: var(--solis-space-lg);
}

.solis-seg :is(button, a) {
  flex: 1;
  border: none;
  /* A tab that opens another view is a link, and must not look like one. */
  text-decoration: none;
  background: none;
  font-family: inherit;
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-secondary);
  padding: var(--solis-space-sm) var(--solis-space-3xl);
  border-radius: var(--solis-radius-sm);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--solis-space-sm);
  transition: background-color 0.12s, color 0.12s;
}

.solis-seg :is(button, a) i {
  font-size: var(--solis-fs-xl);
}

.solis-seg :is(button, a):hover:not(.is-on) {
  color: var(--solis-text-primary);
}

/* Active segment reads as a filled accent tab, not a barely-there raised chip — the searched
   identifier should be obvious at a glance. */
.solis-seg :is(button, a).is-on {
  background: var(--solis-accent-500);
  color: var(--solis-text-on-accent);
  box-shadow: var(--solis-shadow-seg-on);
}

.solis-seg :is(button, a).is-on i {
  color: var(--solis-text-on-accent);
}

/* Part of the segment's own label — it inherits the colour so it inverts with the active fill
   rather than needing a second rule for the on state. Lived under .solis-table-bar until the
   notifications toolbar wanted one too; nothing about it was ever about tables.
   Tabular figures so a changing count does not shift the label beside it. */
/* The same switch at a card header's size: as wide as its options, for a short choice such as a period
   that sits beside the card's other controls. */
.solis-seg--compact {
  display: inline-flex;
  width: auto;
  margin-bottom: 0;
  padding: var(--solis-space-3xs);
}

.solis-seg--compact :is(button, a) {
  flex: none;
  padding: var(--solis-space-3xs) var(--solis-space-md);
  font-size: var(--solis-fs-xs);
  font-variant-numeric: tabular-nums;
}

.solis-seg__count {
  font-size: var(--solis-fs-xs);
  font-weight: 700;
  opacity: 0.65;
  font-variant-numeric: tabular-nums;
}

/* The strip's last tab when more exist than fit: a menu of the rest. It takes a tab's share of the
   strip, and its items read as a list, the one in force filled like a tab. */
.solis-seg__more { flex: 1; display: flex; }
.solis-seg__more > button { flex: 1; }
.solis-seg__menu { min-width: 200px; padding: var(--solis-space-2xs); }

.solis-seg .solis-seg__menu button {
  width: 100%;
  justify-content: space-between;
  padding: var(--solis-space-sm) var(--solis-space-md);
}

.solis-seg .solis-seg__menu button:hover:not(.is-on) {
  background: var(--solis-surface-muted);
  color: var(--solis-text-primary);
}

/* ---- a pulsing dot: something happening now ----
   Green is live data (the Dashboard's Live badge); --working is work under way, in In Progress's blue, since green
   means good news. The pulse takes the dot's own colour. */

.solis-live__dot {
  --solis-live-dot: var(--solis-success-500);
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--solis-live-dot);
  animation: solis-live-pulse 1.6s ease-out infinite;
}

.solis-live__dot--working { --solis-live-dot: var(--solis-ic-blue); }

@keyframes solis-live-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--solis-live-dot) 50%, transparent); }
  70% { box-shadow: 0 0 0 8px color-mix(in oklch, var(--solis-live-dot) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--solis-live-dot) 0%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .solis-live__dot { animation: none; }
}

/* ---- page hero — icon tile beside a page heading ---- */

/* The icon is the sidebar's own, read from the menu item the route belongs to (PageHero view
   component). Hard-coding it per page is what lets a menu icon changed in the backend update the
   nav and silently not the page it leads to. */
.solis-hero {
  display: flex;
  align-items: center;
  gap: var(--solis-space-lg);
}

.solis-hero__ic {
  width: 42px;
  height: 42px;
  border-radius: var(--solis-radius-lg);
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--solis-fs-4xl);
  /* Outline: the tile marks the page without spending the accent fill on decoration. */
  /* --solis-ic-accent, not accent-600: the 600 is not redefined for dark mode, so an outlined
     tile using it renders a dark glyph on a dark surface. This token exists for exactly this —
     an accent-coloured icon sitting on a page surface — and is defined for both themes. */
  color: var(--solis-ic-accent);
  background: var(--solis-ic-accent-bg);
  border: 1.5px solid var(--solis-accent-400);
}

/* h1 and h2 now, so the browser's own heading margins and sizes have to be cleared — these two
   were written for a div and inherited nothing. */
/* Filled and round where the tile is a person. The outlined square is right for a page icon and
   wrong for somebody's face: an avatar is round everywhere else in this app — the topbar's, the
   one in every account menu — and a monogram in a rounded square reads as a third thing. */
.solis-hero__ic--avatar {
  border-radius: 50%;
  background: var(--solis-accent-500);
  color: var(--solis-text-on-accent);
}

.solis-hero__initials {
  font-size: var(--solis-fs-lg);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.solis-hero__title { margin: 0; font-size: var(--solis-fs-3xl); font-weight: 700; letter-spacing: -0.01em; }
.solis-hero__sub { margin-top: var(--solis-space-3xs); font-size: var(--solis-fs-sm); color: var(--solis-text-muted); }

/* ---- utilities ---- */

/* .solis-mono existed only as a descendant rule inside solis-customers.css, so any screen outside
   those blocks got the class with no monospacing at all. The scoped rules stay — they also set a
   colour, and their specificity keeps them winning where they apply. */
.solis-mono {
  font-family: var(--solis-mono);
}

/* Machine text shown as it came, such as a provider's payload, pretty-printed. It keeps its lines and
   scrolls both ways within a capped height rather than stretching the drawer it sits in. */
.solis-code {
  margin: 0;
  max-height: 320px;
  overflow: auto;
  padding: var(--solis-space-md);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface);
  color: var(--solis-text-primary);
  font-family: var(--solis-mono);
  font-size: var(--solis-fs-xs);
  line-height: 1.5;
  white-space: pre;
  tab-size: 2;
}

/* A label above a code block, with its copy button at the end. */
.solis-code-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--solis-space-sm);
  margin-bottom: var(--solis-space-xs);
  color: var(--solis-text-muted);
  font-size: var(--solis-fs-xs);
}

/* Available to screen readers, absent from the page. For a header or control whose meaning is
   carried by an icon or by position, where visible text would be redundant. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ============================================================================
   Refused content — a section the reader may not see, and a stat tile whose
   figure they may not see. Both keep the space their content would have taken:
   a gap reads as "nothing here", which is a different and possibly untrue
   statement. Rendered by Partials/_PermissionDenied and Partials/_StatCard.
   ============================================================================ */

.solis-denied-block {
  display: flex;
  align-items: flex-start;
  gap: var(--solis-space-lg);
  padding: var(--solis-space-4xl) var(--solis-space-4xl);
  border: 1px dashed var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface-muted);
}

.solis-denied-block > i {
  font-size: var(--solis-fs-3xl);
  color: var(--solis-text-muted);
  flex: none;
  /* Optically level with the title's cap height rather than its line box. */
  margin-top: var(--solis-space-3xs);
}

.solis-denied-block__title {
  margin: 0;
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-primary);
}

.solis-denied-block__text {
  margin: var(--solis-space-2xs) 0 0;
  font-size: var(--solis-fs-sm);
  line-height: 1.5;
  color: var(--solis-text-muted);
}

/* The tile keeps its box, its icon and its label — only the figure is withheld, so the strip does
   not reflow and two staff comparing screens still see the same shape. */
.solis-stat--locked .solis-stat__ic {
  opacity: 0.55;
}

/* What the permission actually grants, in the catalog's own words. A second line rather than more
   of the sentence above: the first line says which permission, this says what it is for, and
   someone scanning only needs the first. */
/* The way on from a refusal: to the access-denied page's request form. */
.solis-denied-block__request { margin-top: var(--solis-space-lg); width: fit-content; }

/* The same, inside a sentence: takes the sentence's size rather than the link button's own. */
.solis-request-link {
  font-size: inherit;
  margin-left: var(--solis-space-2xs);
  /* Never underlined, as every link here: the accent colour says it is one. */
  text-decoration: none;
}

.solis-request-link:hover { text-decoration: none; }

/* A link-styled button that is itself the value or the end of a sentence: the text's size, no lead-in
   gap. Request Access keeps its gap, since it always follows a sentence. */
.solis-inline-link {
  font-size: inherit;
  text-align: inherit;
  /* Never underlined, as every link here: the accent colour says it is one. */
  text-decoration: none;
}

.solis-inline-link:hover { text-decoration: none; }

.solis-denied-block__grant,
.solis-denied__grant {
  margin: var(--solis-space-sm) 0 0;
  font-size: var(--solis-fs-sm);
  line-height: 1.5;
  color: var(--solis-text-muted);
}

.solis-denied-block__grant strong,
.solis-denied__grant strong {
  color: var(--solis-text-secondary);
}

.solis-stat__value--locked {
  display: flex;
  align-items: center;
  gap: var(--solis-space-xs);
  color: var(--solis-text-muted);
  font-weight: 600;
}

.solis-stat__value--locked i {
  font-size: var(--solis-fs-xl);
}

/* ---- A field checked against a service as it is typed ----
   A spinner inside the input while the check is in flight; each field says the answer in its own
   words. The account number lookup and the merchant username both use it. */

.solis-input-state { position: relative; }

/* Room for the state marker, so a long value never runs under it. */
.solis-input-state .solis-input { padding-right: var(--solis-space-3xl); }

.solis-input-state__mark {
  position: absolute;
  right: var(--solis-space-md);
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  pointer-events: none;
}

/* A ring that spins while the lookup is in flight. Drawn in CSS rather than as an icon so it needs
   nothing loaded and nothing scripted to appear. */
.solis-input-state__mark[data-state="busy"] {
  border: 2px solid var(--solis-border);
  border-top-color: var(--solis-accent-500);
  border-radius: 50%;
  animation: solis-input-state-spin 600ms linear infinite;
}

@keyframes solis-input-state-spin {
  to { transform: translateY(-50%) rotate(360deg); }
}

/* Motion is decoration here — the marker's presence already says a lookup is running. */
@media (prefers-reduced-motion: reduce) {
  .solis-input-state__mark[data-state="busy"] { animation: none; }
}

/* ---- Account number lookup field ----
   The shared field that resolves a typed account number to its account, drawn with the marker
   above. Lives here rather than in a page stylesheet because it is reused across screens. */



/* ---- Account card ----
   The answer to "is this the account I meant". A card rather than a caption, because it is the
   thing being checked rather than a note about the box above it. Shared by the lookup field and by
   any screen that already knows the account and is stating it. */

.solis-acctcard {
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-2xs);
  padding: var(--solis-space-md);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface-muted);
}

.solis-acctlookup__found.solis-acctcard { margin-top: var(--solis-space-xs); }

.solis-acctcard__row {
  display: flex;
  align-items: center;
  /* Name left, status hard right. The status qualifies the whole card rather than the name, so it
     sits at the edge instead of trailing the words. */
  justify-content: space-between;
  gap: var(--solis-space-sm);
  flex-wrap: wrap;
}

.solis-acctcard__row .solis-pill { flex: none; }

.solis-acctcard__name {
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-primary);
  /* A business name is long and must not push the pill off the card. */
  min-width: 0;
  overflow-wrap: anywhere;
}

.solis-acctcard__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--solis-space-md);
  flex-wrap: wrap;
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-secondary);
}

.solis-acctcard__bal {
  display: inline-flex;
  align-items: baseline;
  gap: var(--solis-space-2xs);
}

.solis-acctcard__bal-label { color: var(--solis-text-muted); }

/* ---- Submission preview ----
   What is about to be submitted, filling in as it is typed — the same idea as the message
   composer's "what recipients receive". Set apart from the fields above it, because it is the one
   block on the form that is read rather than filled. A definition list, because that is what it
   is, and it aligns labels to values without a grid to maintain. */

.solis-review {
  margin-top: var(--solis-space-xl);
  padding: var(--solis-space-lg);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface-muted);
}

.solis-review__title {
  display: flex;
  align-items: center;
  gap: var(--solis-space-xs);
  margin-bottom: var(--solis-space-sm);
}

.solis-review__list { margin: 0; }

.solis-review__row {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: var(--solis-space-md);
  padding: var(--solis-space-md) 0;
  border-bottom: 1px solid var(--solis-border);
}

.solis-review__row:last-child { border-bottom: 0; padding-bottom: 0; }

.solis-review__row:first-of-type { padding-top: 0; }

.solis-review__row dt {
  font-size: var(--solis-fs-xs);
  font-weight: 500;
  color: var(--solis-text-muted);
}

.solis-review__row dd {
  margin: 0;
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-primary);
  overflow-wrap: anywhere;
}

/* The figure is the number somebody is signing off on. */
.solis-review__figure { font-size: var(--solis-fs-lg); }

.solis-review__sub {
  display: block;
  font-size: var(--solis-fs-xs);
  font-weight: 400;
  color: var(--solis-text-muted);
  margin-top: var(--solis-space-3xs);
}

.solis-review__note {
  display: flex;
  align-items: center;
  gap: var(--solis-space-xs);
  margin: var(--solis-space-lg) 0 0;
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
}

@media (max-width: 560px) {
  .solis-review__row { grid-template-columns: minmax(0, 1fr); gap: var(--solis-space-2xs); }
}

/* ---- A pickable row ----
   A checkbox and what it picks, the whole row its label. The Request Access drawer's permissions and
   the Merchant Roles editor. Group headings stay with each screen, which arrange rows differently. */

/* The box itself is the shared checkbox; this only sets it on the name's first line. */
.solis-pick input[type="checkbox"] {
  margin: 2px 0 0;
}

/* A ticked row is the thing being picked, so it stops looking like the rows around it. */
.solis-pick:has(input:checked) {
  background: var(--solis-accent-50);
  border-color: var(--solis-accent-200);
}

/* The whole row is the label, so the checkbox's hit area is the line rather than the box — these
   are read and picked in a list, and a 16px target per row is the wrong size for that.

   font-size is set rather than inherited: the name was rendering at the document's 16px while the
   granted list beside it uses the 13px scale, so the same permission was a third larger here and
   every row carried the extra height twice over. */
.solis-pick {
  display: flex;
  align-items: flex-start;
  gap: var(--solis-space-sm);
  padding: var(--solis-space-sm) var(--solis-space-md);
  border: 1px solid transparent;
  border-radius: var(--solis-radius-md);
  cursor: pointer;
  font-size: var(--solis-fs-md);
  line-height: 1.4;
}

.solis-pick:hover {
  background: var(--solis-surface-muted);
}

.solis-pick:has(input:focus-visible) {
  border-color: var(--solis-accent-500);
}

.solis-pick > span {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.solis-pick__desc {
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
  line-height: 1.35;
}

/* A name with a pill beside it, such as Approval, kept on the name's line. */
.solis-pick__name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--solis-space-sm);
  font-weight: 600;
  color: var(--solis-text-primary);
}

/* A row that can't be picked, shown so what it means is visible. */
.solis-pick:has(input:disabled) {
  cursor: default;
  opacity: 0.6;
}

/* The search and Selected Only above a role's permission list. It stays at the top of the drawer's
   scroll, so a long list can be narrowed from anywhere in it. A hidden row or set takes no room:
   the display set on the row would otherwise win over the attribute. */
.solis-pick-bar {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--solis-space-lg);
  padding-block: var(--solis-space-sm);
  background: var(--solis-surface);
}

.solis-pick-bar .solis-table-search { flex: 1; order: 0; min-width: 0; max-width: none; }

.solis-pick-bar__selected {
  display: inline-flex;
  align-items: center;
  gap: var(--solis-space-sm);
  flex: none;
  font-size: var(--solis-fs-md);
  color: var(--solis-text-secondary);
  white-space: nowrap;
  cursor: pointer;
}

.solis-pick[hidden],
.solis-drawer__perm-set[hidden],
.solis-pick-empty[hidden] { display: none; }

.solis-pick-empty {
  margin: var(--solis-space-lg) 0 0;
  color: var(--solis-text-muted);
}

/* ---- Number field ----
   A figure with its unit inside the box — a currency in front, a percent sign behind — so the
   figure and the unit read as one value and the box keeps the width of every other field in the
   form. No spinner: see behaviors/amountInput.ts. */

.solis-number__control { position: relative; }

.solis-number__prefix,
.solis-number__suffix {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: var(--solis-fs-sm);
  font-weight: 600;
  color: var(--solis-text-muted);
  pointer-events: none;
}

.solis-number__prefix { left: var(--solis-space-md); }
.solis-number__suffix { right: var(--solis-space-md); }

/* Room for a unit only where one is showing — an unprefixed box should not carry a gap waiting for
   a currency that never arrives. */
.solis-number__prefix:not([hidden]) ~ .solis-input { padding-left: var(--solis-space-4xl); }
/* The suffix's own inset, its width, and a clear gap before the figure: 10 %, not 10%. */
.solis-number__control:has(.solis-number__suffix:not([hidden])) .solis-input {
  padding-right: calc(var(--solis-space-md) + 1.5em);
}

/* Figures read right to left when they are being compared, and one that grows leftward from a
   fixed edge is easier to scan than one that grows rightward from a ragged one. */
.solis-number__control .solis-input { text-align: right; }

/* ---- Status legend ----
   The key under a table, rendered by Partials/_StatusLegend. Each pill explains itself on hover or
   focus; the note, where there is one, sits at the far end of the row. */
.solis-legend {
  margin-top: var(--solis-space-3xl);
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
  display: flex;
  flex-wrap: wrap;
  gap: var(--solis-space-xs) var(--solis-space-xl);
  align-items: center;
}

.solis-legend b {
  color: var(--solis-text-secondary);
  font-weight: 600;
}

.solis-legend .solis-pill[data-tooltip] { cursor: help; }

.solis-legend__note { margin-left: auto; }

/* A phone wraps the key wherever a pill runs out of room, which reads as a jumble. There it is a
   grid: the label on its own line, then the pills in columns that line up. */
@media (max-width: 560px) {
  .solis-legend {
    display: grid;
    /* As many equal columns as fit, each as wide as the widest pill (behaviors/legendColumns.ts): a long
       label makes fewer columns rather than running past the card, which a fixed minimum let it do. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--solis-legend-col, 104px)), 1fr));
    justify-items: start;
  }

  .solis-legend > span:first-child,
  .solis-legend__note { grid-column: 1 / -1; margin-left: 0; }
}

/* ---- Choice ----
   Two or three values side by side as radio cards, each with the line that says what it means. For
   a choice a checker reads first — which way a hold runs, how a fee is worked out — where a select
   would hide the other option behind a click. A large target: the whole label is the radio. */
.solis-choice { border: 0; padding: 0; margin: 0; min-width: 0; }

.solis-choice__row { display: flex; gap: var(--solis-space-md); }

/* Options that read as a list rather than a pair (a goal's funding: balance, wallet, reminders), one
   under another at every width. */
.solis-choice__row--stack { flex-direction: column; }

/* Four or more options in a drawer, two to a line, so a card's hint keeps its width (a loan's payout). */
.solis-choice__row--grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }

.solis-choice__option {
  flex: 1;
  display: flex;
  align-items: flex-start;
  gap: var(--solis-space-sm);
  padding: var(--solis-space-md);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  cursor: pointer;
}

.solis-choice__option:has(input:checked) {
  border-color: var(--solis-accent-400);
  background: var(--solis-surface-muted);
}

/* A choice whose outcome is a status takes that status's colour when chosen, as its pill would: a
   return violet, a rejection rose. */
.solis-choice__option--violet:has(input:checked) {
  border-color: var(--solis-ic-violet);
  background: var(--solis-ic-violet-bg);
}

.solis-choice__option--rose:has(input:checked) {
  border-color: var(--solis-ic-rose);
  background: var(--solis-ic-rose-bg);
}

.solis-choice__title i { margin-right: var(--solis-space-2xs); }

/* An option that can't be chosen, shown so its hint can say why (a weekly loan under 7 days). */
.solis-choice__option:has(input:disabled) {
  cursor: default;
  opacity: 0.6;
}

.solis-choice__option:has(input:focus-visible) {
  outline: var(--solis-focus-ring);
  outline-offset: var(--solis-focus-ring-offset);
}

/* The radio's dot takes its card's colour, never the browser's own blue. */
.solis-choice__option input { margin-top: 2px; flex: none; accent-color: var(--solis-accent-500); }
.solis-choice__option--violet input { accent-color: var(--solis-ic-violet); }
.solis-choice__option--rose input { accent-color: var(--solis-ic-rose); }

.solis-choice__body {
  display: flex;
  flex-direction: column;
  /* Start, not stretch: a pill is sized by its word, and a stretched one spans the card with its
     text stranded at the left. */
  align-items: flex-start;
  gap: var(--solis-space-2xs);
  min-width: 0;
}

/* The size a pill's word is set at, so a card titled in words matches one titled with a pill. */
.solis-choice__title {
  font-size: var(--solis-fs-sm);
  font-weight: 600;
  line-height: 1.3;
  color: var(--solis-text-primary);
}

.solis-choice__hint {
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
}

@media (max-width: 900px) {
  .solis-choice__row { flex-direction: column; }
}

/* ---- Checkbox ----
   Drawn rather than native: a native box ignores border-radius, and accent-color tints only the
   tick. An element rule rather than a class, so a checkbox on a new screen is this one without
   anybody remembering to ask. The switch is a checkbox too and draws itself, so it is left out.

   The unticked edge is text-muted, not border: at 0.85 lightness the border token is 1.5:1 on
   white, under the 3:1 a control's outline needs to be seen. The fill is accent-500 in both themes —
   accent-600 goes light in dark mode, and a white tick on it would disappear. The tick is a mask
   over on-accent, so its colour is a token and not baked into the image. */
input[type="checkbox"]:not(.solis-switch) {
  appearance: none;
  flex: none;
  display: inline-grid;
  place-content: center;
  width: 16px;
  height: 16px;
  margin: 0;
  border: 1.5px solid var(--solis-text-muted);
  border-radius: var(--solis-radius-xs);
  background: var(--solis-surface);
  vertical-align: middle;
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

input[type="checkbox"]:not(.solis-switch)::before {
  content: "";
  width: 12px;
  height: 12px;
  background: var(--solis-text-on-accent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.2 5 8.6l4.5-5' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.2 5 8.6l4.5-5' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  transform: scale(0);
  transition: transform 0.12s ease;
}

input[type="checkbox"]:not(.solis-switch):is(:checked, :indeterminate) {
  border-color: var(--solis-accent-500);
  background: var(--solis-accent-500);
}

input[type="checkbox"]:not(.solis-switch):is(:checked, :indeterminate)::before {
  transform: scale(1);
}

/* Some of the rows are picked, not all — Messages' select-all. */
input[type="checkbox"]:not(.solis-switch):indeterminate::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6h6' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6h6' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
}

input[type="checkbox"]:not(.solis-switch):hover:not(:disabled) {
  border-color: var(--solis-accent-500);
}

input[type="checkbox"]:not(.solis-switch):disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Only an empty box takes the muted fill: over a ticked one it hid the tick, so a column that is
   always shown read as one switched off. */
input[type="checkbox"]:not(.solis-switch):disabled:not(:checked, :indeterminate) {
  background: var(--solis-surface-muted);
}

/* A row that dims itself for a box it can't change — a pickable row, the column chooser's locked
   column — would otherwise dim the box a second time, to near invisible. */
:is(.solis-pick, .solis-pop__check) > input[type="checkbox"]:disabled {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  input[type="checkbox"]:not(.solis-switch),
  input[type="checkbox"]:not(.solis-switch)::before {
    transition: none;
  }
}

/* ---- Switch ----
   An on/off setting that applies as a whole, e.g. notification preferences. A native checkbox with
   role="switch", so keyboard, forms and screen readers get the real control; only its look changes.
   A choice among several, or a consent box, stays a plain checkbox. */
.solis-switch {
  appearance: none;
  flex: none;
  position: relative;
  width: 38px;
  height: 22px;
  margin: 0;
  border-radius: var(--solis-radius-pill);
  background: var(--solis-border);
  cursor: pointer;
  transition: background 0.15s ease;
}

.solis-switch::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--solis-switch-knob);
  box-shadow: var(--solis-shadow-knob);
  transition: left 0.15s ease;
}

.solis-switch:checked {
  background: var(--solis-accent-600);
}

.solis-switch:checked::before {
  left: 18px;
}

.solis-switch:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: var(--solis-focus-ring-offset);
}

.solis-switch:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .solis-switch,
  .solis-switch::before {
    transition: none;
  }
}

/* ---- Page frame ---- */

/* A back-office screen's column: bounded, centred, its sections spaced alike. */
.solis-page {
  max-width: var(--solis-page-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-xl);
}

/* The page title with the one action that creates rather than filters. Above the card, not in the
   toolbar: the toolbar is sticky and entirely about narrowing the list, so a create button inside
   it competes with the filters every time the page scrolls. */
.solis-page-head { display: flex; align-items: center; gap: var(--solis-space-xl); }
.solis-page-head .solis-hero { flex: 1; min-width: 0; }
.solis-page-head__action { flex: none; }

/* The bar above a record's list holding the one action that adds to it (Add Account), where the list
   has no toolbar of its own to carry it; never in the record's own header. */
.solis-list-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--solis-space-md);
  margin-bottom: var(--solis-space-2xl);
}

/* A bar that names the list under it: the name at the start, the add at the end, close over the rows. */
.solis-list-bar:has(> .solis-list-bar__title) {
  justify-content: space-between;
  margin-bottom: var(--solis-space-md);
}

.solis-list-bar__title {
  margin: 0;
  font-size: var(--solis-fs-sm);
  font-weight: 600;
  color: var(--solis-text-primary);
}

/* A switch beside the title (a period) drops under it at phone width and spans the row, never running past the card. */
.solis-list-bar { flex-wrap: wrap; }

@media (max-width: 640px) {
  .solis-list-bar > .solis-seg { flex: 1 1 100%; }
  .solis-list-bar > .solis-seg > button { flex: 1 1 0; }
}

/* The title and its action stop sharing a row: narrower, the subtitle was squeezed into a column
   beside the button and wrapped to five lines. */
@media (max-width: 640px) {
  .solis-page-head { flex-wrap: wrap; }
  .solis-page-head .solis-hero { flex: 1 0 100%; }
  .solis-page-head__action { width: 100%; }
}

/* ---- Thin scrollbar ---- */

/* A flat thumb for a pane that scrolls inside the page. The OS default renders as its own boxed
   control against the pane's border; this reads as part of it. */
.solis-scroll-thin {
  scrollbar-width: thin;
  scrollbar-color: var(--solis-border) transparent;
}

.solis-scroll-thin::-webkit-scrollbar { width: 8px; }
.solis-scroll-thin::-webkit-scrollbar-track { background: transparent; }

.solis-scroll-thin::-webkit-scrollbar-thumb {
  background-color: var(--solis-border);
  border-radius: var(--solis-radius-md);
  border: 2px solid transparent;
  background-clip: content-box;
}

.solis-scroll-thin::-webkit-scrollbar-thumb:hover { background-color: var(--solis-text-muted); }

/* ---- Vertical timeline ---- */

/* A record's steps, one under the next, joined by a rail from each dot to the one below. The
   feature draws its own dots and sets where the rail leaves them, since that follows the dot's
   size: --solis-timeline-rail-x is the dot's centre, --solis-timeline-rail-top its foot. */
.solis-timeline__step {
  position: relative;
  display: flex;
  gap: var(--solis-space-lg);
  padding: 0 0 var(--solis-space-2xl) 0;
}

/* Stopping at the last step rather than trailing into nothing. */
.solis-timeline__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: var(--solis-timeline-rail-x, 12px);
  top: var(--solis-timeline-rail-top, 26px);
  bottom: 2px;
  width: 1px;
  background: var(--solis-border);
}

.solis-timeline__step:last-child { padding-bottom: 0; }

/* The line above a timeline: where the record stands, as a pill and whatever qualifies it. */
.solis-status-row {
  display: flex;
  align-items: center;
  gap: var(--solis-space-sm);
  margin-bottom: var(--solis-space-2xl);
}

/* ---- Date and time pickers ----
   A field, not a filter: shared by Messages' send date and Interest Runs' day. The calendar itself
   is solis-table.css's .solis-drp__* grid, reused rather than reimplemented.
   What differs is the trigger: a filter's is a chip, a field's has to look like the inputs beside
   it, so it borrows .solis-input and lays its own contents out. */

.solis-datepick,
.solis-timepick { position: relative; }
.solis-datepick__trigger,
.solis-timepick__trigger {
  display: flex;
  align-items: center;
  gap: var(--solis-space-sm);
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  font-size: var(--solis-fs-md);
  color: var(--solis-text-primary);
}
.solis-datepick__label,
.solis-timepick__label { flex: 1; }
.solis-datepick.is-empty .solis-datepick__label { color: var(--solis-text-muted); }
.solis-datepick__cv { color: var(--solis-text-muted); font-size: var(--solis-fs-lg); }
.solis-datepick__panel,
.solis-timepick__panel {
  position: absolute;
  left: 0;
  top: calc(100% + 5px);
  background: var(--solis-surface);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  box-shadow: var(--solis-shadow-pop);
  z-index: 25;
  display: none;
}
.solis-datepick.is-open .solis-datepick__panel,
.solis-timepick.is-open .solis-timepick__panel { display: block; }
.solis-datepick__panel { padding: var(--solis-space-2xs); min-width: 252px; }
/* Its calendar is the same shared grid, so it inherits the same fixed-column gap on a phone. */
@media (max-width: 900px) {
  .solis-datepick__panel { left: 0; right: 0; min-width: 0; }
  .solis-datepick__panel .solis-drp__cal { padding: var(--solis-space-xs) var(--solis-space-sm) var(--solis-space-sm); }
}
/* The scrolling list is the panel's only child and paints to its edges, so without this the
   content and its scrollbar overrun the rounded corners and the border appears to break where they
   cross it. */
.solis-timepick__panel { overflow: hidden; }
/* Scrolls rather than growing to 48 rows, and opens on the current choice. The padding lives here
   rather than on the panel: on the panel it would sit outside the scroll area, leaving a fixed
   frame around a moving list that reads as a box inside a box. */
.solis-timepick__list {
  max-height: 244px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--solis-space-2xs);
  min-width: 148px;
  scrollbar-width: thin;
}
.solis-timepick__opt {
  display: block;
  width: 100%;
  border: none;
  background: none;
  font: inherit;
  font-size: var(--solis-fs-sm);
  font-variant-numeric: tabular-nums;
  color: var(--solis-text-primary);
  text-align: left;
  padding: var(--solis-space-xs) var(--solis-space-sm);
  border-radius: var(--solis-radius-sm);
  cursor: pointer;
}
.solis-timepick__opt:hover:not(:disabled) { background: var(--solis-surface-muted); }
.solis-timepick__opt.is-on { background: var(--solis-accent-50); color: var(--solis-accent-ink); font-weight: 600; }
.solis-timepick__opt.is-disabled { opacity: 0.35; cursor: not-allowed; }
/* A time left behind by moving the date onto today. */
.solis-timepick.is-invalid .solis-timepick__trigger { border-color: var(--solis-danger-border); }

/* ---- note ----
   Information a drawer or page states: an icon beside a sentence or two. Amber by default, for what
   waits on a person or needs reading before acting; --neutral for a settled state, which spends none
   of the region's colour. One part for what had been built five times (the drawer note, the approval
   thread's soft failure, the "list couldn't be read" state, the earmark release warning and the
   customer edit notice).

   The icon sits in a box exactly one text line tall with the glyph centred in it, so it lines up
   with the first line whether the note runs to one line or several. A top margin never could: the
   glyph is larger than the text, and the gap it needs changes with both. */
.solis-note {
  --solis-note-line: calc(var(--solis-fs-sm) * 1.45);
  margin-bottom: var(--solis-space-3xl);
  display: flex;
  align-items: flex-start;
  gap: var(--solis-space-sm);
  padding: var(--solis-space-md) var(--solis-space-lg);
  border-radius: var(--solis-radius-md);
  background: var(--solis-warn-bg);
  border: 1px solid var(--solis-warn-border);
  font-size: var(--solis-fs-sm);
  line-height: 1.45;
  color: var(--solis-text-secondary);
}

.solis-note[hidden] { display: none; }

/* Stacked notes each say one thing; touching, they read as one box. */
.solis-note + .solis-note { margin-top: var(--solis-space-sm); }

.solis-note > i {
  flex: none;
  display: inline-flex;
  align-items: center;
  height: var(--solis-note-line);
  font-size: var(--solis-fs-xl);
  line-height: 1;
  color: var(--solis-warn-500);
}

.solis-note p { margin: 0; }

/* What a service said, word for word, under the note's own sentence: set apart so it reads as
   quoted rather than as the Portal's explanation. */
.solis-note__quote { font-style: italic; }
/* What the provider or the ledger answered, word for word, under the readable message. */
.solis-note__reply { margin-top: 4px; font-size: 12px; color: var(--solis-text-secondary); }
.solis-note__reply span { font-weight: 600; }
.solis-note__reply code { font-family: var(--solis-mono); word-break: break-word; }
.solis-note b { color: var(--solis-text-primary); font-weight: 600; }

.solis-note--neutral {
  background: var(--solis-surface-muted);
  border-color: var(--solis-border);
}

.solis-note.solis-note--neutral > i { color: var(--solis-text-muted); }

/* A note in the colour of the status it explains, matching that status's pill: green landed, red
   refused, blue on its way, teal carried by something later, violet waiting to run again, rose
   turned down. The tints
   follow the theme through their tokens. */
.solis-note--success {
  background: var(--solis-success-bg);
  border-color: var(--solis-success-border);
}

.solis-note.solis-note--success > i { color: var(--solis-success-500); }

.solis-note--danger {
  background: var(--solis-danger-bg);
  border-color: var(--solis-danger-border);
}

.solis-note.solis-note--danger > i { color: var(--solis-danger-500); }

.solis-note--info {
  background: var(--solis-ic-blue-bg);
  border-color: var(--solis-ic-blue-bg);
}

.solis-note.solis-note--info > i { color: var(--solis-ic-blue); }

.solis-note--teal {
  background: var(--solis-ic-teal-bg);
  border-color: var(--solis-ic-teal-bg);
}

.solis-note.solis-note--teal > i { color: var(--solis-ic-teal); }

.solis-note--violet {
  background: var(--solis-ic-violet-bg);
  border-color: var(--solis-ic-violet-bg);
}

.solis-note.solis-note--violet > i { color: var(--solis-ic-violet); }

/* Rose for refused, as the Rejected pill: an approver said no, which is not a failure (red). */
.solis-note--rose {
  background: var(--solis-ic-rose-bg);
  border-color: var(--solis-ic-rose-bg);
}

.solis-note.solis-note--rose > i { color: var(--solis-ic-rose); }

/* Stone for a draft, as the Draft pill. */
.solis-note--stone {
  background: var(--solis-ic-stone-bg);
  border-color: var(--solis-ic-stone-bg);
}

.solis-note.solis-note--stone > i { color: var(--solis-ic-stone); }

/* ---- Band ladder ----
   A rate ladder entered as break points (Partials/_BandLadder, widgets/bandLadder.ts): a tier per row
   with its From, Up To, rate and a remove control. From is read, never typed; the top tier reads No
   Limit. A fixed rate is one tier, shown as its rate alone. */
.solis-ladder { display: flex; flex-direction: column; gap: var(--solis-space-xl); }

/* The tiers read as a small table: a heading row, then a row per tier, in a box of their own. */
.solis-ladder__grid {
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-sm);
  padding: var(--solis-space-md);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface-muted);
}

.solis-ladder__rows { display: flex; flex-direction: column; gap: var(--solis-space-sm); }

.solis-ladder__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 0.9fr) 28px;
  gap: var(--solis-space-sm);
  align-items: center;
}

.solis-ladder__row--head {
  padding-bottom: var(--solis-space-xs);
  border-bottom: 1px solid var(--solis-border);
  font-size: var(--solis-fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--solis-text-muted);
}

.solis-ladder__from,
.solis-ladder__open {
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.solis-ladder__open { padding-left: var(--solis-space-md); }

.solis-ladder__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--solis-radius-md);
  background: none;
  color: var(--solis-text-muted);
  cursor: pointer;
}

.solis-ladder__remove:hover { background: var(--solis-surface-muted); color: var(--solis-text-primary); }

/* Adds a tier under the last one, as wide as the rows it joins. */
.solis-ladder__add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--solis-space-xs);
  width: 100%;
  padding: var(--solis-space-sm);
  border: 1px dashed var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface);
  font: inherit;
  font-size: var(--solis-fs-sm);
  font-weight: 500;
  color: var(--solis-text-secondary);
  cursor: pointer;
}

.solis-ladder__add:hover:not(:disabled) {
  border-color: var(--solis-accent-400);
  color: var(--solis-text-primary);
  background: var(--solis-surface-muted);
}

/* A fixed rate: the rate field alone, at a number field's width, out of the tiers' box. */
.solis-ladder--fixed .solis-ladder__grid { padding: 0; border: 0; background: none; }

.solis-ladder--fixed .solis-ladder__row--head,
.solis-ladder--fixed .solis-ladder__from,
.solis-ladder--fixed .solis-ladder__open,
.solis-ladder--fixed .solis-ladder__remove,
.solis-ladder--fixed .solis-ladder__hint { display: none; }

.solis-ladder--fixed .solis-ladder__row { grid-template-columns: minmax(0, 200px); }

@media (max-width: 640px) {
  .solis-ladder__row { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 1fr) 28px; }
  .solis-ladder--fixed .solis-ladder__row { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Range bands ----
   Bands with both ends typed (Partials/_RangeBands, widgets/rangeBands.ts), in the ladder's box: From and
   To are inputs alike, so their columns match, and a gap between two bands is allowed. */
.solis-ranges { gap: var(--solis-space-sm); }

.solis-ranges .solis-ladder__row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 0.9fr) 28px; }

/* ---- Split ----
   A short list of records beside the one shown (Partials markup in the screen, behaviors/splitList.ts):
   the list on the left, the selected record's pane on the right, as Charge Packages lists its
   packages. The selection is in the address only when it isn't the first. Below 900px the list sits
   above the pane. */
.solis-split {
  display: grid;
  grid-template-columns: minmax(200px, 260px) minmax(0, 1fr);
  padding: 0;
  overflow: hidden;
}

/* A long list scrolls on its own, so the pane beside it stays where it is. */
.solis-split__list {
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-2xs);
  max-height: 75vh;
  overflow-y: auto;
  padding: var(--solis-space-lg) var(--solis-space-sm);
  border-right: 1px solid var(--solis-border);
  background: var(--solis-surface-muted);
}

/* The list's own head: its name, and the add that grows it. */
.solis-split__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--solis-space-sm);
  padding: 0 var(--solis-space-xs) var(--solis-space-xs) 0;
}

.solis-split__head .solis-split__heading { padding-bottom: 0; }

.solis-split__search {
  position: relative;
  display: block;
  margin: var(--solis-space-xs) var(--solis-space-xs) var(--solis-space-sm);
}

.solis-split__search > i {
  position: absolute;
  left: var(--solis-space-md);
  top: 50%;
  transform: translateY(-50%);
  color: var(--solis-text-muted);
  pointer-events: none;
}

.solis-split__search .solis-input { padding-left: calc(var(--solis-space-md) * 2 + 1em); }

.solis-split__group { display: flex; flex-direction: column; gap: var(--solis-space-2xs); }

.solis-split__group + .solis-split__group { margin-top: var(--solis-space-lg); }

.solis-split__empty {
  margin: var(--solis-space-sm) var(--solis-space-md);
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
}

.solis-split__heading {
  padding: 0 var(--solis-space-md) var(--solis-space-xs);
  font-size: var(--solis-fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--solis-text-muted);
}

/* A group that folds (details.solis-split__group, a long list such as Analytics' questions): its heading is the
   summary, the caret after the words and the count at the end. */
.solis-split__group > summary.solis-split__heading { display: flex; align-items: center; gap: var(--solis-space-xs); }
.solis-split__count { order: 2; margin-left: auto; font-weight: 500; letter-spacing: 0; font-variant-numeric: tabular-nums; }

/* A long group shows its first items and keeps the rest under Show All, until a search looks through them all. */
.solis-split__list:not(.is-searching) .solis-split__group:not(.is-expanded) > .is-extra { display: none; }
.solis-split__list.is-searching [data-split-more] { display: none; }
.solis-split__more { align-self: flex-start; margin: 0 var(--solis-space-md); }

.solis-split__item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "name state" "meta state";
  align-items: center;
  column-gap: var(--solis-space-sm);
  row-gap: var(--solis-space-3xs);
  padding: var(--solis-space-sm) var(--solis-space-md);
  border-radius: var(--solis-radius-md);
  border-left: 3px solid transparent;
  color: inherit;
  text-decoration: none;
}

/* An item that picks in place rather than opening a page is a button, drawn as the link is. */
button.solis-split__item {
  width: 100%;
  border: none;
  border-left: 3px solid transparent;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

button.solis-split__item:disabled { cursor: not-allowed; }

.solis-split__item:hover { background: var(--solis-surface); }

.solis-split__item[aria-current="true"] {
  border-left-color: var(--solis-accent-500);
  background: var(--solis-surface);
}

.solis-split__item:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: var(--solis-focus-ring-offset);
}

.solis-split__name {
  grid-area: name;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--solis-fs-sm);
  font-weight: 600;
  color: var(--solis-text-primary);
}

.solis-split__item[aria-current="true"] .solis-split__name { color: var(--solis-accent-ink); }

.solis-split__meta {
  grid-area: meta;
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
}

.solis-split__state { grid-area: state; }

.solis-split__pane {
  min-width: 0;
  padding: var(--solis-space-2xl);
}

@media (max-width: 900px) {
  .solis-split { grid-template-columns: minmax(0, 1fr); }
  .solis-split__list { border-right: 0; border-bottom: 1px solid var(--solis-border); }
  .solis-split__pane { padding: var(--solis-space-lg); }
}

/* ---- A form page ----
   The form beside a summary that stays in view while it is worked through (New Disbursement). One column
   below 1100px, where two would leave neither enough room. */
.solis-form-page__layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 22rem;
  gap: var(--solis-space-xl);
  align-items: start;
}

.solis-form-page__form {
  display: grid;
  /* A zero floor, so a wide table inside scrolls in its own box rather than widening the column under the aside. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--solis-space-xl);
  min-width: 0;
}

.solis-form-page__aside { position: sticky; top: var(--solis-space-xl); }

/* Already a card in its own right here, rather than a block inside a drawer. */
.solis-form-page__review { margin-top: 0; background: var(--solis-surface); }

/* A note inside the summary, or under a field of a form's drawer: it carries no margin below of its own. */
.solis-form-page__note { margin: var(--solis-space-lg) 0 0; }

/* Two fields to a row on the page, where a drawer's single column would make the form twice as long as the
   summary beside it. */
.solis-form-page__form .solis-fieldset {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--solis-space-lg);
}

/* A card's blocks (fields, notes, a bar of buttons) one gap apart, so none sits against the next; the notes and the bar
   lose the margin they carry on their own. */
.solis-form-page__stack {
  display: grid;
  gap: var(--solis-space-xl);
  min-width: 0;
}

.solis-form-page__stack > :is(.solis-note, .solis-entry-bar, .solis-field, .solis-seg) { margin: 0; }
.solis-form-page__stack .solis-seg { margin: 0; }

/* The footer's pair, in the drawers' order: the dismissive one first, the one primary last. */
.solis-form-page__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--solis-space-sm);
  margin-top: var(--solis-space-lg);
}

/* Equal halves while both labels fit; a label never wraps, so the longer one ("Submit for Approval" in the 22rem
   summary) takes what it needs from its neighbour, and only a pair too wide for the row puts the primary on its own. */
.solis-form-page__actions > * {
  flex: 1 1 0;
  min-width: max-content;
  white-space: nowrap;
}

/* ---- A grid of rows filled in by hand ----
   Recipients typed one by one or read from a file. Each row's cells are inputs, so it is a grid of its own rather
   than a table whose rows turn into cards on a phone; a cell that fails its rule takes the danger border and its
   reason is read from the problems note above the grid. */
.solis-entry-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--solis-space-sm);
  margin-bottom: var(--solis-space-lg);
}

.solis-entry-bar__gap { flex: 1; }

/* The bar's buttons at its trailing edge, under the field they act on. */
.solis-entry-bar--end { justify-content: flex-end; margin: var(--solis-space-sm) 0; }

.solis-entry-grid {
  --solis-entry-columns: 2.25rem minmax(14rem, 1.4fr) 10.5rem 8.5rem minmax(8rem, 1fr) 6rem 2.25rem;
  /* Rows lay themselves out by the grid's own width, not the window's: beside the summary it is narrower than the screen. */
  container-type: inline-size;
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface);
}

.solis-entry-grid__head,
.solis-entry-grid__row {
  display: grid;
  grid-template-columns: var(--solis-entry-columns);
  column-gap: var(--solis-space-md);
  align-items: center;
  padding: var(--solis-space-sm) var(--solis-space-lg);
}

.solis-entry-grid__head {
  font-size: var(--solis-fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--solis-text-muted);
  background: var(--solis-surface-muted);
  border-bottom: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md) var(--solis-radius-md) 0 0;
}

.solis-entry-grid__row + .solis-entry-grid__row { border-top: 1px solid var(--solis-border); }

.solis-entry-grid__num { text-align: right; }

.solis-entry-grid__row .solis-input,
.solis-entry-grid__row .solis-select { width: 100%; min-width: 0; }
.solis-entry-grid__row input[data-disb-amount] { text-align: right; }

.solis-entry-grid__row .solis-input[aria-invalid="true"] { border-color: var(--solis-danger-500); }

.solis-entry-grid__index { font-size: var(--solis-fs-xs); color: var(--solis-text-muted); }

.solis-entry-grid__charge { text-align: right; font-size: var(--solis-fs-sm); }

/* The line under the drop zone, naming the other ways in. */
.solis-entry-grid__or {
  text-align: center;
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
}

/* Narrower than a row needs on one line: the institution leads, the account and amount share the next line, and the
   narrative and charge close it. The column heads go, since each cell carries its own placeholder. */
@container (max-width: 56rem) {
  .solis-entry-grid__head { display: none; }

  .solis-entry-grid__row {
    grid-template-columns: 2.25rem minmax(0, 1fr) minmax(0, 1fr) 2.25rem;
    grid-template-areas:
      "idx inst inst rm"
      ".   acct amt  ."
      ".   narr chg  .";
    row-gap: var(--solis-space-sm);
  }

  .solis-entry-grid__row > [data-disb-cell="index"] { grid-area: idx; }
  .solis-entry-grid__row > [data-disb-cell="inst"] { grid-area: inst; }
  .solis-entry-grid__row > [data-disb-cell="acct"] { grid-area: acct; }
  .solis-entry-grid__row > [data-disb-cell="amt"] { grid-area: amt; }
  .solis-entry-grid__row > [data-disb-cell="narr"] { grid-area: narr; }
  .solis-entry-grid__row > [data-disb-cell="chg"] { grid-area: chg; }
  .solis-entry-grid__row > [data-disb-cell="rm"] { grid-area: rm; }
}

.solis-entry-grid__empty {
  display: grid;
  justify-items: center;
  gap: var(--solis-space-xs);
  padding: var(--solis-space-7xl) var(--solis-space-xl);
  text-align: center;
  color: var(--solis-text-muted);
}

.solis-entry-grid__empty .ti { font-size: var(--solis-fs-2xl); }

.solis-entry-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--solis-space-lg);
  margin-top: var(--solis-space-lg);
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-secondary);
}

.solis-entry-foot__only { display: inline-flex; align-items: center; gap: var(--solis-space-xs); cursor: pointer; }
.solis-entry-foot__pager { display: inline-flex; align-items: center; gap: var(--solis-space-sm); }

@media (max-width: 1199px) {
  .solis-form-page__layout { grid-template-columns: minmax(0, 1fr); }
  .solis-form-page__aside { position: static; }
}

@media (max-width: 640px) {
  .solis-form-page__form .solis-fieldset { grid-template-columns: minmax(0, 1fr); }
}

/* A repeated account is pointed out, not refused: the cell takes the warning border, which an invalid cell's own border outranks. */
.solis-entry-grid__row .solis-input[data-warn="true"] { border-color: var(--solis-warn-border); }
.solis-entry-grid__row .solis-input[aria-invalid="true"][data-warn="true"] { border-color: var(--solis-danger-500); }

/* The institution is a drawn select, so a duplicate row marks its trigger the way the account and amount inputs are marked. */
.solis-entry-grid__row [data-disb-cell="inst"][data-warn="true"] .solis-select__trigger { border-color: var(--solis-warn-border); }

/* What a record still needs before it can go on (a merchant application, a disbursement draft): the shared timeline's rail and spacing, with a dot of its own — met is
   a green tick, outstanding a grey dash rather than a red cross, since a draft is not a fault. */
.solis-readiness {
  list-style: none;
  margin: 0 0 var(--solis-space-lg);
  padding: 0;
  --solis-timeline-rail-x: 11px;
  --solis-timeline-rail-top: 26px;
}

.solis-readiness__step { padding-bottom: var(--solis-space-lg); }

.solis-readiness__dot {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--solis-border);
  background: var(--solis-surface);
  color: var(--solis-text-muted);
  font-size: var(--solis-fs-xs);
  flex: none;
}

.solis-readiness__step.is-met .solis-readiness__dot {
  border-color: var(--solis-success-border);
  background: var(--solis-success-bg);
  color: var(--solis-success-500);
}

.solis-readiness__label {
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-primary);
}

.solis-readiness__at {
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
}

/* What a record still needs before it moves on (a draft loan's required documents), laid out as tiles so a long list
   wraps into rows instead of running down the page. A tile still owed may be a button that starts supplying it. */
.solis-requirements {
  list-style: none;
  margin: 0 0 var(--solis-space-lg);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--solis-space-sm);
}

.solis-requirements__item {
  display: flex;
  align-items: center;
  gap: var(--solis-space-sm);
  width: 100%;
  min-height: 52px;
  padding: var(--solis-space-sm) var(--solis-space-md);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface);
  color: var(--solis-text-primary);
  font: inherit;
  text-align: left;
}

.solis-requirements__item > .ti {
  flex: none;
  font-size: 18px;
}

.solis-requirements__item.is-met > .ti { color: var(--solis-success-500); }
.solis-requirements__item.is-unmet > .ti { color: var(--solis-warn-500); }

.solis-requirements__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.solis-requirements__name {
  font-size: var(--solis-fs-md);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.solis-requirements__meta {
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
}

.solis-requirements__item.is-unmet .solis-requirements__meta { color: var(--solis-warn-ink); }

button.solis-requirements__item { cursor: pointer; }

button.solis-requirements__item:hover {
  border-color: var(--solis-warn-border);
  background: var(--solis-warn-bg);
}

button.solis-requirements__item:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: 2px;
}

@media (max-width: 900px) {
  .solis-requirements { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  .solis-requirements { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================================
   Wording — message text as staff read it: a placeholder is a chip carrying its label, never its key.
   Used wherever a template's wording is shown (a template's page, its history, the edit drawer's review).
   ============================================================================ */
.solis-wording {
  padding: var(--solis-space-lg);
  background: var(--solis-surface-muted);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  color: var(--solis-text-primary);
  font-size: var(--solis-fs-lg);
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ---- handset ----
   A payer's phone running a USSD menu (UssdMenu/_Simulator): a handset outline in the theme's own surfaces, the Solis
   mark until a session starts, then the screen's text with its own line breaks over a reply line Enter sends. */
.solis-handset-card {
  padding: var(--solis-space-2xl);
}

.solis-handset {
  max-width: 240px;
  margin: 0 auto;
  padding: var(--solis-space-xl) var(--solis-space-md) var(--solis-space-3xl);
  border: 2px solid var(--solis-border);
  border-radius: calc(var(--solis-radius-xl) * 2);
  background: var(--solis-surface-muted);
}

.solis-handset__screen {
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-lg);
  min-height: 260px;
  padding: var(--solis-space-lg);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface);
}

.solis-handset__screen[aria-busy="true"] .solis-handset__text {
  opacity: 0.5;
}

.solis-handset__idle {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: center;
}

.solis-handset__idle .solis-wordmark {
  height: 40px;
  color: var(--solis-text-primary);
}

.solis-handset__text {
  flex: 1;
  color: var(--solis-text-primary);
  font-size: var(--solis-fs-md);
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.solis-handset__reply {
  display: flex;
  align-items: center;
  gap: var(--solis-space-xs);
  border-bottom: 1px solid var(--solis-border);
}

.solis-handset__input {
  flex: 1;
  min-width: 0;
  padding: var(--solis-space-xs) var(--solis-space-sm);
  border: 0;
  background: none;
  color: var(--solis-text-primary);
  font: inherit;
  font-size: var(--solis-fs-md);
}

.solis-handset__input:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: var(--solis-focus-ring-offset);
  border-radius: var(--solis-radius-sm);
}

.solis-handset__input::placeholder {
  color: var(--solis-text-muted);
}

.solis-handset__send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--solis-radius-sm);
  background: none;
  color: var(--solis-accent-500);
  cursor: pointer;
}

.solis-handset__send:disabled {
  color: var(--solis-text-muted);
  cursor: default;
}

.solis-handset__start {
  display: flex;
  margin: var(--solis-space-xl) auto 0;
}

.solis-handset__note {
  margin: var(--solis-space-md) 0 0;
  color: var(--solis-text-muted);
  font-size: var(--solis-fs-sm);
  text-align: center;
}

/* The same wording without its box, where a box would sit on another: a USSD menu's text over its options table,
   a prompt in a table's cell. */
.solis-wording--plain {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  font-size: inherit;
  line-height: 1.5;
}

.solis-wording--plain + .solis-table-wrap {
  margin-top: var(--solis-space-xl);
}

.solis-wording__token {
  display: inline-flex;
  align-items: center;
  gap: var(--solis-space-2xs);
  padding: 0 var(--solis-space-xs);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-xs);
  background: var(--solis-surface);
  color: var(--solis-text-muted);
  font-size: var(--solis-fs-md);
  /* Shorter than the line it sits in, so a pill never makes its line taller than the others. */
  line-height: 1.4;
  margin: 0 var(--solis-space-3xs);
  white-space: nowrap;
}

.solis-wording__token--unknown {
  border-color: var(--solis-danger-ink);
  color: var(--solis-danger-ink);
}

.solis-wording-label {
  margin: var(--solis-space-lg) 0 var(--solis-space-xs);
  color: var(--solis-text-muted);
  font-size: var(--solis-fs-sm);
  font-weight: 600;
}

/* A document a service rendered from Markdown (a developer legal document): its headings, lists and links read as the
   reader will see them, in the Portal's own type and tokens. The HTML is the service's, raw HTML escaped. */
.solis-prose {
  padding: var(--solis-space-lg);
  background: var(--solis-surface-muted);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  color: var(--solis-text-primary);
  font-size: var(--solis-fs-lg);
  line-height: 1.6;
  overflow-wrap: anywhere;
}

.solis-prose > :first-child { margin-top: 0; }
.solis-prose > :last-child { margin-bottom: 0; }
.solis-prose h1, .solis-prose h2, .solis-prose h3 { margin: var(--solis-space-lg) 0 var(--solis-space-xs); line-height: 1.3; }
.solis-prose h1 { font-size: var(--solis-fs-2xl); }
.solis-prose h2 { font-size: var(--solis-fs-xl); }
.solis-prose h3 { font-size: var(--solis-fs-lg); }
.solis-prose p, .solis-prose ul, .solis-prose ol { margin: 0 0 var(--solis-space-sm); }
.solis-prose ul, .solis-prose ol { padding-left: var(--solis-space-xl); }
.solis-prose a { color: var(--solis-accent-600); font-weight: 600; text-decoration: none; }

/* Text and its rendering side by side (a developer legal document's wording and partner-service's preview): two panes
   of one height on a wide screen; at 900px and below a .solis-tabs strip shows one at a time. */
.solis-editor-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--solis-space-xl);
}

.solis-editor-split__pane {
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-xs);
  min-width: 0;
}

.solis-editor-split__text,
.solis-editor-split__preview {
  height: max(420px, calc(100vh - 360px));
  overflow-y: auto;
}

.solis-editor-split__text {
  resize: none;
  font-family: var(--solis-mono);
  line-height: 1.6;
}

.solis-editor-split__tabs {
  display: none;
  margin-bottom: var(--solis-space-md);
}

.solis-wording-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--solis-space-lg);
  margin-bottom: var(--solis-space-sm);
}

.solis-wording-about {
  margin: 0;
  color: var(--solis-text-secondary);
  font-size: var(--solis-fs-lg);
  line-height: 1.5;
}

/* A placeholder to insert, in the edit drawer: the same chip as in the wording, as a button. */
.solis-wording-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--solis-space-xs);
}

.solis-wording__token--button {
  cursor: pointer;
  font-family: inherit;
}

.solis-wording__token--button:hover {
  border-color: var(--solis-accent-600);
  color: var(--solis-text-primary);
}

.solis-wording-hint {
  margin: var(--solis-space-xs) 0 0;
  color: var(--solis-text-muted);
  font-size: var(--solis-fs-sm);
}

/* A switch with its name and what it does, in a drawer or a form: the label and hint on the left, the switch at the end. */
.solis-switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--solis-space-lg);
  padding: var(--solis-space-sm) 0;
}

.solis-switch-row__title {
  display: block;
  color: var(--solis-text-primary);
  font-size: var(--solis-fs-md);
  font-weight: 600;
  cursor: pointer;
}

.solis-switch-row__hint {
  margin: var(--solis-space-3xs) 0 0;
  color: var(--solis-text-muted);
  font-size: var(--solis-fs-sm);
}

/* The wording editor: a field whose text holds placeholders as pills (widgets/wordingEditor.ts). A pill sits in the
   line like a word, so the line is tall enough for it, and keeps what it is made of: a label and a remove button. */
.solis-wording-editor {
  min-height: 74px;
  line-height: 1.9;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  cursor: text;
}

.solis-wording-editor--line {
  min-height: 0;
  white-space: nowrap;
  overflow-x: auto;
}

.solis-wording-editor.is-disabled {
  background: var(--solis-surface-muted);
  color: var(--solis-text-muted);
  cursor: not-allowed;
}

/* Where a dragged placeholder would land: the field takes the accent, as a drop target does. */
.solis-wording-editor.is-dropping {
  border-color: var(--solis-accent-600);
}

.solis-wording__token--in-text {
  user-select: none;
  cursor: grab;
  padding-right: var(--solis-space-3xs);
  color: var(--solis-text-primary);
}

.solis-wording__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0 var(--solis-space-3xs);
  border: 0;
  background: transparent;
  color: var(--solis-text-muted);
  font: inherit;
  line-height: 1;
  cursor: pointer;
}

.solis-wording__remove:hover {
  color: var(--solis-danger-ink);
}

.is-disabled .solis-wording__remove {
  display: none;
}

/* With no × to carry the right side, the label is centred in its pill as the read-only chips are. */
.is-disabled .solis-wording__token--in-text {
  padding-right: var(--solis-space-xs);
}

.is-disabled .solis-wording__token--in-text {
  cursor: default;
}

/* A placeholder already in the message: the chip stays to show it, and can't be added again. */
.solis-wording__token--button.is-used {
  opacity: 0.5;
  cursor: not-allowed;
}

.solis-wording__token--button.is-used:hover {
  border-color: var(--solis-border);
}

.solis-wording__token--button:not(.is-used):not([aria-disabled="true"]) {
  cursor: grab;
}

/* The channel switch at the top of a form drawer: one channel's fields at a time, with room below it. */
.solis-seg--spaced {
  margin-bottom: var(--solis-space-lg);
}

/* A secret placeholder's marker, beside its name. */
.solis-wording-secret {
  margin-left: var(--solis-space-sm);
}

/* ---- toggle chips ----
   Values picked by a tap rather than from a list: a filter's "is any of", the figures a chart draws, the functions a
   formula can start with. The checkbox inside stays the value; the chip shows it on. A filter chip names a filter in
   force, with its remove button at the end. */
.solis-toggle-chips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--solis-space-2xs); }

.solis-toggle-chip,
.solis-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--solis-space-2xs);
  padding: 2px var(--solis-space-sm);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-pill);
  background: var(--solis-surface);
  font: inherit;
  font-size: var(--solis-fs-xs);
  font-weight: 600;
  color: var(--solis-text-secondary);
}

.solis-toggle-chip { cursor: pointer; }
.solis-toggle-chip input { position: absolute; opacity: 0; pointer-events: none; }
/* On is a filled chip with a tick, as a switch's picked option is: a wash beside white chips read as off. */
.solis-toggle-chip.is-on { border-color: var(--solis-accent-500); background: var(--solis-accent-500); color: var(--solis-text-on-accent); }
.solis-toggle-chip__tick { display: none; }
.solis-toggle-chip.is-on .solis-toggle-chip__tick { display: inline; }
/* The ring is for the keyboard only: a click focuses the hidden box, and a ring round the border read as a second state. */
.solis-toggle-chip:has(input:focus-visible),
.solis-toggle-chip:focus-visible { outline: var(--solis-focus-ring); outline-offset: 1px; }
.solis-toggle-chip:not(.is-on):hover { border-color: var(--solis-accent-400); }

.solis-filter-chip { background: var(--solis-surface-muted); padding-right: var(--solis-space-2xs); }
.solis-filter-chip button { border: none; background: none; padding: 0 2px; color: var(--solis-text-muted); cursor: pointer; }
.solis-filter-chip button:hover { color: var(--solis-text-primary); }

/* ---- A month calendar (the Change Calendar, Public Holidays) ---- */

.solis-calendar { border: 1px solid var(--solis-border); border-radius: var(--solis-radius-md); overflow: hidden; }
.solis-calendar__grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }

.solis-calendar__dow {
  padding: var(--solis-space-xs) var(--solis-space-sm);
  font-size: var(--solis-fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--solis-text-muted);
  background: var(--solis-surface-muted);
  border-bottom: 1px solid var(--solis-border);
}

.solis-calendar__day {
  position: relative;
  min-height: 112px;
  padding: var(--solis-space-xs);
  border-right: 1px solid var(--solis-border);
  border-bottom: 1px solid var(--solis-border);
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-3xs);
  min-width: 0;
}

.solis-calendar__grid > .solis-calendar__day:nth-child(7n) { border-right: none; }
.solis-calendar--week .solis-calendar__day { min-height: 260px; }
.solis-calendar__day--out { background: var(--solis-surface-muted); }
.solis-calendar__day--out .solis-calendar__num { color: var(--solis-text-muted); }

.solis-calendar__num {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--solis-space-2xs);
  font-size: var(--solis-fs-xs);
  font-weight: 600;
  color: var(--solis-text-secondary);
}

/* Today: a line across the top of the day, in the accent. */
.solis-calendar__day--today { box-shadow: inset 0 3px 0 var(--solis-accent-500); }
.solis-calendar__day--today .solis-calendar__num { color: var(--solis-accent-ink); }

/* A change on a day: a block in its status's pill colour, which the pill classes supply. */
.solis-calendar__event {
  display: flex;
  align-items: center;
  gap: var(--solis-space-3xs);
  width: 100%;
  max-width: 100%;
  border-radius: var(--solis-radius-sm);
  padding: var(--solis-space-3xs) var(--solis-space-2xs);
  font: inherit;
  font-size: var(--solis-fs-xs);
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.solis-calendar__event span { overflow: hidden; text-overflow: ellipsis; }
.solis-calendar__event:focus-visible { outline: var(--solis-focus-ring); outline-offset: var(--solis-focus-ring-offset); }
.solis-calendar__add {
  margin-top: auto;
  align-self: flex-start;
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: var(--solis-fs-xs);
  color: var(--solis-text-muted);
  cursor: pointer;
  opacity: 0;
}

.solis-calendar__day:hover .solis-calendar__add,
.solis-calendar__add:focus-visible { opacity: 1; color: var(--solis-accent-ink); }

.solis-calendar-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--solis-space-md); margin-bottom: var(--solis-space-lg); }
.solis-calendar-bar .solis-seg { width: auto; margin-bottom: 0; }
.solis-calendar-bar__month { font-size: var(--solis-fs-lg); font-weight: 600; color: var(--solis-text-primary); min-width: 150px; text-align: center; }
.solis-calendar-bar__nav { display: flex; align-items: center; gap: var(--solis-space-2xs); }
.solis-calendar-bar__end { margin-left: auto; display: flex; gap: var(--solis-space-sm); flex-wrap: wrap; }

/* An event that is not a status (a public holiday): a quiet block in the card's own colours. */
.solis-calendar__event--plain {
  border: 1px solid var(--solis-border);
  background: var(--solis-surface-muted);
  color: var(--solis-text-primary);
}

/* A weekend in a calendar where weekdays matter (leave is counted in them): its number is muted. */
.solis-calendar__day--weekend .solis-calendar__num { color: var(--solis-text-muted); }

/* The calendar's list stands in for the grid on a phone, where a month can't be read. */
.solis-calendar-list--phone { display: none; }
.solis-calendar-list--phone, .solis-calendar + .solis-rec-panel__note { margin-top: var(--solis-space-lg); }

@media (max-width: 900px) {
  .solis-calendar__day { min-height: 88px; }
}

@media (max-width: 640px) {
  .solis-calendar, .solis-calendar + .solis-rec-panel__note, .solis-calendar-bar__nav { display: none; }
  .solis-calendar-list--phone { display: block; margin-top: 0; }
}

/* ---------- Sortable tree ----------
   A nested list whose rows move by a grip (behaviors/treeSort.ts): a row is its handle, an optional fold,
   an icon, the name over a muted line, markers, and its own moves at the end. A group's rows sit under it,
   indented by one step. A dragged row stays where it was, dimmed, while a line shows where it will land;
   a drop into a group outlines the group's row instead. */
.solis-sort-tree,
.solis-sort-tree__children {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--solis-space-3xs);
}

.solis-sort-tree__children {
  padding-left: var(--solis-space-4xl);
  margin-top: var(--solis-space-3xs);
}

.solis-sort-tree__children[hidden] {
  display: none;
}

.solis-sort-tree__row {
  display: flex;
  align-items: center;
  gap: var(--solis-space-sm);
  min-height: 44px;
  padding: var(--solis-space-2xs) var(--solis-space-sm) var(--solis-space-2xs) var(--solis-space-2xs);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface);
  cursor: pointer;
}

.solis-sort-tree__row:hover {
  background: var(--solis-surface-muted);
}

.solis-sort-tree__row.is-selected {
  border-color: var(--solis-accent-500);
  background: var(--solis-accent-50);
}

/* A section heads the rows after it, so it reads as a heading, not a row like them. */
.solis-sort-tree__row--section {
  border-style: dashed;
  background: transparent;
}

.solis-sort-tree__row--section .solis-sort-tree__label {
  font-size: var(--solis-fs-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--solis-text-secondary);
}

.solis-sort-tree__handle,
.solis-sort-tree__fold {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 24px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: var(--solis-radius-sm);
  background: none;
  color: var(--solis-text-muted);
}

.solis-sort-tree__handle {
  cursor: grab;
  touch-action: none;
}

.solis-sort-tree__fold:is(button) {
  cursor: pointer;
}

/* A grip that can't move its row (the list is filtered) dims its icon, so its tooltip keeps full strength. */
.solis-sort-tree__handle[aria-disabled="true"] {
  cursor: not-allowed;
}

.solis-sort-tree__handle[aria-disabled="true"] i {
  opacity: 0.35;
}

.solis-sort-tree__handle:hover,
.solis-sort-tree__fold:is(button):hover {
  color: var(--solis-text-primary);
}

.solis-sort-tree__handle:focus-visible,
.solis-sort-tree__fold:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: -2px;
}

.solis-sort-tree__icon {
  display: inline-flex;
  justify-content: center;
  flex: none;
  width: 20px;
  font-size: var(--solis-fs-xl);
  color: var(--solis-text-secondary);
}

.solis-sort-tree__main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

.solis-sort-tree__label {
  font-size: var(--solis-fs-md);
  font-weight: 600;
  color: var(--solis-text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.solis-sort-tree__sub {
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.solis-sort-tree__marks {
  display: flex;
  align-items: center;
  gap: var(--solis-space-md);
  flex: none;
}

.solis-sort-tree__acts {
  display: flex;
  align-items: center;
  gap: var(--solis-space-3xs);
  flex: none;
}

/* While a row is dragged: its place kept and dimmed, the landing line drawn above or below a row, and a
   group's row outlined when the drop goes into it. */
.solis-sort-tree__item.is-dragging > .solis-sort-tree__row {
  opacity: 0.45;
}

.solis-sort-tree__item.is-drop-before > .solis-sort-tree__row {
  box-shadow: 0 -3px 0 0 var(--solis-accent-500);
}

.solis-sort-tree__item.is-drop-after > .solis-sort-tree__row {
  box-shadow: 0 3px 0 0 var(--solis-accent-500);
}

.solis-sort-tree__item.is-drop-inside > .solis-sort-tree__row {
  border-color: var(--solis-accent-500);
  border-style: dashed;
  background: var(--solis-accent-50);
}

.solis-sort-tree.is-sorting,
.solis-sort-tree.is-sorting * {
  cursor: grabbing;
  user-select: none;
}

/* The row being carried, following the pointer. */
.solis-sort-tree__ghost {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  box-shadow: var(--solis-shadow-panel);
  opacity: 0.95;
}

/* ---------- Icon picker ----------
   A name box with the chosen icon at its start, and a grid of the icons whose names match (widgets/iconPicker.ts).
   The icon is placed against the box alone: placed against the whole picker it sat halfway down an open grid. */
.solis-icon-pick__box {
  position: relative;
}

.solis-icon-pick .solis-input {
  padding-left: 40px;
}

.solis-icon-pick__shown {
  position: absolute;
  left: 1px;
  top: 1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: calc(100% - 2px);
  font-size: var(--solis-fs-2xl);
  color: var(--solis-text-primary);
  pointer-events: none;
}

.solis-icon-pick__shown.is-empty {
  color: var(--solis-text-muted);
}

.solis-icon-pick__grid {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: var(--solis-space-3xs);
  margin-top: var(--solis-space-2xs);
  padding: var(--solis-space-xs);
  border: 1px solid var(--solis-border);
  border-radius: var(--solis-radius-md);
  background: var(--solis-surface);
  max-height: 220px;
  overflow-y: auto;
}

.solis-icon-pick__grid[hidden] {
  display: none;
}

.solis-icon-pick__option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  padding: 0;
  border: none;
  border-radius: var(--solis-radius-sm);
  background: none;
  font-size: var(--solis-fs-2xl);
  color: var(--solis-text-secondary);
  cursor: pointer;
}

.solis-icon-pick__option:hover {
  background: var(--solis-surface-muted);
  color: var(--solis-text-primary);
}

.solis-icon-pick__option:focus-visible {
  outline: var(--solis-focus-ring);
  outline-offset: -2px;
}

/* Named by the floating tooltip: the drawer's scroll would clip the CSS bubble. */
.solis-icon-pick__option[data-tooltip]::before,
.solis-icon-pick__option[data-tooltip]::after {
  display: none;
}

.solis-icon-pick__none {
  grid-column: 1 / -1;
  margin: 0;
  padding: var(--solis-space-sm);
  font-size: var(--solis-fs-sm);
  color: var(--solis-text-muted);
}

/* On a phone the marks give way to the name: the row keeps its handle, name and moves. */
@media (max-width: 640px) {
  .solis-sort-tree__marks {
    display: none;
  }

  .solis-sort-tree__children {
    padding-left: var(--solis-space-xl);
  }

  .solis-icon-pick__grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .solis-editor-split { grid-template-columns: minmax(0, 1fr); }
  .solis-editor-split__tabs { display: flex; }
  .solis-editor-split__pane--hidden-narrow { display: none; }
}
