/* ==========================================================================
   Settings — the shell every settings section page shares.

   One page per section: a left Configuration nav card beside the active
   panel, `.cols-1-2` doing the split. These rules govern that shell only;
   the panels themselves are plain design-system components.
   ========================================================================== */

.settings-shell { --settings-nav-w: 288px; }

/* The Configuration nav is the same 14 links on every settings page, so its
   column must not resize with whatever the active panel happens to hold.
   `.cols-1-2` is `1fr 2fr`, and an `fr` track floors at its content's
   min-content width — so a panel carrying a wide table (Client Groups' is
   min-width 1265px, the API page's 898px) steals width from the nav, and the
   nav jumps sideways every time you switch section. Pin the nav; let the panel
   take the remainder.

   ⭐ THE RAIL ONLY APPEARS WHEN IT COSTS THE PANEL NOTHING — 1440px, raised from
   1101px on 2026-09-09. The content width is W − 208 (sidebar 160 + page padding
   48), so the panel is W − 496 with the rail and W − 208 without it, and its
   `.table-wrap` sits 36px inside that. At 1180×820 the rail left the wrap 648px
   while these tables want 880–1100px, and four sections scrolled sideways INSIDE
   the panel — the document never overflowed, so the page looked clean while the
   reader dragged a table to reach Enabled. The tell is that the module was BETTER
   at 1100 (wrap 855) than at 1180. 1440 is where the two-column wrap (908px) again
   holds every table that fits anywhere without column work; Stock Statuses (980px)
   and Client Types (1100px) still need their columns reworked.
   ⚠ The pair must stay ADJACENT (1440 / 1439) — see the max-width block below. */
@media (min-width: 1440px) {
  .settings-shell { grid-template-columns: var(--settings-nav-w) minmax(0, 1fr); }
}

/* `minmax(0, …)` frees the TRACK to go below min-content; the card is still a
   grid item with `min-width: auto`, which would push straight back out. Both
   halves are needed, or the pin above does nothing on the wide-table pages.
   Under-width panels scroll inside their own `.table-wrap` — which is what it
   is for — instead of forcing horizontal overflow on the page. */
.settings-shell > .card { min-width: 0; }

/* --------------------------------------------------------------------------
   One surface, not two cards.

   Sections are tabs of a single Settings page, so the nav and the active panel
   are joined into one bordered block rather than floating as two separate
   cards with a gap between them: shared top edge, one seam, outer corners
   rounded only on the outside.
   -------------------------------------------------------------------------- */
@media (min-width: 1440px) {
  .settings-shell { gap: 0; }

  .settings-shell__nav {
    border-radius: var(--radius-md) 0 0 var(--radius-md);
    box-shadow: none;
  }
  /* Pulled onto the nav's right border so the two 1px edges collapse into one
     seam. The panel keeps its own left border, which is what continues the
     block's left edge below where the shorter nav ends. */
  .settings-shell__panel {
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    margin-left: -1px;
  }
}

/* The nav sits at its natural height (`align-self: start`) so a tall panel does
   not stretch it into a mostly-empty column; the panel keeps the grid's default
   stretch, so a short panel still fills the block down to the nav's full height
   and every section reads as the same size.

   That stretch is why the panel is a flex column: a section shorter than the nav
   would otherwise leave its footer floating in the middle of the card with dead
   space beneath it (139px on Terminology, 212px on Locations). The body absorbs
   the slack; the footer stays on the card's bottom edge. */
.settings-shell__panel { display: flex; flex-direction: column; }
.settings-shell__panel > .card-body { flex: 1 1 auto; }

/* Six sections edit nothing inline — roles, locations and client groups commit from
   their dialogs, and attributes, selling constraints and stock statuses are read-only
   lists — so their footer holds the metadata line alone, with no Save beside it. The
   footer's height came from that button (10px padding + a 31px btn-sm = 51px); without
   it the bar would collapse to ~39px on exactly those six and the whole block would
   change height as you tab between sections. That is the drift change 0077 measured and
   fixed, so the height is pinned here instead of being an accident of the content. */
.settings-shell__panel > .card-footer { min-height: 51px; }

