/* ECF Staff Portal design system.
   Extracted verbatim from the supplied design bundle
   ("ECF Staff Portal (standalone).html"): the Modernist token/component layer
   followed by the ECF brand override layer. Asset URLs repointed at wwwroot.
   Retune tokens in :root rather than patching component rules. */

/* Modernist — design-system tokens and component classes. This file is the source of truth for the system's look; retune it here and see readme.md. */

/* The faces below say optional rather than the bundle's swap, and the head preloads the Latin
   one so that it is usually there in time.

   swap draws the page in whatever the system has and re-draws it in Archivo the moment the
   file lands. The two are not the same shape — measured against Segoe UI Variable, Archivo
   sets the same string 2.6% wider and carries a much shorter ascent (88 against 108 per
   100px) — so text that had wrapped one way wraps the other, and a page of tables settles a
   row or two further up. /Resources moved 82 elements and lost 83px of height between the
   two, which is the jump somebody sees a moment after the page appears.

   optional forbids that. The browser gives the font a moment to arrive; if it does, the page
   is drawn in it from the start, and if it does not, the page is drawn in the system's font
   and stays that way — no re-draw, no re-flow. The file still downloads and is cached, so the
   next page in is Archivo. Every click here is a navigation, so "the next page" is the very
   next thing anybody does.

   What that costs: on a cold cache over a slow link, the first page somebody sees is in the
   system font. What it buys is that no page ever moves under them. Put swap back if that
   trade is the wrong way round. */
/* vietnamese */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-vietnamese.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-vietnamese.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 600;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-vietnamese.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 800;
  font-stretch: 100%;
  font-display: optional;
  src: url("/fonts/archivo-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


:root {
  --color-bg: #f3f2f2;
  --color-surface: #eae9e9;
  --color-text: #201e1d;
  --color-accent: #ec3013;
  --color-accent-2: #e15b47;
  --color-divider: color-mix(in srgb, #201e1d 40%, transparent);

  /* Tonal ramps — generated in OKLCH on one shared lightness scale, so the
     same step of any role matches the others in visual value. */
  --color-neutral-100: #f8f4f4;
  --color-neutral-200: #eae7e7;
  --color-neutral-300: #d7d3d3;
  --color-neutral-400: #bab6b6;
  --color-neutral-500: #9b9797;
  --color-neutral-600: #7d7979;
  --color-neutral-700: #605d5d;
  --color-neutral-800: #444141;
  --color-neutral-900: #2d2b2b;

  --color-accent-100: #fff2ef;
  --color-accent-200: #ffe0d9;
  --color-accent-300: #ffc4b8;
  --color-accent-400: #ff9783;
  --color-accent-500: #ff563c;
  --color-accent-600: #dd2b0f;
  --color-accent-700: #ae1800;
  --color-accent-800: #7c1405;
  --color-accent-900: #4d170e;

  --color-accent-2-100: #fff2ef;
  --color-accent-2-200: #ffe0da;
  --color-accent-2-300: #ffc4b9;
  --color-accent-2-400: #ff9784;
  --color-accent-2-500: #ef6853;
  --color-accent-2-600: #c94b39;
  --color-accent-2-700: #9e3526;
  --color-accent-2-800: #71261b;
  --color-accent-2-900: #471d16;

  --font-heading: "Archivo", system-ui, sans-serif;
  --font-heading-weight: 800;
  --font-body: "Archivo", system-ui, sans-serif;

  --space-1: 4.0px;
  --space-2: 8.0px;
  --space-3: 12.0px;
  --space-4: 16.0px;
  --space-6: 24.0px;
  --space-8: 32.0px;

  --radius-sm: 0px;
  --radius-md: 0px;
  --radius-lg: 0px;

  /* Elevation — derived from the ground: soft ink-tinted shadows on a
     light theme, a hairline edge + ambient darkness on a dark one. */
  --shadow-sm: 0 1px 2px color-mix(in srgb, #2d2b2b 14%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #2d2b2b 16%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #2d2b2b 22%, transparent);
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
}
h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }

.grayscale{filter:grayscale(1) contrast(1.08)}

/* ══════════════════════════════════════════════════════════════════════════
   Components — built with the tokens above. Plain CSS
   on plain HTML: no JavaScript, no build step. Each class is documented in
   readme.md and demonstrated in foundations/ and components/.
   ══════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400; }
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
h1 { font-size: 42px; }
h2 { font-size: 32px; }
h3 { font-size: 25px; }
h4 { font-size: 20px; }
h5 { font-size: 16px; }
h6 { font-size: 13px; }
h6 { letter-spacing: 0.08em; text-transform: uppercase; }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent); text-underline-offset: 3px; }
img { display: block; max-width: 100%; }
figure { margin: 0; }
figcaption {
  font-size: 11px; margin-top: var(--space-1);
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

/* — rules — */
.hr {
  height: 2px; border: 0; margin: var(--space-4) 0;
  background: var(--color-divider);
}

/* — buttons — */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1.2; color: var(--color-text); /* matches the .input's 14px —
     the pair sits side by side in sign-up rows */
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
}

/* What a button comes out at: the line box of its 14px text at 1.2, the padding above
   and below it, and its border top and bottom — the three declarations above, added up.
   Named because a header row has to stand as tall as a button whether or not it holds
   one, and a row with no button in it has nothing to ask. See .ph.

   The sum is written out rather than measured off a screen, so it reads as a restatement
   of the rule above and has to be changed with it. It is not applied back to .btn as a
   floor: the smaller buttons — .btn-xs and the icon buttons — are shorter on purpose,
   and a floor set from the full-size one would stretch every one of them. */
:root { --btn-height: calc(14px * 1.2 + var(--space-2) * 2 + 2px); }

.btn svg { display: block; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { background: var(--color-accent); color: var(--color-bg); }
/* The colour is restated on both states on purpose. A primary button is sometimes an
   anchor — starting a request, opening a form — and the bare `a:hover` further down this
   sheet is more specific than `.btn-primary`, so without this the label turns accent
   purple on an accent purple fill and all but disappears. A <button> never had the
   problem, which is why the two looked different. */
.btn-primary:hover { background: var(--color-accent-600); color: var(--color-bg); }
.btn-primary:active { background: var(--color-accent-700); color: var(--color-bg); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
.btn-icon { width: 36px; height: 36px; padding: 0; }
.btn-block { width: 100%; margin-top: var(--space-2); justify-content: flex-start; text-align: left; }

/* — forms — */
.field > label {
  display: block; font-size: 12px; margin-bottom: 5px;
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.input {
  width: 100%; min-height: 36px; padding: 6px 10px; font: inherit;
  font-size: 14px; color: var(--color-text); caret-color: var(--color-accent);
  background: var(--color-surface);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }
.radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.radio input, .seg-opt input {
  position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.radio .dot {
  width: 16px; height: 16px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--color-divider);
}
.radio:hover .dot { border-color: var(--color-accent); }
.radio input:checked + .dot {
  border-color: var(--color-accent); background: var(--color-accent);
  box-shadow: inset 0 0 0 4px var(--color-bg);
}
.radio input:focus-visible + .dot { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.seg {
  display: inline-flex; overflow: hidden;
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.seg-opt {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; font-size: 13px; cursor: pointer;
}
.seg-opt + .seg-opt { border-left: 1px solid var(--color-divider); }
.seg-opt:has(input:checked) { background: var(--color-accent); color: var(--color-bg); }
.seg-opt:not(:has(input:checked)):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }

/* — cards — */
.card {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface);
}
/* A form section a rule has closed is drawn as a hidden card — see _FormFields — and the
   display above outranks the browser's own [hidden] rule, so the section went on being drawn
   with every control in it disabled. Said on .card rather than .fsec because .card is what
   sets the display; no card is hidden while meaning to be seen. */
.card[hidden] { display: none; }
.card-kicker { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); }
.card-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 17px; line-height: 1.2;
}
.card-body { margin: 0; font-size: 13px; opacity: 0.8; flex: 1; }
.card-meta {
  display: flex; align-items: center; gap: 6px; font-size: 11px;
  color: color-mix(in srgb, var(--color-text) 50%, transparent);
}
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* — tags — */
.tag {
  display: inline-flex; align-items: center; font-size: 11px;
  letter-spacing: 0.02em; padding: 3px 10px;
  border-radius: calc(var(--radius-md) * 0.75);
}
.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); }
.tag-accent-2 { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.tag-neutral { background: var(--color-neutral-100); color: var(--color-neutral-800); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

/* The tag for a thing that did not happen — a session somebody did not attend. The portal's
   red tint, the same pair .pdftag and .sbalert use, rather than a token of its own: nothing
   here is an error, and there is one red in the design. */
.tag-late { background: #fdecec; color: #c73a3a; }

/* — navigation — */
.nav {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: 2px solid var(--color-divider);
}
.nav-brand {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 18px; margin-right: auto;
}
.nav a { color: inherit; text-decoration: none; font-size: 14px; }
.nav a:hover, .nav a[aria-current='page'] { color: var(--color-accent); }

/* — tables — */
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  padding: var(--space-2); border-bottom: 2px solid var(--color-divider);
}
.table td {
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-divider);
}
.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }

/* A row that belongs to the row above it: the sessions of a group, opened by the ± on its
   first cell. Tinted and indented rather than nested in a table of its own, so its cells go
   on lining up with the columns they are under — a session's date sits below the group's
   start date, and its head-count below the group's.

   The tint is the same mix the hover rule uses, which is why the hover is stated again at
   half again as much: without it the sub-rows would look permanently hovered, and hovering
   one would do nothing. */
.table tbody tr.subrow { background: color-mix(in srgb, var(--color-text) 4%, transparent); }
.table tbody tr.subrow:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.table tbody tr.subrow > td:first-child { padding-left: var(--space-6); }

/* A table wide enough to outgrow the page scrolls inside itself instead of taking the page
   sideways with it. Same bargain as .permwrap further down, for a list rather than a grid,
   and made for the same reason: the Admin users table gained a column and stopped fitting
   somewhere around a laptop. A page that scrolls sideways scrolls the sidebar and the header
   off with it, which is a worse answer than a scrollbar on the one thing that is too wide.

   Only where it is asked for. Most tables in the portal are three or four columns and would
   gain nothing but a wrapper.

   Positioned so that it is what anything absolute inside the table is placed against. The
   visually hidden labels (.vh) are absolute; placed against the page instead, one in a column
   scrolled out of sight is not clipped by the wrapper and takes the page sideways by itself —
   the Actions heading on My Tasks did exactly that at phone width. */
.tablewrap { overflow-x: auto; position: relative; }

/* — dialog — */
.dialog-backdrop {
  position: fixed; inset: 0; display: grid; place-items: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);

  /* Above the page, explicitly. Being fixed and last in the document is not enough:
     a positioned element with a z-index of its own paints in a later layer whatever
     the document order, so the builder's row controls came through the backdrop and
     stayed clickable through it. 200 sits under .toast, which should still be
     readable over a dialog. */
  z-index: 200;
}
.dialog {
  width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 20px;
}
.dialog-body { font-size: 14px; opacity: 0.85; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }




