/* ============================================================================
   MyRoof Modules — components.
   Loads after tokens.css + base.css. Every colour here is a var(--…), which is
   why the whole set flips theme without a single dark-mode override below.
   ============================================================================ */

/* ============================================================================
   1. BUTTONS
   One .btn geometry; the variant only changes colour. Height 36px matches the
   input height, so a button and a field sit level on the same row.
   ============================================================================ */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  height:36px;padding:0 14px;border-radius:var(--r-btn);
  font-size:var(--fs-sm);font-weight:600;line-height:1;white-space:nowrap;
  border:1px solid transparent;background:var(--surface-2);color:var(--text);
  cursor:pointer;user-select:none;
  transition:background var(--t),color var(--t),border-color var(--t),box-shadow var(--t),transform var(--t-fast);
}
.btn svg{width:16px;height:16px;flex:none}
.btn:hover{background:var(--border)}
.btn:active{transform:translateY(1px)}
.btn:focus-visible{outline:none;box-shadow:var(--ring);border-color:var(--primary)}
.btn:disabled,.btn[aria-disabled="true"]{opacity:.5;cursor:not-allowed;transform:none;pointer-events:none}

.btn-primary{background:var(--primary);color:var(--on-primary);border-color:transparent}
.btn-primary:hover{background:var(--primary-hover);box-shadow:var(--glow-primary)}

.btn-secondary{background:var(--surface);color:var(--text);border-color:var(--border-strong)}
.btn-secondary:hover{background:var(--surface-2);border-color:var(--neutral)}

.btn-ghost{background:transparent;color:var(--text-2)}
.btn-ghost:hover{background:var(--surface-2);color:var(--text)}