/* A selected item is a tab, not a visited link: the primary rail is the part
   that says "you are here" at a glance, before colour or weight register.

   Fill / colour / weight moved here from a `style` attribute repeated on all 14
   pages (change 0085) — one rule now, and the `.is-wip` muting below cannot be
   beaten by an inline declaration when the active section is a muted one. */
#settings-config-nav [aria-current="page"] {
  position: relative;
  background: var(--color-primary-50);
  color: var(--color-primary-700);
  font-weight: 600;
}
#settings-config-nav [aria-current="page"]::before {
  content: "";
  position: absolute;
  left: 0; top: 6px; bottom: 6px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--color-primary);
}

/* The header must not change height between sections — a section whose actions
   differ would shift the whole shell down the page as you navigate. */
.page-header .page-actions { min-height: 36px; align-items: center; }

/* ==========================================================================
   Client Groups — the member roster inside Add / Edit (change 0085)

   A group is location-scoped, so the picker is a roster of the clients at that
   location rather than a free-text field. Checkbox left, label right — the same
   shape every Y/N row in the portal uses.
   ========================================================================== */

.cg-search { max-width: none; }

.cg-roster {
  display: flex;
  flex-direction: column;
  max-height: 244px;                 /* ~5 rows, then it scrolls inside the dialog */
  overflow-y: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.cg-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 12px;
  cursor: pointer;
  border-top: 1px solid var(--color-border);
}
.cg-row:first-child { border-top: 0; }
.cg-row:hover { background: var(--color-surface-alt); }
.cg-row[hidden] { display: none; }
/* The row is a <label>, so the whole strip toggles the box; the box still needs a
   focus ring of its own for anyone arriving by keyboard. */
.cg-row:focus-within { background: var(--color-primary-50); }

.cg-check { margin-top: 2px; flex: none; accent-color: var(--color-primary); }

.cg-row__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* Non-bold: this is a value being chosen, not a heading (memory.md rule 5). */
.cg-row__name { font-size: 13px; color: var(--color-text); font-weight: 400; }
.cg-row__sub  { font-size: 11px; color: var(--color-text-subtle); }

.cg-tally {
  margin: 0;
  font-size: 12px;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* A location with no clients is a real state, not an error — say what it means for
   the group rather than showing an empty box. */
.cg-empty {
  margin: 0;
  padding: 14px 12px;
  font-size: 13px;
  color: var(--color-text-muted);
}

/* --------------------------------------------------------------------------
   Sections still in development (change 0085)

   The nav is ordered finished-first; everything below the divider is shipped but
   unfinished. Muted rather than disabled — a section you cannot open is a section
   nobody can review, and these are exactly the ones under review.
   -------------------------------------------------------------------------- */
.settings-nav__wip-head {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--color-border);
}

/* `--color-text-muted`, not `--color-text-subtle`: subtle sits near 3:1 on the
   card and these are still links people have to read. Muted clears 4.5:1. */
#settings-config-nav .dropdown-item.is-wip { color: var(--color-text-muted); }
#settings-config-nav .dropdown-item.is-wip i { opacity: 0.65; }
/* Hover and the active state come back to full strength — the item is reachable,
   so it must not look inert once you are pointing at it or standing on it. */
#settings-config-nav .dropdown-item.is-wip:hover { color: var(--color-text); }
#settings-config-nav .dropdown-item.is-wip:hover i { opacity: 1; }
#settings-config-nav .dropdown-item.is-wip[aria-current="page"] { color: var(--color-primary-700); }
#settings-config-nav .dropdown-item.is-wip[aria-current="page"] i { opacity: 1; }

/* Tablet: when .cols-1-2 stacks, the Configuration nav would become a sparse
   full-width single column with a large empty band on its right. Flow the
   section links into space-filling columns instead.

   ⭐ This branch now also owns 1101–1439 — tablet landscape and small laptops —
   because the rail moved to 1440. At 1180 the panel is the full content width, so
   the wrap is 936px instead of 648px and Locations and Product Types stop
   scrolling sideways.
   ⚠ THE STACK IS DECLARED HERE, not inherited. `app.css` collapses `.cols-1-2` at
   max-width 1100px only, so without this line the 1101–1439 range falls back to
   its unpinned `1fr 2fr` — the exact bug the pin above exists to stop. */