body{margin:0;background:#fff}a{color:var(--color-accent-700)}a:hover{color:var(--color-accent)}
:root{--color-bg:#ffffff;--color-surface:#ffffff;--color-text:#1b1f26;--color-divider:#e4e8ee;accent-color:#67308c;
--color-accent:#67308c;--color-accent-100:#f4eef9;--color-accent-200:#e6d9f1;--color-accent-300:#cfb4e0;--color-accent-400:#a97fc6;--color-accent-500:#8253a2;--color-accent-600:#71418f;--color-accent-700:#5e2c80;--color-accent-800:#4e2369;--color-accent-900:#3a1a4e;
--color-accent-2:#009fcb;--color-accent-2-100:#e6f6fb;--color-accent-2-200:#c4ebf5;--color-accent-2-300:#9fdcee;--color-accent-2-400:#4dbfdd;--color-accent-2-500:#009fcb;--color-accent-2-600:#008ab3;--color-accent-2-700:#007399;--color-accent-2-800:#005c7c;--color-accent-2-900:#00465e;
--color-neutral-100:#f5f7fa;--color-neutral-200:#eaeef3;--color-neutral-300:#dbe1e8;--color-neutral-400:#c2cad4;--color-neutral-500:#98a2ae;--color-neutral-600:#717c89;--color-neutral-700:#59636f;--color-neutral-800:#353c45;--color-neutral-900:#161a1f;
--radius-sm:6px;--radius-md:8px;--radius-lg:12px;
--shadow-sm:0 1px 2px rgba(16,24,40,.05);--shadow-md:0 2px 8px rgba(16,24,40,.08);--shadow-lg:0 12px 32px rgba(16,24,40,.16)}
input[type="checkbox"],input[type="radio"],input[type="date"],input[type="time"]{accent-color:var(--color-accent)}
/* A file box is a button the browser draws with a filename beside it, sitting inside the frame
   .input gives every control. The frame is already right; the button is the part no stylesheet
   can leave to the browser and have match the ones around it. */
input[type="file"].input{padding:5px 9px;cursor:pointer}
input[type="file"].input::file-selector-button{margin:0 10px 0 0;padding:4px 10px;font:inherit;font-size:13px;color:var(--color-text);background:var(--color-neutral-100);border:1px solid var(--color-neutral-500);border-radius:var(--radius-sm);cursor:pointer}
input[type="file"].input:hover:not(:disabled)::file-selector-button{border-color:var(--color-accent)}
input[type="file"].input:disabled,input[type="file"].input:disabled::file-selector-button{cursor:default;color:var(--color-neutral-600)}
.card{border:1px solid var(--color-neutral-200);border-radius:var(--radius-md)}
.dialog{border:1px solid var(--color-neutral-200);border-radius:var(--radius-md)}
.input{background:#fff;border-radius:var(--radius-sm)}
.btn,.tag,.chip,.tcard,.seg,.seldot,.refcard,.toast{border-radius:var(--radius-sm)}
.app{display:flex;min-height:100vh}
.sb{width:232px;flex:none;border-right:2px solid var(--color-divider);position:sticky;top:0;height:100vh;display:flex;flex-direction:column;box-sizing:border-box;overflow-y:auto}
.sb.mini{width:58px}.sb.mini .nl{display:none}.sb.mini .bx{display:none}
.brand{padding:0 16px;border-bottom:2px solid var(--color-divider);height:64px;box-sizing:border-box;display:flex;align-items:center}
.blogo{width:158px;height:auto;display:block}
.bmark{display:none}.sb.mini .bmark{display:block}
.bmark img{width:34px;height:34px;object-fit:cover;object-position:left;display:block}
.sbnav{padding:8px 0;flex:1}
.sbi{display:flex;align-items:center;gap:10px;width:100%;padding:10px 18px;font:inherit;font-size:13px;border:0;background:none;cursor:pointer;text-align:left;color:var(--color-text);box-sizing:border-box}
.sbi:hover{background:var(--color-neutral-100)}
.sbi.on{box-shadow:inset 3px 0 0 var(--color-accent);background:var(--color-neutral-100);font-weight:600;color:var(--color-accent-700)}
.sbi svg{flex:none}
.sbcar{margin-left:auto;transition:transform .15s ease}
.sbcar.open{transform:rotate(90deg)}
.sbsub{display:flex;flex-direction:column;padding:2px 0 6px}
.sb.mini .sbsub{padding:0}
.sb.mini .sbs{padding:10px 18px}
.sbs{display:flex;align-items:center;gap:10px;width:100%;padding:7px 18px 7px 34px;font:inherit;font-size:12.5px;border:0;background:none;cursor:pointer;text-align:left;color:var(--color-neutral-800);box-sizing:border-box}
.sbs svg{flex:none}
.sbs:hover{background:var(--color-neutral-100)}
.sbs.on{box-shadow:inset 3px 0 0 var(--color-accent);background:var(--color-neutral-100);font-weight:600;color:var(--color-accent-700)}
.sbfoot{padding:12px 18px;border-top:1px solid var(--color-neutral-300);font-size:11px;color:var(--color-neutral-700)}
/* Said where the sections would have been, for somebody granted none of them. Indented to
   the same 18px the rows are, so it reads as standing in for the list rather than as a
   notice bolted above it. It goes with the rail, so it collapses with it. */
.sbnone{margin:8px 0;padding:0 18px;line-height:1.5}
.sb-mini .sbnone{display:none}
.main{flex:1;min-width:0;display:flex;flex-direction:column}
.topbar{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:0 24px;border-bottom:2px solid var(--color-divider);height:64px;box-sizing:border-box}
.tbl{display:flex;align-items:center;gap:10px}
.lbl{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--color-neutral-700)}
.psel{min-width:210px}

/* The corner of the header while a super admin is looking at somebody else's portal.

   Boxed and in the accent rather than left as another line of header text, and deliberately
   the loudest thing in the bar. Everything else on the screen is now somebody else's — their
   programs, their sections, their clients — and looks exactly as it would if they were the
   one signed in, which is the whole point and also the whole risk: what is written in that
   state is written as them. This is the only thing on the page that says so, so it is not
   allowed to read as decoration.

   The badge is the accent filled in rather than .tag-accent's tint, so it does not read as
   one more tag of the kind the pages below are full of. "viewing as" is dimmed against the
   two names either side of it, so the pair are what the eye picks up. */
.imp{padding:5px 6px 5px 8px;gap:8px;background:var(--color-accent-100);
     border:1px solid var(--color-accent);border-radius:var(--radius-md)}
.impbadge{background:var(--color-accent);color:#fff;font-weight:600}
.imp .impas{color:var(--color-accent-800);opacity:.75;margin:0 2px}
.pg{padding:22px 26px 44px;max-width:1280px;box-sizing:border-box;width:100%}
/* For a page whose table has more columns than 1280px reads comfortably — the same escape
   .srchfull is for the search box. The padding stays, so the content never meets the edge. */
.pg.wide{max-width:none}
.ph{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin:0 0 16px;flex-wrap:wrap}
.ph h1{margin:0;font-size:23px}
.sysmsg{background:var(--color-accent-100);color:var(--color-accent-900);border-left:3px solid var(--color-accent);padding:9px 14px;font-size:12.5px;margin:0 0 18px}
/* A system message above every page rather than inside one — the Entra secret reminder. Set
   in by the page's own padding, so it lines up with the heading under it; the page's top
   padding then sits between the two. */
.sysbanner{margin:16px 26px 0;max-width:calc(1280px - 52px)}
/* The same message once the thing it warned about has happened: red, as .formerror is. */
.sysmsg.sysmsg-late{background:#fdf6f6;color:#8f2b2b;border-left-color:#c73a3a}
/* The banner above the Financial Assistance list, for somebody with applications waiting on
   them. A link the width of the page rather than a notice: the whole bar is the way through
   to them, so the whole bar is what takes the pointer.

   Boxed in the accent like .imp rather than tinted like .tag-accent, because it is the one
   thing on the page addressed to the person reading it — everything under it is a list of
   work in general, and this is their share of it. Quieter than .imp all the same: a hairline
   and a tint, not a filled badge. Nothing is going wrong.

   The dot is decoration and says so in the markup. It stands where a count would in a
   notification, and the count is already in the sentence beside it. */
.apprbar{display:flex;align-items:center;gap:10px;width:100%;box-sizing:border-box;margin:0 0 16px;padding:10px 14px;text-align:left;text-decoration:none;color:var(--color-text);background:var(--color-accent-100);border:1px solid var(--color-accent);border-radius:var(--radius-md);cursor:pointer}
.apprbar:hover{background:var(--color-accent-200)}
.apprdot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--color-accent)}
/* Pushed to the far end, and the last thing rather than the loudest: the sentence is what
   the reader came for and this only says it leads somewhere. */
.apprlink{margin-left:auto;white-space:nowrap;font-weight:600;color:var(--color-accent-700)}
.kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:22px}
.kpi{cursor:pointer}
.knum{font-size:30px;font-weight:700;font-family:var(--font-heading);line-height:1;margin:6px 0 2px}
.two{display:grid;grid-template-columns:1fr 1.4fr;gap:16px;align-items:start}
.chips{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 12px}
.chip{border:1px solid var(--color-neutral-300);background:none;padding:5px 10px;font:inherit;font-size:11.5px;cursor:pointer}
.chip:hover{border-color:var(--color-accent)}
.chip.on{background:var(--color-text);color:#fff;border-color:var(--color-text)}
.srch{position:relative;flex:1;max-width:430px;min-width:200px}
.srch svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);opacity:.5}
.srch .input{padding-left:32px;width:100%;box-sizing:border-box}
.tbar{display:flex;gap:8px;align-items:center;margin:0 0 12px;flex-wrap:wrap}
.tbar select.input{width:auto}.tbar input[type="date"]{width:auto}
/* A filter and the label naming it, kept together when the bar wraps. The other bars hide
   their labels, so their pairs could not come apart; a bar that draws them can leave "From" at
   the end of one line and its box at the start of the next, naming nothing. */
.tbf{display:inline-flex;align-items:center;gap:8px}
.srchfull{max-width:none}
/* The pager under a paged list — see _Pager.cshtml. Three things on one line while there is
   room: where the reader is, the pages, and how long a page is; wrapped when there is not,
   with the summary first because it is the one that reads on its own. */
.pager{display:flex;gap:10px 18px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin:12px 0 0;font-size:13px}
.pager-pages{display:flex;gap:4px;align-items:center;list-style:none;margin:0;padding:0;flex-wrap:wrap}
.pager-n{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:30px;padding:0 6px;border:1px solid transparent;border-radius:var(--radius-sm);text-decoration:none;color:var(--color-neutral-800)}
.pager-n:hover{background:color-mix(in srgb, var(--color-text) 7%, transparent)}
.pager-n.on{border-color:var(--color-accent);color:var(--color-accent-700);font-weight:600}
/* The same page number as a submit button, which is what the client picker's pager is made
   of — see _ClientSearch, whose links would walk away from the selection. A browser gives a
   <button> a fill and a font of its own, and both have to come back off so that a pressed
   number and a followed one are the same thing to look at. */
button.pager-n{background:none;font:inherit;cursor:pointer}
.pager-gap{color:var(--color-neutral-600);padding:0 4px}
.pager-size a,.pager-size b{margin-left:6px}
.pager-size a{color:var(--color-accent-700)}
/* A button that is present but leads nowhere: Previous on the first page, Next on the last.
   Not a button at all — a span wearing the styling — so nothing can be pressed, and dimmed
   the way a disabled button is so that it does not look as though it could be. */
.btn[aria-disabled="true"]{opacity:.45;pointer-events:none}
.ib{border:0;background:none;cursor:pointer;padding:4px;color:var(--color-neutral-700)}
.ib:hover{color:var(--color-accent)}
/* The same icon button as a link. Inline-flex so the icon sits on the row's centre line
   rather than the text baseline, which is where an inline anchor would drop it. */
a.ib{display:inline-flex;align-items:center;text-decoration:none}
/* The same icon in the same place, for a row somebody may see and not open. Greyed and
   not a button: there is nothing to press, and a control that looks pressable and is not
   is worse than one that plainly is not. */
span.ibnone{display:inline-flex;align-items:center;padding:4px;color:var(--color-neutral-500);cursor:default}
/* An icon button that is present but cannot be pressed. Dimmed and off the hover colour,
   so it does not invite a click the page will not answer. */
.ib[disabled]{color:var(--color-neutral-400);cursor:default}
.ib[disabled]:hover{color:var(--color-neutral-400)}
.rowgo tbody tr{cursor:pointer}
.notebar{display:flex;gap:9px;align-items:flex-start;border:1px solid var(--color-neutral-300);background:var(--color-neutral-100);padding:10px 14px;font-size:12.5px;margin:0 0 16px;line-height:1.5;border-radius:var(--radius-sm)}
.notebar svg{flex:none;margin-top:1px;color:var(--color-accent-700)}
.cv{display:grid;grid-template-columns:378px minmax(0,1fr)}
.cinfo{border-right:2px solid var(--color-divider);padding:18px 20px 40px;position:sticky;top:0;height:100vh;overflow-y:auto;box-sizing:border-box}
.cmain{padding:18px 26px 44px;min-width:0}
/* A client page with no client panel down the left: a pre-intake's person being taken on, who
   is not in the portal yet and so has no record to show there. The main column takes the row
   rather than dropping into the panel's 378px, and is held to the width Add Client — the other
   page a client is made on — and every page for filling in one form is (see .wizbox). */
.cv.solo{grid-template-columns:minmax(0,1fr)}
.cv.solo>.cmain{max-width:920px;box-sizing:border-box}
.backlink{font-size:12px;display:inline-flex;gap:6px;align-items:center;background:none;border:0;cursor:pointer;color:var(--color-accent-700);padding:0;font-family:inherit}
.backlink:hover{color:var(--color-accent)}
/* The same back link as an anchor, which is what it is on every page here: going back is
   navigation, so it should middle-click and bookmark. Only the underline needs undoing —
   the rest of the rule already suits an <a>. */