.btn-danger{background:var(--bad-solid);color:#fff;border-color:transparent}
.btn-danger:hover{filter:brightness(1.08)}

/* quiet destructive — the common case; the loud one is for the confirm dialog */
.btn-danger-ghost{background:transparent;color:var(--bad-fg);border-color:var(--bad-line)}
.btn-danger-ghost:hover{background:var(--bad-bg)}

.btn-sm{height:30px;padding:0 10px;font-size:var(--fs-xs)}
.btn-sm svg{width:14px;height:14px}
.btn-lg{height:44px;padding:0 20px;font-size:var(--fs-body)}
.btn-block{width:100%}

/* icon-only — always needs an aria-label */
.btn-icon{width:36px;height:36px;padding:0;border-radius:var(--r-pill);color:var(--text-2);background:transparent}
.btn-icon:hover{background:var(--surface-2);color:var(--text)}
.btn-icon.sm{width:28px;height:28px}
.btn-icon svg{width:18px;height:18px}

/* segmented control — mutually exclusive views (Light/Dark, List/Board).
   Scrolls inside itself rather than pushing the top bar wider than the screen,
   so it takes any number of options without a media query per case. */
.segmented{
  display:inline-flex;gap:3px;padding:3px;background:var(--surface-2);border-radius:var(--r-btn);
  max-width:100%;overflow-x:auto;scrollbar-width:none;-ms-overflow-style:none;
}
.segmented::-webkit-scrollbar{width:0;height:0}
.segmented button{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;flex:none;
  height:30px;padding:0 12px;border-radius:var(--r-chip);border:none;
  font-size:var(--fs-xs);font-weight:600;color:var(--text-2);
  transition:background var(--t),color var(--t),box-shadow var(--t);
}
@media (max-width:640px){.segmented button{padding:0 9px}}
.segmented button svg{width:14px;height:14px}
.segmented button:hover{color:var(--text)}
.segmented button[aria-pressed="true"],.segmented button.is-on{background:var(--surface);color:var(--text);box-shadow:var(--shadow-sm)}

/* ============================================================================
   2. FORMS
   .field is the unit: label + control + hint/error, stacked. Put fields in a
   .form-grid to lay them out; never set widths on the control itself.
   ============================================================================ */
.form-grid{display:grid;gap:var(--s4) var(--s5);grid-template-columns:repeat(2,minmax(0,1fr))}
.form-grid.cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.form-grid.cols-1{grid-template-columns:minmax(0,1fr)}
.field.full{grid-column:1/-1}
@media (max-width:860px){.form-grid,.form-grid.cols-3{grid-template-columns:minmax(0,1fr)}}

.field{display:flex;flex-direction:column;gap:6px;min-width:0}
.field > label,.label{
  display:flex;align-items:center;gap:6px;
  font-size:var(--fs-xs);font-weight:600;color:var(--text-2);line-height:1.3;
}
/* Required is marked on the label, not by colouring the input — an empty
   required field is not an error until the user tries to leave it. */
.label .req{color:var(--bad-fg);font-weight:700}
.label .opt{color:var(--neutral);font-weight:500;text-transform:none;letter-spacing:0}

/* ⛔⛔ `--text-2`, NOT `--neutral`, and the tokens' own comments say why: `--text-2` is
   "secondary ink: labels, help text" and `--neutral` is "tertiary ink: placeholders, disabled,
   overlines". `.hint` is help text — 49 uses, carrying things like "A mistyped ID is recorded as
   a passport rather than refused, so check the confirmation" and the whole of what "Not prepared"
   means. Copy the app leans on to explain itself, in the placeholder colour.

   Measured in Chrome on modules-dev, 2026-08-19, driving S13:

     theme    --neutral   --text-2
     light      2.63       5.11
     dark       5.63       7.43
     dim        5.48       7.51
     sepia      2.56       4.73

   ⚠ The two LIGHT themes were the failures — below WCAG AA's 4.5:1 for body text, and below
   even the 3:1 allowed for large text. The dark pair were always fine, which is why nobody had
   noticed: the default is dark. */
.hint{font-size:var(--fs-xs);color:var(--text-2);line-height:1.4}
.error-text{display:flex;align-items:flex-start;gap:5px;font-size:var(--fs-xs);color:var(--bad-fg);font-weight:500}
.error-text svg{width:13px;height:13px;flex:none;margin-top:1px}

/* ---- the controls -------------------------------------------------------- */
.input,.select,.textarea{
  width:100%;height:36px;padding:0 11px;
  border:1px solid var(--border-strong);border-radius:var(--r-btn);
  background:var(--surface);color:var(--text);
  font-size:var(--fs-sm);
  transition:border-color var(--t),box-shadow var(--t),background var(--t);
}
.textarea{height:auto;min-height:96px;padding:9px 11px;line-height:1.5;resize:vertical}
.input::placeholder,.textarea::placeholder{color:var(--neutral)}
.input:hover,.select:hover,.textarea:hover{border-color:var(--neutral)}
.input:focus,.select:focus,.textarea:focus{outline:none;border-color:var(--primary);box-shadow:var(--ring)}
.input:disabled,.select:disabled,.textarea:disabled{background:var(--surface-2);color:var(--text-2);cursor:not-allowed;border-color:var(--border)}
.input[readonly]{background:var(--surface-2);border-style:dashed;color:var(--text-2)}

/* file input — ⚠ the only control the browser will not let you restyle wholesale. The wrapper is
   ours, the BUTTON inside is the OS's, and left alone it renders as raw chrome in the middle of a
   themed form. ::file-selector-button is the standard; the -webkit- alias is for Safari < 16. */
.input[type=file]{height:auto;padding:5px;line-height:1.5;cursor:pointer}
.input[type=file]::file-selector-button{
  margin:0 var(--s3) 0 0;padding:0 12px;height:26px;
  border:1px solid var(--border-strong);border-radius:var(--r-btn);
  background:var(--surface-2);color:var(--text);
  font:inherit;font-size:var(--fs-xs);font-weight:600;cursor:pointer;
  transition:background var(--t),border-color var(--t);
}
.input[type=file]::file-selector-button:hover{background:var(--border);border-color:var(--neutral)}
.input[type=file]::-webkit-file-upload-button{
  margin:0 var(--s3) 0 0;padding:0 12px;height:26px;
  border:1px solid var(--border-strong);border-radius:var(--r-btn);
  background:var(--surface-2);color:var(--text);
  font:inherit;font-size:var(--fs-xs);font-weight:600;cursor:pointer;
}
.input.is-invalid,.select.is-invalid,.textarea.is-invalid{border-color:var(--bad-line);background:var(--bad-bg)}
.input.is-invalid:focus,.textarea.is-invalid:focus{box-shadow:0 0 0 3px var(--bad-bg)}

/* native select needs its own caret once we take the OS styling off */
.select{
  appearance:none;padding-right:32px;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 16px) 15px,calc(100% - 11px) 15px;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
}