@media (max-width: 1439px) {
  .settings-shell { grid-template-columns: minmax(0, 1fr); }

  #settings-config-nav {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 2px 8px;
  }
  /* A divider is a divider at every width: as a plain grid item it would sit in
     the first cell with links beside it, reading as a heading for that one link. */
  .settings-nav__wip-head { grid-column: 1 / -1; }
}

/* ==========================================================================
   Terminology — the name picker (change 0085)

   One card per concept. The candidate names are laid out as tiles rather than
   folded into a <select>, because the whole point of the section is that the
   trade has several words for one thing — a dropdown hides exactly that, and
   closes the list to a supplier whose word is not on it. A text field above the
   tiles both narrows the list and accepts a new name.
   ========================================================================== */

.pick-lead {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--color-text-muted);
  max-width: 72ch;
}

.pick-list { display: flex; flex-direction: column; gap: 12px; }

/* Ten concepts in one flat list read as one long form. The three groups are real
   divisions — your own records, the storage chain, the words for stock — so the
   heading earns its place rather than decorating the scroll. */
.pick-group {
  margin: 8px 0 0;
  font-family: 'Hanken Grotesk', sans-serif;
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}
.pick-group:first-child { margin-top: 0; }

.pick-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  min-width: 0;
}

/* The concept and the frozen system name are the row's two fixed facts, so they
   hold the two ends of the head; everything the supplier can change sits below. */
.pick-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.pick-card__where {
  margin: -4px 0 0;
  font-size: 12px;
  color: var(--color-text-subtle);
}
/* A label, not data, so it takes the same treatment a column header does —
   `.data-table thead th`'s family / case / tracking (app.css:1124). The names
   beside it stay in the body face; the label is the only structural word here. */
.pick-card__where-label {
  font-family: 'Hanken Grotesk', sans-serif;
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-subtle);
  margin-right: 4px;
}

.pick-body { display: flex; flex-direction: column; gap: 8px; }

/* Singular and plural are one answer, so they sit on one line joined by the same
   slash the tiles use, rather than as two unrelated fields stacked apart. */
.pick-entry {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
/* Sized to content, not to the card: the longest name these hold is
   "Production Lot"/"Production Lots". A full-width box would say the opposite. */
.pick-entry__sg { max-width: 168px; }
.pick-entry__pl { max-width: 168px; }
.pick-entry__slash {
  color: var(--color-text-subtle);
  font-size: 14px;
  line-height: 1;
  user-select: none;
}

/* Wrapping flex, not a fixed grid: a track sized to the longest option stretches every
   tile to it, so "Meters (m)" reserved as much room as "Square Centimeters (sqcm)" and
   five short units spread across a whole row of half-empty boxes. Sized to content, the
   same five fit on one line — memory.md rule 9, and the reason it exists. */
.pick-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* Units of Measure has no rename button, so the tile drops the lane reserved for it. */
.pick-grid--plain .pick-opt { padding-right: 11px; }

/* The Regional Profile picker sits above the list and drives every axis below it. */
.uom-profile {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.pick-opt {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 38px;
  padding: 8px 30px 8px 11px;   /* right pad reserves the rename button's lane */
  font-size: 13px;
  color: var(--color-text);
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color 120ms, background-color 120ms;
}
.pick-opt:hover { border-color: var(--color-primary); }
.pick-opt:focus-visible {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}
.pick-opt[hidden] { display: none; }

.pick-opt__name { min-width: 0; overflow-wrap: anywhere; }

/* The pair reads as one answer with the singular leading: the plural is the same
   word inflected, so it takes a lighter colour rather than a second line. */
.pick-opt__sep { color: var(--color-text-subtle); margin: 0 3px; }
.pick-opt__pl  { color: var(--color-text-muted); }

/* The chosen name has to read as chosen from across the card, so it takes the
   primary fill — the tick is confirmation, not the signal by itself. */
.pick-opt.is-on {
  background: var(--color-primary-50);
  border-color: var(--color-primary);
  color: var(--color-primary-700);
  font-weight: 500;
}
.pick-opt.is-on::before {
  content: "✓";
  font-size: 12px;
  line-height: 1;
  color: var(--color-primary);
}

/* Revealed on hover or keyboard focus. Kept out of the flow so showing it never
   reflows the tile — a name that shifts sideways as the pointer arrives is
   harder to click than one that does not. */
.pick-opt__edit {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--color-text-subtle);
  cursor: pointer;
  opacity: 0;
  transition: opacity 120ms, color 120ms;
}
.pick-opt:hover .pick-opt__edit,
.pick-opt:focus-within .pick-opt__edit,
.pick-opt__edit:focus-visible { opacity: 1; }
.pick-opt__edit:hover { color: var(--color-primary); }
.pick-opt__edit i { width: 13px; height: 13px; }

/* Renaming happens in the tile, on both forms. The fields inherit the tile's type
   so the words do not jump size the moment they become editable. Stacked, because
   two fields side by side inside a 176px tile leave neither one readable. */
.pick-opt.is-editing { cursor: default; }
.pick-opt__edit-box {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  min-width: 0;
}
.pick-opt__field {
  width: 100%;
  min-width: 0;
  padding: 0;
  font: inherit;
  color: var(--color-text);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--color-border-strong);
  outline: none;
}
.pick-opt__field:focus { border-bottom-color: var(--color-primary); }
/* Matches how the plural reads at rest, so the editor looks like the tile it replaces. */
.pick-opt__field--pl { color: var(--color-text-muted); }