a.backlink{text-decoration:none}
.cname{font-size:20px;font-weight:700;font-family:var(--font-heading);margin:10px 0 4px}
.alertbox{background:var(--color-accent-100);color:var(--color-accent-900);border-left:3px solid var(--color-accent);padding:8px 11px;font-size:12px;margin:10px 0;line-height:1.45}
.kv{display:grid;grid-template-columns:110px 1fr;gap:2px 10px;font-size:12px;padding:2px 0;margin:0}
.kv dt{color:var(--color-neutral-700)}.kv dd{margin:0;overflow-wrap:break-word}
.warn{color:var(--color-accent-700);font-weight:600}
.accs{margin-top:14px;border-top:2px solid var(--color-divider)}
.acc{border-bottom:1px solid var(--color-divider)}
.acch{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:11px 0;cursor:pointer;font-family:var(--font-heading);font-weight:700;font-size:13px;color:var(--color-text)}
.acch .chev{flex:none;color:var(--color-neutral-600);transition:transform .15s}
.acch-red{color:#c73a3a}.acch-red .chev{color:#c73a3a}
.chev.up{transform:rotate(180deg)}
.accb{padding:0 0 14px}
.sbalert{border:1px solid var(--color-neutral-300);border-left:3px solid #c73a3a;background:#fdf6f6;padding:9px 11px;font-size:12px;line-height:1.5;margin-bottom:8px}
.sbmeta{font-size:11px;color:var(--color-neutral-600);margin:6px 0 0}
.sbby{color:var(--color-accent-2-600);text-decoration:underline}
.btn-xs{padding:2px 10px;font-size:11.5px;line-height:1.4}
.sbrow{display:flex;gap:10px;justify-content:space-between;align-items:baseline;padding:5px 0;border-bottom:1px solid var(--color-neutral-200);font-size:12px}
.sbrow:last-of-type{border-bottom:0}
.sbtable{width:100%;border-collapse:collapse;font-size:11.5px}
.sbtable th{text-align:left;background:var(--color-neutral-100);padding:5px 6px;font-size:10.5px;text-transform:uppercase;letter-spacing:.03em;color:var(--color-neutral-700)}
.sbtable td{padding:6px;border-bottom:1px solid var(--color-neutral-200);vertical-align:top}
.doclink{color:var(--color-accent-2-600);text-decoration:underline;background:none;border:0;cursor:pointer;font:inherit;padding:0;text-align:left}
.pdftag{background:#fdecec;color:#c73a3a;font-size:10px;font-weight:700;padding:2px 6px;border-radius:4px}
.sbcard{border:1px solid var(--color-neutral-300);border-radius:8px;padding:10px 12px;font-size:12px;margin-bottom:8px;line-height:1.5}
.sbcard-t{color:var(--color-accent-2-600);font-weight:700;font-size:13px}
.kvw{grid-template-columns:128px 1fr;gap:4px 10px}
.cihead{font-family:var(--font-heading);font-weight:700;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--color-neutral-600);margin:14px 0 0}
.secttl{font-family:var(--font-heading);font-size:15px;font-weight:700;margin:26px 0 8px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.sumgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}
.sum{display:flex;flex-direction:column;gap:3px;text-align:left;border:1px solid var(--color-neutral-300);border-radius:8px;padding:12px 14px;background:var(--color-surface);font:inherit;text-decoration:none;color:inherit}
a.sum:hover{border-color:var(--color-accent);background:var(--color-accent-100)}
.sumk{font-size:11px;text-transform:uppercase;letter-spacing:.03em;color:var(--color-neutral-700)}
.sumn{font-family:var(--font-heading);font-weight:700;font-size:22px;line-height:1.1}
.sumc{font-size:11.5px;color:var(--color-neutral-600);line-height:1.4}
/* A card counting something the portal does not hold yet. Greyed and not a link, so it reads
   as a space being held rather than a number that happens to be nought. */
.sumnone{color:var(--color-neutral-600);background:var(--color-neutral-100)}
.mt2{margin-top:2px}.mt4{margin-top:4px}.mt6{margin-top:6px}
.tac{text-align:center}
.cnav{display:flex;flex-direction:column;margin-top:14px;border-top:2px solid var(--color-divider);padding-top:4px}
.cni{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:9px 6px;font:inherit;font-size:12.5px;border:0;border-bottom:1px solid var(--color-neutral-200);background:none;cursor:pointer;text-align:left;color:var(--color-text)}
.cni:hover{background:var(--color-neutral-100)}
.cni.on{box-shadow:inset 3px 0 0 var(--color-accent);font-weight:700;color:var(--color-accent-700);background:var(--color-neutral-100)}
.yrow{display:flex;justify-content:space-between;background:var(--color-accent-2-100);padding:7px 10px;font-weight:700;font-size:12.5px;margin:12px 0 2px;border-radius:var(--radius-sm)}
.minirow{display:flex;justify-content:space-between;gap:10px;font-size:12px;padding:6px 0;border-bottom:1px solid var(--color-neutral-200)}
.tabs{display:flex;border-bottom:2px solid var(--color-divider);margin:0 0 14px}
.tab{padding:9px 16px;font:inherit;font-size:13px;background:none;border:0;cursor:pointer;color:var(--color-neutral-700)}
.tab.on{box-shadow:inset 0 -3px 0 var(--color-accent);color:var(--color-text);font-weight:600}
.steps{display:flex;border:1px solid var(--color-neutral-300);margin:0 0 20px;border-radius:var(--radius-sm);overflow:hidden}
.step{flex:1;padding:10px 14px;font-size:12px;color:var(--color-neutral-700);border-right:1px solid var(--color-neutral-300);display:flex;gap:8px;align-items:center}
.step:last-child{border-right:0}
/* The same bar as an ordered list, which is what it is: fixed steps in a fixed order. Only
   the browser's own numbering and indent need undoing — the step's own number is drawn. */
ol.steps{list-style:none;padding:0}
.step.on{background:var(--color-text);color:#fff;font-weight:600}
.step.done{color:var(--color-accent-700);font-weight:600}
.stepn{font-family:var(--font-heading);font-weight:700}
.tgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.tgrid.two{grid-template-columns:repeat(2,minmax(0,1fr))}
.tcard{border:1px solid var(--color-neutral-300);padding:14px;cursor:pointer;background:none;text-align:left;font:inherit;color:var(--color-text)}
.tcard:hover{border-color:var(--color-accent)}
.tct{font-weight:700;font-size:13.5px;margin:0 0 4px;font-family:var(--font-heading)}
.tcd{font-size:12px;color:var(--color-neutral-700);line-height:1.45;margin:0}
.fsec{margin:0 0 14px}
.dashph{border:1.5px dashed var(--color-neutral-400,#c7c7c7);border-radius:12px;padding:64px 24px;text-align:center;font-size:13px;color:var(--color-neutral-500,#8a8a8a)}
.fgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}
.fgrid2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.cbgrid{display:grid;grid-template-columns:repeat(2,1fr);gap:6px 14px}
/* A row of radios rather than a grid of checkboxes. Two or three options, each as wide as
   its own words: a question in one column of a three-column section has half of a third of
   the form to fit them in, and equal columns spend that on splitting "Did not attend" over
   two lines while "Attended" sits in the space it did not need. Wraps when it has to. */
.rbrow{display:flex;flex-wrap:wrap;gap:6px 14px}
.ck{display:flex;gap:8px;align-items:center;font-size:12.5px;cursor:pointer}
.wizfoot{display:flex;justify-content:space-between;gap:10px;margin-top:16px}
.splitrow{display:grid;grid-template-columns:130px 90px 1fr 30px;gap:8px;margin:0 0 8px}
.tally{font-size:12px;font-weight:600}.tally.bad{color:var(--color-accent-700)}
.toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);background:var(--color-text);color:#fff;padding:10px 18px;font-size:13px;z-index:300;box-shadow:var(--shadow-lg)}
.dlg-med{width:620px;max-width:92vw;max-height:84vh;overflow-y:auto;box-sizing:border-box}
.refcard{border:1px solid var(--color-neutral-300);padding:10px 12px;display:flex;justify-content:space-between;align-items:center;gap:10px;margin:0 0 8px;font-size:12.5px;cursor:pointer}
.refcard.on{border-color:var(--color-accent);box-shadow:inset 3px 0 0 var(--color-accent)}
.permrow{display:grid;grid-template-columns:140px 1fr;gap:12px;align-items:center;font-size:12.5px;padding:5px 0;border-bottom:1px solid var(--color-neutral-200)}
.pend{font-size:11.5px;background:var(--color-neutral-100);border:1px dashed var(--color-neutral-400);padding:8px 10px;color:var(--color-neutral-700);line-height:1.5}
.muted{color:var(--color-neutral-700)}
.table{font-size:13px}
.wizbox{max-width:920px}
.seldot{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--color-neutral-300);padding:4px 10px;font-size:12px}
.rpwrap{display:grid;grid-template-columns:minmax(220px,300px) minmax(0,1fr);gap:16px;align-items:start}
.rlist{padding:10px;max-height:72vh;overflow-y:auto}
.rpk{font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--color-neutral-700);padding:12px 8px 4px}
.rli{display:flex;justify-content:space-between;align-items:center;gap:8px;width:100%;padding:7px 8px;font:inherit;font-size:12.5px;border:0;background:none;cursor:pointer;text-align:left;color:var(--color-text);border-radius:var(--radius-sm)}
.rli:hover{background:var(--color-neutral-100)}
.rli.on{background:var(--color-accent-100);color:var(--color-accent-800);font-weight:600;box-shadow:inset 3px 0 0 var(--color-accent)}
.pmrow{display:grid;grid-template-columns:minmax(0,1fr) auto 150px;gap:10px;align-items:center;padding:6px 0;border-bottom:1px solid var(--color-neutral-200);font-size:12.5px}
.pmn{font-weight:600}
.pmoff .pmn{color:var(--color-neutral-600);font-weight:400}
.pacts{display:flex;gap:6px;justify-content:flex-end}
.pact{width:30px;height:24px;border:1px solid var(--color-neutral-300);background:none;font:inherit;font-size:11px;font-weight:700;cursor:pointer;color:var(--color-neutral-500);border-radius:var(--radius-sm)}
.pact:hover{border-color:var(--color-accent)}
.pact.on{background:var(--color-accent);border-color:var(--color-accent);color:#fff}
.tfrow{display:grid;grid-template-columns:24px minmax(0,1fr) auto auto;gap:10px;align-items:center;padding:8px 0;border-bottom:1px solid var(--color-neutral-200);font-size:12.5px}
.rlim td{color:var(--color-neutral-600);background:var(--color-neutral-100)}
.rlim td .tag{opacity:.7}
.rlim .ib{color:var(--color-neutral-600);cursor:default}
.rlim .ib:hover{color:var(--color-neutral-600)}
.lockbadge{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--color-neutral-300);background:var(--color-neutral-100);color:var(--color-neutral-700);padding:3px 9px;font-size:11px;border-radius:var(--radius-sm)}
/* readability: larger type + higher contrast */
:root{--color-divider:#c8d0da}
.card,.dialog{border-color:var(--color-neutral-400)}
.input{border-color:var(--color-neutral-500)}
.minirow,.tfrow,.permrow{border-bottom-color:var(--color-neutral-300)}
.chip,.seldot,.steps,.step,.notebar{border-color:var(--color-neutral-400)}
.tag-outline{border-color:var(--color-neutral-500)}
.pend{border-color:var(--color-neutral-500)}
.table th,.table td{border-color:var(--color-neutral-300)}
.btn-secondary,.btn-ghost{border-color:var(--color-neutral-500)}
.fs11{font-size:12.5px}.fs12{font-size:13.5px}.fs13{font-size:14.5px}
.muted{color:var(--color-neutral-800)}
.table{font-size:14px}
.input,select.input{font-size:14px}
.field label{font-size:12.5px;color:var(--color-neutral-800)}
.lbl{font-size:11.5px;color:var(--color-neutral-800)}
.chip{font-size:13px}
.tag{font-size:12px}
.minirow{font-size:13.5px}
.pend{font-size:13px;color:var(--color-neutral-800)}
.tcd{font-size:13.5px;color:var(--color-neutral-800)}
.tab{font-size:14px;color:var(--color-neutral-800)}
.step{font-size:13px}
.seldot{font-size:13px}
.tfrow{font-size:13.5px}
.permrow{font-size:13.5px}
.sbfoot{font-size:12px;color:var(--color-neutral-800)}
.ib{color:var(--color-neutral-800)}
.card-meta{color:var(--color-neutral-800)}
@media (max-width:1000px){.rpwrap{grid-template-columns:1fr}.rlist{max-height:none}}


/* ── utilities ────────────────────────────────────────────────────────────
   Not from the design stylesheet: these atomics were generated at runtime by
   the bundler, so they are restated here to match the markup that uses them. */
.fw6 { font-weight: 600; }
.fw7 { font-weight: 700; }
.nowrap { white-space: nowrap; }
.mt0 { margin-top: 0; }
.mt8 { margin-top: 8px; }
.mt12 { margin-top: 12px; }
.mb8 { margin-bottom: 8px; }
.mb12 { margin-bottom: 12px; }
.mb0 { margin-bottom: 0; }

/* Anchors standing in for the design's SPA <button> nav items. */
.sbi { text-decoration: none; }
.sbs { text-decoration: none; }

/* ---- Groups in the rail --------------------------------------------------
   The design's expanding sections are an SPA button with a class on it. Here they are a
   <details> with a <summary>, which is the same thing with nothing behind it: it opens and
   closes on its own, it is a button to a keyboard and to a screen reader without being told
   to be, and the state it is in is an attribute the stylesheet can read. These rules are
   what it takes to make that element look like the design's row. */

/* The disclosure triangle the browser draws, which the design's row has its own caret for.
   Taken away rather than styled — the second rule is the same removal for WebKit, which
   does not honour the first on its own.

   Note what is not here: a display. The row inside is what is laid out as a flex line, and
   the summary is left exactly as the browser made it, because Safari before 16.4 stops
   treating a summary whose display has been changed as the thing that opens the details. */
.sbhead { list-style: none; cursor: pointer; }
.sbhead::-webkit-details-marker { display: none; }

/* The caret is the design's own, and the open state is the element's rather than a class on
   it: <details> already knows, so nothing has to be told. The design's .sbcar.open is left
   as it is and is simply not what drives this. */
.sbgroup[open] > .sbhead .sbcar { transform: rotate(90deg); }

/* A row is the same height whether it is a link, a button or a summary, and a sub-row is
   the same height whether or not its label is showing. Same reasoning as .sbi's floor
   further down: left alone the height comes from whichever child is tallest, which changes
   when the labels go away. */
.sb .sbs { line-height: 20px; min-height: 34px; }

/* Somebody who has asked for less movement gets the two states and nothing between them,
   the same bargain the collapsing rail makes below. */
@media (prefers-reduced-motion: reduce) {
  .sbcar { transition: none; }
}

/* Likewise the tabs; inline-flex keeps a count badge centred against the label. */
.tab { text-decoration: none; display: inline-flex; align-items: center; }

/* And the rail down a client's record, for the same reason: these are anchors so a section
   can be middle-clicked and bookmarked, but they are read as a list of places rather than
   as a paragraph of links, and an underline on every row makes the panel look like link
   soup. Hovering still moves the background, which is what says they are clickable. */
.cni { text-decoration: none; }

/* The design defines a wide dialog; only .dlg-med made it into the extracted sheet. */
.dlg-wide { width: 940px; max-width: 94vw; max-height: 84vh; overflow-y: auto; box-sizing: border-box; }

/* Sub-programs are indented under their parent. The design used two non-breaking
   spaces in the label; this keeps the indent out of the text itself. */
.permchild { padding-left: 18px; }

/* Super admin marker in the user list. Trailing rather than leading, so names stay
   left-aligned down the column. */
.sa-icon { color: var(--color-accent); vertical-align: -2px; margin-left: 6px; }

/* The Microsoft mark beside them: two pixels smaller, since four solid squares read heavier
   than an outline at the same size, and lowered by one less to sit on the same line. */
.sa-icon.ms-icon { vertical-align: -1px; }

/* ── Header rows ──────────────────────────────────────────────────────────
   What a thing is on the left, what to do with it on the right. .ph is the
   page's, .cardhd a card's or a panel's, .fbhd the form builder's — the same
   shape at three sizes, so they are declared together and stay in step.

   Two children, a heading and an actions group, in that order. A third takes a
   full-width line of its own underneath the actions rather than beside them,
   which is what .fbhd-meta is: a line that can run to any length belongs under
   everything rather than squeezed into what the buttons left over.

   The actions never shrink and never move to the left. The design's .ph spread
   its children with space-between, and a wrapped row spread that way drops its
   last item onto a line of its own at the LEFT edge — so the buttons ended up
   under the heading, on the wrong side, in a different spot on every page
   depending on how long its heading ran. The auto margin here is what puts
   them right instead, on the line they share with the heading and equally on a
   line of their own.

   Aligned to the top, and the heading's basis left at its own content width so
   that the row keeps them beside the heading for as long as there is room for
   both and gives way only when there is not. A rigid two-column grid was tried
   here first and is wrong: the columns cannot yield, so a .ph inside a narrow
   pane — the Touchpoints rail on a client's record is the one that showed it —
   printed its heading straight through its own buttons. Wrapping is the honest
   answer to not enough room, and wrapping here still leaves the actions above
   anything on the third line. */
.ph, .cardhd, .fbhd {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px 16px;
}

/* The heading takes the width the actions do not, and may wrap inside it. */
.ph > :first-child, .cardhd > :first-child, .fbhd > :first-child { flex: 1 1 auto; min-width: 0; }

/* Their own width, hard right, whichever line they end up on. */
.ph > :nth-child(2), .cardhd > :nth-child(2), .fbhd > :nth-child(2) { flex: none; margin-left: auto; }

/* A page header stands as tall as a button whether or not it has one in it. A heading is
   shorter than a button — 26px against 35 — so without this the header of a page with
   nothing to press is 9px shorter than the header of the page beside it, and everything
   under it sits 9px higher. On a client's record that is the tab strip: changing section
   moved the tabs, which reads as the strip shifting rather than as the heading changing.

   A floor rather than a fixed height, so a header that needs more still takes it: a long
   heading wrapping to two lines, or actions wrapping onto a line of their own.

   Only .ph. A card's header and the builder's are the same shape at a smaller size, with
   smaller buttons in them, and a full-size button's height is not their measure. */
.ph { min-height: var(--btn-height); }

/* Same right-aligned row as .dialog-actions, for a form that is a page rather than a
   dialog. Separate class so page markup does not have to borrow dialog names. */
.formactions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-4); }

/* ---- Form builder --------------------------------------------------------
   Verbatim from the design stylesheet. These were first written from the
   markup alone and drifted; they now match declaration for declaration, so
   diffing this block against the design finds nothing. */