/* input with a leading icon or a trailing unit/affix */
.input-group{position:relative;display:flex;align-items:center}
.input-group > svg{position:absolute;left:11px;width:16px;height:16px;color:var(--neutral);pointer-events:none}
.input-group > svg ~ .input{padding-left:34px}
.input-group .affix{
  position:absolute;right:1px;top:1px;bottom:1px;display:grid;place-items:center;
  padding:0 11px;color:var(--text-2);font-size:var(--fs-xs);
  background:var(--surface-2);border-left:1px solid var(--border);
  border-radius:0 var(--r-btn) var(--r-btn) 0;pointer-events:none;
}
.input-group .affix ~ .input{padding-right:60px}

/* ---- choice controls ----------------------------------------------------- */
/* The whole row is the hit target — 36px tall, matching the inputs. */
.check{display:inline-flex;align-items:flex-start;gap:var(--s2);cursor:pointer;font-size:var(--fs-sm);line-height:1.4;padding:2px 0}
.check input[type=checkbox],.check input[type=radio]{
  appearance:none;flex:none;width:17px;height:17px;margin-top:1px;
  border:1.5px solid var(--border-strong);background:var(--surface);
  border-radius:var(--r-chip);cursor:pointer;position:relative;
  transition:background var(--t),border-color var(--t);
}
.check input[type=radio]{border-radius:var(--r-pill)}
.check input:hover{border-color:var(--primary)}
.check input:checked{background:var(--primary);border-color:var(--primary)}
.check input[type=checkbox]:checked::after{
  content:"";position:absolute;left:5px;top:1.5px;width:4px;height:9px;
  border:solid var(--on-primary);border-width:0 2px 2px 0;transform:rotate(42deg);
}
.check input[type=radio]:checked::after{
  content:"";position:absolute;inset:3.5px;border-radius:var(--r-pill);background:var(--on-primary);
}
.check input:focus-visible{outline:none;box-shadow:var(--ring)}
.check input:disabled{opacity:.5;cursor:not-allowed}
.check .check-body{display:flex;flex-direction:column;gap:2px}
.check .check-body .hint{margin:0}

/* switch — for a setting that takes effect immediately (no Save) */
.switch{display:inline-flex;align-items:center;gap:var(--s3);cursor:pointer;font-size:var(--fs-sm)}
.switch input{
  appearance:none;flex:none;width:38px;height:22px;border-radius:var(--r-pill);
  background:var(--border-strong);position:relative;cursor:pointer;
  transition:background var(--t);
}
.switch input::after{
  content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:var(--r-pill);
  background:#fff;box-shadow:var(--shadow-sm);transition:transform var(--t) var(--ease);
}
.switch input:checked{background:var(--primary)}
.switch input:checked::after{transform:translateX(16px)}
.switch input:focus-visible{outline:none;box-shadow:var(--ring)}
.switch input:disabled{opacity:.5;cursor:not-allowed}

/* fieldset — a named group of related fields */
.fieldset{border:1px solid var(--border);border-radius:var(--r-panel);padding:var(--s4);background:var(--surface)}
.fieldset > legend{padding:0 var(--s2);margin-left:-4px;font-size:var(--fs-xs);font-weight:700;color:var(--text-2);text-transform:uppercase;letter-spacing:.06em}

/* sticky action bar — a long form's Save must never be below the fold */
/* Fixed to the bottom of the VIEWPORT, not to the end of the form.
   ⚠ Sticky was not enough: it pins to the bottom of its own container, so on a short form the bar
   sat mid-screen and on a long one it was only reachable by scrolling to the end. A Save button
   somebody has to hunt for is a Save button they do not find.
   z-index 50: above the content and the sticky top bar (40), below the modal scrim (200) — a
   confirm dialog must cover it, since that is where a destructive action is confirmed. */