.pick-empty {
  margin: 0;
  font-size: 12px;
  color: var(--color-text-subtle);
}
.pick-empty b { color: var(--color-text); font-weight: 500; }

/* Catalog and Stock are never counted — the app writes "the Catalog", never
   "3 Catalogs". Those cards carry one field, and say why, instead of showing an
   empty plural box the operator would try to fill. */
.pick-note {
  margin: 0;
  font-size: 12px;
  color: var(--color-text-subtle);
}

/* Narrow: a tile whose label is longer than the panel must not force the page to
   scroll sideways, so it wraps its text instead. The two entry fields split the row. */
@media (max-width: 560px) {
  .pick-opt { max-width: 100%; }
  .pick-entry__sg,
  .pick-entry__pl { flex: 1 1 0; max-width: none; min-width: 0; }
}

/* ---------- Client Types: what the mapping costs ---------------------------
   Method, Base and Effective do not hold anything — they restate the mapped
   price list in money, against one sample base, so seven tiers can be compared
   down a column instead of worked out one at a time. Fixed and narrow: figures
   are read down their right edge, and the mapping column keeps the room its
   region, badge and rate need.

   Base is muted because it is the same figure on every row — the constant the
   comparison is made against, not a result. Effective is the result. */
#client-types th.col-ct-method,
#client-types td.col-ct-method { width: 140px; }
#client-types th.col-ct-base,
#client-types td.col-ct-base   { width: 90px; }
#client-types th.col-ct-eff,
#client-types td.col-ct-eff    { width: 104px; }
#client-types td.col-ct-eff    { font-weight: 500; }
/* Edit / Delete pinned to the right edge (owner 2026-10-27, QA Bug Log #59). The table
   holds a 1100px floor inside a narrower panel, so at common desktop widths the actions
   sat past the card's edge behind a sideways scroll. Pricing's pattern (pricing.html,
   #tbl-flex-1 .col-add):
   · a SOLID ground or the cells scrolling under it show through — white at rest, the
     row's own hover ground on hover (inherited: the tr carries it, opaque, then);
   · every header cell is already sticky at z-index 1 (G612), so the pinned header sits
     at 2 and the pinned body cells at 1, above the scrolling cells;
   · `.table-wrap` keeps its 10px side standoff (memory.md §20) and a sticky cell stops at
     that padding, so a shadow of the panel's own colour, one standoff wide, closes the
     gutter beside it. */