.fbwrap{display:grid;grid-template-columns:210px minmax(320px,1fr) 300px;gap:14px;align-items:start}
@media (max-width:1180px){.fbwrap{grid-template-columns:minmax(320px,1fr) 300px}.fbwrap .fbpal{grid-column:1 / -1;order:3}}
@media (max-width:760px){.fbwrap{grid-template-columns:1fr}.fbwrap .fbpal{order:0}}
.fbpanel{border:1px solid var(--color-neutral-300);background:#fff;padding:13px;box-sizing:border-box}
.fbadd{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.fbaddb{padding:7px 8px;font:inherit;font-size:12px;border:1px solid var(--color-neutral-300);background:#fff;cursor:pointer;text-align:left}
.fbaddb:hover{border-color:var(--color-accent)}
.fblibi{display:block;width:100%;padding:7px 9px;border:1px dashed var(--color-neutral-400);background:var(--color-neutral-100);cursor:pointer;font:inherit;text-align:left;font-size:12px;margin-bottom:6px;box-sizing:border-box}
.fblibi:hover{border-color:var(--color-accent)}
.fbitem{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--color-neutral-300);background:#fff;cursor:pointer;margin-bottom:8px}
.fbitem.sel{border-color:var(--color-accent);box-shadow:0 0 0 1px var(--color-accent)}
.fbitem.fbsec{background:var(--color-neutral-100);border-style:dashed}
.fbctl{display:flex;gap:4px;margin-left:auto;flex:none;align-items:center}
.optrow{display:flex;gap:6px;margin-bottom:6px;align-items:center}

/* Additions, not in the design sheet.

   The design makes the whole .fbitem a button, which cannot be done literally
   here because the row also holds the move and remove buttons and a button
   cannot contain buttons. Instead the select button is stretched across the
   whole row underneath the content: the content is transparent to clicks so
   they fall through to it, and the controls are lifted back above it. The
   result is that anywhere in the row selects the field, except the controls. */
.fbitem { position: relative; }
.fbpick {
  position: absolute; inset: 0; width: 100%; padding: 0;
  border: 0; background: none; cursor: pointer; font: inherit;
}
.fbpick:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -3px; }

/* Holds the label and tags. pointer-events is what lets a click on the label
   reach the button behind it; nothing in here is interactive. */
.fbmain { position: relative; z-index: 1; pointer-events: none; min-width: 0; }

/* The field's help text under its name. Held to two lines: it is here to say what the
   question means at a glance, and a paragraph of it would push the rows apart and bury
   the shape of the form, which is what this list is for. The whole of it is in the box
   that edits it, and on the form itself. */
.fbdesc {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; line-clamp: 2;
  overflow: hidden;
}

/* Back above the stretched button, so the controls stay clickable. */
.fbctl { position: relative; z-index: 1; }

/* Sections. .fbitem.fbsec comes from the design sheet above; these are what the
   design implies but does not state, since it shows sections without saying how
   a field reads as being inside one.

   The section button in the palette spans both columns and borrows the section
   row's dashed, tinted look, so the one control that adds a container rather
   than a question is not mistaken for another field type. */
.fbaddsec {
  grid-column: 1 / -1;
  border-style: dashed;
  background: var(--color-neutral-100);
  font-weight: 600;
}

/* The managed-list buttons take the section button's full width and nothing else
   of its look: they add a question like every other button in the palette, and
   the dashed, tinted treatment is what says "container" rather than "question".
   Width alone here, because they are already set apart by the heading above
   them — this only stops two lonely buttons sitting in one column of a grid
   built for pairs. */
.fbaddwide { grid-column: 1 / -1; }

/* A step darker than the design's neutral-300, which reads as almost nothing
   against the white the box is filled with. Overridden here rather than changed
   in the block above, so that block still diffs clean against the design sheet.
   The preview's .pvcell is kept in step with it: it is the same field box drawn
   somewhere else, and only one of the two changing would look like a mistake. */
.fbitem { border-color: var(--color-neutral-400); }

/* Indented under the heading above it. The line is what carries the grouping
   once a section's fields run past the top of the screen and the heading has
   scrolled away. */
.fbitem.fbfield {
  margin-left: var(--space-4);
  border-left: 2px solid var(--color-neutral-400);
}

/* .fbitem.sel and .fbitem.fbfield carry the same weight, and the indent rule is
   written second, so without this a selected field keeps its grey left edge and
   is accented on only three sides. */
.fbitem.fbfield.sel { border-left-color: var(--color-accent); }

/* The builder's header is a header row like any other — see .ph above — with one
   addition: what the form is goes on a line of its own underneath, below the
   actions rather than beside them.

   The programs are what make this long: five of them run past the middle of the
   page, and they are the part that should be free to run on. Beside the buttons
   they would have to fit in whatever was left over, which on a narrow window is
   not enough to be worth having. A basis of the whole row puts them under
   everything instead, so they simply carry on under the buttons and nothing here
   has to be squeezed. Third in the markup as well as on the page, so what is read
   matches what is seen — and the buttons stay above them at every width, which is
   the point of ordering it this way rather than letting them fall wherever. */
.fbhd-meta { flex: 1 0 100%; }

/* ---- Form preview --------------------------------------------------------
   The preview draws the form the way it will be asked, rather than listing the
   fields down a table. Each section is its own grid, as wide as the section
   says, and a field occupies the cells the builder said it would. */
.pvsec { margin-bottom: var(--space-4); }
.pvsec:last-child { margin-bottom: 0; }

.pvsech {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-2); padding-bottom: 5px; margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--color-neutral-300);
}

/* The column count arrives inline on the element, since it is a property of the
   section rather than of the stylesheet. minmax(0,1fr) rather than 1fr so a long
   field name cannot push a column wider than its share. */
.pvgrid {
  display: grid;
  grid-template-columns: repeat(var(--pvcols, 1), minmax(0, 1fr));
  gap: var(--space-2);
}

/* Placed explicitly from the same numbers the builder shows, not flowed. The
   two would usually agree, but "usually" is not something a preview can be. */
.pvcell {
  grid-row: var(--pvrow, auto);
  grid-column: var(--pvcol, auto) / span var(--pvspan, 1);
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--color-neutral-400);
  /* #fff rather than --color-surface, which is the dialog's own background: a cell
     painted in it would be a border and nothing else. Matches .fbitem in the builder. */
  background: #fff;
}
.pvname { font-size: 13px; font-weight: 600; }

/* The real control, disabled. Browsers grey a disabled control out to say "not now",
   which is the wrong message here — the point of showing it is what it will look like
   when it is somebody's to fill in. So it keeps its own colours and says it is not for
   filling in through the cursor instead. */
.pvctl :disabled {
  opacity: 1;
  color: var(--color-text);
  background: #fff;
  cursor: default;
}
.pvctl .ck { cursor: default; }

/* A preview cell is narrower than a form field, so the options stack rather than
   sitting two abreast and wrapping mid-word. */
.pvctl .cbgrid { grid-template-columns: 1fr; }

/* Below this there is no room to sit fields side by side, so the section falls
   back to one per row. The placement is read from custom properties precisely so
   this rule can win — an inline grid-column would need !important to override. */
@media (max-width: 700px) {
  .pvgrid { grid-template-columns: 1fr; }
  .pvcell { grid-row: auto; grid-column: 1 / -1; }
}

/* Where a field lands in its section's grid. Set apart from the type and
   mapping tags beside it because it describes the drawn form rather than the
   field: it changes when a field above it changes, without this field having
   been touched. Monospaced so the numbers line up down the column. */
.tag-grid {
  border: 1px dashed var(--color-neutral-400);
  background: var(--color-neutral-100);
  color: var(--color-neutral-700);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10px;
}

/* ---- Rendered form -------------------------------------------------------
   A form being filled in, drawn by _FormRender from its stored configuration.

   The design has .fgrid and .fgrid2 for two and four fixed columns, which is as
   many as its mock-ups needed. A section carries its own column count, so this
   is the same grid with the number read off the section instead of the class
   name; the gap matches .fgrid2 so a two-column section is indistinguishable
   from the hand-written one. minmax(0,1fr) rather than 1fr so a long option
   label cannot push a column past its share. */
.fgridn {
  display: grid;
  grid-template-columns: repeat(var(--fcols, 1), minmax(0, 1fr));
  gap: 12px;
}

/* Placed from the same numbers the builder shows and the preview draws, rather
   than flowed, so all three agree by construction and not by coincidence. */
.fcell {
  grid-row: var(--frow, auto);
  grid-column: var(--fcol, auto) / span var(--fspan, 1);
  min-width: 0;
}

/* Marks a question that has to be answered. Its own colour rather than the
   accent: the accent means "this is where you are" throughout the portal, and a
   column of accented asterisks down a form would read as attention rather than
   as obligation. aria-hidden in the markup, since the input carries
   aria-required and a screen reader would otherwise say it twice. */