.form-actions{
  position:fixed;left:0;right:0;bottom:0;z-index:50;
  height:var(--actions-h);
  border-top:1px solid var(--border);background:var(--bg);
}
/* The row inside is capped and centred like .wrap, so the buttons line up with the content column
   instead of drifting to the window edges on a wide screen. */
.form-actions > .wrap{
  display:flex;align-items:center;gap:var(--s2);
  height:100%;padding:0 var(--s5);
}
.form-actions .spacer{flex:1}

/* ⚠ Reserves the bar's height in normal flow. Without it the fixed bar covers the last field on
   the page, which is invisible until somebody scrolls to the bottom and cannot reach it. */
.form-actions-gap{height:var(--actions-h);flex:none}

/* ============================================================================
   3. SURFACES — card, panel, popover
   ============================================================================ */
.card{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);
  padding:var(--s4);
  transition:border-color var(--t),box-shadow var(--t),transform var(--t);
}
.card-head{display:flex;align-items:flex-start;gap:var(--s3);margin-bottom:var(--s3)}
.card-head h3{flex:1;min-width:0}
.card-head .card-sub{font-size:var(--fs-xs);color:var(--neutral);margin-top:2px}
.card-foot{margin-top:var(--s4);padding-top:var(--s3);border-top:1px solid var(--border);display:flex;gap:var(--s2);align-items:center}

/* clickable card — lifts on hover, so only use it when the whole card is a link */
.card-link{cursor:pointer}
.card-link:hover{border-color:var(--primary-line);box-shadow:var(--shadow);transform:translateY(-1px)}

/* stat tile — one number, its label, and its movement */
.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:var(--s4)}
.stat .stat-label{font-size:var(--fs-micro);letter-spacing:.08em;text-transform:uppercase;color:var(--neutral);font-weight:600}
.stat .stat-value{font-family:var(--font-display);font-size:28px;line-height:1.1;font-weight:600;letter-spacing:-.03em;margin-top:6px;font-variant-numeric:tabular-nums}
.stat .stat-delta{display:inline-flex;align-items:center;gap:4px;margin-top:6px;font-size:var(--fs-xs);font-weight:600}
.stat .stat-delta.up{color:var(--ok-fg)}
.stat .stat-delta.down{color:var(--bad-fg)}
.stat .stat-delta.flat{color:var(--neutral)}

.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-panel);box-shadow:var(--shadow-lg);padding:var(--s2)}

/* thumbnail strip — images somebody may look at but not rearrange.
   ⚠ Fixed aspect on the TILE, not the image: photographs arrive portrait and landscape and a grid
   that lets each set its own height jumps as they load. object-fit crops instead, so the row stays
   level and nothing reflows. Width is the container's business, as always. */