#client-types .data-table .col-actions { position: sticky; right: 0; box-shadow: var(--table-inset) 0 0 var(--color-surface); }
#client-types .data-table thead th.col-actions { z-index: 2; }
#client-types .data-table tbody td.col-actions { z-index: 1; background: var(--color-surface); }
#client-types .data-table tbody tr:hover > td.col-actions { background: inherit; }
/* ---------- Product Types: the Variants column -----------------------------
   Two of seven rows carry content and five carry an em dash, so the column is
   pinned rather than shared: left to size itself it would collapse to the width
   of "—" and drop `Thickness · Finish` onto two lines on the only rows that say
   anything. Sized to that string plus the usual ~20% slack. */
#product-types th.col-variants,
#product-types td.col-variants { width: 158px; }

/* ---- Selling Constraints subsections --------------------------------------
   Two settings in one card rather than two cards, per the standing shape for a
   detail panel: a heading, what the setting is for, the control, then the one
   line that says what each position actually does.

   THE LINE UNDER THE CONTROL DROPS A STEP from the line above it. A sentence
   explaining a switch is the least important text in the block and must not sit
   level with the sentence introducing the whole subsection, which is the one a
   reader skims first. */
.sc-lead { margin: 0 0 4px; font-size: 12px; color: var(--color-text-muted); }
.sc-note { margin: 0; font-size: 11px; color: var(--color-text-muted); }

/* Settings → Stock Statuses: a status the platform keeps on (Sold) draws its switch locked —
   on, dimmed, and refusing the pointer — so it reads as a fact rather than a choice. */
#tbl-statuses .switch[data-status-locked] { opacity: .6; cursor: not-allowed; }

/* ---- Settings → Email ------------------------------------------------------
   A mail account is three facts of very different widths — a hostname, a port and
   one word of encryption — and the standard two-up grid would give each a half
   column, leaving "587" in a box wide enough for a sentence (rule 9) and pushing
   the encryption select onto a row of its own (rule 16: pack the row).

   So the row is explicit: the host takes what is left, the port is sized to five
   digits, and the select is sized to its widest option. */
.se-server-row {
  gap: 12px;
  flex-wrap: nowrap;
  align-items: flex-start;
}
.se-server-row .se-host       { flex: 1 1 auto; min-width: 0; }
.se-server-row .se-port       { flex: 0 0 96px; }
.se-server-row .se-encryption { flex: 0 0 150px; }

/* The scope switch — which account is being edited (owner ruling 2026-10-16). A
   pair of buttons rather than a `.tabs` strip, because it does NOT switch panels:
   the same form stays and only the row it is bound to changes, so there is no
   `.tab-shell` to owe it (rule 17). */
.se-scope { gap: 8px; flex-wrap: wrap; }

/* Sized to the longest location name rather than stretched: the pair is a control,
   not a header band (rule 9). */
.se-scope__tab { max-width: 280px; }

/* The line under a control, a step below the 12px of the sentence that introduces
   the section (rule 4): it qualifies one field, not the group. */
.se-hint {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  color: var(--color-text-muted);
}

/* The test row ends after its button — the address box is sized to an address, and
   a full-width field with a button pinned to the far edge would be the gap rule 16
   exists to stop. `align-items: flex-end` lands the button on the input's baseline
   rather than on its label. */
.se-test-row {
  gap: 10px;
  flex-wrap: wrap;
  align-items: flex-end;
}
.se-test-row .se-test-address { flex: 0 1 320px; }

/* The outcome sits under the row it answers, and only after a send — it is a
   result, not a standing note. */
.se-test-result { margin: 0; }

/* How this account signs in (2026-10-22): one card naming the transport the saved
   row sends on. A box inside the panel, so it takes the control ground, not the
   panel's surface (rule 13). It is as wide as its content, so Disconnect lands right
   after the facts it acts on instead of at the far edge (rule 16). */