.reqmark { color: #c73a3a; margin-left: 3px; }

/* The line above a multi-select saying how many may be ticked, turned red while more than
   that are. form-render.js marks the question as it happens; too few is left alone, because
   somebody part way through ticking is short of a minimum on the way up to it and has done
   nothing wrong yet. The same red as the asterisk and the server's own messages rather than
   the accent, for the same reason as the asterisk: the accent is "this is where you are".

   Nothing for the builder's preview. Its boxes are disabled and no script judges them, so the
   line there is only ever the instruction, in the muted grey it is drawn in. */
.field[data-selections-over] .selhint { color: #c73a3a; }

/* Below this there is no room to sit fields side by side, so the section falls
   back to one per row. The placement is read from custom properties precisely
   so this rule can win — an inline grid-column would need !important. */
@media (max-width: 700px) {
  .fgridn { grid-template-columns: 1fr; }
  .fcell { grid-row: auto; grid-column: 1 / -1; }

  /* And the design's own two-column page layout, for the same reason: at a phone's
     width its 1fr/1.4fr leaves two columns of about 160px, which a record's labels
     and values cannot sit in. The design has no rule for it — its mock-ups are a
     desktop — so this is the portal's. */
  .two { grid-template-columns: 1fr; }
}

/* Utilities the design's markup uses but its stylesheet does not define — they
   come from a base sheet that was not part of the supplied bundle. Restated to
   the values the markup implies; replace if that sheet turns up. */
.fx { display: flex; }
.col { flex-direction: column; }
.f1 { flex: 1; min-width: 0; }
.ac { align-items: center; }
.jb { justify-content: space-between; }
.wrap { flex-wrap: wrap; }
.gap6 { gap: 6px; }
.gap8 { gap: 8px; }
.gap16 { gap: 16px; }
.fs16 { font-size: 16px; }
.mt16 { margin-top: 16px; }
.m0 { margin: 0; }
.w100 { width: 100%; }
.bbox { box-sizing: border-box; }

/* Sidebar sign-out sits in a form; strip the button chrome so it matches .sbi. */
/* Text for screen readers only: a column of icon buttons still needs a heading, and an
   input inside the search box's own decoration still needs a label. */
.vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
      overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* A chip that removes what it names: the label and its ✕ sitting on one line. */
.chip-x { display: inline-flex; align-items: center; gap: 6px; }
.chip-x svg { opacity: 0.55; }
.chip-x:hover svg { opacity: 1; }

.sbform { margin: 0; }
.sbform .sbi { font: inherit; font-size: 13px; }

/* — a record's two columns —

   A client's record and a company's are the same grid: a 378px panel and whatever is left of
   the page. That stops being two columns worth having well before the layout around it
   stacks, so it has a width of its own rather than riding on the 760px below.

   A thousand is where the second column runs out. Above 760 the rail beside the page is
   still 232px, so the record has the window less 234 to share and the panel's share of it
   does not move — a window of 1001 leaves the main column 376px, which is about the least
   that holds a header row and a table. Under it the record was a fixed 378 and a sliver:
   151px at 761, narrow enough that a longer section title would have overflowed the page.

   On a phone the sliver went negative. A grid track that cannot shrink does not stop at the
   edge of its container, so the panel hung over the right of the page, the record's column
   was sized to what was left of a negative number — nothing — and its heading, its tabs and
   its tables spilled out of the page from there. */
@media (max-width: 1000px) {
  /* One column, and everything in it gets the width the page has.

     minmax(0,1fr) rather than plain 1fr, which is minmax(auto,1fr): an auto minimum is the
     widest thing inside, so a long heading or a wide table would push the track back out past
     the viewport and the page would be too wide again for a different reason. Zero is what
     holds the column to the page's width and no more, and it is what gives the strip of tabs
     below a container narrower than itself to scroll inside. */
  .cv { grid-template-columns: minmax(0, 1fr); }

  /* The same zero for what sits in the track. A grid item is no narrower than its own
     contents unless it is told it may be, and one that is not told overflows the track it was
     placed in — which is the width the column just gave up, given back. */
  .cinfo, .cmain { min-width: 0; }

  /* Stacked, the panel is a block at the top of the record rather than a column beside it.
     Standing as tall as the window and scrolling inside itself is what a column does; here it
     would hold the whole screen and put a second scrollbar inside the page's own. The rule it
     is read against turns the corner with it — the line was down the right because the record
     was to the right of it, and the record is underneath it now. */
  .cinfo { position: static; height: auto; overflow-y: visible;
           border-right: 0; border-bottom: 2px solid var(--color-divider); }

  /* One above the other, the two halves share a left edge, which as columns they had no
     reason to: the panel's narrower inset was the panel's own. .cmain's is what they both
     take, because stacked it is the page's. */
  .cinfo { padding: 18px 26px 40px; }
}

/* Narrow screens: the layout stacks and the sidebar goes across the top. What it looks like
   up there is settled further down, under "The sidebar as a menu" — the sections fold away
   behind a button rather than laying themselves out as tabs. */
@media (max-width: 760px) {
  .app { flex-direction: column; }
  .sb { width: 100%; height: auto; border-right: 0;
        border-bottom: 2px solid var(--color-divider); }
  .topbar { height: auto; padding: 10px 16px; flex-wrap: wrap; }
  .pg { padding: 16px; }
  .sysbanner { margin: 12px 16px 0; }

  /* The header has already stacked by here, and the impersonation block is the widest thing
     in it — a badge, two names and a button. Left alone, the button holds its width and the
     names are squeezed into a column one word deep. Wrapping inside the block drops the
     button under them instead, which keeps the sentence readable as a sentence. */
  .imp { flex-wrap: wrap; }

  /* A record's two halves have been stacked since 1000px — see above. Here the page itself
     comes in to 16px, and they come in with it. */
  .cinfo, .cmain { padding: 16px; }
}

/* — multi-client form tabs —

   Shared answers and one tab per client, down the left of the questions they belong to. No
   JavaScript, so the tabs are radio buttons wearing labels: each tab holds its own radio, and
   the pane that follows it in the markup is the one shown while that radio is checked.

   That markup — tab, pane, tab, pane — is what the CSS has to work around, because a two
   column layout in normal flow cannot put the third tab back up beside the first: a float
   will not rise above the pane it comes after. So the wrapper is a grid and both columns are
   placed explicitly, which ignores source order entirely. The tabs take a row each; the pane
   spans them all and is the only thing in the second column, so whichever one is showing
   starts at the top. */
.ovwrap {
  --ovtabw: 200px;
  --ovgap: 24px;
  display: grid;
  grid-template-columns: var(--ovtabw) minmax(0, 1fr);
  /* A row per tab, then the row that takes up whatever the pane has left over, then the
     row the lock message sits in. The pane spans down to the flexible row and no further,
     which is what stops it stretching the tab rows: a grid item that crosses a flexible
     track has no say in how the tracks it crosses are sized, so the tabs keep their own
     heights and the leftover lands in the one row that can absorb it.

     Sixty is a ceiling on how many tabs are laid out this way, not on how many there can
     be: past that they carry on down the left, below the pane rather than beside it. A
     touchpoint about sixty people at once is not a thing this form is for. */
  grid-template-rows: repeat(60, min-content) 1fr auto;
  margin: 0 0 14px;
  /* The line the tabs sit against, drawn for the wrapper's whole height rather than only as
     far down as the tabs reach — the same job the strip's bottom border used to do. */
  background: linear-gradient(var(--color-divider), var(--color-divider)) no-repeat;
  background-size: 2px 100%;
  background-position: var(--ovtabw) 0;
}
.ovradio { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }

/* A tab and, where the page offers one, its remove button: one row of the column. */
.ovtabrow { grid-column: 1; display: flex; align-items: stretch; min-width: 0; }
.ovtabrow:has(input:checked) { box-shadow: inset -3px 0 0 var(--color-accent); }

.ovtab {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; cursor: pointer; text-align: left;
  color: var(--color-neutral-700);
}
.ovtab:hover { background: color-mix(in srgb, var(--color-text) 5%, transparent); }
.ovtabrow:has(input:checked) .ovtab { color: var(--color-text); }
.ovtab:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.ovtabn { font-size: 13px; font-weight: 600; overflow-wrap: break-word; }
.ovtabsub { font-size: 11px; color: color-mix(in srgb, var(--color-text) 55%, transparent); }

/* The ✕ is its own submit button, so it sits beside its tab rather than inside it — a
   button nested in a label is neither valid markup nor separately clickable. */
.ovtabx {
  flex: 0 0 auto; display: inline-flex; align-items: center; padding: 0 10px;
  background: none; border: 0;
  color: color-mix(in srgb, var(--color-text) 45%, transparent); cursor: pointer;
}
.ovtabx:hover { color: var(--color-text); }

/* The last row of the column: a way of asking for another client, rather than a tab. Nothing
   is shown behind it, so it is drawn quieter than the tabs and carries no active state. */
.ovadd {
  grid-column: 1; display: flex; align-items: center; gap: 8px;
  padding: 9px 12px; font: inherit; text-align: left; cursor: pointer;
  background: none; border: 0;
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.ovadd:hover { background: color-mix(in srgb, var(--color-text) 5%, transparent); color: var(--color-text); }
.ovaddi {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; font-size: 14px; line-height: 1;
  border: 1px dashed currentColor; border-radius: var(--radius-sm);
}

/* Every pane is placed in the same cell, so the one being shown starts at the top of the
   questions column however far down the list its own tab is. */
.ovpane {
  display: none; grid-column: 2; grid-row: 1 / -2;
  min-width: 0; padding-left: var(--ovgap);
}
.ovtabrow:has(input:checked) + .ovpane { display: block; }

/* The gate. While a required question on the shared tab is unanswered, the client tabs are
   not clickable and the reason is shown. This is the browser enforcing it as it is typed;
   the same rule is enforced again on the server, which is what actually refuses a save. */
.ovlock {
  display: none; grid-column: 2; grid-row: -2 / -1;
  padding: 10px 0 0 var(--ovgap);
  color: var(--color-accent);
}
/* Two sources for the same verdict. :invalid is the browser's own, and works with no script
   at all, but it cannot see an empty multi-select — HTML has no way to require one. The
   .ovlocked class is form-render.js reporting on every required field including that one.
   Either shuts the tabs, so the gate is never narrower than the rule it enforces.

   Not every :invalid, though. form-render.js also marks a multi-select invalid for ticking
   too few or too many, and that is a different complaint: the question has been answered,
   just not in a way the form will save. The gate and its message are about what has to be
   answered before the client tabs mean anything, so a box carrying data-limit-error — the
   script's word for "this one is only a count" — is left out of it, and the count is
   refused on the way out instead. A required multi-select left empty is not marked that way
   even where it has a minimum, so it still shuts the tabs. */
.ovwrap:has(.ovpane-shared :invalid:not([data-limit-error])) .ovlock,
.ovwrap.ovlocked .ovlock { display: block; }

/* The tab stops being clickable; the ✕ beside it does not. Taking somebody off the form is
   how you get out of a form you should not have opened about them, and gating that behind
   answering questions about them would be the wrong way round. */
.ovwrap:has(.ovpane-shared :invalid:not([data-limit-error])) .ovtab-client,
.ovwrap.ovlocked .ovtab-client { pointer-events: none; opacity: 0.45; }

/* Too narrow for two columns: one column, with the tabs keeping their order and their
   stacking and the pane they choose following underneath instead of beside. `order` is what
   puts it under all of them rather than under its own tab, since the grid places items in
   order-modified document order. */
@media (max-width: 860px) {
  .ovwrap { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; background: none; }
  .ovtabrow { border-bottom: 1px solid var(--color-divider); }
  .ovtabrow:has(input:checked) { box-shadow: inset 3px 0 0 var(--color-accent); }
  .ovpane { grid-column: 1; grid-row: auto; order: 1; padding: 14px 0 0; }
  .ovlock { grid-column: 1; grid-row: auto; order: 2; padding: 10px 0 0; }
}

/* The last step of the touchpoint process: what was saved, and the ways on from it.

   The tick sits in its own circle rather than beside the words, so the outcome reads before
   the sentence does — this is the one place in the process that reports rather than asks. */
.savedhd { display: flex; align-items: flex-start; gap: 10px; }
.savedic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--color-accent-100);
  color: var(--color-accent-700);
}

/* One row per way on. Buttons and links both: recording something else posts what was just
   saved, opening a record or leaving is navigation — and the two have to be told apart by
   what they do, not by looking different, so the rule covers both elements. */
.nact {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  color: var(--color-text);
  text-decoration: none;
  background: none;
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  cursor: pointer;
}
.nact:hover { border-color: var(--color-accent); }
.nact:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.nactn { display: block; font-weight: 600; font-size: 13.5px; }
.nactd { margin: 4px 0 0; font-size: 12px; line-height: 1.45; color: var(--color-neutral-700); }

/* A client with no answers of their own has nothing to say. The line is drawn regardless so
   script has somewhere to write the count as it changes, and stays out of the way until
   there is a count to write. */
.ovtabsub:empty { display: none; }
/* A name that is also the way into a record. Wears the row's own colour rather than a
   link's: the design makes the row the target and this link only anchors it, so a column
   of accent-coloured names would read as a list of links instead of a table of people. */
a.rowlink{color:inherit;text-decoration:none}
a.rowlink:hover{color:var(--color-accent);text-decoration:underline}


/* ---- Collapsing the sidebar ---------------------------------------------
   The design has this state as .sb.mini — narrow rail, no labels, wordmark swapped for the
   logo mark. These rules say the same thing from :root instead, and that is the whole
   reason they exist: whether the rail is collapsed is remembered in the browser and has to
   be applied before the page is painted, and the only element that exists that early is
   <html>. A class on the sidebar itself cannot be set until the sidebar has been parsed,
   by which time it has already been drawn at full width.

   So the class goes on <html> and these reach down to the rail. The design's own .sb.mini
   is left as it is and is simply not what drives this. */
/* 54px rather than the design's 58: an icon is 16px and the open rail indents its rows by
   18px, so a rail with 52px inside it — 54 less the 2px border, which is drawn within the
   width — centres that same icon on exactly the same 18px. The rows then keep still when
   the rail narrows instead of sliding sideways, and the only thing that visibly changes is
   the labels going away, which is the thing that is actually happening. */
:root.sb-mini .sb { width: 54px; }

/* The rail slides between its two widths rather than snapping between them, so that what
   happened is legible: the labels leave and the icons stay where they were. 200ms is long
   enough to be followed and short enough not to be waited for.

   Nothing animates on the first paint. The class is put on <html> in the head, before the
   rail exists to have a width to transition from, which is exactly the case CSS does not
   animate — so a session that starts collapsed simply starts collapsed. */
.sb { transition: width 200ms ease; }

/* Collapsed rather than removed. display:none cannot be animated between, so the labels
   would blink out while the rail was still moving; a label that narrows to nothing goes
   with it instead. The opacity runs faster than the width so the words are gone before the
   space they were in has finished closing, which is what stops them looking squashed.

   overflow and nowrap are what make the width mean anything: without them the text wraps
   into a column of single letters on the way past 40px. */
.sb .nl, .sb .bx, .sb .bmark {
  overflow: hidden;
  white-space: nowrap;
  transition: opacity 120ms ease, max-width 200ms ease;
}

.sb .nl, .sb .bx { max-width: 160px; opacity: 1; }
:root.sb-mini .sb .nl, :root.sb-mini .sb .bx { max-width: 0; opacity: 0; }

/* The logo mark is the other half of the same swap, and has to be drawn all along to be
   faded in — hence display:block here rather than the display:none it starts from. It is
   held at no width while the wordmark is showing, so the two never both take up space. */
.sb .bmark { display: block; max-width: 0; opacity: 0; }
:root.sb-mini .sb .bmark { max-width: 37px; opacity: 1; }

/* One axis being clipped makes the other one scroll rather than overflow, and mid-slide the
   rail is narrower than the row it is holding. Without this a horizontal scrollbar appears
   for the length of the animation and then goes away again. */
.sb { overflow-x: hidden; }

/* Somebody who has asked for less movement gets the same two states and nothing between
   them. The rail still collapses; it just arrives rather than travels. */
@media (prefers-reduced-motion: reduce) {
  .sb, .sb .nl, .sb .bx, .sb .bmark, .sbtoggle svg { transition: none; }
}

/* A count on a nav row, at the end of it. The tag's accent pair rather than the red one:
   it is work waiting, which is the ordinary state of a queue, and a rail that alarms about
   its own contents alarms about nothing after the first week.

   Only ever drawn with a number in it — see the rail — so there is no empty state to style,
   and it is gone with the labels when the rail is down to icons: a number on its own beside
   an envelope is a number about nothing. display rather than the labels' width-and-fade,
   because a margin-left:auto that survives the fade would push the icon off centre. */
.sbnum {
  margin-left: auto; flex: none;
  font-size: 11px; font-weight: 600; line-height: 1; letter-spacing: 0.02em;
  padding: 3px 7px; border-radius: 999px;
  background: var(--color-accent-100); color: var(--color-accent-800);
}
:root.sb-mini .sb .sbnum { display: none; }

/* The toggle is a nav row like any other, so it narrows with the rest of them rather than
   being a control that has to be positioned twice. */
.sbtoggle { color: var(--color-neutral-800); }
.sbtoggle svg { transition: transform 0.15s; }
:root.sb-mini .sbtoggle svg { transform: rotate(180deg); }

/* Icons centred rather than left-aligned against a label that is no longer there. The gap
   goes too, or it would push each icon off-centre by half of itself. */
:root.sb-mini .sb .sbi { justify-content: center; padding-inline: 0; gap: 0; }

/* The rows inside a group, the same way. Their 34px left indent is what says they are
   underneath something, and there is nothing left for them to be underneath once the labels
   have gone — an indent on a column of centred icons would only push them off the axis the
   rows above them are on. */
:root.sb-mini .sb .sbs { justify-content: center; padding-inline: 0; gap: 0; }

/* And the caret goes with them. A 54px rail has room for one icon per row, and a second one
   saying which way a heading is pointing would be competing with the one that says what the
   heading is. The group still opens and closes — pressing the icon is pressing the summary
   — it just stops announcing which way it is. */
:root.sb-mini .sb .sbcar { display: none; }

/* The brand row keeps the logo mark on the same axis as the icons below it. */
:root.sb-mini .sb .brand { padding: 0; justify-content: center; }

/* "Entertainment Community Fund" has no short form, and at 58px it stacks into a column of
   single letters. Nothing is lost by dropping it — the logo mark above says the same. */
:root.sb-mini .sb .sbfoot { display: none; }

/* Off unless there is script to give it something to do — a button that collapses nothing is
   worse than no button. sb-menu is the head's word for "script ran at all", set on <html>
   before the page is painted, which is what keeps this row from arriving late and pushing the
   rail down by 40px once it does. The menu button on the brand row is drawn the same way.

   Off is stated rather than left to [hidden] because .sbi's display would outrank the browser's
   own rule for it, and because a row that is only ever the stylesheet's to show should not be
   half in the markup as well. */
.sbtoggle { display: none; }
:root.sb-menu .sbtoggle { display: flex; }

/* A row is the same height whether or not it has its label. Left alone, the height comes
   from whichever child is tallest: the label's line box when the rail is open (13px text at
   the body's 1.55 line-height, so about 20px) and the 16px icon when it is closed, which
   made every row shrink by four pixels on collapsing and the whole column shuffle upward.
   Fixing the line box and giving the row a floor settles it at 40px in both states. */
.sb .sbi { line-height: 20px; min-height: 40px; }

/* The logo mark is the left-hand third of the wordmark, cropped to it by object-fit. The
   crop has to be wide enough to hold the mark: at 34px tall the artwork draws 113px wide
   and the mark's right-hand point lands just under 36px along it, so a 34px-wide window cut
   the tip off. 37px clears it with a pixel to spare, and the mark is what sets the width
   here — the rail is wide enough for it either way. */
:root.sb-mini .sb .bmark img { width: 37px; }

/* ---- The sidebar as a menu -----------------------------------------------
   Below the stylesheet's 760px the rail cannot be a rail: the layout stacks and it lays
   itself across the top of the page. Eleven sections laid across the top came to four rows
   of tabs before the page itself had started, on the screens with the least room to spare —
   so they fold away instead, behind a button on the brand row.

   These rules are here, after the collapsing above, rather than up in the narrow-screen
   block with the rest of the stacking. Half of what follows undoes that collapsing, and the
   selectors are the same shape as the ones they undo — so whichever comes last wins, and it
   has to be this one. */

/* Off at every width but this one, so the markup can carry it unconditionally. */
.sbham {
  display: none; align-items: center; justify-content: center;
  margin-left: auto; padding: 8px; border: 0; background: none;
  color: var(--color-text); cursor: pointer;
}
.sbham:hover { color: var(--color-accent-700); }

/* The shade over the page while the menu is open. Nothing at all until then, and nothing at
   any width that has no menu to be behind; what shows it is inside the media block below. */
.sbscrim { display: none; }

@media (max-width: 760px) {
  /* Pinned to the top of the page, which is the whole reason the rail keeps the sticky it
     already has at this width rather than being set back to static. On a rail beside the
     page the sections are always in view because the rail is as tall as the window; folded
     into a bar they would scroll away with everything else, and a page scrolled down is
     exactly when somebody wants to be somewhere else.

     It needs a background of its own to be pinned over anything. Without one the page shows
     through it as it scrolls past, which looks like a rendering fault rather than a bar.
     #fff is body's own, so the bar and the page it sits on are the same white.

     50 is above the page and below the drawer at 60, the dialog backdrop at 200 and the
     toast at 300 — none of which should end up underneath a menu bar. */
  .sb { z-index: 50; background: #fff; }

  /* Open, the sections are longer than a phone screen. Held to the window and left to
     scroll inside itself, so the menu is a thing on the page rather than a thing the page
     has become — there is always some of what you were reading left under it, which is
     also what there is to click on to put it away.

     dvh over vh where it is understood: on a phone vh is the window with the browser's own
     chrome hidden, so a menu measured in it is taller than what you can actually see, and
     the last row or two sit under the address bar. The vh above is the fallback for engines
     that do not know dvh, where it is the same number it always was. */
  .sb { max-height: 100vh; max-height: 100dvh; }

  /* The brand row is the whole of the rail here: wordmark on the left, this hard right. */
  :root.sb-menu .sbham { display: inline-flex; }

  /* Folded, and only where there is a button to unfold it with. sb-menu is set in the head,
     before the rail is drawn, so the sections are folded on the first paint rather than
     appearing and being taken away again. Without script the class is never set and they
     stay listed under the brand, which is the layout this replaces the tabs with anyway. */
  :root.sb-menu .sb .sbnav { display: none; }
  :root.sb-menu .sb.navopen .sbnav { display: block; }

  /* The page dimmed behind an open menu. The same shade as the dialog backdrop, so the two
     read as one thing happening. Fixed to the window rather than to the page, and under the
     bar at 50: the bar and the menu in it stay lit and everything else is put behind them.
     Above the page's own positioned pieces at 1, below the drawer at 60 — which is the one
     thing deliberately still on top, as it already is of the bar.

     Shown by the sidebar's own state rather than by a class of its own, which is why the
     layout puts it right after the sidebar: the rule reads across from the one to the other,
     and there is no second thing for sidebar-menu.js to keep in step. Only under sb-menu,
     like the folding it belongs to — without script the sections are never folded, so there
     is never a menu for it to be behind. */
  :root.sb-menu .sb.navopen + .sbscrim {
    display: block;
    position: fixed; inset: 0;
    z-index: 40;
    background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
  }

  /* Undoing the collapsed rail. Whether it was collapsed is remembered per browser and not
     per window size, so a window narrowed after collapsing arrives here with mini set — and
     mini at this width is a 54px column of unlabelled icons stacked two to a row across the
     top of the page, which is not a state anybody chose to be in. Narrowing a rail to its
     icons is a thing a rail does; there is no rail here to narrow. */
  :root.sb-mini .sb { width: 100%; }
  :root.sb-mini .sb .nl, :root.sb-mini .sb .bx { max-width: 160px; opacity: 1; }
  :root.sb-mini .sb .bmark { max-width: 0; opacity: 0; }
  :root.sb-mini .sb .sbnum { display: inline-block; }
  :root.sb-mini .sb .sbi { justify-content: flex-start; padding-inline: 18px; gap: 10px; }
  :root.sb-mini .sb .sbs { justify-content: flex-start; padding-inline: 34px 18px; gap: 10px; }
  :root.sb-mini .sb .sbcar { display: block; }
  :root.sb-mini .sb .brand { padding: 0 16px; justify-content: flex-start; }

  /* And for the same reason, the row that does the narrowing is not offered. The button on
     the brand row is the one that applies at this width, and two controls over the same
     sections that mean different things is one too many.

     Said through sb-menu, which is the only way the row is ever shown, so that this outranks
     the rule that shows it rather than losing to it on specificity. */
  :root.sb-menu .sb .sbtoggle { display: none; }

  /* The organisation's name, under a logo that has just said it, at the top of the screen
     with the least room for either. */
  .sbfoot { display: none; }
}

/* ---- A form being read rather than filled in -----------------------------
   The renderer's read-only view, which puts what was recorded where the control would be.
   Everything around it is unchanged: same sections, same grid, same order, so a record reads
   as the form it was recorded on. */

/* The question. A <div> and not a <label>, because there is no control for a label to be
   attached to, so it borrows what .field label looks like rather than what it is. */
.field .ansl { font-size: 12.5px; color: var(--color-neutral-800); }

/* The answer. Held to the height of an empty line so a question nobody answered still takes
   up a row: a grid that closed up around its blanks would read as a shorter form. */
.ansv { font-size: 13px; line-height: 20px; min-height: 20px; margin-top: 2px; }

/* Several lines stay several lines. Long enough to need it, and the alternative is a case
   note read as one paragraph it was never written as. */
.ansv-long { white-space: pre-wrap; }

/* Each pick of a multi-select on its own, wrapping rather than running off the column. */
.ansvtags { display: flex; flex-wrap: wrap; gap: 6px; }


/* A program access row carries a third thing the plain permrow does not: the program, the
   role granted, and whether that grant includes intake. The name column is allowed to give
   way rather than being held at 140px, because the two controls beside it are what have to
   keep their size — a role that has wrapped onto two lines is harder to read at a glance
   than a program name that has. */
.permrow.permprog { grid-template-columns: minmax(120px, 1fr) 170px auto; }

/* The account dialog's Sign-in rows: what the account signs in with, and the button that
   changes it. The text wraps when it has to; the button never does, keeps to the right, and
   drops to its own line before the text is squeezed. On a phone the value goes under its
   label, so the button has the dialog's width to sit in. */
.signin-value { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.signin-value > span { flex: 1 1 12em; min-width: 0; overflow-wrap: anywhere; }
.signin-value > .btn { flex: none; white-space: nowrap; margin-left: auto; }

/* On a phone a program access row puts the program's name on a line of its own, with its role
   and Intake tick under it: three columns do not fit, and the account dialog and the import
   page would both scroll sideways to reach Intake. */
@media (max-width: 520px) {
    .permrow.signin-row { grid-template-columns: 1fr; gap: 4px; }
    .permrow.permprog { grid-template-columns: minmax(0, 1fr) auto; }
    .permrow.permprog > :first-child { grid-column: 1 / -1; }
}

/* The import page's Program access rows, held to the width they have in the account dialog.
   Across a whole page the name column's 1fr would put every role a page-width away from the
   program it belongs to. */
.batchaccess { max-width: 620px; }

/* A line above a list on the import page: what it holds on the left, what can be done to all
   of it on the right. */
.importbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; margin: 0 0 8px; }

/* Link To Existing in the import list's choose column, in place of a tick box: one button per
   matching portal account, stacked on the rare row with more than one. */
.linkexisting { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }

/* More than one button in a header row's pinned slot. Wraps inside itself, right-aligned, and
   may be narrower than its buttons laid end to end — so on a phone the second drops under the
   first rather than pushing the page wider. */
.ph-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; max-width: 100%; }


/* ---- A form's security grid ----------------------------------------------
   Who may do what with the responses to one form: a program per row, a role per
   column, and one capability at a time. */

/* The builder's own two halves. The half that is not on screen is still on the
   page carrying its inputs, and .fbwrap's own display would otherwise outrank
   [hidden] — the same reason .btn[hidden] needs saying below. */
.fbwrap[hidden] { display: none; }

/* Drawn before there is anything to say and revealed by script the moment the
   security grid is changed, since that change never reaches the server. .tag's
   own display would otherwise outrank [hidden]; .pend sets none, and is said
   here anyway so the pair cannot drift apart. */
.tag[hidden], .pend[hidden] { display: none; }

/* The palette's two pickers and the library's buttons, which the builder hides
   and shows as the form takes questions and gives them back — see
   form-builder.js. Both classes set a display of their own, which would
   otherwise outrank [hidden] the same way .tag's does above. */
.fx[hidden], .fbstack[hidden], .fblibi[hidden] { display: none; }

/* The capability tabs. Segmented rather than underlined like .tabs above, so
   that two rows of tabs on one page do not read as the same choice twice. */
.fssub { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 10px; }

.fssubtab {
  padding: 6px 12px;
  font: inherit;
  font-size: 12.5px;
  color: var(--color-neutral-800);
  background: var(--color-neutral-100);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.fssubtab:hover { background: var(--color-neutral-200); }
.fssubtab.on {
  background: var(--color-text);
  border-color: var(--color-text);
  color: #fff;
  font-weight: 600;
}

/* The grid scrolls inside itself. A portal that grows a few more roles pushes
   this sideways rather than the whole page. */
.permwrap { overflow-x: auto; }

/* As wide as it needs to be rather than as wide as the page. A .table filling the
   width would push four narrow columns of one word apart across a wide screen, and
   the whole point of a grid is that a row can be read across without losing which
   column is which. */
.fsgrid { width: auto; min-width: min(520px, 100%); }
.fsgrid th { vertical-align: bottom; }
.fsgrid td { padding-top: 5px; padding-bottom: 5px; }

/* Every role column centred — the cells, the button above them that sets the
   whole column, and the role's own name. All three centred rather than only the
   cells, because a heading left against centred cells reads as two columns that
   happen to overlap. The program names stay left, where a name belongs. */
.fsgrid th:not(:first-child), .fsgrid td:not(:first-child) { text-align: center; }

/* The program names take whatever slack is going, so the cells stay together. */
.fsgrid th:first-child, .fsgrid td:first-child { width: 40%; }

/* The role's name above the button that sets its whole column. */
.fsrole { font-size: 12px; margin-bottom: 5px; white-space: nowrap; }

/* Sets a whole column, or a whole row. Deliberately quieter than the cells it
   commands: it is a shortcut to the grid, not another square of it. */
.pill, .fsprog {
  font: inherit;
  font-size: 12px;
  padding: 3px 9px;
  color: var(--color-neutral-700);
  background: none;
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.pill:hover, .fsprog:hover { border-color: var(--color-neutral-500); color: var(--color-text); }
.pill.on { border-color: var(--color-accent); color: var(--color-accent-700); font-weight: 600; }

/* The row's button is the program's name, so it is left where a name would be
   and sized to read as one. */
.fsprog { font-size: 13px; text-align: left; border-color: transparent; padding-left: 0; }
.fsprog:hover { border-color: var(--color-neutral-300); padding-left: 8px; }

/* One cell. Wide enough for the longest of the three words it cycles through, so
   the columns do not shift as the grid is set. Not .fcell — that belongs to the
   form renderer's own grid, and means something else entirely. */
.fscell {
  font: inherit;
  font-size: 12px;
  min-width: 58px;
  padding: 4px 8px;
  color: var(--color-neutral-600);
  background: var(--color-neutral-100);
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.fscell:hover { border-color: var(--color-neutral-500); color: var(--color-text); }

/* A granted cell carries the accent, which is how the rest of the portal says
   "this one is on". Not granted stays grey rather than going red: a grid is read
   by picking out what has been given, not what has not. */
.fscell.on {
  background: var(--color-accent-100);
  border-color: var(--color-accent-400);
  color: var(--color-accent-700);
  font-weight: 600;
}


/* ---- The field being edited ----------------------------------------------
   A panel over the page rather than a column beside it. A field is asked about
   from three directions — what it is, who may see it, when it is asked at all —
   and three columns' worth of that does not fit beside a form that is itself a
   column. */
.fbdrawer {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: 400px;
  max-width: 94vw;
  background: #fff;
  border-left: 1px solid var(--color-neutral-300);
  box-shadow: -10px 0 28px rgba(0, 0, 0, .1);
  z-index: 60;
  overflow-y: auto;
  padding: 16px;
  box-sizing: border-box;

  /* Off to the right until it is opened. There is one of these per row, all of
     them stacked in the same place and all but one pushed off: opening a row is
     then a class moving rather than a page being fetched, and the controls
     inside — which are the only place a row's values are posted from — are never
     out of the page.

     visibility as well as the transform, so the ones that are off cannot be
     tabbed into, read out, or scrolled to. Hidden inputs inside a
     visibility:hidden element still post, which is the whole point. It changes
     at the end of the slide out and at the start of the slide in, which is what
     the two transitions say. */
  transform: translateX(105%);
  visibility: hidden;
  transition: transform .25s ease, visibility 0s linear .25s;
}

.fbdrawer.open {
  transform: translateX(0);
  visibility: visible;
  transition: transform .25s ease, visibility 0s;
}

/* Somebody who has asked not to be moved gets the panel without the movement. */
@media (prefers-reduced-motion: reduce) {
  .fbdrawer { transition: none; }
}

/* The close button, which deselects. Pushed to the end of the header row the
   way the design has it. */
.xbtn {
  margin-left: auto;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: 15px;
  line-height: 1;
  padding: 4px 6px;
  color: var(--color-neutral-600);
}

.xbtn:hover { color: var(--color-text); }

/* A pane of the drawer that is not the open one is still in the page carrying
   its inputs, so it is hidden rather than left out. */
.fbdrawer [data-field-pane-body][hidden] { display: none; }

/* The button beside the program dropdown, which script hides because it changes
   the grid itself. .btn's own display would otherwise outrank [hidden], the same
   way it does for the auto-submit button above. */
.btn[data-field-program-go][hidden] { display: none; }

/* ---- One field's security ------------------------------------------------
   A role per row, and each role's two scopes beside it: what the form granted
   over their own responses, and what it granted over everybody's. */
.facol {
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.farow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 6px 6px 10px;
  border-bottom: 1px solid var(--color-neutral-200);
}

.farow:last-child { border-bottom: 0; }
.farow.fahead { background: var(--color-neutral-100); }

/* The two cells, and the two words above them, on one track so the headings sit
   over the columns they name. */
.fascopes { display: flex; gap: 4px; flex: none; }
.fascopes > * { width: 30px; text-align: center; }

.facell {
  height: 26px;
  padding: 0;
  border: 0;
  background: var(--color-neutral-100);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  color: var(--color-neutral-700);
  cursor: pointer;
  border-radius: 4px;
}

.facell:hover { filter: brightness(0.96); }
.facell.sm {
  width: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Narrowing, in the order it narrows: open in the accent, read-only in its
   lightest tint, hidden in grey. A cell following the form stays the panel's own
   neutral, so what has been overridden is what stands out. */
.facell.fa-edit { background: var(--color-accent); color: #fff; }
.facell.fa-view { background: var(--color-accent-100); color: var(--color-accent-700); }
.facell.fa-hide { background: var(--color-neutral-200); color: var(--color-neutral-700); }

/* Every cell carries all four marks and shows the one its class names, which is
   what lets the script cycle a cell by moving a class and lets the legend below
   reuse the same markup. */
.facell .famark { display: none; align-items: center; justify-content: center; }
.facell .famark[data-mark="none"] { display: inline-flex; }
.facell.fa-edit .famark[data-mark="none"],
.facell.fa-view .famark[data-mark="none"],
.facell.fa-hide .famark[data-mark="none"] { display: none; }
.facell.fa-edit .famark[data-mark="edit"] { display: inline-flex; }
.facell.fa-view .famark[data-mark="view"] { display: inline-flex; }
.facell.fa-hide .famark[data-mark="hide"] { display: inline-flex; }

/* Swatch, word, swatch, word. Each swatch is a cell wearing the class it names,
   so a legend entry cannot say something the grid does not. */
.falegend { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; }
.falegend span { display: flex; align-items: center; gap: 6px; }
.falegend .facell { cursor: default; }

/* ---- One field's conditional rules ---------------------------------------
   A block of conditions, and the rows inside it. The panel is 400px wide, so a
   condition is two lines rather than the one line the design has it on: the
   field it reads across the top with the button that removes it, and how the
   answer is read underneath. */
.condblock {
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--radius-sm);
  padding: 10px;
  margin-bottom: 8px;
}

/* How a block joins the ones above it. Hidden on the first, which has nothing
   above it — and hidden by moving a class rather than by being left out, so it
   still posts. */
.condjoin { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.condjoin[hidden] { display: none; }
.condjoin select.input { width: auto; }

.condrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 30px;
  gap: 6px;
  align-items: center;
  padding-bottom: 8px;
  margin-bottom: 8px;
  border-bottom: 1px dashed var(--color-divider);
}

[data-cond-rows] > .condrow:last-child {
  padding-bottom: 0;
  margin-bottom: 8px;
  border-bottom: 0;
}

.condrow [data-cond-field] { grid-column: 1 / 3; }
.condrow [data-cond-remove] { grid-column: 3; grid-row: 1; }
.condrow [data-cond-op] { grid-column: 1; }
.condrow [data-cond-value] { grid-column: 2 / 4; }

/* Both answer boxes are in the row and the one not in use is disabled, so
   exactly one of them posts; the operators that only ask whether there is an
   answer hide both. .input sets no display of its own, so [hidden] is enough
   for those — the buttons are the ones that need saying twice. */
.btn[hidden], .pact[hidden] { display: none; }

/* ---- The drawer, and the record it shows -----------------------------------

   A panel over the page, opened by the address rather than by script — the same
   arrangement as .dialog-backdrop above, and for the same reasons: it can be
   linked to, the back button closes it, and none of it needs JavaScript.

   Which is why the scrim is an anchor. "Clicking outside closes it" is then a
   link to the page without the panel, rather than a listener that has to be
   right about what counts as outside. */
.drawer-scrim {
  position: fixed; inset: 0; display: block;
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);

  /* The same layer .dialog-backdrop claims, and for the same reason: being
     fixed and last in the document does not put it over a positioned element
     that has a z-index of its own. */
  z-index: 200;
  animation: scrim-in 0.15s ease-out;
}

.drawer {
  position: fixed; top: 0; bottom: 0;
  width: min(460px, 100%);
  display: flex; flex-direction: column;
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);

  /* One above the scrim, so a click lands on the panel rather than closing it. */
  z-index: 201;
}

/* Which side it comes in from. One class per side rather than a default, so a
   panel is never drawn half-placed by a missing one. */
.drawer.dleft { left: 0; border-right: 1px solid var(--color-neutral-200); animation: drawer-in-left 0.18s ease-out; }
.drawer.dright { right: 0; border-left: 1px solid var(--color-neutral-200); animation: drawer-in-right 0.18s ease-out; }

@keyframes scrim-in { from { opacity: 0; } }
@keyframes drawer-in-left { from { transform: translateX(-100%); } }
@keyframes drawer-in-right { from { transform: translateX(100%); } }

/* Somebody who has asked not to be moved gets the panel without the movement,
   the same as the builder's drawer above. */
@media (prefers-reduced-motion: reduce) {
  .drawer-scrim, .drawer.dleft, .drawer.dright { animation: none; }
}

.drawer-head {
  display: flex; align-items: flex-start; gap: var(--space-2);
  padding: 14px 16px;
  border-bottom: 2px solid var(--color-divider);
}

/* The close in the corner is a link here rather than the drawer's button, so it
   needs the alignment .xbtn gets from being the last thing in a flex row. */
.drawer-head a.xbtn { text-decoration: none; }

/* The only part that scrolls: the head and the foot stay put, so the name at the
   top and Edit Record at the bottom are reachable from anywhere in a long
   record. */
.drawer-body { flex: 1; overflow-y: auto; padding: 14px 16px; }

.drawer-foot {
  display: flex; justify-content: flex-end; gap: var(--space-2);
  padding: 12px 16px;
  border-top: 2px solid var(--color-divider);
}

/* One block of the record: a heading and the questions under it. Divided rather
   than carded, because a card apiece down a narrow panel is more border than
   content. */
.frsec { padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--color-divider); }
.frsec:last-child { padding-bottom: 0; margin-bottom: 0; border-bottom: 0; }

/* Wider than the panel down the left, where the same list has 110px to name
   things in. A record's questions are whole sentences — "Seeking member/employee
   assistance service" — and wrapping every one of them onto three lines to keep
   the answers in a narrow column reads worse than a wider label. */
.drawer .kv { grid-template-columns: 160px 1fr; gap: 4px 12px; }

/* The panel in its editing state wraps everything in one form, so the form is
   what has to be the column — head, the part that scrolls, and foot — rather
   than the panel itself. min-height on the body is what lets it shrink: a flex
   item will not go below the height of its content without it, and the whole
   form would scroll instead of the middle of it. */
.drawer > form { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.drawer > form > .drawer-body { min-height: 0; }

/* The element the panel goes in is drawn on every page under a client's record,
   inside the record's own two-column grid. It generates no box of its own, so
   being there costs the grid no cell — and the panel inside it is fixed, which
   takes it out of the flow anyway. */
[data-contact-host] { display: contents; }

/* While a fragment is on its way. The panel that is already there stays put and
   stops answering, which is steadier than emptying it and drawing a spinner in
   the gap. */
[data-contact-host][aria-busy="true"] .drawer { cursor: progress; }
[data-contact-host][aria-busy="true"] .drawer-foot { opacity: 0.6; pointer-events: none; }

/* ── Tab strips that can outgrow their space ──────────────────────────────
   The design's own class beside .tabs, for the strips that can be wider than
   where they sit: the views of a client or company next to the panel on their
   record, or Admin's five sections on a phone. Scrolling sideways keeps a tab
   a tab — wrapping onto a second row would put a second underline across the
   page, and the underline is what the strip is read by. */
.tabsov { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: thin; }
.tabsov .tab { flex: 0 0 auto; white-space: nowrap; }

/* A strip that scrolls clips whatever is drawn outside it, which takes the
   focus ring's outset down to a sliver on one side. Drawn inside the tab
   instead, the way the segmented controls draw theirs. */
.tabsov .tab:focus-visible { outline-offset: -2px; }

/* ── The alerts fold at the top of a client's panel ───────────────────────
   The design's accordion is a pair of divs a script opens and closes. This is
   a <details>, which needs none: it opens on click, closes again, is closed
   when a page loads, and reads to a screen reader as the disclosure it is.

   That costs the marker the browser draws on a <summary>, which the design
   replaces with its own chevron — hence both ways of taking it off, since
   list-style alone does not reach WebKit. */
summary.acch { list-style: none; }
summary.acch::-webkit-details-marker { display: none; }

/* The chevron points down closed and up open. The design says the same thing
   with a class the script adds; here the element that knows is the <details>,
   so the rule hangs off it and .chev.up is left for anything hand-drawn. */
details[open] > .acch .chev { transform: rotate(180deg); }

/* A rule of its own above the fold as well as the one below it, so the block
   reads as a band across the panel rather than as the first of the details
   under it. */
.acc.alerts { border-top: 1px solid var(--color-divider); margin-top: 10px; }

/* .doclink is the design's flat link-shaped button. As an actual link it needs
   a box for the margin it is given to have anywhere to go. */
a.doclink { display: inline-block; }

/* ── Contact card questions ───────────────────────────────────────────────
   A question answered from the referral directory: what has been named, a
   button, and a picker behind it. See _ContactCardField.

   The picker opens with no script at all. .ccftoggle is a real checkbox with
   no name, the button and the backdrop are labels pointing at it, and these
   rules do the rest — the same arrangement the multi-client tabs use, for the
   same reason: this portal has to work with scripting off, and a picker that
   needed it would be a question nobody could answer. */
.ccf { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }

/* Nothing here is a control. What is on the answer is read, and everything
   that changes it is inside the picker — one description of each card, one
   place to add and to take away. */
.ccfsum { min-height: 20px; }

/* The toggle sits between the summary and the button in the document, so it
   is .vh rather than display:none: a hidden control cannot take focus, and
   this one is the whole keyboard path into the picker. The ring is drawn on
   the button beside it instead. */
.ccftoggle:focus-visible + .ccfopen {
  outline: 2px solid var(--color-accent); outline-offset: 2px;
}
.ccftoggle:disabled + .ccfopen { opacity: 0.45; cursor: not-allowed; }

.ccfopen { align-self: flex-start; }

/* Shut until the toggle says otherwise. Both the backdrop and the panel, and
   both as siblings that follow it — which is what the ~ combinator needs and
   why the markup is in that order. */
.ccfback, .ccfpop { display: none; }

.ccftoggle:checked ~ .ccfback {
  display: block; position: fixed; inset: 0;
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);

  /* Under the panel and over everything else, the same layer .dialog-backdrop
     claims and for the same reason: a positioned element with a z-index of its
     own paints later than document order alone would put it. */
  z-index: 200;
}

.ccftoggle:checked ~ .ccfpop {
  display: flex; flex-direction: column; gap: var(--space-3);
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(520px, calc(100vw - var(--space-4) * 2));
  max-height: min(560px, calc(100vh - var(--space-4) * 2));
  box-sizing: border-box; padding: var(--space-4);
  border: 1px solid var(--color-neutral-200); border-radius: var(--radius-md);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
  z-index: 201;

  /* Left-aligned whatever the cell around it is doing. The panel is fixed to
     the window rather than laid out in the form, and a field cell inside a
     read-only grid can otherwise centre its text. */
  text-align: left;
}

.ccfpophd { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ccfpopt {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 16px;
}

/* An .ib is a button everywhere else on the page; here it is a label, and a
   label has no button's cursor of its own. */
label.ccfx { cursor: pointer; }

/* Every control in this picker is a <label> pointing at the toggle, and the whole picker sits
   inside the .field cell of the question it answers — where `.field label` claims the colour
   and size of a caption over an input. That selector is a class and an element, so it
   out-ranks .btn and .btn-primary, which are a class apiece: Done came out neutral-800 on an
   accent-purple fill and could only be read by hovering it, which is the one state
   .btn-primary:hover wins.

   Restated here at a weight that beats it, rather than fixing `.field label` itself: that
   rule is also what colours the checkbox labels of every multi-select on every form, and a
   caption is not what is wrong with it. The values are .btn's own, so the pair has to move
   with it — which is why they are named rather than left to cascade. */
.ccf label.btn { color: var(--color-text); font-size: 11.5px; }
.ccf label.btn-primary { color: var(--color-bg); }

/* Two of the parts here are not there without script — this and the pager
   under the list. See contact-card-field.js, which takes the hidden attribute
   off both. A search box that filtered nothing would look like an empty list
   rather than like nothing typed. */
.ccfsrch { max-width: none; }

/* The list scrolls, the header and the footer do not: a directory of forty
   cards should not push Done off the bottom of the window. */
.ccflist {
  flex: 1 1 auto; overflow-y: auto; min-height: 0;
  display: flex; flex-direction: column; gap: 2px;
  margin: 0 calc(var(--space-3) * -1); padding: 0 var(--space-3);
}

/* Whose cards these are, said once over the group rather than on every row. */
.ccfgrp {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--color-neutral-600);
  padding: var(--space-2) 0 4px;
}
.ccfgrp + .ccfgrp, .ccfrow + .ccfgrp { border-top: 1px solid var(--color-divider); margin-top: 4px; }

/* The divider above is adjacency in the document, and adjacency cannot see
   that the row it follows is off this page. On a page that opens in the middle
   of the shared cards it rules a hairline across the top of the list, reading
   as a stray line under the search box. The script marks the first heading
   still showing — see render() — and this takes the line back off it.

   Three classes rather than two, because .ccfrow + .ccfgrp is also two and
   would otherwise only be beaten by coming later in the file, which is true
   today and is not a thing to leave a rule depending on. */
.ccflist .ccfgrp.ccfgrp1 { border-top: 0; margin-top: 0; }

/* And the same line under a heading hidden because nothing has landed under it
   yet — the own-cards heading of a client who has none, which the partial draws
   so that the script has somewhere to file their first one. Adjacency again does
   not care that what it follows is display:none, so with no script at all the
   shared heading would rule a hairline across the top of the list. With script
   .ccfgrp1 above has already said this, and says the same two things, so the two
   rules cannot disagree at the specificity they share. */
.ccfgrp[hidden] + .ccfgrp { border-top: 0; margin-top: 0; }

/* The whole row is the label, so the name, the details and the tick are one
   thing to click at. */
.ccfrow {
  display: flex; align-items: flex-start; gap: 10px; cursor: pointer;
  padding: 7px 8px; border-radius: var(--radius-sm);
}
.ccfrow:hover { background: var(--color-neutral-100); }

/* The browser's own [hidden] rule is `display: none` at no specificity to speak of, and the
   display above out-ranks it — so a row the search had filtered out went on being drawn.
   Said here rather than by filtering with a class, because `hidden` is what the row means. */
.ccfrow[hidden], .ccfgrp[hidden] { display: none; }
.ccfrow input { margin: 2px 0 0; flex: none; }
.ccfrow:has(input:checked) { background: var(--color-accent-100); }
.ccfrow:has(input:disabled) { cursor: not-allowed; opacity: 0.55; }

.ccfrowb { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ccfrown { font-size: 13px; font-weight: 600; }
.ccfrowd { font-size: 11.5px; color: var(--color-neutral-600); overflow-wrap: anywhere; }

.ccfnone { margin: var(--space-3) 0 0; }

/* The pager under the picker's list — see contact-card-field.js, which fills
   it in and is also what reveals it. Outside .ccflist rather than in it: the
   list is the part that scrolls, and a pager that scrolled away with the rows
   it counts would be furthest from the hand exactly when it was wanted. No
   rule above it, because it belongs to the list it counts — .ccfnew's
   border-top below is already what separates the block under it — and the
   panel's own gap is the whole of the spacing.

   .pager is the class the pager under every list in the portal wears, and it
   is borrowed rather than copied because it is the same control saying the
   same sentence: a row, spread apart, wrapping when there is not room, with
   "Showing 1–25 of 105" at one end. Two of its values are wrong here, and the
   one rule below is both of them. The margin is for a pager under a table,
   where nothing has spaced it, and this sits in a flex column that has already
   put var(--space-3) above it; the column gap has a paragraph of its own
   below. Its font-size is left alone because nothing inherits it: the count
   carries .fs11 and the buttons .btn-xs.

   What is not borrowed is .pager-pages, .pager-n and .pager-size. There are no
   numbered pages here — see the note in contact-card-field.js — and nothing to
   carry a page length.

   The column gap is .pager's own narrowed. Under a table it separates three
   things that each read alone; here the first two are one sentence with a "·"
   between them, and eighteen pixels either side of a middot reads as two
   fragments rather than as one line. */
.ccfpgr { margin: 0; column-gap: 8px; }

/* The count of what is ticked is the end of the sentence the range line
   starts, so it is pulled up against it rather than left to .pager's
   space-between, which with three things on the bar would strand it in the
   middle with a leading "·" pointing at nothing. The same trick .ccfpopf below
   plays on its first button, and for the same reason: an auto margin takes the
   free space before justify-content is asked for an opinion. */
.ccfpgr > [data-cc-chosen] { margin-right: auto; }

.ccfpgn { display: flex; align-items: center; gap: 8px; }

/* Where the reader is, between the two buttons. Given a width it cannot fall
   below, so that "Page 9 of 12" becoming "Page 10 of 12" does not step Next
   sideways under the pointer mid-press. */
.ccfpgn > span { min-width: 82px; text-align: center; }

/* Both of the rules above set display, and so does .pager — all of which
   out-rank the browser's own [hidden], the same trap .ccfrow[hidden] above
   carries the note about. Without this line a picker on a page with no script
   to work it would be drawn with a pager under the list and two dead buttons
   in it, which is the one thing it must never be. It is also what puts the bar
   away at runtime in the one state that has nothing to put on it — a search
   matching nothing with nothing ticked anywhere; see render(). */
.ccfpgr[hidden], .ccfpgn[hidden] { display: none; }

.ccfpopf { display: flex; justify-content: flex-end; }

/* The New Card block inside a contact card picker. Script-only — see
   contact-card-field.js — and it sits between the scrolling list and the footer, so it
   keeps its own space rather than joining the list's scroll. */
.ccfnew {
  border-top: 1px solid var(--color-divider);
  padding-top: var(--space-3);
  max-height: 42vh; overflow-y: auto;
  margin: 0 calc(var(--space-3) * -1); padding-inline: var(--space-3);
}
.ccfnew .fgrid2 { gap: 8px 12px; }
.ccfnew .field label, .ccfnew .lbl { font-size: 11.5px; }

/* Two buttons at the foot rather than one, so the row spreads them. */
.ccfpopf { gap: 8px; }
.ccfpopf > :first-child:not(:last-child) { margin-right: auto; }

/* ── Managed list questions ───────────────────────────────────────────────
   An occupation, a union: the answer on the form, a button under it, and a
   dialog behind that holding what is on offer beside what has been taken
   from it. See _LookupField.

   The same machinery the contact card picker above runs on, and for the same
   reason: .lkftoggle is a real checkbox with no name, the button and the
   backdrop are labels pointing at it, and these rules do the rest. This
   portal has to work with scripting off, and a question that could only be
   answered with it would be a question nobody could answer. */
.lkf { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }

/* Nothing here is a control: it is the answer, drawn by the read-only half of
   the same component. .ansv carries a margin where it sits in a read-only
   grid; here it is the first line of a stack with a gap of its own. */
.lkfsum { min-height: 20px; }
.lkfsum .ansv { margin: 0; }

/* The toggle sits between the summary and the button in the document, so it is
   .vh rather than display:none: a hidden control cannot take focus, and this
   one is the whole keyboard path into the dialog. The ring is drawn on the
   button beside it instead. */
.lkftoggle:focus-visible + .lkfopen {
  outline: 2px solid var(--color-accent); outline-offset: 2px;
}
.lkftoggle:disabled + .lkfopen { opacity: 0.45; cursor: not-allowed; }

.lkfopen { align-self: flex-start; }

/* Shut until the toggle says otherwise. Both the backdrop and the panel, and
   both as siblings that follow it — which is what the ~ combinator needs and
   why the markup is in that order. */
.lkfback, .lkfpop { display: none; }

.lkftoggle:checked ~ .lkfback {
  display: block; position: fixed; inset: 0;
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);

  /* Under the panel and over everything else, the same layer .dialog-backdrop
     claims and for the same reason: a positioned element with a z-index of its
     own paints later than document order alone would put it. */
  z-index: 200;
}

/* Wider than the contact card picker, because it is two lists beside each
   other rather than one under a heading. */
.lkftoggle:checked ~ .lkfpop {
  display: flex; flex-direction: column; gap: var(--space-3);
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(760px, calc(100vw - var(--space-4) * 2));
  max-height: min(620px, calc(100vh - var(--space-4) * 2));
  box-sizing: border-box; padding: var(--space-4);
  border: 1px solid var(--color-neutral-200); border-radius: var(--radius-md);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
  z-index: 201;

  /* Left-aligned whatever the cell around it is doing. The panel is fixed to
     the window rather than laid out in the form, and a field cell inside a
     read-only grid can otherwise centre its text. */
  text-align: left;
}

.lookup-header {
  flex: none; display: flex; align-items: center; justify-content: space-between; gap: 8px;
}

/* An .ib is a button everywhere else on the page; here it is a label, and a
   label has no button's cursor of its own — and inside a .field cell it is
   also up against `.field label`, which is a class and an element and takes
   the colour off it. */
label.lkfx { cursor: pointer; }
.lkf label.ib { color: var(--color-neutral-700); }
.lkf label.ib:hover { color: var(--color-accent); }

/* Every button in this dialog is a <label>, and the whole component sits inside
   the .field cell of the question it answers — where `.field label` claims the
   colour and size of a caption over an input. Restated at a weight that beats
   it, the same way the contact card picker does and for the same reason: that
   rule is what colours every checkbox caption on every form, and a caption is
   not what is wrong with it. */
.lkf label.btn { color: var(--color-text); font-size: 11.5px; }
.lkf label.btn-primary { color: var(--color-bg); }

/* .srch is a row of a toolbar elsewhere and grows to fill one; in a column it
   would take a share of the dialog's height. Not there at all without script —
   see lookup-field.js, which takes the hidden attribute off. */
.selector-search-row { flex: none; max-width: none; }

/* Two panels, and one above the other where there is no room for two. Rows are
   given a floor of zero so the lists inside them scroll rather than pushing
   Close off the bottom. */
.selector-picker-layout {
  flex: 1 1 auto; min-height: 0;
  display: grid; grid-template-columns: 1.4fr 1fr; grid-template-rows: minmax(0, 1fr);
  gap: var(--space-3);
}

.selector-list-panel, .selector-selected-panel {
  display: flex; flex-direction: column; min-height: 0;
  border: 1px solid var(--color-neutral-200); border-radius: var(--radius-sm);
}

/* Which list is which, said once over each rather than on every row. */
.selector-panel-heading {
  flex: none;
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--color-neutral-600);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--color-divider);
}