.thumbs{display:grid;gap:var(--s2);grid-template-columns:repeat(auto-fill,minmax(132px,1fr))}
.thumbs a{
  display:block;position:relative;aspect-ratio:4/3;overflow:hidden;
  border:1px solid var(--border);border-radius:var(--r-panel);background:var(--surface-2);
  transition:border-color var(--t),box-shadow var(--t);
}
.thumbs a:hover{border-color:var(--primary-line);box-shadow:var(--shadow)}
.thumbs a:focus-visible{outline:none;box-shadow:var(--ring);border-color:var(--primary)}
.thumbs img{width:100%;height:100%;object-fit:cover;display:block}
/* A caption sits ON the image, so a long one cannot change the tile's height. */
.thumbs .thumb-tag{
  position:absolute;left:6px;bottom:6px;max-width:calc(100% - 12px);
  padding:2px 7px;border-radius:var(--r-chip);
  background:var(--overlay);color:#fff;
  font-size:var(--fs-micro);font-weight:600;line-height:1.5;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* ============================================================================
   4. NAVIGATION
   ============================================================================ */
.brand{display:flex;align-items:center;gap:9px;font-family:var(--font-display);font-weight:700;letter-spacing:-.04em;font-size:17px}
.brand .mark{width:24px;height:24px;border-radius:7px;background:var(--inverse);color:var(--inverse-text);display:grid;place-items:center;font-size:12px;font-weight:700;flex:none}
/* A real product mark rather than a letter. It is artwork, so no token colours it and it has to
   read on its own against every theme's background — which is why it ships with transparency and
   no plate behind it. Sized here, not in the markup: widths belong to the container. */
.brand .logo{width:26px;height:26px;flex:none;display:block}

/* The screen the operator currently has open, sitting in the bar beside the mark. Truncates
   rather than wraps — the bar is one fixed row, and a long property description must not push
   the theme control off the end of it. */
.screen-title{
  min-width:0;flex:0 1 auto;
  padding-left:var(--s3);border-left:1px solid var(--border);
  font-family:var(--font-display);font-weight:600;letter-spacing:-.02em;font-size:15px;
  color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* theme menu — the whole theme set behind ONE icon.
   The segmented control spends five buttons of bar width on a setting somebody touches once, so
   this shows only the theme currently in force and reveals the rest on hover.
   ⚠ Hover alone would strand keyboard and touch users, so it opens three ways: :hover, :focus-within
   (tabbing in) and .is-open (theme.js, on click). All three do the same thing. */
.theme-menu{position:relative;flex:none}
.theme-menu-pop{
  position:absolute;top:calc(100% + 6px);right:0;z-index:60;
  min-width:170px;display:flex;flex-direction:column;gap:2px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-panel);
  box-shadow:var(--shadow-lg);padding:var(--s2);
  opacity:0;visibility:hidden;transform:translateY(-4px);
  transition:opacity var(--t),transform var(--t),visibility var(--t);
}
/* ⚠ The 6px gap above is a dead zone the pointer has to cross, and crossing it would close the
   menu before it is reachable. This bridges it invisibly — remove it and the menu becomes a
   coordination test. */
.theme-menu-pop::before{content:'';position:absolute;left:0;right:0;top:-8px;height:8px}
.theme-menu:hover > .theme-menu-pop,
.theme-menu:focus-within > .theme-menu-pop,
.theme-menu.is-open > .theme-menu-pop{opacity:1;visibility:visible;transform:none}
.theme-menu-pop button{
  display:flex;align-items:center;gap:var(--s2);width:100%;
  height:32px;padding:0 9px;border:0;border-radius:var(--r-btn);
  background:transparent;color:var(--text-2);
  font-family:inherit;font-size:var(--fs-sm);font-weight:500;text-align:left;cursor:pointer;
  transition:background var(--t),color var(--t);
}
.theme-menu-pop button svg{width:15px;height:15px;flex:none}
.theme-menu-pop button:hover{background:var(--surface-2);color:var(--text)}
.theme-menu-pop button:focus-visible{outline:none;box-shadow:var(--ring)}
.theme-menu-pop button[aria-pressed="true"],.theme-menu-pop button.is-on{color:var(--text);font-weight:600}
/* The tick occupies its row whether or not it is showing, so nothing shifts as the choice moves. */
.theme-menu-pop button .tick{margin-left:auto;opacity:0;color:var(--primary)}
.theme-menu-pop button.is-on .tick{opacity:1}

.nav-link{
  display:flex;align-items:center;gap:9px;width:100%;
  height:34px;padding:0 11px;border-radius:var(--r-btn);
  font-size:var(--fs-sm);font-weight:500;color:var(--text-2);
  transition:background var(--t),color var(--t);
}
.nav-link svg{width:16px;height:16px;flex:none}
.nav-link:hover{background:var(--surface-2);color:var(--text);text-decoration:none}
.nav-link[aria-current],.nav-link.is-active{background:var(--primary-soft);color:var(--primary);font-weight:600}
.nav-link .count{margin-left:auto;font-size:var(--fs-micro);font-weight:600;color:var(--neutral);font-variant-numeric:tabular-nums}
.nav-link[aria-current] .count{color:var(--primary)}

/* stepper — a fixed sequence of stages, and how far along one thing is.
   ⚠ Different from a progress BAR: the stages are named and countable, so the value of it is
   knowing WHICH one you are at, not what percentage is filled.

   ⛔ The connector is drawn on the STEP, not between steps, and clipped at the two ends — a
   separate line element cannot know where the first and last dots are once the labels wrap. */
/* ⚠⚠ `stretch`, not `flex-start`, and it is load-bearing. Two labels wrap to a second line
   ("Prepared for the bank", "Counter-signed OTP received") and the rest do not — so with each step
   sized to its own content the dates below them sat 16px lower than their neighbours’. A row of
   dates at two different heights reads as a rendering fault, not as a design. This makes every step
   as tall as the tallest, and the grid below drops the date to the bottom of that shared box. */
.steps{display:flex;align-items:stretch;gap:0;counter-reset:step}
/* ⚠ A GRID of three rows — dot, label, date — rather than a flex column, because only a grid can
   say "the label takes whatever is left and the date sits under it". The `.sr-only` fourth child is
   `position:absolute` and so out of flow; a visible fourth child would land in an implicit row and
   break the alignment this exists for. */
.steps .step{
  flex:1 1 0;min-width:0;position:relative;
  display:grid;grid-template-rows:auto 1fr auto;justify-items:center;gap:var(--s2);
  padding:0 4px;text-align:center;
}
/* The rail, behind the dot, running the full width of the step. */
.steps .step::before{
  content:'';position:absolute;top:9px;left:0;right:0;height:2px;
  background:var(--border);
}
.steps .step:first-child::before{left:50%}
.steps .step:last-child::before{right:50%}
/* ⚠ A reached step colours the rail BEHIND it — the segment leading INTO it — so the coloured
   length ends at the last dot reached rather than overshooting past it. */
.steps .step.is-done::before{background:var(--ok-solid)}
.steps .step.is-done:first-child::before{background:var(--ok-solid)}
.steps .step .step-dot{
  position:relative;z-index:1;flex:none;
  width:20px;height:20px;border-radius:var(--r-pill);
  background:var(--surface);border:2px solid var(--border);
  display:grid;place-items:center;
}
.steps .step.is-done .step-dot{background:var(--ok-solid);border-color:var(--ok-solid)}
.steps .step .step-dot svg{width:11px;height:11px;color:#fff;display:block}
.steps .step .step-label{font-size:var(--fs-xs);font-weight:600;color:var(--text-2);line-height:1.3}
.steps .step.is-done .step-label{color:var(--text)}
.steps .step .step-at{font-size:var(--fs-micro);color:var(--neutral);font-variant-numeric:tabular-nums}
/* ⚠ Eight named stages do not fit side by side on a phone. Below this it becomes the vertical list
   it was before — same markup, and the rail runs down the left instead of across. */
@media (max-width:860px){
  .steps{flex-direction:column;align-items:stretch;gap:var(--s3)}
  /* ⚠ Back to flex here. The grid above is what aligns the dates ACROSS the row; stacked
     vertically there is no row to align to, and the three parts read left to right instead. */
  .steps .step{display:flex;flex-direction:row;align-items:center;gap:var(--s3);text-align:left;padding:0}
  .steps .step::before{top:auto;bottom:auto;left:9px;right:auto;width:2px;height:100%;top:50%}
  .steps .step:last-child::before{display:none}
  .steps .step:first-child::before{left:9px}
  .steps .step .step-label{flex:1}
}

/* tabs — switching the view of one thing, not navigating away */
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--border);margin-bottom:var(--s4)}
.tabs button{
  padding:9px 12px;margin-bottom:-1px;border:none;border-bottom:2px solid transparent;
  font-size:var(--fs-sm);font-weight:600;color:var(--text-2);
  transition:color var(--t),border-color var(--t);
}
.tabs button:hover{color:var(--text)}
.tabs button[aria-selected="true"],.tabs button.is-on{color:var(--primary);border-bottom-color:var(--primary)}