.se-transport {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  align-self: flex-start;
  max-width: 100%;
  padding: 14px 16px;
  background: var(--color-control);
  border: 1px solid var(--color-control-border);
  border-radius: var(--radius-md);
}
.se-transport__logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 40px;
  height: 40px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
}
.se-transport__logo svg { width: 22px; height: 22px; }
.se-transport__body { min-width: 0; }
.se-transport__title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--color-text);
}
.se-transport__address {
  font-size: 14px;
  color: var(--color-text);
  overflow-wrap: anywhere;
}
.se-transport__meta { font-size: 12px; color: var(--color-text-muted); }
.se-transport__action { flex: 0 0 auto; margin-left: 12px; }

/* The two providers as full-size tiles — the next step when nothing is connected,
   so it has to be seen. Option tiles are controls: control ground, border carries
   state (rule 13). */
.se-providers {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 260px));
  gap: 12px;
}
.se-provider {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--color-control);
  border: 1px solid var(--color-control-border);
  border-radius: var(--radius-md);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.se-provider:hover { border-color: var(--color-border-strong); }
.se-provider:focus-visible { outline: none; border-color: var(--color-primary); }
.se-provider:disabled { opacity: .6; cursor: default; }
.se-provider__logo { flex: 0 0 24px; }
.se-provider .main { font-size: 14px; font-weight: 600; }

@media (max-width: 768px) {
  /* The three server fields stack below tablet: at 375px a 96px port beside a
     150px select leaves the host nothing (responsive skill §3). */
  .se-server-row { flex-wrap: wrap; }
  .se-server-row .se-host { flex: 1 1 100%; }

  .se-test-row .se-test-address { flex: 1 1 100%; }

  /* Card and tiles go full width; Disconnect drops under the facts. */
  .se-transport { flex-wrap: wrap; align-self: stretch; }
  .se-transport__action { margin-left: 0; }
  .se-providers { grid-template-columns: 1fr; }
}

/* =========================================================================
   Settings → Roles (settings-users-roles.html) — change 0416_2026-10-22_role-permissions-dialog
   ========================================================================= */

/* The lock that stands in for pencil + bin on a locked row. It was an inline style on the two
   locked rows; it is a class now because renderRowActions() draws it from script, and no new
   markup may carry a style attribute (memory §14). Same name as the Angular component's class. */
.roles-lock { cursor: default; color: var(--color-text-subtle); }

/* The role modal's closing note (was an inline style on the <p>). */
.er-note { margin: 0; font-size: 12px; }

/* ---- Role Permissions dialog (#modal-role-perms; spec D15-D17, D36) ----
   One role's View / Edit switches grouped by module. The look is the per-user matrix's
   (html/archive/portal/user-management.html .um-*), re-declared under rp- because the dialog sits
   OUTSIDE #users-roles (spec D37) and this page loads no .um-* rule. */
.rp-body { display: flex; flex-direction: column; gap: 12px; }
.rp-content { display: flex; flex-direction: column; gap: 12px; }
.rp-alert { margin: 0; }
.rp-alert__text { flex: 1 1 auto; min-width: 0; }
.rp-retry { flex: none; }