.selector-results-list, .selector-selected-list {
  flex: 1 1 auto; overflow-y: auto; min-height: 0;
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-2);
}

/* The whole row is the box's label, so the name and the + are one thing to
   click at.

   position: relative, and it is load-bearing. The box inside is .vh, which is
   `position: absolute` — so without a positioned row it is laid out against
   the nearest positioned ancestor, and that is .lkfpop, the panel, which does
   not scroll. The list does. Every box then stayed where it was drawn while
   its row scrolled out from under it, leaving seventeen 1px targets sitting
   over whatever row had moved into that place — painted above the rows,
   escaping the list's overflow clip, and each one still a checkbox that toggles
   a union when it is clicked. Clicking the + of a row could tick a different
   union entirely, which reads as the + not working: the union you asked for
   never went on the answer.

   Clicking the row itself was always right, because a label and its box are
   bound by the markup and not by where they are. This is only about where the
   box is, and one line of it puts the box back inside the row it belongs to,
   where the list scrolls it and clips it like everything else. */
.selector-result-row {
  position: relative;
  display: flex; align-items: flex-start; gap: 10px; cursor: pointer;
  padding: 6px 8px; border-radius: var(--radius-sm);
}
.selector-result-row:hover { background: var(--color-neutral-100); }

/* The browser's own [hidden] rule is `display: none` at no specificity to speak
   of, and the display above out-ranks it — so a row the search had filtered out
   would go on being drawn. */