/* breadcrumbs */
.crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px;font-size:var(--fs-xs);margin-bottom:var(--s4)}
.crumbs a{color:var(--text-2);padding:2px 5px;border-radius:var(--r-chip)}
.crumbs a:hover{color:var(--primary);background:var(--surface-2);text-decoration:none}
.crumbs [aria-current]{color:var(--text);font-weight:600;padding:2px 5px}
.crumbs .sep{color:var(--neutral);user-select:none}

/* ============================================================================
   5. DATA DISPLAY
   ============================================================================ */
.table-wrap{border:1px solid var(--border);border-radius:var(--r-card);overflow:hidden;background:var(--surface)}
.table-wrap .scroll{overflow-x:auto}   /* wide tables scroll; the page never does */

.table{font-size:var(--fs-sm)}
.table thead th{
  position:sticky;top:0;z-index:1;
  padding:10px var(--s3);text-align:left;white-space:nowrap;
  font-size:var(--fs-micro);letter-spacing:.06em;text-transform:uppercase;font-weight:600;color:var(--text-2);
  background:var(--surface-2);border-bottom:1px solid var(--border);
}
.table tbody td{padding:11px var(--s3);border-bottom:1px solid var(--border);vertical-align:middle}
.table tbody tr:last-child td{border-bottom:none}
.table tbody tr{transition:background var(--t-fast)}
.table tbody tr:hover{background:var(--surface-2)}
.table tbody tr[aria-selected="true"]{background:var(--primary-soft)}
.table .num,.table th.num{text-align:right;font-variant-numeric:tabular-nums}
.table .cell-primary{font-weight:600;color:var(--text)}
.table .cell-quiet{color:var(--text-2)}