/* Loading — the read in flight (D17 mode 1). A true circle, so 50% is allowed (memory §2). */
.rp-state { display: flex; justify-content: center; padding: 32px 0; }
.rp-spin {
  display: inline-block; flex: none; width: 22px; height: 22px;
  border-radius: 50%;
  border: 2px solid var(--color-border-strong); border-top-color: var(--color-primary);
  animation: rp-spin .8s linear infinite;
}
@keyframes rp-spin { to { transform: rotate(360deg); } }
/* Save Permissions swaps its check for a small white spinner while the save is in flight (memory §12). */
.rp-spin--sm { width: 14px; height: 14px; border-color: rgba(255, 255, 255, .45); border-top-color: #fff; }
.rp-save-check { display: inline-flex; }
.rp-save-spin { display: none; }
#rp-save.is-saving .rp-save-spin { display: inline-block; }
#rp-save.is-saving .rp-save-check { display: none; }

/* Filter + Collapse All. The search box is an .input, so it already sits on the control ground (memory §13). */
.rp-tools { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.rp-search { position: relative; flex: 0 1 260px; }
.rp-search .input { width: 100%; padding-left: 32px; }
.rp-search > i, .rp-search > svg {
  position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--color-text-subtle); pointer-events: none;
}
.rp-link {
  font: inherit; font-size: 12px; font-weight: 500; color: var(--color-primary-700);
  background: none; border: 0; padding: 0; cursor: pointer;
}
.rp-link:hover { color: var(--color-primary); }

/* The switch list draws its own bordered box, so it takes no side standoff (memory §20: a table in
   a box of its own belongs against that box's edges). */
.rp-matrix-wrap { padding-inline: 0; border: 1px solid var(--color-border); border-radius: var(--radius-md); }
.rp-grouprow { cursor: pointer; user-select: none; }
.rp-matrix .rp-grouprow td {
  background: var(--color-surface-alt); font-size: 13px; font-weight: 700; color: var(--color-text);
  padding: 10px 12px;
}
.rp-grp { display: flex; align-items: center; gap: 8px; }
.rp-grp > svg, .rp-grp > i { width: 16px; height: 16px; flex: none; }
.rp-grp .chev { color: var(--color-text-subtle); transition: transform 160ms ease; }
.rp-grouprow.collapsed .chev { transform: rotate(-90deg); }
/* "N of M" sits right after the module name it counts — never pushed to the far edge (memory §6, §16). */
.rp-grp-count { font-size: 12px; font-weight: 600; color: var(--color-text-subtle); font-variant-numeric: tabular-nums; }

/* [switch] [label] [Partial] — switch first, label regular weight, nothing pushed right (memory §5, §16).
   The switch stays a flex child of .rp-permcell, never bare in the <td> (G231). */
.rp-permcell { display: flex; align-items: center; gap: 10px; padding-left: 26px; }
.rp-perm-name { font-size: 13px; font-weight: 400; cursor: pointer; }
.rp-matrix .switch { cursor: pointer; }
/* A locked switch — every switch in a read-only mode, or a D16 lock — reads as unavailable. */
.rp-matrix .switch[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.rp-matrix .switch[aria-disabled="true"] + .rp-perm-name { cursor: default; }
/* The matrix is ONE column, so its only th/td is also :last-child, which app.css right-aligns
   (.data-table thead th:last-child / tbody td:last-child). Same specificity, loaded later. */
.rp-matrix thead th:last-child,
.rp-matrix tbody td:last-child { text-align: left; }
.rp-empty { padding: 14px 12px; font-size: 13px; }

@media (max-width: 768px) {
  /* The filter takes its own row; Collapse All wraps beneath it. */
  .rp-search { flex: 1 1 100%; }
  /* Touch floor (responsive skill §5): the dialog's buttons, the ×, the Collapse All link, each module row. */
  .rp-modal .modal-footer .btn, .rp-retry, .rp-link { min-height: 44px; }
  .rp-modal .modal-close { min-width: 44px; min-height: 44px; }
  .rp-matrix .rp-grouprow td { padding-top: 14px; padding-bottom: 14px; }
  /* The Roles row's icon buttons reach 44px and the pinned actions column grows to hold three of them
     (3 × 44 + the cell's padding). Two ids outrank the page's own #tbl-flex-1 pin in its <style>. */
  #users-roles .data-table td.col-actions .icon-btn { width: 44px; height: 44px; }
  #users-roles #tbl-flex-1 thead th.col-actions { width: 160px; min-width: 160px; max-width: 160px; }
}

/* ---- Roles › Edit Role: Max Quote Discount (#er-max-disc; spec §3.2, §3.13) ----
   A short number box with a % after it, sized to what it holds (at most 100.00) instead of stretched
   across the dialog (memory §9). It is an .input, so it keeps the control ground against the modal
   (§13) and its 5px radius (§2). The hint and the refusal under it are 12px (§4); the refusal is
   danger ink (§19). The rm- prefix is the Angular role modal's own (role-modal.component.css
   already has .rm-hint), so the build copies these back unchanged. */
.rm-pct { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; }
.rm-pct .input { width: 96px; text-align: right; }
.rm-pct__unit { font-size: 13px; color: var(--color-text-muted); }
.rm-hint { margin: 0; font-size: 12px; color: var(--color-text-muted); }
.rm-hint--error { color: var(--color-danger-ink); }