.selector-result-row[hidden] { display: none; }

.selector-result-row:has(input:checked) { background: var(--color-accent-100); }
.selector-result-row:has(input:disabled) { cursor: not-allowed; opacity: 0.55; }

/* The square the .vh checkbox is drawn as, and the one beside a picked entry
   that takes it off again. One shape, because they are the two ends of one
   thing. */
.lkfadd, .lkfrm {
  flex: none; display: grid; place-items: center;
  width: 20px; height: 20px; box-sizing: border-box;
  border: 1px solid var(--color-neutral-400); border-radius: var(--radius-sm);
  background: var(--color-surface);
  font-size: 12px; line-height: 1; color: var(--color-neutral-700);
}

/* + to add, ✓ once it is on the answer. Written as content rather than as two
   spans that take turns, because it is one thing in two states — and it is
   inside an aria-hidden wrapper either way: the box itself is what says ticked
   or not to anything that is listening. */
.lkfadd::after { content: "+"; }
.selector-result-row:has(input:checked) .lkfadd {
  border-color: var(--color-accent); background: var(--color-accent); color: var(--color-bg);
}
.selector-result-row:has(input:checked) .lkfadd::after { content: "\2713"; }

/* The ring the .vh box cannot draw for itself, on the square standing in for
   it. */