/* definition list — the read view of a record */
.dl{display:grid;grid-template-columns:minmax(140px,180px) minmax(0,1fr);gap:10px var(--s4);font-size:var(--fs-sm)}
.dl dt{color:var(--text-2);font-weight:500}
.dl dd{color:var(--text);min-width:0;word-break:break-word}
/* NOT called .empty — that name belongs to the empty-STATE block below, whose
   dashed border and padding would leak in here. */
.dl dd.unset{color:var(--neutral);font-style:italic}
@media (max-width:560px){.dl{grid-template-columns:minmax(0,1fr);gap:2px var(--s2)}.dl dd{margin-bottom:var(--s2)}}

/* badge — a status, not a button. Solid variants are for counts only. */
.badge{
  display:inline-flex;align-items:center;gap:5px;
  padding:2px 9px;border-radius:var(--r-pill);
  font-size:var(--fs-micro);font-weight:600;line-height:18px;white-space:nowrap;
  border:1px solid var(--border);background:var(--surface-2);color:var(--text-2);
}
.badge::before{content:"";width:6px;height:6px;border-radius:var(--r-pill);background:currentColor;flex:none}
.badge.no-dot::before{display:none}
.badge-ok  {color:var(--ok-fg);   background:var(--ok-bg);   border-color:var(--ok-line)}
.badge-warn{color:var(--warn-fg); background:var(--warn-bg); border-color:var(--warn-line)}
.badge-bad {color:var(--bad-fg);  background:var(--bad-bg);  border-color:var(--bad-line)}
.badge-info{color:var(--info-fg); background:var(--info-bg); border-color:var(--info-line)}

/* chip — a removable selection (filter, recipient, tag) */
.chip{
  display:inline-flex;align-items:center;gap:6px;
  padding:3px 6px 3px 10px;border-radius:var(--r-pill);
  font-size:var(--fs-xs);font-weight:600;
  background:var(--primary-soft);color:var(--primary);border:1px solid transparent;
}
.chip button{color:inherit;opacity:.65;line-height:1;padding:0 2px;font-size:14px;border-radius:var(--r-pill)}
.chip button:hover{opacity:1}