.selector-result-row:has(input:focus-visible) .lkfadd {
  outline: 2px solid var(--color-accent); outline-offset: 2px;
}

.selector-name { font-size: 13px; min-width: 0; overflow-wrap: anywhere; }
.selector-meta { display: block; font-size: 11px; color: var(--color-neutral-600); }

/* What has been taken off the list, one row each. Tinted the same as a ticked
   row opposite it, so the two lists read as one answer seen twice. */
.selected-selector-row {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px; border-radius: var(--radius-sm);
  background: var(--color-accent-100);
}
.selected-selector-row .selector-name { font-weight: 600; }

label.lkfrm { cursor: pointer; }
label.lkfrm:hover { border-color: var(--color-accent); color: var(--color-accent); }

/* An empty panel and a panel that has not loaded look the same, so both say
   which they are. */
.lkfnone { margin: 4px 0 0; padding: 0 8px; }

/* Close on its own at the foot. The panel already spaces its children. */
.lkfpop .dialog-actions { flex: none; margin-top: 0; }

/* One list above the other where there is not room beside. The dialog is at
   most 760px wide and a phone is not, which is as often as not the width one
   of these is read at. */
@media (max-width: 720px) {
  .selector-picker-layout {
    grid-template-columns: 1fr; grid-template-rows: minmax(0, 1.3fr) minmax(0, 1fr);
  }
}

/* A palette's picker and its Add button, stacked. Side by side they shared a narrow column
   with names long enough to need all of it — the button kept what it needed and the select
   took what was left. Its own class rather than .fx with a wrap, because this is not a row
   that happens to have wrapped: it is two lines, at every width.

   Its hidden state is with .fx's, further up: a display set here outranks the browser's
   own [hidden] rule, and the builder hides this whole block when the form asks everything
   the palette offers. */
.fbstack { display: flex; flex-direction: column; align-items: stretch; gap: 6px; }
.fbstack > .btn { align-self: flex-start; }

/* A task's details in the My Tasks list: two lines of it, then an ellipsis. A task is a
   paragraph rather than a label, and a row as tall as the longest one would push the rest of
   the list off the screen; the whole of it is on the task, a click away, and in the title.
   A minimum width so a narrow screen squeezes the other columns before this one. */
.taskd { display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; min-width: 220px; max-width: 520px; }

/* ---- Dashboard -----------------------------------------------------------
   Verbatim from the design stylesheet, which is why the two cards are not
   equal: the tasks beside them are paragraphs and the touchpoints are names,
   so the wider column goes to the one with more to say. Aligned to the top so
   the shorter card ends where its rows do rather than stretching to match. */
.wkgrid { display: grid; grid-template-columns: 1.25fr 1fr; gap: 16px; align-items: start; }

/* One column, and minmax(0, 1fr) rather than the bare 1fr: a bare fr never
   goes narrower than what is in it, and what is in these cards is a table.
   The .tablewrap inside can only scroll a table that is wider than its column,
   so a column that grows to fit the table leaves it nothing to do and takes
   the page sideways on a phone.

   The two columns above keep their bare frs on purpose. Side by side, a card
   whose table outgrows its share takes the difference from the card beside
   it, which is a better answer than a scrollbar while the other has room. */
@media (max-width: 1080px) {
  .wkgrid { grid-template-columns: minmax(0, 1fr); }
}

/* The details column inside those cards. .taskd keeps its two-line clamp, but
   not the floor it carries on the full-width My Tasks list: half a page is not
   enough to hold 220px of task and a client and a date besides. */
.wkgrid .taskd { min-width: 0; max-width: none; }

/* A chip naming something the portal does not do yet — the design has no such
   state, because in a mock-up everything works. Plainly not pressable: no
   pointer, and dashed like .pend, which is the portal's other way of saying
   "nothing here yet", so it reads as a label rather than as a tab that will
   not open. */
.chip-soon {
  cursor: default;
  border-style: dashed;
  color: var(--color-neutral-700);
}
.chip-soon:hover { border-color: var(--color-neutral-400); }

/* ---- Quick actions -------------------------------------------------------
   Also verbatim from the design, bar one line. The card is a tinted header
   band over a list, so its padding is on the band and on each row rather than
   on the card — which is what lets a row's hover run the full width of it. */
.qagrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 14px; }
.qacard { border: 1px solid var(--color-neutral-400); background: #fff; }
.qahd {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 14px;
  background: var(--color-neutral-100);
  border-bottom: 1px solid var(--color-neutral-300);
}
.qaic {
  width: 30px; height: 30px; border-radius: 50%;
  border: 1.5px solid var(--color-accent-2-600); color: var(--color-accent-2-600);
  display: flex; align-items: center; justify-content: center; flex: none;
}
.qat { font-family: var(--font-heading); font-weight: 700; font-size: 14px; }
.qalist { display: flex; flex-direction: column; padding: 6px 0; }
.qal {
  display: flex; align-items: center;
  padding: 8px 14px;
  font: inherit; font-size: 13.5px; text-align: left;
  background: none; border: 0; cursor: pointer;
  color: var(--color-accent-2-600);
}
.qal:hover { background: var(--color-neutral-100); text-decoration: underline; }

/* The one line the design did not need: these are anchors rather than its
   buttons, because each is a move to another page, and an anchor arrives
   underlined. The design underlines on hover, so resting has to be clean. */
.qal { text-decoration: none; }


/* ---- Assigned to ---------------------------------------------------------
   The table of people a response is on the desks of, above the form itself. See
   _Assignment.cshtml, which is where the reasons for the shape of it are. */

/* The ✕ column. Hard against the right edge and as narrow as its one control,
   so the three columns that carry words get the width. */
.asgnx { width: 1%; text-align: right; }

/* An .ib is a button everywhere else on the page; here it is a label wrapped
   round a checkbox, and a label has no button's cursor of its own. */
label.asgnrm { cursor: pointer; display: inline-flex; align-items: center; }

/* Crossed off and not yet saved. Only a browser with no script ever draws this:
   with script the row is taken off the table as the ✕ is ticked. Struck through
   rather than hidden, so pressing Save is plainly what finishes the job and the
   ✕ can be unticked by somebody who did not mean it. */
.table tbody tr:has([data-assign-remove]:checked) > td { text-decoration: line-through; }
.table tbody tr:has([data-assign-remove]:checked) { color: var(--color-neutral-600); }

/* The role. Held wide enough to read its longest option, so a narrow window
   squeezes the three columns of words rather than the one control in the row. */
select[data-assign-role] { min-width: 9em; }