.avatar{
  width:30px;height:30px;border-radius:var(--r-pill);flex:none;
  display:grid;place-items:center;overflow:hidden;
  background:linear-gradient(135deg,#6366F1,#4F46E5);color:#fff;
  font-size:var(--fs-xs);font-weight:600;
}
.avatar.lg{width:40px;height:40px;font-size:var(--fs-sm)}
.avatar.sm{width:24px;height:24px;font-size:10px}
.avatar img{width:100%;height:100%;object-fit:cover}

kbd,.kbd{
  display:inline-block;font-family:var(--font-mono);font-size:var(--fs-micro);
  padding:1px 6px;border-radius:var(--r-chip);
  border:1px solid var(--border);background:var(--surface-2);color:var(--text-2);
}

.progress{height:6px;border-radius:var(--r-pill);background:var(--surface-2);overflow:hidden}
.progress > span{display:block;height:100%;border-radius:var(--r-pill);background:var(--primary);transition:width var(--t-slow) var(--ease)}
.progress.ok   > span{background:var(--ok-solid)}
.progress.warn > span{background:var(--warn-solid)}
.progress.bad  > span{background:var(--bad-solid)}

/* ============================================================================
   6. FEEDBACK
   ============================================================================ */
.alert{
  display:flex;align-items:flex-start;gap:var(--s3);
  padding:var(--s3) var(--s4);border-radius:var(--r-panel);
  border:1px solid var(--border);background:var(--surface-2);color:var(--text);
  font-size:var(--fs-sm);
}
.alert svg{width:18px;height:18px;flex:none;margin-top:1px}
.alert .alert-title{font-weight:600;display:block;margin-bottom:2px}
.alert p{margin:0;color:var(--text-2)}
.alert-ok  {background:var(--ok-bg);   border-color:var(--ok-line)}   .alert-ok svg  {color:var(--ok-fg)}
.alert-warn{background:var(--warn-bg); border-color:var(--warn-line)} .alert-warn svg{color:var(--warn-fg)}
.alert-bad {background:var(--bad-bg);  border-color:var(--bad-line)}  .alert-bad svg {color:var(--bad-fg)}
.alert-info{background:var(--info-bg); border-color:var(--info-line)} .alert-info svg{color:var(--info-fg)}

/* toast — floats over everything, high contrast in every theme */
.toast{
  display:inline-flex;align-items:center;gap:10px;
  padding:12px 18px;border-radius:var(--r-panel);
  background:var(--inverse);color:var(--inverse-text);
  font-size:var(--fs-sm);font-weight:500;box-shadow:var(--shadow-lg);
}
.toast svg{width:16px;height:16px;color:var(--ok-solid)}

/* empty state — always offers the next action */
.empty{
  display:flex;flex-direction:column;align-items:center;gap:var(--s3);
  padding:var(--s7) var(--s4);text-align:center;
  border:1px dashed var(--border-strong);border-radius:var(--r-card);color:var(--text-2);
}
.empty svg{width:32px;height:32px;color:var(--neutral)}
.empty h3{color:var(--text)}
.empty p{font-size:var(--fs-sm);max-width:44ch}

/* skeleton — the loading shape, never a spinner over content that has a shape */
.skeleton{border-radius:var(--r-btn);background:var(--surface-2);position:relative;overflow:hidden}
.skeleton::after{
  content:"";position:absolute;inset:0;transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,var(--border),transparent);
  animation:sk 1.4s infinite;
}
@keyframes sk{100%{transform:translateX(100%)}}

/* ---- modal --------------------------------------------------------------- */
.modal-scrim{position:fixed;inset:0;z-index:200;background:var(--overlay);display:flex;align-items:flex-start;justify-content:center;padding:6vh var(--s4);backdrop-filter:blur(2px)}
/* An author `display:flex` outranks the UA's [hidden]{display:none}, so the
   scrim must opt back out explicitly or `hidden` does nothing. Same for any
   other component here that sets display and can be hidden. */
.modal-scrim[hidden]{display:none}
.modal{
  width:100%;max-width:560px;max-height:88vh;display:flex;flex-direction:column;overflow:hidden;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);box-shadow:var(--shadow-lg);
}
.modal-head{padding:var(--s5) var(--s5) var(--s3);flex:none}
.modal-head h2{margin-bottom:2px}
.modal-head p{font-size:var(--fs-sm);color:var(--text-2);margin:0}
.modal-body{flex:1 1 auto;overflow:auto;padding:0 var(--s5)}
.modal-foot{display:flex;gap:var(--s2);padding:var(--s4) var(--s5) var(--s5);flex:none}
.modal-foot .spacer{flex:1}
