:root {
  --portal-navy: #102653;
  --portal-navy-deep: #0a1835;
  --portal-gold: #c69f55;
  --portal-cream: #f6f1e8;
  --portal-white: #fff;
  --portal-ink: #172033;
  --portal-muted: #5e6674;
  --portal-rule: #dcd7ce;
  --portal-danger: #a12929;
  --portal-success: #246b45;

  /* Split out so a dark theme can move a surface without moving the text
     printed on navy, and move a link without moving the sidebar. In the light
     themes each is the colour it was before. */
  --portal-surface: #fff;
  --portal-link: #102653;
}

/* Review the request and name its people before sending it. */
.request-confirmation {
  box-sizing: border-box;
  width: min(620px, calc(100vw - 32px));
  max-height: calc(100dvh - 40px);
  overflow: auto;
  padding: 28px;
  border: 1px solid var(--portal-rule);
  border-radius: 12px;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font-family: inherit;
  box-shadow: 0 20px 70px rgb(0 0 0 / 24%);
}
.request-confirmation::backdrop { background: rgb(10 24 53 / 60%); }
.request-confirmation h2 { margin: 0 0 8px; font-size: 1.35rem; }
.request-confirmation h3 { margin: 22px 0 8px; font-size: 1.1rem; overflow-wrap: anywhere; }
.request-confirmation p { line-height: 1.5; overflow-wrap: anywhere; }
.request-confirmation fieldset { margin: 20px 0; padding: 0; border: 0; }
.request-confirmation legend { margin-bottom: 8px; font-weight: 700; }
.request-confirmation-person { display: flex; align-items: center; gap: 12px; min-height: 48px; padding: 8px; border-bottom: 1px solid var(--portal-rule); cursor: pointer; }
.request-confirmation-person input { width: 20px; height: 20px; flex-shrink: 0; accent-color: var(--portal-link); }
.request-confirmation-person span { overflow-wrap: anywhere; }
.request-confirmation-preview { border-left: 3px solid var(--portal-gold); padding: 0 16px; }
.request-confirmation-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; margin-top: 20px; }
.request-confirmation :focus-visible { outline: 3px solid var(--portal-link); outline-offset: 3px; }
@media (max-width: 480px) {
  .request-confirmation { padding: 20px; }
  .request-confirmation-actions .portal-button { flex: 1 1 auto; min-height: 44px; }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body.portal-body {
  min-height: 100vh;
  margin: 0;
  background: var(--portal-cream);
  color: var(--portal-ink);
  font-family: Arial, sans-serif;
}

/* A unified reading face throughout the attorney console, including values. */
.admin-console, .admin-console :where(*) {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
}

.portal-skip-link {
  position: fixed;
  z-index: 20;
  top: 12px;
  left: 12px;
  padding: 10px 14px;
  background: var(--portal-surface);
  color: var(--portal-link);
  font-weight: 700;
  text-decoration: none;
  transform: translateY(-150%);
}

.portal-skip-link:focus { transform: translateY(0); }

/* Client login landing page */
.login-page {
  background: var(--portal-surface);
}

.login-shell {
  display: grid;
  grid-template-columns: minmax(420px, 1.05fr) minmax(480px, 0.95fr);
  min-height: 100vh;
}

.login-welcome {
  position: relative;
  isolation: isolate;
  display: flex;
  min-height: 100vh;
  padding: clamp(32px, 5vw, 68px);
  flex-direction: column;
  justify-content: space-between;
  overflow: hidden;
  background: var(--portal-navy-deep) url("../images/legal-hero.jpg") center / cover no-repeat;
  color: var(--portal-white);
}

.login-welcome::before {
  position: absolute;
  z-index: -1;
  inset: 0;
  background: linear-gradient(135deg, rgba(5, 18, 44, 0.97) 12%, rgba(10, 32, 75, 0.88) 58%, rgba(10, 24, 53, 0.65));
  content: "";
}

.login-welcome::after {
  position: absolute;
  z-index: -1;
  right: -120px;
  bottom: -210px;
  width: 520px;
  height: 520px;
  border: 1px solid rgba(198, 159, 85, 0.25);
  border-radius: 50%;
  box-shadow: 0 0 0 70px rgba(198, 159, 85, 0.04), 0 0 0 140px rgba(198, 159, 85, 0.025);
  content: "";
}

.login-brand {
  display: inline-flex;
  width: fit-content;
  align-items: center;
  gap: 14px;
  color: var(--portal-white);
  font-family: Georgia, serif;
  font-size: 1.14rem;
  line-height: 1.2;
  text-decoration: none;
}

.login-brand small {
  display: block;
  margin-bottom: 3px;
  color: #e1c489;
  font-family: Arial, sans-serif;
  font-size: 0.61rem;
  font-weight: 700;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}

.login-brand-mark {
  display: grid;
  width: 52px;
  height: 52px;
  place-items: center;
  overflow: hidden;
  border: 1px solid rgba(225, 196, 137, 0.5);
  border-radius: 50%;
  background: var(--portal-navy-deep);
}

.login-brand-mark img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.login-welcome-copy { max-width: 580px; margin: auto 0; padding: 70px 0; }
.login-welcome-copy .portal-eyebrow { color: #e1c489; }

.login-welcome-copy h1 {
  max-width: 560px;
  margin: 0 0 24px;
  font-family: Georgia, serif;
  font-size: clamp(3rem, 5.5vw, 5.4rem);
  font-weight: 400;
  letter-spacing: -0.045em;
  line-height: 0.98;
}

.login-welcome-copy p {
  max-width: 520px;
  margin: 0;
  color: rgba(255, 255, 255, 0.76);
  font-size: clamp(1rem, 1.5vw, 1.16rem);
  line-height: 1.75;
}

.login-assurance {
  display: flex;
  max-width: 520px;
  align-items: center;
  gap: 14px;
  color: rgba(255, 255, 255, 0.68);
  font-size: 0.78rem;
  line-height: 1.5;
}

.login-assurance svg { width: 32px; flex: 0 0 32px; fill: #e1c489; }
.login-assurance strong { display: block; color: var(--portal-white); font-size: 0.82rem; }

.login-panel {
  display: flex;
  min-height: 100vh;
  padding: 34px clamp(32px, 7vw, 110px) 24px;
  flex-direction: column;
  justify-content: space-between;
  background: #fbfaf8;
}

.login-panel-inner { width: min(460px, 100%); margin: auto; }

.login-back {
  display: inline-flex;
  margin-bottom: clamp(50px, 10vh, 100px);
  align-items: center;
  gap: 9px;
  color: var(--portal-muted);
  font-size: 0.8rem;
  font-weight: 700;
  text-decoration: none;
}

.login-back:hover { color: var(--portal-link); }
.login-heading { margin-bottom: 34px; }

.login-heading h2 {
  margin: 0 0 12px;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: clamp(2.3rem, 4vw, 3.2rem);
  font-weight: 400;
  letter-spacing: -0.035em;
}

.login-heading p { margin: 0; color: var(--portal-muted); line-height: 1.65; }
.login-form { gap: 20px; }
/* Named for the control rather than the page: this row is built beside every
   password box in the portal now, not just the one on login.html. */
.password-label-row { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.password-label-row label { margin-bottom: 7px; }

.password-toggle {
  margin: 0 0 7px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--portal-muted);
  font: inherit;
  font-size: 0.74rem;
  font-weight: 700;
  cursor: pointer;
}

.password-toggle:hover { color: var(--portal-link); }
.login-form .portal-field input { min-height: 54px; padding: 14px 15px; border-color: #cfcac1; }
.login-form .portal-field input::placeholder { color: #989ca4; }

.login-submit {
  width: fit-content;
  min-height: 54px;
  margin: 2px auto 0;
  justify-content: center;
  padding-inline: 20px;
  border-radius: 2px;
  background: var(--portal-navy);
  text-decoration: none;
}

.forgot-link { width: fit-content; margin: -6px auto 0; font-size: 0.78rem; text-decoration: underline; text-underline-offset: 3px; }
.login-form .portal-status { text-align: center; }

.login-help {
  margin-top: 34px;
  padding: 20px 22px;
  border-left: 2px solid var(--portal-gold);
  background: #f3efe7;
}

.login-help h3 { margin: 0 0 6px; color: var(--portal-link); font-family: Georgia, serif; font-size: 1rem; }
.login-help p { margin: 0; color: var(--portal-muted); font-size: 0.78rem; line-height: 1.55; }
.login-legal { margin: 48px auto 0; color: #8b8e94; font-size: 0.66rem; line-height: 1.5; text-align: center; }

.portal-topbar {
  background: var(--portal-surface);
  border-bottom: 1px solid var(--portal-rule);
}

.portal-topbar-inner {
  width: min(1180px, 100%);
  min-height: 76px;
  margin: 0 auto;
  padding: 12px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.portal-brand {
  min-width: 0;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.08rem;
  line-height: 1.25;
  text-decoration: none;
}

.portal-brand small {
  display: block;
  color: var(--portal-gold);
  font-family: Arial, sans-serif;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.portal-nav {
  display: flex;
  align-items: center;
  gap: 16px;
}

.portal-nav a,
.portal-link-button {
  border: 0;
  background: transparent;
  color: var(--portal-link);
  font: inherit;
  font-size: 0.84rem;
  font-weight: 700;
  /* Underlined, because colour alone is not an affordance. These sit in a row
     beside plain text at the same weight, and for anybody who cannot separate
     the two hues — or is reading on a washed-out screen in daylight — the
     underline is the only thing saying "this does something".

     Offset and thinned so a row of them does not turn into a row of stripes. */
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
}

.portal-link-button:hover {
  text-decoration-thickness: 2px;
}

/*
  These are the console's most repeated control — every dialog's Close, the
  pager's Previous and Next, "View record", "Dismiss", "Skip for now" on MFA
  enrolment — and unstyled they are a bare 16px line box.

  Asked as a question about the input device rather than the viewport on
  purpose: the same button is just as hard to hit on a 1400px touchscreen, and
  a mouse pointer does not want the extra 28px of chrome. hover: none excludes
  the desktop mouse, so nothing there moves.
*/
@media (hover: none) and (pointer: coarse) {
  .portal-nav a,
  .portal-link-button {
    display: inline-flex;
    min-height: 44px;
    padding-inline: 8px;
    align-items: center;
  }
}

.portal-main {
  width: min(1180px, 100%);
  margin: 0 auto;
  padding: 54px 24px 80px;
}

.portal-main-narrow {
  width: min(560px, 100%);
}

.portal-page-heading {
  margin-bottom: 30px;
}

.portal-eyebrow {
  display: block;
  margin-bottom: 9px;
  color: var(--portal-gold);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.portal-page-heading h1,
.portal-card h2,
.portal-card h3 {
  margin-top: 0;
  color: var(--portal-link);
  font-family: Georgia, serif;
}

.portal-page-heading h1 {
  margin-bottom: 12px;
  font-size: clamp(2rem, 5vw, 3rem);
}

.portal-page-heading p {
  max-width: 720px;
  margin: 0;
  color: var(--portal-muted);
  line-height: 1.65;
}

.portal-page-heading .portal-button {
  margin-top: 18px;
}

.portal-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 22px;
}

.portal-card {
  grid-column: span 6;
  padding: 28px;
  border: 1px solid var(--portal-rule);
  border-radius: 6px;
  background: var(--portal-surface);
  box-shadow: 0 9px 26px rgba(16, 38, 83, 0.05);
}

.portal-card-full {
  grid-column: 1 / -1;
}

.portal-card h2 {
  margin-bottom: 20px;
  font-size: 1.35rem;
}

.portal-card h3 {
  margin-bottom: 8px;
  font-size: 1.08rem;
}

.portal-form {
  display: grid;
  gap: 18px;
}

/* A grid child stretches to the full column, so a link button dropped straight
   into a form (debug.html's Refresh and Clear) became a full-width strip that
   was still only one line tall — a hit box the width of the page and none of
   the height. Shrink it back to its text and the touch rule above can give it
   a real box. */
.portal-form > .portal-link-button { justify-self: start; }

.portal-form-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.portal-field label {
  display: block;
  margin-bottom: 7px;
  color: var(--portal-link);
  font-size: 0.8rem;
  font-weight: 700;
}

.portal-field input,
.portal-field select,
.portal-field textarea {
  width: 100%;
  min-height: 46px;
  padding: 11px 13px;
  border: 1px solid var(--portal-rule);
  border-radius: 4px;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font: inherit;
}

.portal-field textarea {
  min-height: 116px;
  resize: vertical;
}

.portal-field input:focus,
.portal-field select:focus,
.portal-field textarea:focus {
  border-color: var(--portal-gold);
  outline: 3px solid rgba(198, 159, 85, 0.16);
}

/* One card that can send two different things, and the choice between them.
   Each option is a whole clickable block rather than a bare radio, so the
   description is part of the target instead of something to read and then aim
   past. */
.portal-choice {
  margin: 0;
  padding: 0;
  border: 0;
}

.portal-choice legend {
  padding: 0;
  margin-bottom: 7px;
  color: var(--portal-link);
  font-size: 0.8rem;
  font-weight: 700;
}

.portal-choice-option {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 11px 13px;
  border: 1px solid var(--portal-rule);
  border-radius: 4px;
  background: var(--portal-surface);
  cursor: pointer;
}

.portal-choice-option + .portal-choice-option {
  margin-top: 8px;
}

.portal-choice-option input {
  flex: none;
  margin-top: 3px;
  accent-color: var(--portal-navy);
}

.portal-choice-option span {
  display: block;
  color: var(--portal-muted);
  font-size: 0.82rem;
  line-height: 1.45;
}

.portal-choice-option strong {
  display: block;
  margin-bottom: 2px;
  color: var(--portal-ink);
  font-size: 0.9rem;
}

.portal-choice-option:has(input:checked) {
  border-color: var(--portal-navy);
  box-shadow: inset 0 0 0 1px var(--portal-navy);
}

.portal-choice-option:focus-within {
  outline: 3px solid rgba(198, 159, 85, 0.16);
}

/* Sending lives in a dialog now, behind the button in the card header. */
.dashboard-card-header-actions {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-left: auto;
}

.portal-button.portal-button-small {
  min-height: 36px;
  padding: 8px 15px;
  font-size: 0.78rem;
}

/*
  The small variant exists for dense console tables read with a mouse, but the
  "Send this" file pickers on the client's documents list wear it too — and on
  a phone they are the whole answer to "send us this", pressed with a thumb
  while holding the passport being photographed. Only the pickers are labels;
  the console's small buttons stay small on a touch console, where they sit
  ten to a row and the row is the thing being read.
*/
@media (hover: none) and (pointer: coarse) {
  label.portal-button.portal-button-small {
    min-height: 44px;
    padding: 11px 19px;
  }
}

.portal-modal .portal-form { gap: 14px; }
.portal-modal-footer { justify-content: flex-end; margin-top: 4px; }
.portal-modal .admin-send-fields { grid-template-columns: 1fr; }
.portal-modal .admin-send-go .portal-button { width: 100%; }

.portal-choice-inline { display: flex; gap: 8px; flex-wrap: wrap; }

.portal-choice-inline .portal-choice-option {
  padding: 8px 13px;
  align-items: center;
}

.portal-choice-inline .portal-choice-option strong {
  margin-bottom: 0;
  font-size: 0.84rem;
}

.admin-send-fields {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  align-items: end;
}

/* The button sits on the baseline of the inputs beside it, not under its own
   invisible label. */
.admin-send-go { display: flex; align-items: flex-end; }
.admin-send-go .portal-button { width: 100%; justify-content: center; min-height: 46px; }
.admin-send .dashboard-file-note { margin: 2px 0 0; }
.admin-send .portal-status:empty { display: none; }

@media (max-width: 720px) {
  .admin-send-fields { grid-template-columns: 1fr; }
}

/* The five stages, as a strip inside the directory card.
   Underlined rather than boxed: these sit inside a card that already has a
   border, and a second box around them reads as a panel within a panel. */
.portal-subtabs-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--portal-rule);
}

.portal-subtabs {
  display: flex;
  gap: 2px;
  flex-wrap: wrap;
}

/*
  A tab held at the far end of the strip.

  The matter's Log sits there: the other three tabs are the matter as it
  stands and belong together, and the record of what has already happened is
  a different kind of question. The gap says so without a divider.

  It needs the strip to fill the row before margin-left can push anything —
  .portal-subtabs-row is space-between over a single child, which sizes the
  strip to its tabs and leaves nothing to push into. Only this strip grows,
  because it is the only one with a tab to hold apart; the client's strips
  stay content-width. Should the tabs ever wrap, the held tab right-aligns on
  its own line, which still reads as set apart.
*/
#case-tabs { flex: 1; }
#case-tabs .portal-subtab.is-far { margin-left: auto; }

.portal-subtabs-row .dashboard-card-header-actions { margin: 0 0 8px; }

/* ── The client case page ─────────────────────────────────────────────────── */

/*
  A section opens with a line rather than a heading.

  My case, Documents and Your forms were three pages with three h1s. Folded into
  one page they are three sections of it, and the strip already names them - a
  second title under the tab that says the same word is a page shouting its own
  name twice.
*/
/* ── The caret on a card that folds ───────────────────────────── */

/*
  Shared by the two panels that fold a person open: the Overview’s people and
  the Documents cabinet. It was named for the checklist it was drawn for, which
  no longer exists — the shape is the same and the name is not.
*/
.case-fold-caret {
  width: 0;
  height: 0;
  border-top: 5px solid currentColor;
  border-right: 4px solid transparent;
  border-left: 4px solid transparent;
  transform: translateY(-2px);
  transition: transform 0.14s ease;
}

@media (prefers-reduced-motion: reduce) {
  .case-fold-caret { transition: none; }
}

/* ── A matter's documents, filed ──────────────────────────────────────────── */

/* Two controls in the matter header rather than one.

   `flex: none` is `0 0 auto`, which sized this block to max-content and made
   it the one thing in the header that could not give way — the sibling text
   block already carries `min-width: 0` for exactly this reason. `0 1 auto`
   keeps the same natural width and lets it shrink when there is nothing left
   to take from the title. */
.case-detail-head-actions {
  display: flex;
  flex: 0 1 auto;
  flex-wrap: wrap;
  gap: 8px;
}

/* ── What needs the office today ──────────────────────────────────────────── */

/*
  A short list of counts, each one a way in.

  Rows rather than tiles: this is read top to bottom in two seconds and then
  ignored for the rest of the day, and a grid of boxes asks to be looked at
  properly. Nothing here is a chart.
*/
/*
  What needs the office, hung off the topbar.

  It was a panel at the top of the Clients tab. Here it is on every page, which
  is the whole reason it moved — and it is a menu rather than a card, so it
  costs nothing until somebody looks at it.
*/
.dashboard-alerts { position: relative; display: inline-flex; }
.dashboard-alerts[hidden] { display: none; }

/* The count rides on the corner of the button, the way a badge does. */
.dashboard-alerts-button { position: relative; overflow: visible; }

/* Nudged down a hair: the clapper hangs below the body, so the bell's optical
   centre sits above its bounding box. */
.dashboard-alerts-bell { display: block; margin-top: 1px; }

.dashboard-alerts-count {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 17px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--portal-danger);
  color: var(--portal-white);
  font-size: 0.62rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}

/*
  Opened by a press, and by nothing else.

  It opened on hover first, which meant it opened on the way past — crossing the
  topbar to reach the refresh button put a panel over the page. A press is a
  decision, it works the same on a phone and from a keyboard, and it stays open
  while it is read.

  Hidden with visibility rather than display, so the transition has something to
  animate and so the links inside are out of the tab order while it is shut.
*/
.dashboard-alerts-menu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 9;
  width: min(320px, calc(100vw - 32px));
  padding: 14px 16px 12px;
  border: 1px solid var(--portal-rule);
  border-left: 3px solid var(--portal-gold);
  border-radius: 4px;
  background: var(--portal-surface);
  box-shadow: 0 14px 34px rgba(10, 14, 22, 0.18);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.12s ease, transform 0.12s ease, visibility 0s linear 0.12s;
}

.dashboard-alerts.is-open .dashboard-alerts-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

.dashboard-alerts-menu .today-list { margin-top: 8px; }

@media (prefers-reduced-motion: reduce) {
  .dashboard-alerts-menu { transition: none; }
}

/*
  On a small phone it has to hang off the screen, not off the bell.

  right: 0 anchors it to the bell, and the bell sits behind a 44px avatar and a
  gap inside the bar's own 18px gutter — so a 288px panel's left edge computes
  to roughly -38px at 320px, and the count, which is the first thing in every
  row, is off the left of the screen with nothing in the chain able to scroll to
  it. Pinning to the bar instead: with .dashboard-alerts back to static the
  containing block becomes .dashboard-topbar, which is already sticky and so
  already positioned, and left/right of 0 land on the bar's gutter. top stays
  calc(100% + 10px) — measured against the bar now, which is the offset the
  whole shell is built on.

  Deliberately not position: fixed. The topbar carries a backdrop-filter, which
  makes it the containing block for fixed descendants as well, so fixed would
  measure from the bar in supporting browsers and from the viewport in the rest
  — two layouts for one rule. z-index is left alone for the same reason the
  offsets work: the topbar is a stacking context at z-index 8, so anything
  inside it already paints over the 7 of the console tab strip.

  479px because it is on the ladder already, and pinning does no harm above the
  ~389px where the panel starts falling off.
*/
@media (max-width: 479px) {
  .dashboard-alerts { position: static; }

  .dashboard-alerts-menu {
    right: 0;
    left: 0;
    width: auto;
  }
}

/*
  The panel hangs from a sticky bar, so it is pinned ~82px from the top of the
  viewport and the page cannot scroll it — a busy week on a 568px phone put
  its last rows past the bottom with no way to reach them. The panel scrolls
  itself instead: capped at what actually fits under the bar, with a margin so
  its edge reads as an edge and not as a clipped end.
*/
@media (max-width: 900px) {
  .dashboard-alerts-menu {
    max-height: calc(100dvh - 106px);
    overflow-y: auto;
  }
}

.today-list {
  display: flex;
  margin: 10px 0 0;
  padding: 0;
  flex-direction: column;
  gap: 2px;
  list-style: none;
}

.today-row { margin: 0; }

.today-go {
  display: flex;
  padding: 6px 8px;
  align-items: baseline;
  gap: 12px;
  border-radius: 3px;
  color: var(--portal-ink);
  text-decoration: none;
}

.today-go:hover { background: var(--portal-cream); }

/* Lining figures and a fixed width, so a column of counts is a column rather
   than a ragged edge that moves as the numbers change. */
.today-count {
  min-width: 26px;
  color: var(--portal-link);
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  text-align: right;
}

.today-what { font-size: 0.86rem; }

/* Late is red on the number alone. The whole row in red would make a morning
   look like an emergency every time one document is a week old. */
.today-row.is-late .today-count { color: var(--portal-danger); }

/*
  Where the client's matter is on its route.

  It sits between what is happening and when it was last touched, because it
  answers the question the next date is only a footnote to: is this normal, and
  how much is left. The rule above it separates the office's own words from the
  sequence, which is published and the same for everybody on that road.

  The note underneath is small on purpose. "Steps vary by category and by field
  office" is a caveat, and a caveat set at the size of the fact it qualifies
  reads as a warning.
*/
/*
  What the form itself says, under the question as translated.

  Small and muted: it is a reference for somebody holding the paper form, not a
  second question. Left in the language it was printed in, always, because that
  is the whole point of showing it.
*/
.client-question-original {
  margin: 2px 0 6px;
  color: var(--portal-muted);
  font-size: 0.72rem;
  line-height: 1.45;
}

.client-case-route {
  margin: 12px 0 0;
  padding: 11px 0 0;
  border-top: 1px solid var(--portal-rule);
}

.client-case-route-step {
  margin: 0;
  color: var(--portal-ink);
  font-size: 0.92rem;
  line-height: 1.5;
}

.client-case-route-step strong {
  margin-right: 6px;
  color: var(--portal-link);
}

.client-case-route-note { margin: 5px 0 0; font-size: 0.72rem; }

/* Finished is a sentence rather than a position, so it is one element and
   carries the muted colour itself. */
.client-case-route.is-finished {
  margin: 12px 0 0;
  color: var(--portal-muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.dashboard-panel-intro {
  max-width: 720px;
  margin: 0 0 22px;
  color: var(--portal-muted);
  line-height: 1.65;
}

/* The panel takes focus when its tab is pressed, so the keyboard lands inside
   the section rather than back at the top of the document. It should not draw a
   ring around a whole panel to say so. */
.client-case-panel:focus { outline: none; }

.dashboard-main > .portal-subtabs-row { margin-bottom: 22px; }

@media (max-width: 900px) {
  /*
    Below 900px the sidebar is gone and this strip is the only way between
    sections, so it must not scroll away up a list of forty documents.

    72px is .dashboard-topbar min-height, which is what it sticks under. z-index
    6 puts it below the mobile nav at 9 and the topbar at 8, so an open menu
    still covers it.
  */
  .dashboard-main > .portal-subtabs-row {
    position: sticky;
    z-index: 6;
    top: 72px;
    background: var(--portal-surface);
  }
}

/* Blank rows padding the table out to ten. They exist to hold the card's
   height, so they must match a real row exactly and say nothing. */
.portal-row-blank td {
  border-color: transparent;
  color: transparent;
  user-select: none;
}

.portal-pager {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 16px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--portal-rule);
}

.portal-pager .portal-meta { margin: 0; }
.portal-pager .portal-link-button:disabled { opacity: 0.35; cursor: not-allowed; }

.portal-subtab {
  /* Padding grows with the type. Enlarging the label alone would leave the
     underline hugging it and the row feeling cramped, and these are click
     targets as well as labels. */
  padding: 11px 15px;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  background: transparent;
  color: var(--portal-muted);
  font: inherit;
  font-size: 0.98rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.portal-subtab:hover { color: var(--portal-link); }

.portal-subtab.is-active {
  border-bottom-color: var(--portal-gold);
  color: var(--portal-link);
}

.portal-subtab-count {
  display: inline-block;
  min-width: 20px;
  margin-left: 7px;
  padding: 1px 7px;
  border-radius: 10px;
  background: var(--portal-rule);
  color: var(--portal-ink);
  font-size: 0.78rem;
  text-align: center;
}

.portal-subtab-count:empty { display: none; }
.portal-subtab.is-active .portal-subtab-count { background: var(--portal-gold); color: #fff; }

.portal-edit-bar select {
  min-height: 38px;
  padding: 8px 11px;
  border: 1px solid var(--portal-rule);
  border-radius: 4px;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font: inherit;
  font-size: 0.84rem;
}

.portal-edit-bar select:disabled { opacity: 0.5; cursor: not-allowed; }

/* Removing people.
   The checkbox column is in the markup on every row and collapsed until Edit is
   pressed, so turning edit mode on does not reflow the table into a different
   shape — the columns keep the widths the eye already learned. */
/* Always on screen. There is no edit mode to enter: ticking somebody is what
   brings the actions up, so the checkbox has to be there to be ticked. */
.portal-select-cell { width: 1%; padding-right: 0; }
.portal-select-cell input { width: 17px; height: 17px; accent-color: var(--portal-navy); cursor: pointer; }
.portal-table tbody tr:not(.portal-row-blank):hover { background: #fbf8f3; }


/* One band between the tabs and the table, holding either the note about this
   stage or the actions for a selection. They occupy the same grid cell, so the
   band is always as tall as the bar and the table never moves — not when a box
   is ticked, not when the last one is cleared.

   Easing the old growth would not have been enough: the machine this was
   reported on asks for reduced motion, so it would have kept the jump and lost
   only the animation. The fix has to be no movement, not slower movement.

   The bar keeps `visibility: hidden` rather than `display: none` when closed:
   an invisible box still reserves its height, which is the whole trick, and
   `inert` is what actually takes it out of focus order. */
.portal-edit-bar-slot {
  display: grid;
  /* The note starts the band, directly under the tab rule, because that is
     where the eye goes after the tabs. The band still reserves the bar's
     height so nothing moves when a box is ticked — which is why the bar below
     is a slim strip rather than a padded panel: every pixel it does not need
     is a pixel of blank space under this line. */
  align-items: stretch;
  margin-bottom: 10px;
}

.portal-edit-bar-slot > * { grid-area: 1 / 1; }

.portal-edit-bar-slot > .portal-edit-bar {
  margin: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 160ms ease, visibility 160ms;
}

.portal-edit-bar-slot.is-open > .portal-edit-bar { opacity: 1; visibility: visible; }

/* The note fills the band rather than floating in the top of it.
   The band is as tall as the action bar so the table cannot move, and a single
   line of text sitting in it left an obvious hole. Given the same footprint as
   the bar it replaces, the height reads as a strip that belongs there — the
   same space, doing a job. */
.portal-edit-bar-slot > .dashboard-file-note {
  display: flex;
  align-items: center;
  height: 100%;
  margin: 0;
  padding: 5px 12px;
  border-left: 2px solid var(--portal-gold);
  border-radius: 0 4px 4px 0;
  background: #faf8f4;
  transition: opacity 160ms ease;
}

.portal-edit-bar-slot.is-open > .dashboard-file-note { opacity: 0; visibility: hidden; }

@media (prefers-reduced-motion: reduce) {
  .portal-edit-bar-slot > .portal-edit-bar,
  .portal-edit-bar-slot > .dashboard-file-note,
  .portal-table tbody tr { transition: none; }
}

/* The batch action bar.
   Dark on purpose: selecting people is a mode, and a bar the same colour as
   the card reads as one more row of controls that were always there. Actions
   left, count and the way out right — the arrangement Carbon, Polaris and Gmail
   all converged on, so it is where the hand already expects them. */
.portal-edit-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px 20px;
  flex-wrap: wrap;
  margin: 0;
  padding: 5px 6px 5px 12px;
  border-radius: 4px;
  background: var(--portal-navy);
  color: var(--portal-white);
}

/* The bar wraps; the groups inside it did not. A <select> that will not shrink
   below "Former clients" plus Save plus Delete is one unbreakable ~308px run,
   so on a phone Delete finished outside the navy and off the edge of the card.
   Same wrap as the parent, for the same reason. */
.portal-edit-bar-actions,
.portal-edit-bar-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.portal-edit-bar-meta { margin-left: auto; }

.portal-edit-bar [data-edit-count] {
  margin: 0;
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
}

/* Compact on purpose. This bar's height is reserved whether it is showing or
   not, so anything it does not need becomes blank space under the note. */
.portal-edit-bar select {
  min-height: 30px;
  padding: 4px 9px;
  border-color: rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.08);
  color: var(--portal-white);
  font-size: 0.8rem;
}

.portal-edit-bar select option { color: var(--portal-ink); }

.portal-edit-bar .portal-button { min-height: 30px; padding: 5px 14px; font-size: 0.76rem; }

/* Save is the ordinary commit on this bar, so it reads as the light one against
   the navy — Remove keeps red to itself. */
.portal-button.portal-button-apply {
  border-color: var(--portal-gold);
  background: var(--portal-gold);
  color: var(--portal-navy-deep);
}

.portal-button.portal-button-apply:hover:not(:disabled) { background: #b48f47; border-color: #b48f47; }

.portal-button.portal-button-apply:disabled {
  border-color: rgba(255, 255, 255, 0.25);
  background: transparent;
  color: rgba(255, 255, 255, 0.4);
  cursor: not-allowed;
}

/* A quiet way out, not a third thing competing with Move and Remove. */
.portal-edit-bar-cancel {
  padding: 6px 4px;
  border: 0;
  background: transparent;
  color: rgba(255, 255, 255, 0.78);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.portal-edit-bar-cancel:hover { color: var(--portal-white); }

/* Wrapping alone is not enough on a phone: the group is placed on its own line
   but is still measured against ~238px of bar at 320px. The whole line goes to
   the group, the stage picker takes a row of its own — it is the thing that is
   chosen first — and Save and Delete split the row under it. */
@media (max-width: 640px) {
  .portal-edit-bar-actions { width: 100%; }
  .portal-edit-bar-actions select { flex: 1 1 100%; }
  .portal-edit-bar-actions .portal-button { flex: 1 1 auto; }
}

/* Which rows the bar is talking about. Faded in rather than switched, so a row
   settling into place reads as the same row rather than a new one. */
.portal-table tbody tr { transition: background-color 180ms ease; }
.portal-table tbody tr.is-selected { background: #eef2fa; }
.portal-table tbody tr.is-selected td { border-color: #dbe3f2; }

/* While a selection is live, the per-row links are stood down: they act on one
   person and the bar acts on several, and two live targets at once is how the
   wrong one gets pressed. */
.dashboard-card.is-selecting .portal-table-actions button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  text-decoration: none;
}

/* Both classes, not one. This rule sits above `.portal-button` in the file, and
   with equal specificity the later rule would win — a delete button that looks
   exactly like the safe navy one it sits beside. */
.portal-button.portal-button-danger {
  border-color: var(--portal-danger);
  background: var(--portal-danger);
  color: var(--portal-white);
}

.portal-button.portal-button-danger:hover { background: #8c2222; border-color: #8c2222; }
.portal-button.portal-button-danger:disabled { opacity: 0.45; cursor: not-allowed; }

.dashboard-card-header > .portal-link-button { margin-left: auto; align-self: flex-start; }

/* The confirmation before a deletion that cannot be undone. Centred and modal,
   because it is the one thing on the page worth stopping for. */
.portal-modal-backdrop {
  position: fixed;
  z-index: 30;
  inset: 0;
  background: rgba(10, 24, 53, 0.55);
}

.portal-modal {
  position: fixed;
  z-index: 31;
  top: 50%;
  left: 50%;
  width: min(520px, calc(100vw - 32px));
  max-height: min(86vh, 720px);
  padding: 26px 28px;
  overflow-y: auto;
  transform: translate(-50%, -50%);
  border-top: 3px solid var(--portal-gold);
  border-radius: 6px;
  background: var(--portal-surface);
  box-shadow: 0 24px 60px rgba(10, 24, 53, 0.3);
}

/* Red is reserved for the one that deletes. An ordinary dialog wearing it
   teaches people to read the colour as decoration. */
.portal-modal-danger { border-top-color: var(--portal-danger); }

.portal-modal h2 {
  margin: 0 0 12px;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.4rem;
  font-weight: 400;
}

.portal-modal-list,
.portal-modal-names { margin: 0 0 14px; padding-left: 20px; }
.portal-modal-list li { color: var(--portal-muted); font-size: 0.84rem; line-height: 1.6; }

.portal-modal-names {
  padding: 12px 12px 12px 30px;
  border-left: 2px solid var(--portal-danger);
  background: #fdf6f6;
  list-style: disc;
}

.portal-modal-names li { color: var(--portal-ink); font-size: 0.88rem; line-height: 1.7; }
.portal-modal .portal-table-actions { justify-content: flex-end; margin-top: 18px; }

/* Consultation intake — the form portal/pages/intake.js builds.

   These classes went out with no rules behind them at all, so forty questions
   rendered as one long unstyled column and the checkbox groups were bare boxes
   with a ragged left edge. Two columns for the short answers; anything that
   needs the room takes the whole width rather than half of it. */
.portal-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px 20px;
}

/* A row of digit boxes takes the whole width too. Twelve boxes and their
   letter measure about 310px at the desktop size, which does not fit half of a
   two-column card — and a row of twelve that wraps into eight-and-four stops
   looking like one number. */
.portal-form-grid > .portal-field:has(textarea),
.portal-form-grid > .portal-field:has(.portal-checkboxes),
.portal-form-grid > .portal-field:has(.client-digits) {
  grid-column: 1 / -1;
}

.portal-required { color: var(--portal-gold); }

.portal-field-help {
  margin: 6px 0 0;
  color: var(--portal-muted);
  font-size: 0.76rem;
  line-height: 1.5;
}

/* The checkbox questions.

   Each option is a bordered tile on a grid, the same treatment the radio
   options get above: the label is part of the target instead of something to
   read and then aim past, and the tiles line up in columns instead of drifting.
   Three across puts the six family-member options on two even rows. */
.portal-checkboxes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

/* Written as label.portal-checkbox, not .portal-checkbox: the tile IS a label,
   so it is also matched by `.portal-field label` above — which would otherwise
   win on specificity and make every tile a bold navy block on its own line. */
label.portal-checkbox {
  display: flex;
  margin: 0;
  padding: 11px 13px;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--portal-rule);
  border-radius: 4px;
  background: var(--portal-surface);
  cursor: pointer;
}

/* Overrides .portal-field input, which is written for text boxes and would
   otherwise give each checkbox a 46px height and 13px of padding. */
.portal-checkbox input {
  width: 17px;
  height: 17px;
  min-height: 0;
  margin: 0;
  padding: 0;
  flex: 0 0 17px;
  border: 0;
  border-radius: 0;
  accent-color: var(--portal-navy);
  cursor: pointer;
}

/* The option's own text. Ordinary reading weight — the bold navy treatment
   belongs to the question heading above the group, not to six answers. */
.portal-checkbox span {
  color: var(--portal-ink);
  font-size: 0.82rem;
  font-weight: 400;
  line-height: 1.35;
}

.portal-checkbox:hover { border-color: var(--portal-navy); }

.portal-checkbox:has(input:checked) {
  border-color: var(--portal-navy);
  box-shadow: inset 0 0 0 1px var(--portal-navy);
}

.portal-checkbox:focus-within { outline: 3px solid rgba(198, 159, 85, 0.16); }

/* Steps down at 1120 with the rest of the dashboard, while the sidebar is
   still taking its width — three tiles measure 226px there, which holds the
   longest option ("Adjust status inside the U.S.") on one line but leaves
   nothing spare for a longer question later. */
@media (max-width: 1120px) {
  .portal-checkboxes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  .portal-form-grid { grid-template-columns: 1fr; gap: 16px; }
  .portal-checkboxes { grid-template-columns: 1fr; }
}

.portal-button {
  display: inline-flex;
  width: fit-content;
  min-height: 44px;
  padding: 11px 19px;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--portal-navy);
  border-radius: 3px;
  background: var(--portal-navy);
  color: var(--portal-white);
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Links styled as buttons should not carry a link underline. */
  text-decoration: none;
  cursor: pointer;
}

.portal-button:hover {
  background: var(--portal-navy-deep);
}

.portal-button-secondary {
  border-color: var(--portal-rule);
  background: var(--portal-surface);
  color: var(--portal-link);
}

/*
  A secondary button keeps its own colours under the pointer.

  .portal-button:hover paints navy-deep, which is right for the filled button
  and wrong for this one: the label is navy and stays navy, so the words
  vanish into the background the moment the pointer arrives. Every secondary
  button in the console had it — "Show protected identifiers" is simply where
  it was noticed, being a control somebody has to read before pressing.

  Dusk states its own secondary colours further down and is more specific than
  either rule, so it never took the dark hover and does not take this.
*/
.portal-button-secondary:hover {
  border-color: var(--portal-navy);
  background: var(--portal-cream);
  color: var(--portal-link);
}

.portal-button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* ── The decision, in two colours ─────────────────────────────────────────────

   Approve and Decline used to be a navy button beside a plain text link, which
   made them look like an action and a footnote rather than two answers to the
   same question. They are a pair, so they are built as a pair: same size, same
   shape, side by side, telling each other apart by colour.

   Deeper than the usual green and red on purpose. This is a law office, the
   surrounding page is navy and cream, and a bright pair of buttons would look
   borrowed from somewhere else. These are dark enough to sit in that company
   and still clear enough at a glance — both carry white text well past the 4.5:1
   contrast a small uppercase label needs.

   Both are also under 44px only in their small form, matching the other row
   controls; the row is a dense table, not a landing page. */
.portal-button.portal-button-approve {
  border-color: #1f6b3f;
  background: #1f6b3f;
  color: var(--portal-white);
}

.portal-button.portal-button-approve:hover:not(:disabled) {
  border-color: #185531;
  background: #185531;
}

.portal-button.portal-button-refuse {
  border-color: #a03030;
  background: #a03030;
  color: var(--portal-white);
}

.portal-button.portal-button-refuse:hover:not(:disabled) {
  border-color: #862626;
  background: #862626;
}

/* Chasing an invitation is not deciding again — the decision was made and is
   waiting on them. Outlined rather than filled: recognisably the same control,
   visibly not the same weight, so a second press does not read as a second
   approval. */
.portal-button.portal-button-chase {
  border-color: #b7c7bb;
  background: var(--portal-surface);
  color: #1f6b3f;
}

.portal-button.portal-button-chase:hover:not(:disabled) {
  border-color: #1f6b3f;
  background: #f2f7f3;
}

.portal-status {
  min-height: 1.4em;
  margin: 0;
  color: var(--portal-muted);
  font-size: 0.86rem;
  line-height: 1.5;
}

.portal-status[data-state="error"] {
  color: var(--portal-danger);
}

.portal-status[data-state="success"] {
  color: var(--portal-success);
}

.portal-alert {
  margin-bottom: 24px;
  padding: 16px 18px;
  border-left: 3px solid var(--portal-gold);
  background: var(--portal-surface);
  color: var(--portal-muted);
  line-height: 1.6;
}

/* A load failure, not a preview notice: a red edge so it reads as something
   wrong rather than something informational. The strong lead carries the
   office's link colour so the sentence has a clear start. */
.portal-alert-error { border-left-color: #a94848; }
.portal-alert-error strong { color: var(--portal-link); }

.portal-list {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.portal-list-item {
  padding: 16px;
  border: 1px solid var(--portal-rule);
  border-radius: 4px;
  /* The token, not #fff. This is a card, and cards follow the theme — hard
     coded, it stayed white inside the dark client drawer with pale text on it,
     which is the one combination that cannot be read at all. */
  background: var(--portal-surface);
}

.portal-list-item p {
  margin: 6px 0 0;
  color: var(--portal-muted);
  line-height: 1.55;
}

/* ============================================================
   A card that is itself the button.

   The matter's cabinet has no controls on its rows: opening the thing was all
   but one of them and the rest belong to what opens. The padding moves off the
   card and onto the button inside it, so the press covers the whole card
   rather than a word in the middle of it.

   A real <button> rather than a handler on the <li>: keyboard reachable, and
   it announces what it opens. Everything inside is inert to pointer events so
   a stray <h3> cannot swallow the click.
   ============================================================ */
.portal-list-item.is-openable {
  padding: 0;
  transition: border-color 120ms ease, background 120ms ease;
}

.portal-card-open {
  display: block;
  width: 100%;
  padding: 16px;
  border: 0;
  border-radius: inherit;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.portal-card-open > * {
  pointer-events: none;
}

.portal-list-item.is-openable:hover {
  border-color: var(--portal-navy);
}

/*
  The ring belongs to the card, which is the shape somebody sees — but only
  when the keyboard put it there. :focus-within would leave one behind after
  every mouse press, because clicking the button focuses it.
*/
.portal-card-open:focus-visible {
  outline: none;
}

.portal-list-item.is-openable:has(.portal-card-open:focus-visible) {
  outline: 2px solid var(--portal-navy);
  outline-offset: 2px;
  border-color: var(--portal-navy);
}

.portal-card-open:disabled {
  cursor: progress;
  opacity: 0.6;
}

.portal-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  color: var(--portal-muted);
  font-size: 0.78rem;
}

.portal-badge {
  display: inline-block;
  margin-top: 8px;
  padding: 5px 9px;
  border-radius: 999px;
  background: var(--portal-cream);
  color: var(--portal-link);
  font-size: 0.72rem;
  font-weight: 700;
}

.portal-empty {
  color: var(--portal-muted);
  font-style: italic;
}

.portal-table-wrap {
  overflow-x: auto;
}

.portal-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}

.portal-table th,
.portal-table td {
  padding: 12px;
  border-bottom: 1px solid var(--portal-rule);
  text-align: left;
  vertical-align: top;
}

/* Column labels, not a first row of data.
   They were the same size and weight as the names under them, so the header
   read as another record. The eyebrow treatment already used elsewhere in this
   console — small, spaced, uppercase — says "this is what the column is"
   without competing with the content. */
.portal-table th {
  padding-top: 6px;
  padding-bottom: 9px;
  border-bottom-color: #c9cfdb;
  color: var(--portal-muted);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  vertical-align: middle;
  white-space: nowrap;
}

/* The row actions belong against the right edge, where the eye ends up after
   reading across, rather than stranded in the middle of a wide last column. */
.portal-table th:last-child,
.portal-table td:last-child { text-align: right; }

/* A cell spanning the table is not the actions column, whatever :last-child
   thinks — the empty state was being pushed to the right edge by a rule meant
   for a narrow column of buttons. */
.portal-table td[colspan] { text-align: left; }
.portal-table td:last-child .portal-table-actions { justify-content: flex-end; }

/* The select column is a control, so its header keeps the checkbox centred on
   the same axis as the ones below it. */
.portal-table th.portal-select-cell { padding-right: 0; }

/* Two facts in one cell — what was sent, and when — stacked so the column
   stays narrow and the date reads as a caption rather than a second column. */
/* Stacked by block flow, not by flex.

   This was `display: flex` on the <td> itself, which drops the cell out of the
   table's layout: the browser keeps the columns aligned but the cell stops
   stretching to the row height, so its border draws short and leaves a faint
   box around the column. Block children with a margin between them stack the
   same way and leave the cell a cell. */
.portal-stacked-cell { line-height: 1.35; }
.portal-stacked-cell > * { display: block; }
.portal-stacked-cell > * + * { margin-top: 4px; }
.portal-stacked-cell .portal-muted { font-size: 0.76rem; }

/* ── A person, as one thing ───────────────────────────────────────────────────

   Name and email were two columns of equal weight, so a row of ten people was
   twenty pieces of undifferentiated text and a long address shoved everything
   useful off to the right. Together they make one block with an obvious first
   line, which is how a directory is actually read: find the name, confirm with
   the address.

   Rows also grew. 12px of padding on a single line of text is fine; on a
   two-line cell it crowds, and a list you scan wants air more than it wants to
   fit one more row on screen. */
.portal-table td { padding: 14px 12px; vertical-align: middle; }

.portal-identity { display: flex; align-items: center; gap: 11px; }

.portal-identity-text {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 1px;
  line-height: 1.35;
}

.portal-identity-text strong {
  overflow: hidden;
  color: var(--portal-ink);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The address is support, not the headline — small enough to sit under the name
   without competing, and allowed to truncate rather than widen the column. */
.portal-identity-text span {
  overflow: hidden;
  color: var(--portal-muted);
  font-size: 0.78rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Somewhere for the eye to land when running down a column of ten. Purely a
   scanning aid, so it is aria-hidden in the markup: two letters announced
   before every name would make the table worse to listen to, not better. */
.portal-avatar {
  display: grid;
  width: 32px;
  height: 32px;
  flex: 0 0 32px;
  border-radius: 50%;
  background: #eef1f7;
  color: #46526b;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  place-items: center;
}

/* ── Status, as a shape ───────────────────────────────────────────────────────

   A column of sentences all weigh the same, so finding the one person who has
   done something means reading all ten. A small coloured label registers before
   it is read: you can pick the submitted form out of a full page at a glance.

   The colour carries the meaning rather than decorating it — green is done,
   amber is waiting on them, grey is nothing yet — and each keeps enough contrast
   against its own background to be read on its own, not only by hue. */
.portal-pill {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.5;
  white-space: nowrap;
}

.portal-pill-done { background: #e3f2e8; color: #1f6b3f; }
.portal-pill-waiting { background: #fbf0dc; color: #8a5d10; }
.portal-pill-idle { background: #eef0f4; color: #5e6674; }
/* Turned down. The same red as the Decline button, so the decision and the
   record of it are recognisably the same thing. */
.portal-pill-refused { background: #f7e4e4; color: #97272d; }

/* A case is a title with a qualifier, not two equal facts. */
.portal-case-line { line-height: 1.4; }
.portal-case-line + .portal-case-line { margin-top: 4px; }
.portal-case-line em {
  color: var(--portal-muted);
  font-size: 0.76rem;
  font-style: normal;
}
.portal-case-line em::before { content: " · "; }

/* How far through the form somebody is.
   A bar makes a column of these scannable; the fraction beside it is what you
   repeat on the phone. */
.portal-progress { display: flex; align-items: center; gap: 8px; white-space: nowrap; }

.portal-progress-track {
  width: 62px;
  height: 5px;
  overflow: hidden;
  border-radius: 3px;
  background: var(--portal-rule);
}

.portal-progress-fill {
  height: 100%;
  min-width: 3px;
  border-radius: 3px;
  background: var(--portal-gold);
}

.portal-progress span { color: var(--portal-muted); font-size: 0.8rem; }

.portal-security-note {
  margin-top: 24px;
  color: var(--portal-muted);
  font-size: 0.8rem;
  line-height: 1.6;
}

[hidden] {
  display: none !important;
}

/* Client dashboard */
.client-dashboard {
  --dashboard-bg: #f4f6fa;
  --dashboard-border: #e3e7ee;
  --dashboard-blue: #244b91;
  background: var(--dashboard-bg);
}

.dashboard-shell { display: flex; min-height: 100vh; }

.dashboard-sidebar {
  position: fixed;
  z-index: 10;
  inset: 0 auto 0 0;
  display: flex;
  width: 258px;
  padding: 30px 22px 24px;
  flex-direction: column;
  background: var(--portal-navy-deep);
  color: var(--portal-white);
}

.dashboard-brand {
  display: flex;
  margin: 0 8px 50px;
  align-items: center;
  gap: 12px;
  color: var(--portal-white);
  font-family: Georgia, serif;
  font-size: 0.98rem;
  line-height: 1.25;
  text-decoration: none;
}

.dashboard-brand small {
  display: block;
  color: #d9ba7b;
  font-family: Arial, sans-serif;
  font-size: 0.56rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.dashboard-brand-mark {
  width: 42px;
  height: 42px;
  flex: 0 0 42px;
  overflow: hidden;
  border: 1px solid rgba(217, 186, 123, 0.5);
  border-radius: 50%;
}

.dashboard-brand-mark img { width: 100%; height: 100%; object-fit: cover; }
.dashboard-nav { display: grid; gap: 7px; }

.dashboard-nav a {
  display: flex;
  min-height: 44px;
  padding: 11px 14px;
  align-items: center;
  gap: 12px;
  border-radius: 5px;
  color: rgba(255, 255, 255, 0.65);
  font-size: 0.82rem;
  font-weight: 700;
  text-decoration: none;
}

.dashboard-nav a:hover,
.dashboard-nav a.is-active { background: rgba(255, 255, 255, 0.09); color: var(--portal-white); }
.dashboard-nav a.is-active { box-shadow: inset 3px 0 #d9ba7b; }
.dashboard-nav-icon { display: inline-grid; width: 20px; place-items: center; color: #d9ba7b; font-size: 1rem; }

/*
  The console's rail icons are drawn, not typed.

  The client portal's nav still uses characters and reads fine on the ones it
  has — a house, an envelope, a gear. The console needed a person, a
  briefcase, a calendar and a page, and Unicode has no such characters outside
  emoji: the geometric shapes it does have were standing in for those ideas
  and saying nothing. A square meant Calendar. A parallelogram meant
  Documents.

  Emoji were the other option and are the wrong one here — they arrive in the
  system's own colours, at the system's own weight, and a colour picture in a
  navy-and-gold rail of serif type reads as something pasted in.

  So: inline SVG, stroked in currentColor, which is why nothing here sets a
  colour. The icon takes the rail's gold from the rule above, and if that
  colour is ever themed the icons follow without being touched. Sized in the
  stylesheet rather than on each of the seven, so they cannot drift apart.
*/
.dashboard-nav-icon svg { display: block; width: 17px; height: 17px; }

/*
  The intake page shows one tab until the form is sent.

  Hidden in the stylesheet rather than by script, so a client on a slow phone
  never sees the full portal paint and then vanish. `intake.js` puts
  `intake-unlocked` on <body> once the intake is on file, and the rest of the
  portal comes back without a reload. `display` is only ever removed here —
  nothing is restored to a guessed value — so the sidebar links, the mobile
  links and the Menu button each return to whatever their own rules say.
*/
body[data-portal-page="intake"]:not(.intake-unlocked) [data-intake-gated] { display: none; }

.dashboard-sidebar-help {
  margin-top: auto;
  padding: 19px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.055);
}

.dashboard-sidebar-help > span { display: grid; width: 29px; height: 29px; margin-bottom: 12px; place-items: center; border-radius: 50%; background: #d9ba7b; color: var(--portal-navy-deep); font-weight: 700; }
.dashboard-sidebar-help strong { font-family: Georgia, serif; font-size: 0.94rem; }
.dashboard-sidebar-help p { margin: 6px 0 13px; color: rgba(255, 255, 255, 0.58); font-size: 0.7rem; line-height: 1.5; }
.dashboard-sidebar-help a { color: #e2c689; font-size: 0.7rem; font-weight: 700; text-underline-offset: 3px; }

.dashboard-logout {
  display: flex;
  margin: 20px 10px 0;
  padding: 10px 0;
  align-items: center;
  gap: 10px;
  border: 0;
  background: transparent;
  color: rgba(255, 255, 255, 0.58);
  font: inherit;
  font-size: 0.76rem;
  font-weight: 700;
  cursor: pointer;
}

.dashboard-logout:hover { color: var(--portal-white); }
.dashboard-content { width: calc(100% - 258px); margin-left: 258px; }

.dashboard-topbar {
  position: sticky;
  z-index: 8;
  top: 0;
  display: flex;
  min-height: 72px;
  padding: 12px clamp(24px, 4vw, 56px);
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--dashboard-border);
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(12px);
}

.dashboard-topbar-title { display: flex; flex-direction: column; color: var(--portal-link); font-family: Georgia, serif; }
.dashboard-topbar-title span { color: var(--portal-success); font-family: Arial, sans-serif; font-size: 0.62rem; font-weight: 700; letter-spacing: 0.05em; }
.dashboard-topbar-actions { display: flex; align-items: center; gap: 13px; }
.dashboard-icon-button,
.dashboard-avatar { display: grid; width: 38px; height: 38px; place-items: center; border: 1px solid var(--dashboard-border); border-radius: 50%; background: var(--portal-surface); color: var(--portal-link); font: inherit; font-weight: 700; text-decoration: none; cursor: pointer; }
.dashboard-icon-button.is-refreshing {
  border-color: var(--portal-gold);
  color: var(--portal-gold);
  animation: dashboard-refresh-spin 0.7s linear infinite;
  cursor: wait;
}
.dashboard-icon-button:disabled { opacity: 1; }

@keyframes dashboard-refresh-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .dashboard-icon-button.is-refreshing { animation-duration: 1.8s; }
}
.dashboard-avatar { border-color: var(--portal-navy); background: var(--portal-navy); color: var(--portal-white); font-size: 0.68rem; }
.dashboard-menu { display: none; }

.dashboard-main { width: min(1440px, 100%); margin: 0 auto; padding: 46px clamp(24px, 4vw, 56px) 30px; }
.dashboard-config-alert { border: 1px solid #e3d2ac; border-left: 4px solid var(--portal-gold); border-radius: 4px; }
.dashboard-welcome { display: flex; margin-bottom: 30px; align-items: flex-end; justify-content: space-between; gap: 24px; }
.dashboard-welcome h1 { margin: 0 0 7px; color: var(--portal-link); font-family: Georgia, serif; font-size: clamp(2.1rem, 4vw, 3.4rem); font-weight: 400; letter-spacing: -0.035em; }
.dashboard-welcome p { margin: 0; color: var(--portal-muted); }

/* The greeting and the Secure portal badge on one line.

   The badge sat at the far right of the welcome row, a card-sized box facing
   a two-line heading across the whole width of the page, and was hidden on
   phones for want of room. As a pill beside the name it is read with the
   name — the moment a client wonders whether the page is private — and it
   is small enough to keep on a phone, where it wraps under the greeting.
   Tokens rather than the pinned greens it had, so the dusk theme gets a pill
   of its own colour instead of a white one. */
.dashboard-welcome-heading { display: flex; margin: 0 0 7px; flex-wrap: wrap; align-items: center; gap: 6px 14px; }
.dashboard-welcome-heading h1 { margin: 0; }
.dashboard-secure-badge { display: inline-flex; padding: 7px 12px 7px 10px; align-items: center; gap: 7px; border: 1px solid color-mix(in srgb, var(--portal-success) 32%, transparent); border-radius: 999px; background: color-mix(in srgb, var(--portal-success) 9%, var(--portal-surface)); color: var(--portal-success); font-size: 0.64rem; font-weight: 700; letter-spacing: 0.07em; line-height: 1; text-transform: uppercase; white-space: nowrap; }
.dashboard-secure-badge > span:first-child { font-size: 0.7rem; }

.dashboard-stats { display: grid; margin-bottom: 26px; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
/* A row of two rather than four. The Overview's second row counts different
   nouns from the first and is kept out of it on purpose, so it needs a width of
   its own; two tiles left sitting in a four-column grid would be two quarters
   and half a row of nothing. Declared before the breakpoints below, which
   already narrow every stats row to two and so override this unchanged. */
.dashboard-stats-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dashboard-stat { display: flex; min-height: 92px; padding: 19px; align-items: center; gap: 14px; border: 1px solid var(--dashboard-border); border-radius: 7px; background: var(--portal-surface); box-shadow: 0 5px 18px rgba(16, 38, 83, 0.035); }
.dashboard-stat-icon { display: grid; width: 45px; height: 45px; flex: 0 0 45px; place-items: center; border-radius: 6px; font-weight: 700; }
.dashboard-stat-icon.is-blue { background: #e9eff9; color: #315b9b; }
.dashboard-stat-icon.is-gold { background: #f7f0e2; color: #9a7430; }
.dashboard-stat-icon.is-green { background: #e8f4ed; color: #397455; }
.dashboard-stat-icon.is-purple { background: #f0ebf7; color: #765493; }
.dashboard-stat-icon.is-teal { background: #e4f1f0; color: #2f6b68; }
.dashboard-stat-icon.is-rose { background: #f9ebe9; color: #97524e; }
.dashboard-stat strong { display: block; color: var(--portal-link); font-family: Georgia, serif; font-size: 1.6rem; font-weight: 400; line-height: 1; }
.dashboard-stat small { display: block; margin-top: 6px; color: var(--portal-muted); font-size: 0.68rem; }

/*
  An explanatory list inside a card: what a number above it counts, in words.

  Bulleted, and deliberately not .portal-list — that one is a grid of bordered
  cards, one per thing you can open, and these are three sentences about one
  thing. Given the discs they read as a definition of the numbers rather than
  as three more matters to click.
*/
.dashboard-plain-list { margin: 0; padding-left: 20px; list-style: disc; }
.dashboard-plain-list li { margin-top: 8px; color: var(--portal-muted); font-size: 0.84rem; line-height: 1.6; }
.dashboard-plain-list li:first-child { margin-top: 0; }
.dashboard-plain-list strong { color: var(--portal-ink); font-weight: 600; }

/* ==========================================================================
   The console: shell, rail and Overview
   ==========================================================================

   EVERYTHING HERE IS SCOPED TO .admin-console, AND THAT IS THE POINT.

   .dashboard-shell, .dashboard-sidebar, .dashboard-content, .dashboard-main
   and .dashboard-card are shared with seven client pages — the case page, the
   calendar, messages, settings, intake, help, and the client's own Overview.
   The console wants a wider working area, a narrower rail and larger cards
   than a client reading one matter on a laptop does, and widening the shared
   rules to suit the console would quietly restyle all seven.

   So the shared rules stay exactly as they are and the console overrides them
   under its own body class. `.admin-console .dashboard-content` is two classes
   against one, which is also why the collapse at 900px has to be restated
   below: the shared rule that clears the offset would otherwise lose to this
   one and the rail would vanish while the page stayed indented by its width. */

.admin-console {
  /* One number for the rail, because three rules depend on it agreeing with
     itself: the rail's own width, the offset on the content beside it, and the
     scroll margin under the sticky topbar. */
  --admin-rail: 238px;
}

.admin-console .dashboard-sidebar { width: var(--admin-rail); padding: 26px 16px 20px; }
.admin-console .dashboard-content { width: calc(100% - var(--admin-rail)); margin-left: var(--admin-rail); }

/* The working area. 1560px rather than 1440: at 1920 the old value left almost
   380px of cream down each side while the three-column rows underneath were
   squeezing five-word headings onto two lines. The clamped padding closes the
   rest of the gap without letting the text run to the glass on a laptop. */
.admin-console .dashboard-main { width: min(1560px, 100%); padding: 34px clamp(20px, 2.4vw, 44px) 40px; }

/* --------------------------------------------------------------------------
   The rail
   --------------------------------------------------------------------------

   Restrained rather than decorated: it is read once on the way to somewhere
   else, every hour of the working day. What it owes the reader is that the
   current section be findable without looking for it. */

.admin-console .dashboard-brand { margin: 0 6px 34px; }
.admin-console .admin-tabs { gap: 3px; }

.admin-console .admin-tab {
  min-height: 42px;
  padding: 10px 12px;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  gap: 11px;
  border-radius: 7px;
  font-size: 0.83rem;
  font-weight: 600;
}

/* The active one, said three ways: filled, barred in gold, and the only label
   at full white. One of the three would do at a glance; all three survive a
   dim screen, a colour-blind reader, and a rail seen out of the corner of an
   eye. */
.admin-console .admin-tab.is-active {
  background: rgba(217, 186, 123, 0.14);
  box-shadow: inset 3px 0 #d9ba7b;
  color: #fff;
  font-weight: 700;
}

.admin-console .admin-tab.is-active .dashboard-nav-icon { color: #eBcB8f; }

/* One size for every icon in the rail, set here rather than on each of the
   eight so they cannot drift apart. */
.admin-console .dashboard-nav-icon { width: 22px; font-size: 1.02rem; }
.admin-console .dashboard-nav-icon svg { width: 18px; height: 18px; }

.admin-console .admin-rail-label { margin: 0 12px 9px; }
.admin-console .dashboard-sidebar-help { padding: 16px; }

/* --------------------------------------------------------------------------
   The header
   -------------------------------------------------------------------------- */

.admin-console .admin-overview-heading { margin-bottom: 24px; }

/* 30px at a laptop and 34 at a desk: large enough to be the first thing read,
   short of the display size the client portal uses to greet somebody who has
   opened it once this month. */
.admin-console .admin-overview-heading h1 {
  margin: 0 0 6px;
  font-size: clamp(1.75rem, 2.1vw, 2.15rem);
  letter-spacing: -0.025em;
}

.admin-console .admin-overview-heading p { max-width: 72ch; font-size: 0.9rem; }

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

   Softer corners, a lighter shadow and a floor under the height, so a card
   with nothing in it still reads as a card rather than as a gap. */

.admin-console .dashboard-card {
  padding: 22px;
  border-radius: 12px;
  box-shadow: 0 1px 2px rgba(16, 38, 83, 0.04), 0 6px 18px rgba(16, 38, 83, 0.035);
}

.admin-console .dashboard-card-header { margin-bottom: 16px; align-items: baseline; }
.admin-console .dashboard-card h2 { font-size: 1.06rem; font-weight: 600; letter-spacing: -0.01em; }
.admin-console .dashboard-card .portal-eyebrow { margin-bottom: 5px; color: var(--portal-gold); }
.admin-console .dashboard-card .portal-link-button { font-size: 0.76rem; white-space: nowrap; }

/* --------------------------------------------------------------------------
   The three rows
   --------------------------------------------------------------------------

   Grid, not floats and not fixed positions, so a card that grows pushes its
   row rather than overlapping its neighbour. `align-items: stretch` is the
   whole reason the rows read as rows: every card in one is the height of the
   tallest, so the rules across them line up. */

.admin-overview-grid { display: grid; margin-bottom: 18px; gap: 18px; align-items: stretch; }

/* Half, a quarter, a quarter. The caseload is the only card that answers
   "where is everything" and is read first. */
.admin-overview-grid-top { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr); }
.admin-overview-grid-mid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.admin-overview-grid-foot { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

/* A floor, not a fixed height: an empty card holds its place in the row and a
   full one still grows. */
.admin-overview-grid > .dashboard-card,
.admin-overview-grid > * > .dashboard-card { min-height: 232px; }
.admin-overview-grid-top > .admin-overview-wide { min-height: 300px; }
.admin-overview-counts { margin-top: 4px; }

/* --------------------------------------------------------------------------
   The caseload rail
   --------------------------------------------------------------------------

   The same drawing as .case-progress-rail on a single matter, and it means
   something different: that one is a position and this is a distribution. So
   there is no "done" half and no current stop — the line is a continuous grey
   because the four statuses are a sequence, not a journey anybody is on. */

.admin-overview-pipeline {
  display: grid;
  margin: 4px 0 22px;
  padding: 0;
  grid-auto-columns: 1fr;
  grid-auto-flow: column;
  gap: 0;
  list-style: none;
}

.admin-overview-stop {
  --stop: var(--portal-muted);
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  text-align: center;
}

/* The connecting line, drawn behind the markers rather than between them: a
   border on each stop would break at every gap and read as a dashed rule. */
.admin-overview-stop::before {
  position: absolute;
  z-index: 0;
  top: 13px;
  left: 0;
  width: 100%;
  height: 2px;
  background: var(--portal-rule);
  content: "";
}

.admin-overview-stop:first-child::before { left: 50%; width: 50%; }
.admin-overview-stop:last-child::before { width: 50%; }

.admin-overview-stop.is-start { --stop: #5b7fb9; }
.admin-overview-stop.is-work { --stop: var(--portal-gold); }
.admin-overview-stop.is-filed { --stop: var(--portal-success); }
.admin-overview-stop.is-closed { --stop: var(--portal-muted); }
/* Rose, because a status nobody recognises is a thing to look at rather than a
   stage to be pleased about. */
.admin-overview-stop.is-other { --stop: var(--portal-danger); }

.admin-overview-stop-marker {
  position: relative;
  z-index: 1;
  width: 28px;
  height: 28px;
  border: 2px solid var(--stop);
  border-radius: 50%;
  background: color-mix(in srgb, var(--stop) 22%, var(--portal-surface));
}

.admin-overview-stop-name {
  max-width: 15ch;
  color: var(--portal-muted);
  font-size: 0.73rem;
  font-weight: 600;
  line-height: 1.35;
}

.admin-overview-stop-count {
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.85rem;
  font-weight: 400;
  line-height: 1;
}

/* The figures under the rail, boxed together because the first row is one
   claim: the stops are exclusive and cover everything, so they add up to
   All matters. */
.admin-overview-figures {
  display: grid;
  overflow: hidden;
  border: 1px solid var(--portal-rule);
  border-radius: 10px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* The second row is what is ahead rather than what is on the books, so it is
   a separate box under the first rather than three more cells in it. */
.admin-overview-figures-ahead { margin-top: 10px; background: color-mix(in srgb, var(--portal-gold) 5%, var(--portal-surface)); }

.admin-overview-figure { padding: 14px 12px; border-left: 1px solid var(--portal-rule); text-align: center; }
.admin-overview-figure:first-child { border-left: 0; }

.admin-overview-figure strong {
  display: block;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.7rem;
  font-weight: 400;
  line-height: 1;
}

/* Red only while there is something to be red about — the class is put on and
   taken off by overview.js, because a nought in the danger colour is an alarm
   about the absence of a problem. */
.admin-overview-figure strong.is-late { color: var(--portal-danger); }
.admin-overview-figure small { display: block; margin-top: 7px; color: var(--portal-muted); font-size: 0.7rem; font-weight: 600; }

/* --------------------------------------------------------------------------
   The rows
   --------------------------------------------------------------------------

   One shape for every card: something small on the left, the thing itself in
   the middle, and when it is on the right. Ruled between rather than boxed,
   because these are lines in a card and not cards of their own — the tab each
   one links to is where they get to be rows you can act on. */

.admin-overview-list { display: grid; margin: 0; padding: 0; list-style: none; align-content: start; }

.admin-overview-row {
  display: grid;
  padding: 11px 0;
  border-top: 1px solid var(--portal-rule);
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
}

.admin-overview-list > li:first-child { padding-top: 0; border-top: 0; }

.admin-overview-row-body { min-width: 0; }

/* The title wraps; the caption under it does not.

   Both used to truncate on one line, which put the ellipsis through the one
   phrase on the row that carries the meaning — "Master calendar he…",
   "Adjustment of sta…" — while the client's name beside it sat with room to
   spare. Two lines is enough for every matter title and event name the office
   writes, and the clamp stops a pathological one from pushing the card open.

   The caption stays on one line because it is metadata: a name and a matter,
   both recoverable from the tab this row links to. */
.admin-overview-row-body strong {
  display: -webkit-box;
  overflow: hidden;
  color: var(--portal-ink);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.35;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

.admin-overview-row-body small {
  display: block;
  margin-top: 3px;
  overflow: hidden;
  color: var(--portal-muted);
  font-size: 0.755rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-overview-when { color: var(--portal-muted); font-size: 0.735rem; white-space: nowrap; }

/* The word "Overdue" is already in the text beside the date; this is the same
   fact again in colour, for whoever reads the shape of a page before its
   words. Neither one is carrying it alone. */
.admin-overview-row.is-late .admin-overview-when { color: var(--portal-danger); font-weight: 700; }

.admin-overview-note {
  padding: 14px 0;
  color: var(--portal-muted);
  font-size: 0.83rem;
}

/* An empty card is not a broken one.

   The floor under a card's height keeps a row of them a row, which left an
   empty one as a tall box with a sentence stranded at the top — the shape of
   something that failed to load rather than of something with nothing to say.
   Centred, it reads as an answer. overview.js puts the class on, so the CSS
   never has to guess which state a list is in. */
.admin-overview-list.is-empty { align-content: center; min-height: 116px; }
.admin-overview-list.is-empty .admin-overview-note { padding: 0; }

.admin-overview-mark {
  display: grid;
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--portal-muted) 16%, var(--portal-surface));
  color: var(--portal-muted);
  font-size: 0.8rem;
  font-weight: 700;
  place-items: center;
}

.admin-overview-row.is-late .admin-overview-mark {
  background: color-mix(in srgb, var(--portal-danger) 18%, var(--portal-surface));
  color: var(--portal-danger);
}

/* The one place in this console a clock time is printed, so it is given a chip
   of its own: the diary is read down the times first and the titles second. */
.admin-overview-time {
  display: grid;
  min-width: 68px;
  padding: 7px 8px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--portal-gold) 18%, var(--portal-surface));
  color: var(--portal-ink);
  font-size: 0.72rem;
  font-weight: 700;
  place-items: center;
}

/* The kind of diary entry, on the meta line rather than in a column of its own
   — see the note in drawDiary. It rode down the right for a while and cost the
   title a third of the row: at a quarter of the grid "Master calendar hearing"
   arrived as "Master calendar hea…", which is the one phrase on the line
   somebody actually needs. */
.admin-overview-tag {
  display: inline-block;
  margin-top: 5px;
  padding: 2px 7px;
  border: 1px solid var(--portal-rule);
  border-radius: 999px;
  color: var(--portal-muted);
  font-size: 0.575rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Two columns for this one, the tag having moved inside the body. */
.admin-overview-diary-row { grid-template-columns: auto minmax(0, 1fr); }

.admin-overview-unread-row .portal-avatar { width: 32px; height: 32px; flex: 0 0 32px; }

/* The timeline's marks are joined, because a run of updates is a sequence and
   the line is what says so. Drawn on the mark rather than between rows, so it
   cannot fall out of step with them. */
.admin-overview-timeline .admin-overview-row { position: relative; }

/* The mark is top-anchored in the timeline, and that is what makes the
   connector's arithmetic true rather than approximately true.

   `.admin-overview-row` centres its cells, so the 28px mark floats to the
   middle of a row whose height is set by the text beside it — which is always
   taller, and taller again when a title takes the second line the clamp
   allows. An offset measured from the row's padding edge then lands inside the
   circle on every row and short of the one below. Pinning the mark to the top
   makes its position a constant, and the two offsets below are that constant. */
.admin-overview-timeline .admin-overview-mark { align-self: start; }

/* Anchored top and bottom rather than given a height, so it reaches the next
   mark whatever the row grew to. 39px is the row's 11px of padding plus the
   28px mark; -12px carries the line past the next row's rule and padding to
   the top of the mark below. */
.admin-overview-timeline .admin-overview-row:not(:last-child) .admin-overview-mark::after {
  position: absolute;
  z-index: 0;
  top: 39px;
  bottom: -12px;
  left: 13px;
  width: 2px;
  background: var(--portal-rule);
  content: "";
}

/* The first row has no top padding, so its mark starts 11px higher and its
   line has to as well — otherwise the timeline opens with a floating dot. */
.admin-overview-timeline > li:first-child .admin-overview-mark::after { top: 28px; }

.admin-overview-timeline .admin-overview-mark {
  background: color-mix(in srgb, var(--portal-success) 16%, var(--portal-surface));
  color: var(--portal-success);
}

/* --------------------------------------------------------------------------
   Questionnaires out
   -------------------------------------------------------------------------- */

.admin-overview-form {
  display: grid;
  padding: 11px 0;
  border-top: 1px solid var(--portal-rule);
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px 12px;
  align-items: center;
}

.admin-overview-form-name {
  overflow: hidden;
  color: var(--portal-ink);
  font-size: 0.83rem;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-overview-form-share { color: var(--portal-muted); font-size: 0.735rem; white-space: nowrap; }

/* .portal-progress-track is 62px wide, which is right in a table cell and
   wrong here: this one is the width of the card. */
.admin-overview-form-track { width: auto; height: 6px; grid-column: 1 / -1; }

/* --------------------------------------------------------------------------
   Quick actions
   --------------------------------------------------------------------------

   Six links, and nothing that acts. Each lands on the tab that owns the job,
   so a press here can never do something the reader cannot then see. Tiles
   rather than a list, because they are a menu of ways in rather than a run of
   rows to read down. */

.admin-overview-actions { display: grid; gap: 10px; grid-template-columns: repeat(3, minmax(0, 1fr)); }

.admin-overview-action {
  display: grid;
  padding: 13px;
  border: 1px solid var(--portal-rule);
  border-radius: 10px;
  color: inherit;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 11px;
  text-decoration: none;
}

.admin-overview-action:hover {
  border-color: var(--portal-gold);
  background: color-mix(in srgb, var(--portal-gold) 7%, var(--portal-surface));
}

.admin-overview-action:hover strong { color: var(--portal-link); }

.admin-overview-action-mark {
  display: grid;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--portal-gold) 18%, var(--portal-surface));
  color: var(--portal-gold);
  font-size: 0.92rem;
  place-items: center;
}

/* These wrap. They are the only labels on the panel that are not data — they
   name what a control does, and a control whose name is cut is a control
   nobody presses.

   Truncating them was an English-only assumption. Three tiles across a 2fr card
   leaves the label about 138px at a 1366px laptop: "Send a portal invitation"
   fits, "Enviar una invitación al portal" does not, and "Отправить приглашение
   в портал" arrived as "Отправить приглашен…". Every other row on this panel
   still truncates, because every other row is a matter, a name or a file that
   the tab it links to shows in full. */
.admin-overview-action-body { min-width: 0; }
.admin-overview-action-body strong { display: block; color: var(--portal-ink); font-size: 0.83rem; font-weight: 600; line-height: 1.3; }
.admin-overview-action-body small { display: block; margin-top: 3px; color: var(--portal-muted); font-size: 0.715rem; line-height: 1.35; }

/* --------------------------------------------------------------------------
   Narrower
   --------------------------------------------------------------------------

   The caseload card keeps the full width for as long as there is one to keep:
   it is the only thing on the panel that is drawn rather than listed, and it
   is the first thing squeezing it makes unreadable. */

/* The half-quarter-quarter row needs a wide desk to be worth having. Below
   about 1500 the two quarter columns fall under 290px, and a diary entry then
   has ~130px for its title after the clock chip — which is where the ellipsis
   started eating event names. The caseload takes the full width instead and
   the two of them split it, which is roomier at 1440 than three columns were. */
@media (max-width: 1500px) {
  .admin-overview-grid-top { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .admin-overview-grid-top > .admin-overview-wide { grid-column: 1 / -1; min-height: 0; }
}

@media (max-width: 1320px) {
  .admin-overview-grid-mid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .admin-overview-grid-foot { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 1120px) {
  .admin-console .admin-overview-actions { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The rail goes at 900px and the shared rule that clears the offset is a
   single class, so it would lose to .admin-console .dashboard-content above.
   Restated here, or the page keeps a 238px indent against a rail that is no
   longer on screen. */
@media (max-width: 900px) {
  .admin-console .dashboard-content { width: 100%; margin-left: 0; }
  .admin-overview-grid { grid-template-columns: minmax(0, 1fr); }
  .admin-overview-grid-top > .admin-overview-wide { grid-column: auto; }
  .admin-overview-grid > .dashboard-card,
  .admin-overview-grid > * > .dashboard-card,
  .admin-overview-grid-top > .admin-overview-wide { min-height: 0; }
}

@media (max-width: 620px) {
  .admin-console .dashboard-main { padding: 24px 14px 28px; }
  .admin-console .admin-overview-actions { grid-template-columns: minmax(0, 1fr); }
  .admin-overview-stop-marker { width: 22px; height: 22px; }
  .admin-overview-stop::before { top: 10px; }
  .admin-overview-stop-name { font-size: 0.64rem; }
  .admin-overview-stop-count { font-size: 1.4rem; }
  .admin-overview-figure { padding: 12px 6px; }
  .admin-overview-figure strong { font-size: 1.4rem; }
  .admin-overview-figure small { font-size: 0.62rem; }
  .admin-overview-time { min-width: 60px; }
}

/*
  The log of what has been checked, folded away.

  A fold rather than a list, because the answer somebody wants from it is
  usually just its own summary line — "has anybody checked this month" is
  answered by opening it once a month. Left open it would be a column of
  timestamps under the thing it is about.
*/
.form-scan-log { margin-top: 14px; border-top: 1px solid var(--portal-rule); padding-top: 12px; }
.form-scan-log > summary {
  cursor: pointer;
  color: var(--portal-muted);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.form-scan-log > summary:hover { color: var(--portal-ink); }
.form-scan-log[open] > summary { margin-bottom: 12px; }
/* Quieter than a matter's rows: this is a record, not a list of things to do. */
.form-scan-log .portal-list-item { padding: 10px 12px; }
.form-scan-log .portal-list-item strong { font-size: 0.86rem; }

.dashboard-layout { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(280px, 0.75fr); gap: 22px; align-items: start; }
.dashboard-primary,
.dashboard-secondary { display: grid; gap: 22px; min-width: 0; }
.dashboard-card { min-width: 0; padding: 25px; border: 1px solid var(--dashboard-border); border-radius: 7px; background: var(--portal-surface); box-shadow: 0 6px 22px rgba(16, 38, 83, 0.035); scroll-margin-top: 92px; }
.dashboard-card-header { display: flex; margin-bottom: 21px; align-items: flex-start; justify-content: space-between; gap: 16px; }
.dashboard-card h2 { margin: 0; color: var(--portal-link); font-family: Georgia, serif; font-size: 1.3rem; font-weight: 400; }
.dashboard-card .portal-eyebrow { margin-bottom: 6px; font-size: 0.58rem; }
.dashboard-live { color: var(--portal-success); font-size: 0.65rem; font-weight: 700; }
.dashboard-live i { display: inline-block; width: 6px; height: 6px; margin-right: 5px; border-radius: 50%; background: #48a06c; }
.dashboard-file-note { color: var(--portal-muted); font-size: 0.62rem; }

/* The line explaining the stage you are looking at.

   It shared a class with the "PDF, JPEG and PNG only" hints under file inputs,
   which are afterthoughts at 0.62rem — about ten pixels. This one is not an
   afterthought: it is the only thing on the page that says what a stage means,
   and it is read by somebody deciding whether to approve a person. At that size
   it was skipped rather than read.

   Given its own size, colour and measure instead of enlarging the shared class,
   because the file hints are correctly small and should stay that way. */
/* No measure cap. 78ch is the right limit for a paragraph somebody reads down,
   and the wrong one for a single line inside a band that is already 1000px
   wide — it wrapped the sentence in half and left the right side of the band
   empty. It still wraps on a narrow window, which is what should happen there;
   what it no longer does is wrap on a wide one. */
#directory-note {
  /* A token, not a literal: this line sits on a card that changes colour with
     the theme, and a pinned ink stays dark when the card goes dark. */
  color: var(--portal-ink);
  font-size: 0.86rem;
  line-height: 1.6;
}

.dashboard-case-list .portal-list-item { position: relative; padding: 22px 22px 20px 27px; border-color: var(--dashboard-border); overflow: hidden; }
.dashboard-case-list .portal-list-item::before { position: absolute; inset: 0 auto 0 0; width: 4px; background: var(--portal-gold); content: ""; }
.dashboard-case-list .portal-list-item h3 { padding-right: 100px; font-size: 1.15rem; }
.dashboard-case-list .portal-badge { position: absolute; top: 16px; right: 18px; margin: 0; background: #eef3fb; color: #315b9b; }
.dashboard-case-list .portal-meta { margin-top: 13px; padding-top: 13px; border-top: 1px solid var(--dashboard-border); }

/* A divider inside a portal list — open matters from finished ones, a matter's
   own documents from the ones filed against no matter. Not a card: a label on
   the list, so the eye reads it as a break rather than as another row. Left
   unscoped deliberately; both lists that use it want it identical, and a second
   copy under a second parent is how the two drift apart. */
.portal-list-heading { margin: 20px 0 4px; padding: 0; border: 0; background: none; list-style: none; font-size: 0.76rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--portal-muted); }
.portal-list-heading:first-child { margin-top: 0; }

/* The matter picker, beside the sub-tabs. .portal-subtabs-row is space-between,
   so with the strip as the only other child this lands hard right on a wide
   screen and wraps underneath on a narrow one, which is the order it should be
   read in either way. */
.client-matter-picker { display: flex; align-items: center; gap: 8px; }
.client-matter-picker label { font-size: 0.76rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--portal-muted); }
.client-matter-picker select { max-width: 22rem; }

/* A closed matter, quieted. It keeps its place — it is still the client's, and
   still theirs to look back at — but it stops competing with the live ones.
   The gold bar is this page's mark for "running"; a finished matter has not
   earned it. Tokens only: these cards are drawn in four themes. */
.dashboard-case-list .portal-list-item.is-closed::before { background: var(--portal-rule); }
.dashboard-case-list .portal-list-item.is-closed h3 { color: var(--portal-muted); }
.dashboard-case-list .portal-list-item.is-closed .portal-badge { background: transparent; border: 1px solid var(--portal-rule); color: var(--portal-muted); }

#doc-choice-choices.document-action-menu {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.document-action-menu > .portal-button { width: 100%; margin: 0; white-space: normal; }
.document-action-menu > [hidden] { display: none; }
#doc-choice-view { grid-column: 1 / -1; }
#doc-choice-unsend { order: 2; }
#doc-choice-action-help { grid-column: 1 / -1; order: 4; margin: 0; line-height: 1.5; }
#doc-choice-delete { order: 3; color: var(--portal-danger); border-color: var(--portal-danger); }
@media (max-width: 400px) {
  #doc-choice-choices.document-action-menu { grid-template-columns: minmax(0, 1fr); }
}
.request-upload-dialog {
  width: min(520px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  box-sizing: border-box;
  padding: 28px;
  border: 1px solid var(--portal-rule);
  border-top: 4px solid var(--portal-gold);
  border-radius: 8px;
  background: var(--portal-surface);
  color: var(--portal-ink);
}
.request-upload-dialog::backdrop { background: rgb(0 0 0 / 55%); }
.request-upload-dialog h2 { margin: 0 0 12px; }
.request-upload-dialog p { line-height: 1.5; overflow-wrap: anywhere; }
.request-upload-name { font-weight: 700; }
.request-upload-dialog label { display: block; margin: 20px 0 8px; }
.request-upload-dialog input { width: 100%; box-sizing: border-box; padding: 16px; border: 1px dashed var(--portal-rule); border-radius: 4px; }
.request-upload-selected { font-size: 0.82rem; color: var(--portal-muted); }
.request-upload-dialog progress { width: 100%; accent-color: var(--portal-gold); }
.request-upload-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; margin-top: 20px; }
.dashboard-document-list { margin-bottom: 20px; }
.dashboard-document-list .portal-list-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 5px 14px; align-items: center; border-color: var(--dashboard-border); }
.dashboard-document-list .portal-list-item h3 { margin: 0; font-family: Arial, sans-serif; font-size: 0.84rem; }
.dashboard-document-list .portal-list-item .portal-meta { grid-column: 1; }
/* The heading here is the same identity block the directory uses — initials,
   client, file underneath — so it takes the first column the <h3> used to. */
.dashboard-document-list .portal-list-item .portal-identity { grid-column: 1; min-width: 0; }
.dashboard-document-list .portal-list-item .portal-identity-text span { font-size: 0.8rem; }
.dashboard-document-list .portal-list-item .portal-button { grid-column: 2; grid-row: 1 / span 2; }

/* A row for a document that is not here yet.

   Same row as the files around it, because that is the point: the library holds
   everything the office has, and a request is the shape of the one thing it
   does not. Marked with the gold bar the portal uses for "this is live", in
   tokens rather than a colour, since these rows are drawn in four themes and
   the cream one has a gold of its own. Not a coloured background: in the dark
   theme the warm tints are the page, so a tinted row reads as a hole in the
   card rather than as something asking to be dealt with. */
.dashboard-document-list .portal-list-item.is-needed { border-color: var(--portal-gold); box-shadow: inset 3px 0 0 var(--portal-gold); }
.dashboard-document-list .portal-list-item.is-needed h3 { font-family: Georgia, serif; font-size: 0.95rem; }
.portal-needed-flag { font-size: 0.6rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; }
.dashboard-document-list .portal-list-item.is-needed .portal-needed-flag { color: var(--portal-gold); }

.dashboard-message-form { margin-top: 20px; padding-top: 22px; border-top: 1px solid var(--dashboard-border); }

.dashboard-form-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.dashboard-form-footer small { color: var(--portal-muted); font-size: 0.65rem; }

/* ==========================================================================
   The conversation
   ==========================================================================

   Shared by the client's Messages page, the client's Overview preview and the
   attorney's console. See portal/lib/chat.js for what is deliberately borrowed
   from WhatsApp and what is deliberately left out.

   Two colours carry the whole thing: the thread sits on warm paper, and the
   only saturated surface is the navy of your own words. Gold appears twice —
   the ring around the firm's seal and the rule under the header — and nowhere
   else, so it still reads as the firm's mark rather than decoration.
*/

.chat {
  display: flex;
  min-height: 0;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--dashboard-border);
  border-radius: 8px;
  background: var(--portal-surface);
  box-shadow: 0 6px 22px rgba(16, 38, 83, 0.05);
}

/* Letterhead, not an app bar: the seal, who you are writing to, and when to
   expect an answer — which is the question the absence of a typing indicator
   would otherwise leave open. */
.chat-header {
  display: flex;
  padding: 14px 20px;
  flex: 0 0 auto;
  align-items: center;
  gap: 13px;
  border-bottom: 2px solid var(--portal-gold);
  background: var(--portal-navy);
  color: var(--portal-white);
}

.chat-header-text { min-width: 0; }
/* h1 on the client's page, strong in the console — the conversation header is
   the page heading on one and a panel header on the other. */
.chat-header h1,
.chat-header strong { display: block; margin: 0; color: inherit; font-family: Georgia, serif; font-size: 1rem; font-weight: 400; letter-spacing: 0.01em; }
.chat-header small { display: block; margin-top: 2px; color: rgba(255, 255, 255, 0.62); font-size: 0.66rem; }
.chat-header .chat-seal { width: 38px; height: 38px; flex: 0 0 38px; }
.chat-header-aside { margin-left: auto; color: rgba(255, 255, 255, 0.62); font-size: 0.62rem; font-weight: 700; white-space: nowrap; }

.chat-thread {
  display: flex;
  min-height: 0;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 3px;
  margin: 0;
  padding: 18px clamp(14px, 3vw, 26px) 20px;
  list-style: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* A shade deeper than the page's cream. They were close enough that the panel
     dissolved into the page and the white bubbles had nothing to sit on. */
  background: #f0ebe0;
}

/* The day turning over is the one structural marker in the thread, because it
   is the one piece of information the bubbles cannot carry themselves. */
/* The Overview's preview of the same thread, inside an ordinary card rather
   than the full-height panel — so it needs a height of its own. */
.chat-thread.is-preview {
  max-height: 330px;
  border: 1px solid var(--dashboard-border);
  border-radius: 6px;
}

.chat-day {
  display: flex;
  margin: 14px 0 10px;
  justify-content: center;
}

.chat-day:first-child { margin-top: 0; }

.chat-day span {
  padding: 5px 13px;
  border: 1px solid #e4dccd;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.85);
  color: var(--portal-muted);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.chat-row {
  display: flex;
  max-width: 100%;
  align-items: flex-end;
  gap: 9px;
}

.chat-row.is-own { justify-content: flex-end; }
/* Tighter inside a group, looser between turns — the spacing is what makes
   three quick messages read as one turn. */
.chat-row.is-head { margin-top: 11px; }

.chat-seal {
  display: grid;
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  place-items: center;
  overflow: hidden;
  border: 1px solid var(--portal-gold);
  border-radius: 50%;
  background: var(--portal-surface);
  color: var(--portal-link);
  font-size: 0.6rem;
  font-weight: 700;
}

.chat-seal img { width: 100%; height: 100%; object-fit: cover; }
.chat-seal.is-initials { background: #eef2f9; }
/* Holds the column open for the rest of a group so the bubbles stay aligned. */
.chat-seal.is-blank { border: 0; background: none; visibility: hidden; }

.chat-bubble {
  min-width: 0;
  max-width: min(78%, 62ch);
  padding: 9px 13px 7px;
  border: 1px solid #e7dfd1;
  border-radius: 13px;
  background: var(--portal-surface);
  box-shadow: 0 1px 2px rgba(16, 38, 83, 0.05);
}

/* One squared corner on the last bubble of a group, pointing at the speaker.
   Only the tail carries it, so a group reads as a single shape. */
.chat-row.is-tail.is-them .chat-bubble { border-bottom-left-radius: 4px; }
.chat-row.is-tail.is-own .chat-bubble { border-bottom-right-radius: 4px; }

.chat-row.is-own .chat-bubble {
  border-color: var(--portal-navy);
  background: var(--portal-navy);
  color: var(--portal-white);
}

.chat-body {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.55;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.chat-time {
  display: block;
  margin-top: 3px;
  color: #8b8f98;
  font-size: 0.59rem;
  text-align: right;
}

.chat-row.is-own .chat-time { color: rgba(255, 255, 255, 0.55); }

/* Under the last thing you sent, aligned with it. Quiet on purpose: it answers
   a question when you look for it and says nothing when you do not. */
.chat-receipt {
  margin: 2px 2px 0;
  color: var(--portal-muted);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-align: right;
  text-transform: uppercase;
}

/* Unread count in the console's conversation list. */
.chat-unread {
  display: inline-grid;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  place-items: center;
  border-radius: 999px;
  background: var(--portal-navy);
  color: var(--portal-white);
  font-size: 0.62rem;
  font-weight: 700;
}

.chat-list .chat-unread { grid-area: 2 / 3 / 3 / 4; align-self: center; justify-self: end; }
.chat-list button.has-unread strong { font-weight: 700; }
.chat-list button.has-unread small { color: var(--portal-ink); }

.chat-empty {
  margin: auto;
  padding: 30px 20px;
  color: var(--portal-muted);
  font-size: 0.8rem;
  text-align: center;
}

/* Composer */
.chat-composer {
  display: grid;
  padding: 13px clamp(14px, 3vw, 20px);
  flex: 0 0 auto;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px 12px;
  border-top: 1px solid var(--dashboard-border);
  background: var(--portal-surface);
}

.chat-composer textarea {
  min-height: 46px;
  max-height: 168px;
  padding: 12px 16px;
  border: 1px solid var(--portal-rule);
  border-radius: 23px;
  background: #fbfaf8;
  color: var(--portal-ink);
  font: inherit;
  font-size: 0.86rem;
  line-height: 1.5;
  resize: none;
}

.chat-composer textarea:focus-visible {
  border-color: var(--portal-navy);
  outline: 2px solid rgba(16, 38, 83, 0.18);
  outline-offset: 1px;
}

.chat-composer .portal-button {
  align-self: end;
  height: 46px;
  padding-inline: 22px;
  border-radius: 23px;
}

.chat-composer-note {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--portal-muted);
  font-size: 0.64rem;
}

.chat-composer-note a { color: var(--portal-link); font-weight: 700; }
.chat-composer .portal-status { grid-column: 1 / -1; margin: 0; }

/* ==========================================================================
   Full-height chat page (client Messages)
   ==========================================================================

   The thread scrolls inside itself and the composer stays put, so the page
   behaves like the app it is modelled on rather than a document you scroll to
   the end of to find the box. Only this page opts in: `dvh` rather than `vh`
   because a mobile browser's toolbars otherwise hide the composer behind the
   fold at exactly the moment it is wanted.
*/
.chat-page .dashboard-content { display: flex; height: 100dvh; flex-direction: column; }
.chat-page .dashboard-main { display: flex; min-height: 0; flex: 1 1 auto; flex-direction: column; padding-block: 22px 20px; }
.chat-page .chat { flex: 1 1 auto; }
/* The footer keeps its links but gives the thread back the height it was
   spending on white space. */
.chat-page .dashboard-footer { flex: 0 0 auto; margin-top: 16px; padding-top: 13px; }

/* ==========================================================================
   Conversation list (attorney console)
   ========================================================================== */

/* The console's page scrolls, so the conversation needs a height of its own
   rather than one inherited from a full-height shell. */
.chat-shell { display: grid; height: clamp(430px, 68vh, 780px); min-height: 0; grid-template-columns: minmax(230px, 300px) minmax(0, 1fr); gap: 18px; }

.chat-aside {
  display: flex;
  min-height: 0;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--dashboard-border);
  border-radius: 8px;
  background: var(--portal-surface);
  box-shadow: 0 6px 22px rgba(16, 38, 83, 0.035);
}

.chat-aside-header {
  padding: 15px 18px;
  flex: 0 0 auto;
  border-bottom: 1px solid var(--dashboard-border);
}

.chat-aside-header h2 { margin: 0; color: var(--portal-link); font-family: Georgia, serif; font-size: 1.05rem; font-weight: 400; }
.chat-aside-header p { margin: 3px 0 0; color: var(--portal-muted); font-size: 0.66rem; }

.chat-list { margin: 0; padding: 6px; flex: 1 1 auto; list-style: none; overflow-y: auto; }

.chat-list button {
  display: grid;
  width: 100%;
  padding: 11px 12px;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  gap: 2px 11px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.chat-list button:hover { background: #f4f6fa; }
.chat-list button:focus-visible { outline: 2px solid var(--portal-navy); outline-offset: -2px; }

/* The selected conversation is marked with the firm's gold rather than a fill,
   so the list stays quiet while still answering "which one am I in". */
.chat-list button.is-active { background: #eef2f9; box-shadow: inset 3px 0 var(--portal-gold); }

/* Every cell is placed explicitly. The row mixes a spanning avatar with three
   auto-flow children, and letting the browser decide puts the timestamp in the
   wrong row the moment one of them is absent. */
.chat-list .chat-seal { grid-area: 1 / 1 / span 2 / 2; align-self: center; width: 34px; height: 34px; flex-basis: 34px; }
.chat-list strong { grid-area: 1 / 2 / 2 / 3; min-width: 0; color: var(--portal-link); font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-list time { grid-area: 1 / 3 / 2 / 4; color: var(--portal-muted); font-size: 0.6rem; white-space: nowrap; }
.chat-list small { grid-area: 2 / 2 / 3 / 3; min-width: 0; color: var(--portal-muted); font-size: 0.68rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-list .chat-list-empty { padding: 22px 14px; color: var(--portal-muted); font-size: 0.76rem; text-align: center; }

@media (max-width: 1080px) {
  /* On one column the list becomes a short index above the thread rather than a
     panel beside it, and the thread keeps a workable height of its own. */
  .chat-shell { height: auto; grid-template-columns: 1fr; }
  .chat-aside { max-height: 236px; }
  .chat-shell .chat { height: clamp(400px, 62vh, 640px); }
}

@media (max-width: 640px) {
  .chat-bubble { max-width: 86%; }
  .chat-composer { grid-template-columns: minmax(0, 1fr); }
  .chat-composer .portal-button { width: 100%; }
  /* On a phone the header had the firm's name over two lines, the office-hours
     note over three, and a reassurance squeezed against them. The name and the
     hours are what the client needs; the panel is visibly private without being
     told so in the same breath. */
  .chat-header-aside { display: none; }
  .chat-header { padding: 11px 14px; }
  .chat-header small { font-size: 0.62rem; }
  /* The composer is worth more than the footer on a screen this size. Privacy
     and terms stay one tap away in the menu. */
  .chat-page .dashboard-footer { display: none; }
}

/*
  A height question, not a width one. Rotate a 375×667 phone and the viewport is
  667px wide — past every width breakpoint on this file's ladder — and only
  375px tall before the browser chrome takes its share. (The keyboard is not
  what trips this: media queries measure the layout viewport, which a software
  keyboard does not shrink — 375 is short enough on its own.) The full-height
  column above then hands the header, the thread and the composer a share of
  almost nothing, and .chat's overflow: hidden clips whatever is left over with
  nothing in the chain able to scroll it back.

  So below 620px of height the page stops pretending to be an app: the column
  grows to its content and the document scrolls, the way it does everywhere
  else in the portal. The thread keeps half the viewport so the composer and
  the last message are both on screen at once.
*/
@media (max-height: 620px) {
  .chat-page .dashboard-content { height: auto; min-height: 100dvh; }
  .chat-page .chat { overflow: visible; }
  .chat-page .chat-thread { max-height: 50dvh; }
}

.dashboard-next-step { position: relative; overflow: hidden; border-top: 3px solid var(--portal-gold); }
.dashboard-card-symbol { display: grid; width: 37px; height: 37px; margin-bottom: 18px; place-items: center; border-radius: 50%; background: #f5edde; color: #9a7430; font-weight: 700; }
.dashboard-next-step p,
.dashboard-help p,
.dashboard-muted { color: var(--portal-muted); font-size: 0.78rem; line-height: 1.6; }
.dashboard-next-step > a { display: flex; margin-top: 18px; justify-content: space-between; color: var(--portal-link); font-size: 0.72rem; font-weight: 700; text-decoration: none; }
.dashboard-date { display: flex; align-items: center; gap: 13px; }
.dashboard-calendar-icon { display: grid; width: 43px; height: 43px; place-items: center; border-radius: 6px; background: #eef3fb; color: #315b9b; }
.dashboard-date strong { display: block; color: var(--portal-link); font-family: Georgia, serif; font-size: 1rem; }
.dashboard-date small { display: block; margin-top: 4px; color: var(--portal-muted); font-size: 0.66rem; }
.dashboard-muted { margin: 16px 0 0; padding-top: 15px; border-top: 1px solid var(--dashboard-border); font-size: 0.68rem; }

/* The next step as a banner across the top of the case panel.

   It was a card in the right-hand column, under the case list on a phone and
   beside it on a desktop, at the same weight as a calendar card that mostly
   said "Not scheduled". It is the one thing on the page asking the client to
   do something, so it goes first and takes the full width: tick, words, and
   the link drawn as the button it behaves like. Tokens throughout — the tick's
   pinned colours were there before and are kept for the light themes only. */
.dashboard-next-step-banner { display: grid; margin-bottom: 22px; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 18px 20px; }
.dashboard-next-step-banner .dashboard-card-symbol { margin-bottom: 0; }
.dashboard-next-step-body { min-width: 0; }
.dashboard-next-step-body p { margin: 6px 0 0; color: var(--portal-ink); font-size: 0.9rem; }
.dashboard-next-step-banner > a.portal-button { display: inline-flex; margin-top: 0; justify-content: center; gap: 8px; color: var(--portal-white); font-size: 0.72rem; white-space: nowrap; }

/* The date, as a strip inside Case status rather than a card of its own.
   Hidden by client.js when there is nothing to put in it. */
.dashboard-case-date { margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid var(--dashboard-border); }

/* Where a matter is: the office's four statuses as four nodes on a line.

   The badge on the same card says the word; this says where the word sits.
   Each node is a third of the way to the next, the segment leading into a
   reached node is gold and the rest is the rule colour, and the current node
   is a ring so it reads as "here" rather than "done" — a distinction colour
   alone would lose in the dusk theme. Tokens only; four themes draw this. */
.client-case-phases { display: grid; margin: 16px 0 0; padding: 0; grid-template-columns: repeat(4, minmax(0, 1fr)); list-style: none; }
.client-case-phases li { position: relative; display: flex; flex-direction: column; align-items: center; gap: 7px; text-align: center; }
.client-case-phases li::before { position: absolute; top: 5px; right: 50%; left: -50%; height: 2px; background: var(--portal-rule); content: ""; }
.client-case-phases li:first-child::before { display: none; }
.client-case-phases li.is-done::before,
.client-case-phases li.is-current::before { background: var(--portal-gold); }
.client-case-phases i { position: relative; z-index: 1; box-sizing: border-box; width: 12px; height: 12px; border: 2px solid var(--portal-rule); border-radius: 50%; background: var(--portal-surface); }
.client-case-phases li.is-done i { border-color: var(--portal-gold); background: var(--portal-gold); }
.client-case-phases li.is-current i { width: 14px; height: 14px; margin-top: -1px; border: 3px solid var(--portal-gold); background: var(--portal-surface); box-shadow: 0 0 0 3px rgba(198, 159, 85, 0.22); }
.client-case-phases span { color: var(--portal-muted); font-size: 0.62rem; font-weight: 700; letter-spacing: 0.06em; line-height: 1.3; text-transform: uppercase; }
.client-case-phases li.is-current span { color: var(--portal-link); }
.client-case-phases li.is-done span { color: var(--portal-ink); }
.dashboard-case-list .portal-list-item.is-closed .client-case-phases i { border-color: var(--portal-rule); background: var(--portal-rule); }
.dashboard-case-list .portal-list-item.is-closed .client-case-phases li::before { background: var(--portal-rule); }
.dashboard-case-list .portal-list-item.is-closed .client-case-phases span { color: var(--portal-muted); }

.dashboard-quick-actions > a { display: grid; padding: 13px 0; grid-template-columns: 34px minmax(0, 1fr) auto; align-items: center; gap: 10px; border-bottom: 1px solid var(--dashboard-border); color: var(--portal-link); text-decoration: none; }
.dashboard-quick-actions > a:last-child { padding-bottom: 0; border-bottom: 0; }
.dashboard-quick-actions > a > span:first-child { display: grid; width: 31px; height: 31px; place-items: center; border-radius: 5px; background: #f3f5f8; color: var(--dashboard-blue); font-size: 0.8rem; }
.dashboard-quick-actions strong { display: block; font-size: 0.72rem; }
.dashboard-quick-actions small { display: block; margin-top: 3px; color: var(--portal-muted); font-size: 0.62rem; }
.dashboard-quick-actions i { color: var(--portal-gold); font-style: normal; }
.dashboard-help { background: linear-gradient(145deg, #fff 40%, #faf6ee); }
.dashboard-help .portal-button { width: 100%; margin-top: 8px; text-decoration: none; }

.dashboard-footer { display: grid; margin-top: 34px; padding-top: 22px; grid-template-columns: 1fr auto 1fr; gap: 20px; border-top: 1px solid var(--dashboard-border); color: #848994; font-size: 0.62rem; }
.dashboard-footer p { margin: 0; }
.dashboard-footer nav { display: flex; gap: 16px; }
.dashboard-footer a { color: inherit; text-decoration: none; }
.dashboard-footer > span { justify-self: end; color: var(--portal-success); }

.dashboard-section-page { max-width: 1320px; }
.dashboard-page-heading { max-width: 720px; margin-bottom: 32px; }
/* The open form on the case page is an h2: that page has one h1 and it is
   "My case". Same rule, because it is the same heading in the same place. */
.dashboard-page-heading h1,
.dashboard-page-heading h2 { margin: 0 0 10px; color: var(--portal-link); font-family: Georgia, serif; font-size: clamp(2.2rem, 4vw, 3.4rem); font-weight: 400; letter-spacing: -0.035em; }
.dashboard-page-heading p { margin: 0; color: var(--portal-muted); line-height: 1.65; }
/* The two-column documents layout is gone with the upload card that filled its
   narrow side. The panel is one library card the width of the page. */
.dashboard-security-strip { display: grid; margin-top: 22px; padding: 20px 24px; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 16px; border: 1px solid #dfe8df; border-radius: 7px; background: #f8fbf8; color: var(--portal-success); }
.dashboard-security-strip > span { display: grid; width: 36px; height: 36px; place-items: center; border-radius: 50%; background: #e6f1e9; }
.dashboard-security-strip strong { color: var(--portal-link); font-family: Georgia, serif; font-size: 0.95rem; }
.dashboard-security-strip p { margin: 4px 0 0; color: var(--portal-muted); font-size: 0.68rem; line-height: 1.45; }
.dashboard-security-strip a { color: var(--portal-link); font-size: 0.7rem; font-weight: 700; text-underline-offset: 3px; }
.dashboard-help-grid { display: grid; margin-bottom: 22px; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px; }
.dashboard-help-grid .dashboard-card { display: flex; min-height: 255px; flex-direction: column; }
.dashboard-help-grid p { color: var(--portal-muted); font-size: 0.76rem; line-height: 1.65; }
.dashboard-help-grid a { margin-top: auto; color: var(--portal-link); font-size: 0.72rem; font-weight: 700; text-decoration: none; }
.dashboard-faq details { border-top: 1px solid var(--dashboard-border); }
.dashboard-faq details:last-child { border-bottom: 1px solid var(--dashboard-border); }
.dashboard-faq summary { padding: 17px 2px; color: var(--portal-link); font-size: 0.8rem; font-weight: 700; cursor: pointer; }
.dashboard-faq details p { margin: -5px 0 17px; padding-right: 30px; color: var(--portal-muted); font-size: 0.76rem; line-height: 1.65; }

.calendar-page-layout { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.6fr); gap: 22px; align-items: start; }
.calendar-toolbar { display: flex; margin-bottom: 15px; align-items: center; justify-content: space-between; gap: 20px; }
.calendar-toolbar h2 { margin: 0; }
/* Wraps because the console's toolbar carries five items, one of them a
   full-sentence button in Russian and Spanish. It never reaches a second line
   at desktop width; below 640px the wrap is what lets "Add event" take a row
   of its own above the pager. */
.calendar-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
.calendar-controls button { min-height: 32px; padding: 7px 10px; border: 1px solid var(--dashboard-border); border-radius: 4px; background: var(--portal-surface); color: var(--portal-link); font: inherit; font-size: 0.68rem; font-weight: 700; cursor: pointer; }
.calendar-controls button:hover { border-color: var(--portal-gold); }
.calendar-controls strong { min-width: 132px; color: var(--portal-link); font-family: Georgia, serif; font-size: 0.88rem; text-align: center; }
.calendar-legend { display: flex; margin-bottom: 15px; flex-wrap: wrap; gap: 10px 17px; color: var(--portal-muted); font-size: 0.62rem; }
.calendar-legend span { display: flex; align-items: center; gap: 5px; }
.calendar-legend i { width: 7px; height: 7px; border-radius: 50%; background: #7b8799; }
.calendar-legend .is-deadline i,
.calendar-event.is-deadline,
.calendar-upcoming-item.is-deadline::before { background: #a94848; }
.calendar-legend .is-meeting i,
.calendar-event.is-meeting,
.calendar-upcoming-item.is-meeting::before { background: #315b9b; }
/* Court is the one date on this calendar somebody can be ordered removed for
   missing, so it gets a hue of its own rather than sharing "appointment". */
.calendar-legend .is-court i,
.calendar-event.is-court,
.calendar-upcoming-item.is-court::before { background: #6a4a9c; }
.calendar-legend .is-appointment i,
.calendar-event.is-appointment,
.calendar-upcoming-item.is-appointment::before { background: #397455; }
.calendar-legend .is-reminder i,
.calendar-event.is-reminder,
.calendar-upcoming-item.is-reminder::before { background: #9a7430; }
.month-calendar { min-width: 0; overflow: hidden; border: 1px solid var(--dashboard-border); border-radius: 6px; }
.calendar-weekdays,
.calendar-days { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.calendar-weekdays { border-bottom: 1px solid var(--dashboard-border); background: #f7f8fa; }
.calendar-weekdays span { padding: 9px 5px; color: var(--portal-muted); font-size: 0.59rem; font-weight: 700; text-align: center; text-transform: uppercase; }
.calendar-day { min-width: 0; min-height: 104px; padding: 7px; border-right: 1px solid var(--dashboard-border); border-bottom: 1px solid var(--dashboard-border); background: var(--portal-surface); }
.calendar-day:nth-child(7n) { border-right: 0; }
.calendar-day:nth-last-child(-n + 7) { border-bottom: 0; }
.calendar-day.is-outside { background: #f7f5f2; color: #adaaa4; }
.calendar-day-number { display: grid; width: 24px; height: 24px; margin-bottom: 4px; place-items: center; border-radius: 50%; color: var(--portal-link); font-size: 0.68rem; font-weight: 700; }
.calendar-day.is-today .calendar-day-number { background: var(--portal-navy); color: var(--portal-white); }
.calendar-day.is-outside .calendar-day-number { color: #adaaa4; }
.calendar-event { display: block; margin-top: 3px; padding: 4px 5px; overflow: hidden; border-radius: 3px; color: var(--portal-white); font-size: 0.54rem; font-weight: 700; line-height: 1.2; text-overflow: ellipsis; white-space: nowrap; }
.calendar-day > small { display: block; margin-top: 4px; color: var(--portal-muted); font-size: 0.54rem; }
/* The one thing somebody wants two minutes before a video call, and until now
   it had no rule at all — a bare link at the tail of a <small>. Given its own
   line, and the URL breaks rather than widening the card. The real box comes
   only where a finger needs one, below: 44px of link in every desktop day cell
   would make the month grid taller for a pointer that never missed it. */
.calendar-join { display: block; margin-top: 6px; }
.calendar-join a { overflow-wrap: anywhere; }

@media (hover: none) and (pointer: coarse) {
  .calendar-join a { display: inline-flex; min-height: 44px; padding: 0 2px; align-items: center; font-size: 0.72rem; font-weight: 700; }
}

.calendar-upcoming { gap: 0; }
.calendar-upcoming-item { position: relative; display: grid; padding: 3px 0 17px 15px; gap: 4px; border-left: 1px solid var(--dashboard-border); }
.calendar-upcoming-item:last-child { padding-bottom: 0; border-left-color: transparent; }
.calendar-upcoming-item::before { position: absolute; top: 4px; left: -4px; width: 7px; height: 7px; border: 2px solid var(--portal-white); border-radius: 50%; box-shadow: 0 0 0 1px currentColor; content: ""; }
.calendar-upcoming-item time { color: var(--portal-gold); font-size: 0.58rem; font-weight: 700; text-transform: uppercase; }
.calendar-upcoming-item strong { color: var(--portal-link); font-size: 0.74rem; }
.calendar-upcoming-item small { overflow-wrap: anywhere; color: var(--portal-muted); font-size: 0.62rem; line-height: 1.4; text-transform: capitalize; }
/* ==========================================================================
   Calendar — the console's additions
   ==========================================================================

   The grid, the legend colours and the upcoming list above are shared with the
   client's calendar and are not touched here. What follows is only what a
   firm-wide calendar needs and one client's does not: filters, a clickable day,
   an agenda, and rows that can be acted on.
*/

.calendar-filters {
  display: flex;
  margin-bottom: 14px;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px 14px;
}

.calendar-filter { display: grid; min-width: 0; gap: 4px; }
.calendar-filter label,
.calendar-filter-label {
  color: var(--portal-muted);
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.calendar-filter select,
.calendar-filter input {
  min-height: 34px;
  padding: 6px 9px;
  border: 1px solid var(--dashboard-border);
  border-radius: 4px;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font: inherit;
  font-size: 0.74rem;
}

/* Combobox: an input that filters a list. See portal/lib/combobox.js. */
.portal-combobox { position: relative; }
.portal-combobox input { width: 100%; padding-right: 26px; }

.portal-combobox-clear {
  position: absolute;
  top: 50%;
  right: 6px;
  display: grid;
  width: 20px;
  height: 20px;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--portal-muted);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  transform: translateY(-50%);
}

.portal-combobox-clear:hover { background: #eef2f9; color: var(--portal-link); }

.portal-combobox-list {
  position: absolute;
  z-index: 12;
  top: calc(100% + 4px);
  left: 0;
  width: max(100%, 220px);
  max-height: 260px;
  margin: 0;
  padding: 4px;
  list-style: none;
  overflow-y: auto;
  border: 1px solid var(--dashboard-border);
  border-radius: 6px;
  background: var(--portal-surface);
  box-shadow: 0 14px 34px rgba(16, 38, 83, 0.16);
}

.portal-combobox-option {
  display: grid;
  padding: 7px 9px;
  gap: 1px;
  border-radius: 4px;
  cursor: pointer;
}

.portal-combobox-option strong { color: var(--portal-link); font-size: 0.76rem; font-weight: 700; }
.portal-combobox-option small { overflow: hidden; color: var(--portal-muted); font-size: 0.64rem; text-overflow: ellipsis; white-space: nowrap; }
.portal-combobox-option.is-active { background: #eef2f9; }
.portal-combobox-option[aria-selected="true"] { box-shadow: inset 3px 0 var(--portal-gold); }
.portal-combobox-option.is-empty-choice strong { color: var(--portal-muted); font-style: italic; }
.portal-combobox-empty { padding: 12px 9px; color: var(--portal-muted); font-size: 0.7rem; text-align: center; }

.calendar-filter-search { flex: 1 1 190px; }
.calendar-filter-search input { width: 100%; }
.calendar-filter-reset { margin-bottom: 8px; margin-left: auto; font-size: 0.68rem; }

.calendar-view-toggle { display: inline-flex; overflow: hidden; border: 1px solid var(--dashboard-border); border-radius: 4px; }
.calendar-view-toggle button {
  min-height: 34px;
  padding: 6px 13px;
  border: 0;
  background: var(--portal-surface);
  color: var(--portal-link);
  font: inherit;
  font-size: 0.7rem;
  font-weight: 700;
  cursor: pointer;
}

.calendar-view-toggle button + button { border-left: 1px solid var(--dashboard-border); }
.calendar-view-toggle button.is-active { background: var(--portal-navy); color: var(--portal-white); }

/* The legend doubles as the type filter, so its swatches are buttons. A type
   that is off keeps its colour and loses its weight — the colour is how you
   know which one you switched off. */
.calendar-legend-filter button {
  padding: 4px 9px 4px 6px;
  align-items: center;
  border: 1px solid var(--dashboard-border);
  border-radius: 999px;
  background: var(--portal-surface);
  color: var(--portal-muted);
  font: inherit;
  font-size: 0.62rem;
  cursor: pointer;
}

.calendar-legend-filter button i { background: currentColor; opacity: 0.35; }
.calendar-legend-filter button.is-on { border-color: var(--portal-navy); color: var(--portal-link); font-weight: 700; }
.calendar-legend-filter button.is-on i { opacity: 1; }
.calendar-legend-filter .is-deadline i { color: #a94848; }
.calendar-legend-filter .is-meeting i { color: #315b9b; }
.calendar-legend-filter .is-court i { color: #6a4a9c; }
.calendar-legend-filter .is-appointment i { color: #397455; }
.calendar-legend-filter .is-reminder i { color: #9a7430; }
.calendar-legend-filter button:focus-visible { outline: 2px solid var(--portal-navy); outline-offset: 2px; }

/*
  A 20px clear circle and a 22px filter chip under a thumb.

  Asked about the pointer rather than the width, like the link buttons up at the
  top of the file: neither of these is any easier to hit on a 1024px tablet, and
  a console read with a mouse does not want the extra height. The input already
  reserves room for the circle, so only the reserve and the circle move; the
  legend already wraps, so taller chips take a second row instead of pushing the
  toolbar sideways, and its gap comes in to pay for the height they gain.
*/
@media (hover: none) and (pointer: coarse) {
  .portal-combobox input { padding-right: 40px; }

  .portal-combobox-clear {
    right: 2px;
    width: 34px;
    height: 34px;
    font-size: 1.1rem;
  }

  .calendar-legend { gap: 8px; }

  .calendar-legend-filter button {
    min-height: 40px;
    padding: 8px 14px 8px 11px;
    font-size: 0.72rem;
  }
}

.calendar-summary { margin: 0 0 12px; color: var(--portal-muted); font-size: 0.68rem; }

/* A day you can open. Only the console's cells get this — see renderMonthGrid. */
.calendar-day.is-interactive { cursor: pointer; }
.calendar-day.is-interactive:hover { background: #f7f9fc; }
.calendar-day.is-interactive:focus-visible { outline: 2px solid var(--portal-navy); outline-offset: -2px; }
.calendar-day.is-selected { background: #eef2f9; box-shadow: inset 0 0 0 2px var(--portal-navy); }
.calendar-event.is-done { opacity: 0.5; text-decoration: line-through; }

/* Agenda */
.calendar-agenda { gap: 0; }
.calendar-agenda-day {
  margin: 16px 0 8px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--dashboard-border);
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 0.86rem;
}

.calendar-agenda-day:first-child { margin-top: 0; }

/* One event, in the agenda or the day panel. */
.calendar-event-row {
  position: relative;
  display: grid;
  padding: 11px 12px 11px 15px;
  gap: 4px;
  border: 1px solid var(--dashboard-border);
  border-radius: 6px;
  background: var(--portal-surface);
}

.calendar-agenda .calendar-event-row,
.calendar-day-list .calendar-event-row { margin-bottom: 9px; }
.calendar-event-row::before { position: absolute; inset: 0 auto 0 0; width: 3px; border-radius: 6px 0 0 6px; background: #7b8799; content: ""; }
.calendar-event-row.is-deadline::before { background: #a94848; }
.calendar-event-row.is-meeting::before { background: #315b9b; }
.calendar-event-row.is-court::before { background: #6a4a9c; }
.calendar-event-row.is-appointment::before { background: #397455; }
.calendar-event-row.is-reminder::before { background: #9a7430; }
.calendar-event-row.is-done { background: #fafafa; }
.calendar-event-row.is-done .calendar-event-head strong { color: var(--portal-muted); text-decoration: line-through; }

.calendar-event-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.calendar-event-head strong { min-width: 0; color: var(--portal-link); font-size: 0.82rem; }
.calendar-event-head time { flex: 0 0 auto; color: var(--portal-muted); font-size: 0.62rem; font-weight: 700; }
.calendar-event-who { margin: 0; color: var(--portal-link); font-size: 0.68rem; font-weight: 700; }
.calendar-event-detail { margin: 0; overflow-wrap: anywhere; color: var(--portal-muted); font-size: 0.68rem; line-height: 1.45; }

.calendar-event-flag {
  justify-self: start;
  padding: 2px 8px;
  border-radius: 999px;
  background: #f6e7e7;
  color: #8f3b3b;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.calendar-event-source { margin: 3px 0 0; color: var(--portal-muted); font-size: 0.62rem; font-style: italic; }
.calendar-event-actions { display: flex; margin-top: 3px; gap: 14px; }
.calendar-event-actions .portal-link-button { font-size: 0.66rem; }
.calendar-event-actions .is-danger { color: var(--portal-danger); }

/* Open and Delete on one line, with the destructive one set apart rather than
   sitting flush against the button somebody actually means to press. */
.portal-document-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.portal-document-delete {
  margin-left: auto;
  color: var(--portal-danger);
  font-size: 0.72rem;
}

.portal-document-delete[data-armed="true"] { font-weight: 700; }
.portal-document-delete[disabled] { opacity: 0.5; cursor: default; }

/* The way back, sitting in the status line beside what it undoes. Underlined
   and in the link colour so it reads as the one thing here to press — the text
   next to it is a report, this is an offer. */
.portal-undo {
  margin-left: 10px;
  color: var(--portal-link);
  font-weight: 700;
  text-decoration: underline;
}

.portal-undo[disabled] { opacity: 0.5; cursor: default; text-decoration: none; }

/* A binned document reads as set aside rather than as one of the live ones:
   muted, and its name struck through, so the two lists cannot be confused at a
   glance if both are on screen. */
.portal-binned { opacity: 0.85; }
.portal-binned h3 { color: var(--portal-muted); text-decoration: line-through; }
.portal-binned .portal-meta { color: var(--portal-danger); }

#admin-document-bin-count { color: var(--portal-muted); font-weight: 400; }

.calendar-day-list { gap: 0; margin-bottom: 14px; }

@media (max-width: 1080px) {
  .calendar-filter-reset { margin-left: 0; }
}

.client-history-card { margin-top: 22px; }
.history-count { color: var(--portal-muted); font-size: 0.65rem; }
.client-history { gap: 0; }
.client-history-item { display: grid; padding: 0 0 23px; grid-template-columns: 22px minmax(0, 1fr); gap: 13px; }
.client-history-item:last-child { padding-bottom: 0; }
.client-history-marker { position: relative; width: 11px; height: 11px; margin-top: 3px; border: 3px solid var(--portal-white); border-radius: 50%; background: var(--portal-gold); box-shadow: 0 0 0 1px var(--portal-gold); }
.client-history-marker::after { position: absolute; top: 10px; left: 2px; width: 1px; height: 42px; background: var(--dashboard-border); content: ""; }
.client-history-item:last-child .client-history-marker::after { display: none; }
.client-history-item.is-document .client-history-marker { background: #315b9b; box-shadow: 0 0 0 1px #315b9b; }
.client-history-item.is-message .client-history-marker { background: #397455; box-shadow: 0 0 0 1px #397455; }
.client-history-item strong { color: var(--portal-link); font-size: 0.76rem; }
.client-history-item p { margin: 4px 0; color: var(--portal-muted); font-size: 0.68rem; line-height: 1.45; }
.client-history-item time { color: #92969e; font-size: 0.58rem; }

@media (max-width: 1120px) {
  .dashboard-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dashboard-layout { grid-template-columns: 1fr; }
  .dashboard-secondary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dashboard-quick-actions { grid-row: span 2; }
  .calendar-page-layout { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  .dashboard-sidebar { display: none; }
  .dashboard-content { width: 100%; margin-left: 0; }
  /* With the sidebar gone this button is the only route to every other page in
     the portal, so it gets a thumb-sized box rather than a 31px label. */
  .dashboard-menu { display: inline-flex; min-height: 44px; padding: 10px 14px; align-items: center; border: 1px solid var(--dashboard-border); border-radius: 4px; background: var(--portal-surface); color: var(--portal-link); font: inherit; font-size: 0.72rem; font-weight: 700; }

  /*
    An overlay, not a strip in the flow. Sticky-in-flow it shoved the page down
    by its own height every time it opened; on Messages, where
    .dashboard-content is a fixed 100dvh column, that pushed the composer past
    the bottom edge with nothing in the chain able to scroll it back — the Send
    button was simply gone. Taking the sheet out of flow leaves the page
    underneath exactly where it was.

    top: 72px is .dashboard-topbar's min-height, the same offset the subtabs row
    and the console tab strip are pinned to; the topbar must stay one row for
    all three to hold. z-index 9 clears the tab strip at 7 and the topbar at 8,
    so an open menu covers both. It scrolls inside itself because seven rows at
    44px plus a sign-out do not fit a landscape phone.
  */
  .dashboard-mobile-nav {
    position: fixed;
    z-index: 9;
    top: 72px;
    right: 0;
    left: 0;
    display: grid;
    max-height: calc(100dvh - 72px);
    padding: 12px 20px;
    gap: 2px;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-bottom: 1px solid var(--dashboard-border);
    background: var(--portal-surface);
    box-shadow: 0 12px 24px rgba(16, 38, 83, 0.18);
  }

  /* The menu button opens and closes this by toggling [hidden]. An author
     `display` beats the UA rule for that attribute, so the closed state has to
     be restated here or the sheet never shuts. */
  .dashboard-mobile-nav[hidden] { display: none; }

  /* Full-height rows with a hairline of ground between them, so a thumb that
     lands between two links lands on neither instead of on the wrong one. */
  .dashboard-mobile-nav a { display: flex; min-height: 44px; padding: 10px 12px; align-items: center; color: var(--portal-link); font-size: 0.78rem; font-weight: 700; text-decoration: none; }

  /* Sign out belongs in the menu on a phone: the sidebar that used to carry it
     is display: none from here down. Ruled off above so it reads as leaving
     rather than as one more place to go. */
  .dashboard-mobile-logout {
    display: flex;
    width: 100%;
    min-height: 44px;
    padding: 10px 12px;
    align-items: center;
    border: 0;
    border-top: 1px solid var(--dashboard-border);
    background: transparent;
    color: var(--portal-link);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    text-align: left;
    cursor: pointer;
  }
  .dashboard-help-grid { grid-template-columns: 1fr; }
  .dashboard-help-grid .dashboard-card { min-height: 0; }
}

@media (max-width: 640px) {
  .dashboard-topbar { padding-inline: 18px; }

  /*
    portal.js prepends a language <select> and a theme <select> into this bar,
    and a native select refuses to shrink below its widest option — so the
    action cluster measured a fixed 362px and carried the bell and the avatar
    off the right-hand edge of a 375px phone. Nothing in the chain scrolls, so
    they were not merely awkward, they were unreachable.

    The bar is NOT allowed to wrap out of it. Three rules pin themselves to a
    72px topbar — the subtabs row, the mobile nav sheet, the console tab strip
    — and a second row would slide all three under it. So the selects are
    capped instead.

    The budget is the content box less the Menu button, which is a flex sibling
    in the same row and is easy to leave out of the sum: at 320px that is 284px
    less 60px of Menu, so the actions cluster has 224px to spend. The 420px step
    below settles it at 38 + 52 + 44 + 44 + three 6px gaps = 196px, leaving the
    title around 28px to ellipse into.

    Both circles are pinned with a flex basis rather than width alone. Width on
    its own loses to flex-shrink in a row this tight: measured, they came out
    28px at 320px and 39px at 390px — under the 44px this rule exists to
    guarantee. 44px still clears the bar's 72px floor (44 + 24px of padding =
    68), so the offset the other three rules depend on does not move.
  */
  .dashboard-topbar-title { min-width: 0; }
  /* Both lines, not just the bold one: the subtitle wrapping to two lines took
     the bar to 77px at 320px, and the three rules pinned to top: 72px do not
     move with it. */
  .dashboard-topbar-title strong,
  .dashboard-topbar-title span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* The cluster is held out of the shrink negotiation altogether. Pinning only
     the circles just moves the loss along the row — the appearance picker then
     absorbed it and measured 14px, narrower than the mark it exists to show.
     With the cluster fixed, the title is the one thing that gives, which is
     what the ellipsis above is for. */
  .dashboard-topbar-actions { flex: 0 0 auto; gap: 8px; }
  .dashboard-topbar-actions .portal-lang { flex: 0 0 auto; min-width: 0; }
  .dashboard-topbar-actions .portal-lang-select { min-width: 0; width: 4.75rem; }
  /* The appearance picker is read by its mark, not its word, so it can lose
     more — but the mark still needs its 26px of room on the left. */
  .dashboard-topbar-actions .portal-theme-switch .portal-lang-select { width: 3.4rem; padding-left: 26px; padding-right: 4px; }
  .dashboard-icon-button,
  .dashboard-avatar { flex: 0 0 44px; width: 44px; height: 44px; }

  .dashboard-main { padding: 30px 16px 22px; }
  .dashboard-welcome { align-items: flex-start; flex-direction: column; }
  .dashboard-stats { grid-template-columns: 1fr 1fr; gap: 10px; }
  .dashboard-stat { min-height: 82px; padding: 13px; }
  .dashboard-stat-icon { width: 38px; height: 38px; flex-basis: 38px; }
  .dashboard-stat strong { font-size: 1.35rem; }
  .dashboard-card { padding: 20px 16px; }
  /* Cards carry text nobody composed for a 288px column — a file name straight
     off a phone camera, the signed-in address. One inherited guard covers every
     card body rather than chasing each one; the ≤560px net a few blocks down
     lists the portal-page selectors but never reaches .dashboard-card. */
  .dashboard-card { overflow-wrap: anywhere; }
  .dashboard-secondary { grid-template-columns: 1fr; }
  .dashboard-quick-actions { grid-row: auto; }
  .dashboard-card-header { flex-direction: column; }
  .dashboard-case-list .portal-list-item h3 { padding-right: 0; }
  .dashboard-case-list .portal-badge { position: static; margin-top: 8px; }
  .dashboard-document-list .portal-list-item { grid-template-columns: 1fr; }
  .dashboard-document-list .portal-list-item .portal-button { grid-column: 1; grid-row: auto; margin-top: 8px; }
  .dashboard-upload .portal-button { width: 100%; }
  .dashboard-form-footer { align-items: stretch; flex-direction: column; }
  .dashboard-form-footer .portal-button { width: 100%; }

  /* A form waiting to be filled in is [name + when][Start] on a 1fr auto grid
     with the button spanning both rows — the same shape the documents list two
     lines up has already had collapsed. Below 640 the button takes its own row
     at full width, which is where a thumb looks for the one action on a card.

     Both classes are on the selector because the base rule sits ~3,400 lines
     further down: at equal specificity this block would lose on source order,
     and client-forms.js writes the element as "portal-list-item
     client-form-row", so the pair always matches. */
  .portal-list-item.client-form-row { grid-template-columns: minmax(0, 1fr); }
  .portal-list-item.client-form-row button {
    width: 100%;
    grid-column: 1;
    grid-row: auto;
    margin-top: 10px;
  }

  /* "Send this", against a document the office has asked for — the primary
     action on this tab, and the file it sends is usually a photograph taken on
     the phone that is reading it. The height comes from the coarse-pointer rule
     on picker labels; what is left is the width, since that rule is
     `fit-content` by default. The row lives in the library now, so the selector
     names the row rather than the card it used to sit in. */
  .dashboard-document-list .portal-list-item.is-needed .portal-button { width: 100%; }

  /* Same reasoning for the intake's action row, now that it has a rule at all. */
  .portal-form-actions .portal-button { width: 100%; }

  .dashboard-footer { grid-template-columns: 1fr; text-align: center; }
  /* The links are 0.62rem with no padding — about a 13px target, and on the
     client pages this row is where Privacy and Terms live. Boxes rather than
     bigger type, so the footer stays the quiet thing it is; the 16px gap comes
     in to 4px because the boxes now carry their own space and three of them at
     the old gap do not fit 288px. */
  .dashboard-footer nav { justify-content: center; gap: 4px; }
  .dashboard-footer a { display: inline-flex; min-height: 44px; padding: 0 6px; align-items: center; }
  .dashboard-footer > span { justify-self: center; }

  /* The one link the dashboard exists to push a client toward, and it renders
     as two 16px text fragments at opposite ends of a row. Negative margins keep
     the words where they were drawn while the box grows outwards into the
     card's own padding. */
  .dashboard-next-step > a { min-height: 44px; margin-inline: -12px; padding: 0 12px; align-items: center; }
  /* The banner stacks: tick beside the words, button underneath at full
     width. The negative margin above is for the bare link it replaced. */
  .dashboard-next-step-banner { grid-template-columns: auto minmax(0, 1fr); }
  .dashboard-next-step-banner > a.portal-button { grid-column: 1 / -1; width: 100%; margin-inline: 0; padding: 11px 19px; white-space: normal; }
  .client-case-phases span { font-size: 0.56rem; letter-spacing: 0.02em; }
  .dashboard-security-strip { grid-template-columns: auto 1fr; padding: 17px; }
  .dashboard-security-strip a { grid-column: 2; }

  /* The matter header is a title beside two buttons, and the pair needs ~329px
     against a 288px column in English — more in ru and es, where "Опубликовать
     обновление" and "Publicar una actualización" carry the whole page into
     horizontal scroll at 320, 390 and 414px. Below 640 the header stops being
     a row: the title takes its line, the buttons take theirs and split it.

     Scoped through .case-detail because the header's own rule lives further
     down the file — an equal-specificity override up here would lose on source
     order. Width rather than align-items: stretch for the same reason; the
     base rule sets align-items: flex-start and would win. */
  .case-detail .case-detail-head { flex-direction: column; }
  .case-detail .case-detail-head-main,
  .case-detail .case-detail-head-actions { width: 100%; }
  /* Splitting the row fixed where the buttons sit but not what they are: both
     wear .portal-button-small, whose 36px floor this rule never reset — the
     one target the pass left short. */
  .case-detail-head-actions .portal-button { flex: 1 1 auto; min-height: 44px; justify-content: center; }

  .calendar-toolbar { align-items: flex-start; flex-direction: column; }
  .calendar-controls { width: 100%; justify-content: space-between; }
  .calendar-controls strong { min-width: 0; }
  /* The arrows are the only way off the current month and they were a 32px
     box around a single glyph. */
  .calendar-controls button { min-height: 44px; min-width: 44px; padding: 7px 12px; }
  /* Add event is the one thing on this toolbar that is not navigation, so on a
     phone it stops competing for the pager's row and takes its own above it.
     The ID is needed to beat .portal-button-small's 36px floor. */
  #admin-calendar-add { order: -1; min-height: 44px; flex: 1 1 100%; justify-content: center; }
  .calendar-card { padding-inline: 10px; }
  .calendar-weekdays span { font-size: 0.5rem; }
  .calendar-day { min-height: 74px; padding: 4px 2px; }
  .calendar-day-number { width: 20px; height: 20px; font-size: 0.6rem; }
  .calendar-event { height: 7px; padding: 0; border-radius: 99px; color: transparent; font-size: 0; }
  /* Seven columns in a 264px card is a 37px day, and every day is a real
     target — calendar.js gives each one role="button". Rather than shrink the
     cell below a fingertip, the grid keeps a 44px column (7 × 44 = 308) and
     scrolls sideways inside the card, which already has its own padding. The
     page body stays put; only the month moves. */
  .month-calendar { overflow-x: auto; overflow-y: hidden; }
  .calendar-weekdays,
  .calendar-days { min-width: 308px; }
}

/*
  The narrowest phones still in service, where the topbar sum above stops
  balancing.

  At 640px the two pickers can afford their comfortable widths. At 320px they
  cannot: Menu (60) + theme (54) + language (76) + two 44px circles + three 8px
  gaps comes to 302px inside a 284px content box, and the overflow is paid for
  by whatever will shrink — which, before the flex basis was pinned, was the
  circles. Taking the pickers down and the gaps in by 2px brings the row to
  196px and hands the page title what is left.

  This block must stay AFTER the 640px one: the selectors are identical, so it
  wins on source order rather than specificity.
*/
@media (max-width: 420px) {
  .dashboard-topbar-actions { gap: 6px; }
  .dashboard-topbar-actions .portal-lang-select { width: 3.25rem; }
  .dashboard-topbar-actions .portal-theme-switch .portal-lang-select { width: 2.4rem; padding-left: 24px; padding-right: 2px; }
}

@media (max-width: 820px) {
  .login-shell { grid-template-columns: 1fr; }
  .login-welcome { min-height: 390px; padding: 28px; }
  .login-welcome-copy { margin: 60px 0; padding: 0; }
  .login-welcome-copy h1 { font-size: clamp(2.7rem, 11vw, 4.5rem); }
  .login-panel { min-height: auto; padding: 42px 28px 24px; }
  .login-back { margin-bottom: 50px; }

  .portal-card {
    grid-column: 1 / -1;
  }

  .portal-form-row {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .login-welcome { min-height: 340px; padding: 22px 20px; }
  .login-welcome-copy { margin: 52px 0 36px; }
  .login-welcome-copy p { font-size: 0.93rem; }
  .login-assurance { display: none; }
  .login-panel { padding: 34px 20px 20px; }
  /* Grown from a bare line box to a real target. The 12px it gains in height
     comes back off the margin below it so the heading does not move, and the
     negative left margin keeps the arrow on the same edge as everything else
     in the panel now that the box has padding of its own. */
  .login-back { min-height: 44px; margin-bottom: 30px; margin-left: -10px; padding: 0 10px; }
  .login-heading { margin-bottom: 28px; }

  /* The two controls a client reaches for when the password does not work, and
     both shipped as ~14px of underlined text. Show password sits in a flex row
     against its label, so it gets the box back off its own right margin;
     Forgot your password? is a grid child under the 54px submit button and its
     -6px pull-up would have put the two in contact once it grew. */
  .password-toggle {
    display: inline-flex;
    min-height: 44px;
    margin: 0 -8px 0 0;
    padding: 0 8px;
    align-items: center;
  }

  .forgot-link {
    display: inline-flex;
    min-height: 44px;
    margin: 0 auto;
    align-items: center;
  }

  .portal-topbar-inner,
  .portal-main {
    padding-left: 18px;
    padding-right: 18px;
  }

  .portal-topbar-inner {
    align-items: flex-start;
  }

  .portal-nav {
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
  }

  .portal-card {
    padding: 22px 18px;
  }

  .portal-page-heading,
  .portal-card,
  .portal-field,
  .portal-list-item {
    min-width: 0;
    overflow-wrap: anywhere;
  }
}

@media (max-width: 420px) {
  .portal-topbar-inner {
    flex-direction: column;
    gap: 10px;
  }

  /*
    The ≤560px block above stacks this nav safely, and this rule used to undo
    that — forcing it back into a single unbreakable row at the width where
    there is least room for one. On welcome.html and debug.html portal.js
    mounts a language select and an appearance select into it (login.html gets
    the language one only), and a native select will not shrink below its
    widest option, so the row measured wider than the 320px page it sat on.

    A wrapping row rather than a return to the column: the links are short and
    two of them fit side by side, which is the layout this rule was reaching
    for. The selects take a whole line when that is what they need. align-items
    comes back to centre because the ≤560px block set flex-end for its column —
    right-alignment there, but in a row it bottom-aligns a short link against
    the taller select beside it.
  */
  .portal-nav {
    width: 100%;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
  }

  .portal-nav .portal-lang { flex: 1 1 auto; }
  .portal-nav .portal-lang-select { width: 100%; }
}

/* Two-step verification setup (security.html) */
.portal-qr {
  display: block;
  width: 208px;
  height: 208px;
  margin: 24px 0 8px;
  padding: 12px;
  background: var(--portal-surface);
  border: 1px solid var(--portal-rule);
  border-radius: 8px;
}

.portal-secret {
  display: inline-block;
  margin-top: 6px;
  padding: 6px 10px;
  background: var(--portal-cream);
  border: 1px solid var(--portal-rule);
  border-radius: 6px;
  color: var(--portal-ink);
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  word-break: break-all;
}

/* Diagnostics view (debug.html) */
.portal-log {
  margin-top: 20px;
  padding: 16px;
  max-height: 60vh;
  overflow: auto;
  background: var(--portal-navy-deep);
  color: var(--portal-cream);
  border-radius: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.75rem;
  line-height: 1.6;
  white-space: pre;
  tab-size: 2;
}

/* ==========================================================================
   Attorney administration console (admin.html)
   Shares the client dashboard shell so both halves of the portal match.
   The sidebar doubles as a tab rail: sections switch in place, no page loads.
   ========================================================================== */

/* Card borders and accents come from the dashboard tokens. The page keeps the
   cream background set by body.portal-body, matching the client dashboard. */
.admin-console {
  --dashboard-bg: #f4f6fa;
  --dashboard-border: #e3e7ee;
  --dashboard-blue: #244b91;
}

.admin-rail-label {
  margin: 0 14px 10px;
  color: rgba(255, 255, 255, 0.38);
  font-size: 0.56rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.admin-tabs { display: grid; gap: 7px; }

.admin-tab {
  display: grid;
  width: 100%;
  min-height: 44px;
  padding: 11px 14px;
  grid-template-columns: 20px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: rgba(255, 255, 255, 0.65);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

.admin-tab:hover { background: rgba(255, 255, 255, 0.09); color: var(--portal-white); }

.admin-tab.is-active {
  background: rgba(255, 255, 255, 0.09);
  box-shadow: inset 3px 0 #d9ba7b;
  color: var(--portal-white);
}

.admin-tab:focus-visible {
  outline: 2px solid #d9ba7b;
  outline-offset: 2px;
}

/* Counts are real information — how much is waiting in each section — so they
   only appear once there is something to report. */
.admin-tab-count {
  min-width: 21px;
  padding: 2px 6px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.82);
  font-size: 0.62rem;
  font-weight: 700;
  text-align: center;
}

.admin-tab.is-active .admin-tab-count { background: #d9ba7b; color: #33240a; }
.admin-rail-note { margin-top: auto; }

/* Compact tab strip that replaces the rail on narrow screens. */
.admin-mobile-tabs { display: none; }

.admin-panel-heading { margin-bottom: 26px; }

.admin-panel-heading h1 {
  margin: 0 0 7px;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: clamp(1.9rem, 3.4vw, 2.7rem);
  font-weight: 400;
  letter-spacing: -0.03em;
}

.admin-panel-heading p { margin: 0; max-width: 60ch; color: var(--portal-muted); }

.admin-layout { display: grid; align-items: start; gap: 20px; }
.admin-layout-split { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
.admin-layout-even { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.admin-layout-single { grid-template-columns: minmax(0, 760px); }

/* Several cards stacked inside one column of an .admin-layout grid. */
.admin-column { display: grid; align-content: start; gap: 20px; min-width: 0; }

.admin-console .portal-list { max-height: 470px; overflow-y: auto; }
.admin-console .dashboard-card .portal-table-wrap { margin-top: 0; }

@media (max-width: 1120px) {
  .admin-layout-split,
  .admin-layout-even { grid-template-columns: 1fr; }
}

@media (max-width: 900px) {
  .admin-mobile-tabs {
    position: sticky;
    z-index: 7;
    top: 72px;
    display: flex;
    padding: 10px 16px;
    gap: 8px;
    overflow-x: auto;
    border-bottom: 1px solid var(--dashboard-border);
    background: var(--portal-surface);
    scrollbar-width: none;
  }

  .admin-mobile-tabs::-webkit-scrollbar { display: none; }

  .admin-mobile-tabs button {
    display: inline-flex;
    padding: 9px 15px;
    flex: 0 0 auto;
    align-items: center;
    gap: 7px;
    border: 1px solid var(--dashboard-border);
    border-radius: 99px;
    background: var(--portal-surface);
    color: var(--portal-muted);
    font: inherit;
    font-size: 0.74rem;
    font-weight: 700;
    cursor: pointer;
  }

  .admin-mobile-tabs button.is-active {
    border-color: var(--portal-navy);
    background: var(--portal-navy);
    color: var(--portal-white);
  }

  .admin-mobile-tabs button:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

  /*
    Log out is the strip's one non-tab, and the strip scrolls with its
    scrollbar hidden — left in the flow it sat past the fold with nothing
    saying more existed. Sticky pins it to the right edge of the scrollport
    while the tabs pass underneath; the auto margin holds it at that edge even
    when the tabs don't fill the row. The ring of surface colour is a backplate,
    so a pill scrolling under it reads as behind rather than colliding, and the
    ink colour marks it as a door out rather than a ninth section.
  */
  .admin-mobile-tabs .admin-mobile-logout {
    position: sticky;
    right: 0;
    margin-left: auto;
    border-color: var(--portal-link);
    color: var(--portal-link);
    box-shadow: 0 0 0 6px var(--portal-surface);
  }
}

@media (max-width: 640px) {
  .admin-panel-heading { margin-bottom: 20px; }
  .admin-console .portal-list { max-height: none; }
}

/* Account settings — shared by the attorney console tab and the client page */
.settings-grid {
  display: grid;
  align-items: start;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
}

.settings-readout {
  margin: 0 0 18px;
  color: var(--portal-muted);
  font-size: 0.82rem;
  line-height: 1.6;
}

.settings-readout strong { color: var(--portal-link); }

.settings-facts { display: grid; margin: 0; gap: 14px; }
.settings-facts > div { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.settings-facts > div + div { padding-top: 14px; border-top: 1px solid var(--dashboard-border); }

.settings-facts dt {
  color: var(--portal-muted);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.settings-facts dd {
  margin: 0;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 0.92rem;
  text-align: right;
}

/*
  A fact with something to press beside it.

  The row is right-aligned, so the control follows the value rather than
  floating away from it; wrapped so a long date and a button do not push the
  label off a narrow drawer.
*/
.detail-fact-action {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
}

@media (max-width: 1120px) {
  .settings-grid { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  .settings-facts > div { align-items: flex-start; flex-direction: column; gap: 4px; }
  .settings-facts dd { text-align: left; }
}

/* Portal language control (mounted by portal/i18n.js) */
.portal-lang { display: inline-flex; align-items: center; }

.portal-lang-select {
  min-height: 38px;
  padding: 7px 10px;
  border: 1px solid var(--dashboard-border, var(--portal-rule));
  border-radius: 5px;
  background: var(--portal-surface);
  color: var(--portal-link);
  font: inherit;
  font-size: 0.76rem;
  font-weight: 700;
  cursor: pointer;
}

.portal-lang-select:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

/* On the login page the control sits above the heading, aligned right. */
.login-panel-inner .portal-lang { display: flex; margin-bottom: 18px; justify-content: flex-end; }

@media (max-width: 640px) {
  /* This used to shrink the picker to 34px — smaller on a phone than the 38px
     it gets on a desktop with a mouse, which is backwards. The width is what
     was actually costing the topbar its room, and that is capped above; the
     height goes the other way, to something a thumb can find. */
  .portal-lang-select { min-height: 44px; padding: 6px 8px; font-size: 0.7rem; }
}

/* Settings sub-tabs */
.settings-tabs {
  display: flex;
  margin-bottom: 24px;
  padding-bottom: 2px;
  gap: 6px;
  overflow-x: auto;
  border-bottom: 1px solid var(--dashboard-border, var(--portal-rule));
  scrollbar-width: none;
}

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

.settings-tab {
  position: relative;
  padding: 11px 16px;
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--portal-muted);
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.settings-tab:hover { color: var(--portal-link); }

.settings-tab.is-active { color: var(--portal-link); }

.settings-tab.is-active::after {
  position: absolute;
  right: 12px;
  bottom: -3px;
  left: 12px;
  height: 2px;
  background: var(--portal-gold);
  content: "";
}

.settings-tab:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: -2px; }
.settings-panel:focus { outline: none; }

@media (max-width: 640px) {
  .settings-tabs { margin-bottom: 18px; }
  .settings-tab { padding: 10px 12px; font-size: 0.74rem; }

  /* The strip scrolls sideways with the scrollbar deliberately hidden and no
     fade or arrow in its place, so nothing on the page says Preferences is
     there at all — and in Russian the four labels run ~460px against 288px of
     column, putting two of them off the end. Four tabs over two rows need no
     discovering; that is worth more here than the tidy single line. */
  .settings-tabs { flex-wrap: wrap; overflow-x: visible; }
  .settings-tab { flex: 1 1 auto; }
}

/* Inline checkbox inside a portal form */
.portal-check {
  display: flex;
  margin-top: -4px;
  align-items: flex-start;
  gap: 10px;
  color: var(--portal-muted);
  font-size: 0.78rem;
  line-height: 1.5;
  cursor: pointer;
}

.portal-check input { width: 17px; height: 17px; margin: 1px 0 0; flex: 0 0 17px; accent-color: var(--portal-navy); }
.portal-check:hover { color: var(--portal-link); }

/* Client record panel (attorney console) */
.portal-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.portal-table-actions { text-align: right; white-space: nowrap; }

/* Spacing by margin, not by flex.

   This class is on the <td> itself, and `display: flex` on a table cell drops it
   out of the table's layout — the cell stops stretching to the row height and
   its border draws short. Margins between the children keep the buttons apart
   and leave the cell a cell.

   `vertical-align` matters now that two of the three are inline-flex buttons
   rather than text links: without it they sit on the text baseline and the row
   looks a pixel out. */
.portal-table-actions > * + * { margin-left: 9px; }
.portal-table-actions .portal-button,
.portal-table-actions .portal-link-button { vertical-align: middle; }

/* A centred dialog, for a form. The client drawer beside it slides in from the
   edge because it is a record you read; this is a short thing you fill in and
   dismiss, so it sits in the middle and does not move. */
.portal-modal-backdrop {
  position: fixed;
  z-index: 30;
  inset: 0;
  background: rgba(10, 24, 53, 0.45);
}

.portal-modal {
  position: fixed;
  z-index: 31;
  top: 50%;
  left: 50%;
  display: flex;
  width: min(620px, calc(100% - 32px));
  max-height: min(88vh, 780px);
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--dashboard-border);
  border-radius: 9px;
  background: var(--portal-surface);
  box-shadow: 0 26px 60px rgba(16, 38, 83, 0.26);
  transform: translate(-50%, -50%);
}

.portal-modal-head {
  display: flex;
  padding: 20px clamp(18px, 3vw, 26px) 16px;
  flex: 0 0 auto;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 1px solid var(--dashboard-border);
}

.portal-modal-head h2 { margin: 0; color: var(--portal-link); font-family: Georgia, serif; font-size: 1.25rem; font-weight: 400; }
.portal-modal-head p { margin: 5px 0 0; color: var(--portal-muted); font-size: 0.72rem; }
.portal-modal-head p:empty { display: none; }
.portal-modal-body { padding: 20px clamp(18px, 3vw, 26px) 24px; flex: 1 1 auto; overflow-y: auto; }
.portal-modal-actions { display: flex; margin-top: 4px; justify-content: flex-end; gap: 10px; }

/* The dialog itself clips, and every dialog on this page gets its scroll back
   from the .portal-modal-body above. A dialog that was written without that
   wrapper therefore has no scroll container anywhere in its chain: whatever
   falls past min(88vh, 780px) — a confirmation password box, Cancel, Delete
   permanently — cannot be reached by scrolling, dragging or tabbing.

   The markup fix is to give those dialogs a body like the other fourteen have.
   This is the guard for the next one somebody writes without it, and it only
   ever matches a dialog that is already broken at every width. */
.portal-modal:not(:has(> .portal-modal-body)) { overflow-y: auto; }

body.has-open-modal { overflow: hidden; }

/* Both head rows are [text block][Close] over a parent that clips, and neither
   flex item can shrink below its widest word by default. An address like
   maria.gonzalez-hernandez@example.com is one such word, so the text block grew
   to fit it and pushed Close outside the clip — leaving no visible way to shut
   the dialog or the drawer. min-width lets the text column give way, the wrap
   lets the address break, and Close is pinned out of the negotiation entirely.

   #form-editor-modal (below) and #doc-choice-heading already carry this fix as
   one-offs; this is the same repair stated once for every dialog. */
.portal-modal-head > div,
.client-detail-head > div { min-width: 0; }
.portal-modal-head p,
.client-detail-head p { overflow-wrap: anywhere; }
.portal-modal-head .portal-link-button,
.client-detail-head .portal-link-button { flex: none; }

@media (max-width: 640px) {
  .portal-modal-actions { flex-direction: column-reverse; }
  .portal-modal-actions .portal-button { width: 100%; }

  /* Two dialogs stop being cards floating in the middle of a phone and become
     the whole screen. Both are long — an invitation with a form and a parts
     picker, a deletion with a name list and a password — and a centred box
     capped at 88vh spends the room it has on the gap around itself. Taking
     the full height gives the body every pixel to scroll inside, and squaring
     the corners stops a full-bleed sheet from looking like a mis-sized card.

     The ids are what beat .portal-modal's own geometry; nothing here needs to
     out-rank a class. */
  #invite-modal,
  #remove-confirm {
    top: 0;
    left: 0;
    width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    transform: none;
  }
}

.client-detail-backdrop {
  position: fixed;
  z-index: 30;
  inset: 0;
  background: rgba(10, 24, 53, 0.45);
}

.client-detail {
  position: fixed;
  z-index: 31;
  inset: 0 0 0 auto;
  display: flex;
  width: min(560px, 100%);
  flex-direction: column;
  overflow: hidden;
  border-left: 1px solid var(--dashboard-border);
  background: var(--portal-surface);
  box-shadow: -18px 0 50px rgba(16, 38, 83, 0.16);
}

.client-detail-head {
  display: flex;
  padding: 24px clamp(18px, 3vw, 30px);
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 1px solid var(--dashboard-border);
}

.client-detail-head h2 {
  margin: 0;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.5rem;
  font-weight: 400;
}

.client-detail-head p { margin: 5px 0 0; color: var(--portal-muted); font-size: 0.8rem; }

.client-detail-body {
  padding: 6px clamp(18px, 3vw, 30px) 26px;
  flex: 1;
  overflow-y: auto;
}

.client-detail-section { padding-top: 24px; }

.client-detail-section h3 {
  margin: 0 0 14px;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.05rem;
  font-weight: 400;
}

.client-detail-section .portal-list-item h4 {
  margin: 0 0 6px;
  color: var(--portal-link);
  font-size: 0.92rem;
}

.client-detail-section .portal-list { max-height: none; }
.client-detail .portal-status { padding: 0 clamp(18px, 3vw, 30px) 18px; }
body.has-open-detail { overflow: hidden; }

@media (max-width: 640px) {
  .client-detail { width: 100%; }
}

/* The sealed identifiers sit apart from the answers above them, because they
   are not an answer you scroll past — they are a thing you decide to look at. */
/*
   A person on the matter's Overview, shut and open.

   The whole head is the control, so the target is the card rather than a
   caret somebody has to aim at — and it is a button, so it is reachable from
   a keyboard and announces itself as one. `text-align: left` and `font:
   inherit` are undoing the browser's button defaults, which would otherwise
   centre a name and shrink it.
*/
/*
  Three bands down the left edge: the client, their family, everybody else.

  Seven cards on a family petition are a wall of near-identical rows, and the
  thing somebody scans for is "which of these are the family" — the joint
  sponsor and the interpreter are on the case without being related to anybody
  on it, and they are the ones that should fall away when you are not looking
  for them.

  Existing tokens rather than new ones, and specifically these three. Every
  palette already defines gold, link and muted, so the stripe follows a theme
  change for free — and all three stay legible against their own surface in
  dusk, which navy does not: --portal-navy there is #2c3444 against a #1d1a22
  card, a stripe nobody would find at night.

  A stripe rather than a tinted card. The detail inside is a record being read,
  and a wash behind thirty label-and-value rows is a wash behind the thing
  somebody came to read.
*/
.case-person {
  padding: 0;
  border-left-width: 4px;
}

/* The matter is theirs, and gold is what this console accents with. */
.case-person[data-person="client"] { border-left-color: var(--portal-gold); }

/* Spouse, children, parents, siblings — the ones a petition asks about. */
.case-person[data-person="family"] { border-left-color: var(--portal-link); }

/* 058's roles: joint sponsor, employer, interpreter. On the case, not in the
   family, and quiet until somebody wants them. */
.case-person[data-person="other"] { border-left-color: var(--portal-muted); }

.case-person-head {
  display: flex;
  width: 100%;
  align-items: baseline;
  padding: 16px;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  font: inherit;
  gap: 10px;
  text-align: left;
}

.case-person-head:hover .case-person-who strong { color: var(--portal-link); }

/* The checklist's caret, borrowed. Its rotation rule keys on the checklist's
   own class, so shut it would sit pointing down here and say the opposite of
   what the card is doing. */
.case-person-head[aria-expanded="false"] .case-fold-caret {
  transform: translateY(-2px) rotate(-90deg);
}

/* The name, then the quiet lines under it — each on its own row. */
.case-person-who {
  display: flex;
  min-width: 0;
  flex: 1;
  flex-direction: column;
  gap: 4px;
}

/*
  The People list is not one of the lists the cap was written for.

  .admin-console .portal-list stops at 470px and scrolls, which is right for a
  list of messages that could run to a hundred. Here the rows open, and one
  open card is taller than that on its own — so the record somebody just asked
  to see arrived inside a 470px window with its own scrollbar, three lines
  visible at a time, while the page behind it sat still. The list is short by
  nature; let the page grow instead.
*/
#case-detail-relatives { max-height: none; overflow-y: visible; }

/*
  And the cabinet, for exactly the same reason.

  `.admin-console .portal-list` caps a list at 470px and scrolls it, which is
  right for a feed and wrong for a filing cabinet: it put a second scrollbar
  down the middle of the page and hid half the family behind it. The inner
  grids are `.portal-list` too, so they were being capped as well — a person
  with twenty papers would have scrolled inside their own card, inside the
  scrolling list, inside the page.
*/
.admin-console .case-documents-people,
.admin-console .case-person-detail .portal-list {
  max-height: none;
  overflow-y: visible;
}

/*
  A drawer of cards can use the whole card.

  case-person-detail is 720px because the Overview opens onto label-and-value
  rows, and past that width the eye stops carrying a label to its value. The
  cabinet opens onto a grid, where the measure that matters is the card's, and
  720 left a third of the panel empty and the fold line stopping short of the
  edge.
*/
.case-documents-people .case-person-detail {
  max-width: none;
}

.case-person-detail {
  /* A record is read down a column, not across a card. Past about this width
     the eye stops being able to carry a label to its value. */
  max-width: 720px;
  padding: 0 16px 16px;
  border-top: 1px solid var(--portal-rule);
  margin-top: 0;
  padding-top: 14px;
}

.case-person-detail[hidden] { display: none; }

/*
  Label beside value, not label and value at opposite ends of the card.

  .settings-facts is built for the drawer, where it is 360px wide and
  space-between puts the value a comfortable inch from its label. On a
  full-width matter card the same rule threw them a thousand pixels apart:
  "COUNTRY OF CITIZENSHIP" on the left margin and "United States" on the
  right, with the reader tracking across empty paper to pair them up.

  Two columns instead, both left-aligned, the label column wide enough for the
  longest one in the catalogue. The rule between every row goes too — with the
  pairs sitting together the lines were fencing off rows that no longer needed
  separating, and it is the group headings that carry the structure now.
*/
.case-person-detail .settings-facts { gap: 0; }

.case-person-detail .settings-facts > div {
  display: grid;
  grid-template-columns: minmax(0, 230px) minmax(0, 1fr);
  align-items: baseline;
  justify-content: start;
  gap: 2px 22px;
  padding: 6px 0;
}

.case-person-detail .settings-facts > div + div {
  padding-top: 6px;
  border-top: 0;
}

.case-person-detail .settings-facts dt {
  font-size: 0.7rem;
  letter-spacing: 0.03em;
}

.case-person-detail .settings-facts dd {
  font-size: 0.95rem;
  text-align: left;
}

/*
  And on a phone, one under the other.

  A 230px label track plus a 22px gutter does not fit the ~222px this card has
  at 320px, so the value track was solved to nothing and "United States" came
  out as a column of single letters beside a label that had the row to itself.
  Stacked instead — the shape .intake-sheet-row already takes at its own
  breakpoint, and the reason the drawer's own rule stacks too.

  Its own 640px block, sitting with the rules it corrects, because the
  dashboard's .settings-facts block is ~450 lines above these declarations: an
  equal-specificity override up there would lose on source order and change
  nothing.
*/
@media (max-width: 640px) {
  .case-person-detail .settings-facts > div {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }

  .case-person-detail .settings-facts dd { overflow-wrap: anywhere; }
}

/*
  The catalogue's own grouping — Name, Person, Address, Contact, Travel.

  It has to look like a heading and not like another label. Muted uppercase at
  0.72rem is exactly what the row labels wear, so ADDRESS and STREET ADDRESS
  read as two rows of one list and the grouping did no work at all. The ink
  colour and the rule under it are what separate them.
*/
.case-person-group {
  margin: 22px 0 6px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--portal-rule);
  color: var(--portal-link);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.case-person-group:first-child { margin-top: 0; }

.portal-sealed {
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--portal-rule);
}

.portal-sealed .portal-status { margin-top: 10px; }

/* ==========================================================================
   Themes
   ==========================================================================

   Three palettes over one set of tokens. Nothing below changes a layout, a
   size or a weight — only colour — so a theme cannot break the page, only
   recolour it.

   What they are for, honestly: the American Academy of Ophthalmology's
   position is that screen discomfort comes from staring without blinking
   rather than from blue light. None of these is protective. What a warmer,
   dimmer palette does do is stop the screen being a lamp brighter than the
   room around it, which is a real cause of evening strain — so the aim here is
   matching the room, not filtering a wavelength.

   Daylight is the default and lives in :root at the top of this file. */

/* ── Warm ────────────────────────────────────────────────────────────────────
   The office palette with the blue pulled out of it: amber paper, ink that is
   brown-black rather than blue-black, and rules a shade softer. Contrast stays
   comfortably above 4.5:1 for body text — "softer" must not mean "greyer and
   harder to read", which is the usual failure of a warm theme. */
:root[data-theme="warm"] {
  --portal-navy: #2b3a4f;
  --portal-navy-deep: #1d2838;
  --portal-gold: #b8873c;
  --portal-cream: #f4ece0;
  --portal-white: #fdf8f1;
  --portal-ink: #2b2418;
  --portal-muted: #6b6152;
  --portal-rule: #ddd0bd;
  --portal-surface: #fdf8f1;
  --portal-link: #2b3a4f;
}

:root[data-theme="warm"] body.portal-body { background: var(--portal-cream); }
:root[data-theme="warm"] .portal-table tbody tr:not(.portal-row-blank):hover { background: #f7efe3; }
:root[data-theme="warm"] .portal-avatar { background: #ece0cf; color: #5c5140; }
:root[data-theme="warm"] .portal-pill-idle { background: #ece3d5; color: #6b6152; }

/* ── Dusk ────────────────────────────────────────────────────────────────────
   For a dark room. Warm near-black rather than grey-black, because a blue-grey
   dark theme is the thing this is meant to be an alternative to.

   Text sits at #e8dfd2 rather than white: pure white on near-black is the
   highest-contrast pairing available and reads as glare at night, which is the
   opposite of the point. */
:root[data-theme="dusk"] {
  /* Warm and dim, but not one colour.

     Two attempts got this wrong in opposite directions. The first mixed a blue
     sidebar with brown cards at almost the same lightness, which read as muddy.
     The second put everything on one brown axis, which read as monotone — a
     page dipped in tea.

     So: the depths stay close in lightness and differ in hue. The page leans
     violet, cards lean plum, the sidebar leans slate. None of it is saturated
     enough to be noticed as colour; what you notice is that the edges of things
     are findable without looking for them.

     Low-blue is still the point, so the accents avoid the high-energy end —
     amber, sage and rose carry the meaning instead of a bright blue. */
  --portal-cream: #120f0b;      /* the page: warm brown, as before */
  --portal-surface: #1d1a22;    /* cards, lifted and plum-leaning */
  --portal-navy-deep: #181d26;  /* the sidebar, slate-leaning */
  --portal-navy: #2c3444;       /* dark accents that are not the sidebar */

  --portal-gold: #e3b878;
  --portal-white: #f4ece0;      /* ink on a dark accent, not a surface */
  --portal-ink: #ece4da;        /* off-white: #fff at night is glare */
  --portal-muted: #a69d92;
  --portal-rule: #332f3b;
  --portal-link: #ece4da;

  --portal-danger: #e2908c;
  --portal-success: #7fc3a8;
}

/* The primary button carries the accent rather than disappearing into the card.
   Amber on near-black is the highest-contrast pairing in this palette, and it
   is the one control on a page that should be findable at a glance. */
:root[data-theme="dusk"] .portal-button {
  border-color: var(--portal-gold);
  background: var(--portal-gold);
  color: #241a0d;
}

:root[data-theme="dusk"] .portal-button:hover:not(:disabled) {
  border-color: #f0c98d;
  background: #f0c98d;
}

:root[data-theme="dusk"] .portal-button.portal-button-secondary {
  border-color: var(--portal-rule);
  background: transparent;
  color: var(--portal-ink);
}

/* Approve and Decline keep their own meaning rather than turning amber. */
:root[data-theme="dusk"] .portal-button.portal-button-approve {
  border-color: #2f6d4c;
  background: #2f6d4c;
  color: #eaf6ef;
}

:root[data-theme="dusk"] .portal-button.portal-button-refuse {
  border-color: #93393b;
  background: #93393b;
  color: #fbeceb;
}

:root[data-theme="dusk"] .portal-button.portal-button-chase {
  border-color: #3f5a4c;
  background: transparent;
  color: #8fcfae;
}

:root[data-theme="dusk"] body.portal-body { background: var(--portal-cream); }

/* The surfaces that carry a literal colour rather than a token. Each is a card
   or a band that would otherwise stay bright and glow out of the page. */
:root[data-theme="dusk"] .dashboard-card,
:root[data-theme="dusk"] .portal-card,
:root[data-theme="dusk"] .portal-list-item { border-color: var(--portal-rule); }

/* "View record" and the other quiet row actions read as blue.

   Headings and links are off-white now, which is what was asked for — but that
   left the text actions in a row looking like the text beside them. A soft
   blue is the one colour on this page that means "clickable" without competing
   with the amber button, and it is the convention every reader already knows.

   Deliberately short of a true bright blue: #4da3ff on near-black is the kind
   of glare a dim theme exists to avoid. This sits a few steps back, still
   comfortably past the 4.5:1 contrast a small label needs. */
:root[data-theme="dusk"] .portal-link-button { color: #7fb0e0; }
:root[data-theme="dusk"] .portal-link-button:hover { color: #9cc6f0; }

:root[data-theme="dusk"] .portal-table tbody tr:not(.portal-row-blank):hover { background: #262231; }
:root[data-theme="dusk"] .portal-table tbody tr.is-selected { background: #2a2740; }
:root[data-theme="dusk"] .portal-table tbody tr.is-selected td { border-color: #2c3a4f; }
:root[data-theme="dusk"] .portal-table th { border-bottom-color: #3c352b; }

:root[data-theme="dusk"] .portal-avatar { background: #2e2a38; color: #cfc6d6; }

/* Pills keep their meaning: green is still done, amber still waiting, red still
   refused. Only the lightness moves — dark ground, light text. */
:root[data-theme="dusk"] .portal-pill-done { background: #1c3327; color: #8fd6ab; }
:root[data-theme="dusk"] .portal-pill-waiting { background: #3a2e18; color: #e3c07f; }
:root[data-theme="dusk"] .portal-pill-idle { background: #272219; color: #a2988a; }
:root[data-theme="dusk"] .portal-pill-refused { background: #3a2020; color: #e79a95; }

:root[data-theme="dusk"] .portal-button-secondary { background: transparent; color: var(--portal-ink); }
:root[data-theme="dusk"] input,
:root[data-theme="dusk"] select,
:root[data-theme="dusk"] textarea {
  border-color: #3d3849;
  background: #15131b;
  color: var(--portal-ink);
}

/* A field being written in should be findable without being bright. */
:root[data-theme="dusk"] input:focus,
:root[data-theme="dusk"] select:focus,
:root[data-theme="dusk"] textarea:focus {
  border-color: var(--portal-gold);
  outline-color: var(--portal-gold);
}

:root[data-theme="dusk"] .portal-status.is-error { color: var(--portal-danger); }
:root[data-theme="dusk"] .portal-status.is-success { color: var(--portal-success); }

/* The appearance picker in settings.

   Each option shows a swatch of the palette it selects, because "Warm" and
   "Dusk" are names for colours and a name is a poor way to choose a colour. */
.portal-fieldset { margin: 0; padding: 0; border: 0; display: grid; gap: 8px; }

.portal-theme-option {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 11px 14px;
}

.portal-theme-option strong { display: block; font-size: 0.92rem; }
.portal-theme-option small { display: block; color: var(--portal-muted); font-size: 0.78rem; line-height: 1.5; }

/* Two bands: the page behind, and the ink and accent on top of it. Written as
   literals rather than tokens on purpose — a swatch has to show the palette it
   offers, not the one currently applied. */
.portal-theme-swatch {
  display: block;
  width: 34px;
  height: 34px;
  overflow: hidden;
  border: 1px solid var(--portal-rule);
  border-radius: 50%;
}

.portal-theme-swatch[data-swatch="daylight"] { background: linear-gradient(135deg, #f6f1e8 0 50%, #102653 50% 100%); }
.portal-theme-swatch[data-swatch="warm"] { background: linear-gradient(135deg, #f4ece0 0 50%, #b8873c 50% 100%); }
.portal-theme-swatch[data-swatch="dusk"] { background: linear-gradient(135deg, #14110d 0 50%, #d5a962 50% 100%); }
.portal-theme-swatch[data-swatch="auto"] { background: linear-gradient(135deg, #f6f1e8 0 50%, #14110d 50% 100%); }

#settings-theme-note { margin-top: 12px; font-size: 0.78rem; line-height: 1.6; }

/* The surfaces that still carried a literal light colour, found by walking the
   rendered page in Dusk and measuring rather than by reading the stylesheet —
   each of these was a bright panel glowing out of a dark card. */
:root[data-theme="dusk"] .portal-edit-bar-slot > .dashboard-file-note { background: #241f2d; }
:root[data-theme="dusk"] .calendar-weekdays,
:root[data-theme="dusk"] .calendar-day { background: #1a1820; color: var(--portal-ink); }
/* Days from the neighbouring month.

   These were a different hue from the days beside them — brown cells against
   plum ones — so the leading and trailing weeks read as a separate block of
   colour rather than as the edges of this month. On top of that they were
   inheriting full-brightness ink from the rule above, which made them the most
   legible numbers on the grid.

   The convention is the quiet one: same cell, dimmer number. A day that is not
   in this month should recede, not announce itself in another colour. The cell
   is a shade darker than its neighbours — enough to feel the boundary, not
   enough to see a band. */
:root[data-theme="dusk"] .calendar-day.is-outside {
  background: #181621;
  color: #6f6879;
}

:root[data-theme="dusk"] .calendar-day.is-outside .calendar-day-number { color: #6f6879; }
:root[data-theme="dusk"] .portal-alert { background: #2a2118; color: var(--portal-ink); }

/* Warm keeps the same band a shade off its card, rather than the cool grey. */
:root[data-theme="warm"] .portal-edit-bar-slot > .dashboard-file-note { background: #f7f0e4; }
:root[data-theme="warm"] .calendar-weekdays,
:root[data-theme="warm"] .calendar-day { background: #fbf6ee; }

/* The conversation, which keeps its own surfaces rather than the card's. */
:root[data-theme="dusk"] .chat-thread { background: #17151d; }
:root[data-theme="dusk"] .chat-seal.is-initials { background: #2c2620; color: #cbbfae; }
:root[data-theme="dusk"] .chat-bubble { background: #262231; color: var(--portal-ink); }
:root[data-theme="dusk"] .chat-row.is-mine .chat-bubble { background: #223047; color: #e6edf8; }
:root[data-theme="dusk"] .chat-day { color: var(--portal-muted); }

:root[data-theme="warm"] .chat-thread { background: #f8f1e6; }
:root[data-theme="warm"] .chat-seal.is-initials { background: #ece0cf; color: #5c5140; }

/* The sticky topbar carries its own translucent white so it stays legible while
   content scrolls under it. Translucency over a dark page is still white, so
   each theme needs its own. */
:root[data-theme="dusk"] .dashboard-topbar {
  border-bottom-color: var(--portal-rule);
  background: rgba(18, 16, 20, 0.95);
}

:root[data-theme="warm"] .dashboard-topbar {
  border-bottom-color: var(--portal-rule);
  background: rgba(253, 248, 241, 0.95);
}

/* The topbar subtitle borrows --portal-success, which is a dark green in the
   light themes and reads as a muted label. At dusk that token is a bright mint
   for pills, and a bright mint subtitle looks like a status message. */
:root[data-theme="dusk"] .dashboard-topbar-title span { color: var(--portal-muted); }

/* Warm keeps the out-of-month cells on its own paper rather than a cool grey. */
:root[data-theme="warm"] .calendar-day.is-outside { background: #f5ede1; color: #a89e8f; }
:root[data-theme="warm"] .calendar-day.is-outside .calendar-day-number { color: #a89e8f; }

/* The sign-in page is deliberately not themed. It is the front door, reached
   from the public website, and it keeps that site's palette whoever is
   arriving — so login.html does not load portal/theme-boot.js and never gets a
   data-theme to match on. Autofill styling stays, because Chrome paints its own
   background there regardless of theme. */
:root .login-form input:-webkit-autofill,
:root .login-form input:-webkit-autofill:hover,
:root .login-form input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--portal-ink);
  box-shadow: inset 0 0 0 1000px var(--portal-surface);
}

/* ── The stages, in their own colours ────────────────────────────────────────

   Each tab carries the colour of what it means, so the strip reads as a
   sequence — waiting, waiting on you, taken on, turned down, finished — before
   any of it is read as words. The same colours the pills in the table use, so
   a row and its tab agree.

   Held in a per-tab custom property rather than five copies of every rule.
   `--stage` is set once per tab and per theme; the rules below just use it.

   Inactive tabs sit at 80% so the selected one still leads. The count badge
   takes the same colour, which is where the eye usually lands first. */
/* Blue for an enquiry that has not been acted on — the colour this console
   already uses for "something to click", and distinct from the five stages a
   client passes through. Missing entirely at first, which made the count on the
   selected tab invisible rather than absent: the active rule paints the badge
   text in var(--stage), and an undefined variable there fell back to the card
   colour on a transparent background. */
.portal-subtab[data-stage="requests"] { --stage: #2f5d8f; }  /* blue    */
.portal-subtab[data-stage="intake"]   { --stage: #9a7a12; }  /* yellow  */
.portal-subtab[data-stage="awaiting"] { --stage: #b3671b; }  /* orange  */
.portal-subtab[data-stage="approved"] { --stage: #1f6b3f; }  /* green   */
.portal-subtab[data-stage="declined"] { --stage: #a3282f; }  /* red     */
.portal-subtab[data-stage="former"]   { --stage: #6b7280; }  /* grey    */

/* Lifted for a dark ground: the light-theme versions are chosen for contrast
   against cream and go muddy on near-black. */
:root[data-theme="dusk"] .portal-subtab[data-stage="requests"] { --stage: #7fb0e0; }
:root[data-theme="dusk"] .portal-subtab[data-stage="intake"]   { --stage: #e3c07f; }
:root[data-theme="dusk"] .portal-subtab[data-stage="awaiting"] { --stage: #e39a63; }
:root[data-theme="dusk"] .portal-subtab[data-stage="approved"] { --stage: #7fc3a8; }
:root[data-theme="dusk"] .portal-subtab[data-stage="declined"] { --stage: #e2908c; }
:root[data-theme="dusk"] .portal-subtab[data-stage="former"]   { --stage: #9aa0aa; }

:root[data-theme="warm"] .portal-subtab[data-stage="requests"] { --stage: #2b5580; }
:root[data-theme="warm"] .portal-subtab[data-stage="intake"]   { --stage: #8f6f10; }
:root[data-theme="warm"] .portal-subtab[data-stage="awaiting"] { --stage: #a85f18; }
:root[data-theme="warm"] .portal-subtab[data-stage="approved"] { --stage: #1c6039; }
:root[data-theme="warm"] .portal-subtab[data-stage="declined"] { --stage: #97262c; }
:root[data-theme="warm"] .portal-subtab[data-stage="former"]   { --stage: #6a6459; }

.portal-subtab[data-stage] { color: var(--stage, var(--portal-muted)); opacity: 0.8; }
.portal-subtab[data-stage]:hover { color: var(--stage); opacity: 1; }

.portal-subtab[data-stage].is-active {
  border-bottom-color: var(--stage);
  color: var(--stage);
  opacity: 1;
}

.portal-subtab[data-stage] .portal-subtab-count {
  background: color-mix(in srgb, var(--stage, var(--portal-muted)) 18%, transparent);
  color: var(--stage, var(--portal-muted));
}

.portal-subtab[data-stage].is-active .portal-subtab-count {
  background: var(--stage, var(--portal-muted));
  /* Against its own colour, not against the theme: an amber badge needs dark
     text and a green one needs light, and both exist in this strip. */
  color: var(--portal-surface);
}

/* The appearance picker announces its subject; the language one does not need
   to, because "English" already says what it is. */
.portal-theme-switch { position: relative; }

.portal-theme-icon {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: 10px;
  color: var(--portal-muted);
  font-size: 0.9rem;
  line-height: 1;
  pointer-events: none;
  transform: translateY(-50%);
}

/* Room for the mark, so it sits inside the control rather than over the value. */
.portal-theme-switch .portal-lang-select { padding-left: 28px; }

/* The decision buttons share a width so the actions before them line up.

   Renaming "Resend invitation" to "Resend" got these close; a shared minimum
   makes them identical. The row's actions are right-aligned, so every pixel of
   difference here pushes "View record" left or right — and a column of them
   stepping in and out is the thing that reads as unfinished. */
.portal-table-actions .portal-button-approve,
.portal-table-actions .portal-button-chase {
  min-width: 106px;
}

/* The Status column, centred under its own title.

   Everything else in this table is left-aligned because it is read as text —
   a name, a matter, a date. Status is a token: a pill, a progress bar, a
   countdown. Centring keeps each one under the word it answers to, instead of
   a column of short badges hanging off the left edge with a ragged gap beside
   them. */
.portal-table th.portal-col-status { text-align: center; }
.portal-table td.portal-col-status { text-align: center; }
.portal-table td.portal-col-status .portal-progress { justify-content: center; }

/* ── Every column centred ────────────────────────────────────────────────────

   The whole table, not just Status. A mixed table — some columns left, one
   centred — reads as a mistake before it reads as emphasis, so if one is
   centred they all are.

   Worth knowing the trade: a left margin gives the eye a common edge to run
   down, which is why directories usually left-align names. Centring trades
   that for symmetry. With ten rows to a page it is a fair trade; if the firm
   ever pages through hundreds, this is the first thing to put back.

   The actions column stays right. It has no title to centre under, and buttons
   pulled into the middle leave a gap at the edge where the eye ends up. */
.portal-table th,
.portal-table td { text-align: center; }

/* The two cells that lay their contents out with flex rather than text flow,
   and so ignore text-align. */
.portal-table .portal-identity { justify-content: center; }
.portal-table .portal-progress { justify-content: center; }

/* An empty state spans the table and is a sentence, so it reads centred too —
   but the rule below it must not treat it as the actions column. */
.portal-table td[colspan] { text-align: center; }

.portal-table th:last-child,
.portal-table td:last-child:not([colspan]) { text-align: right; }

/* The name starts at the avatar, not in the middle of its own box.

   With the table centred, the identity text inherited `text-align: center` —
   so the name centred itself over the longer email line beneath it and drifted
   away from the initials, leaving a gap that looked like a layout fault.

   The block as a whole still centres in the column (that is the flex rule
   above); only its two lines are anchored left, so the name and the address
   share an edge and both sit against the avatar. */
.portal-table .portal-identity-text { text-align: left; }

/* The initials line up down the column.

   Centring each identity block independently meant a row with a long address
   sat wider than one with a short name, so every avatar started at a slightly
   different x — a column of circles that wobbled by a few pixels down the page.

   A fixed-width block centred in the cell fixes both things at once: the block
   is still centred, but its contents start at its own left edge, which is the
   same x on every row. The width fits the longest address the firm is likely
   to hold; anything longer truncates, which the text rules above already do. */
.portal-table .portal-identity {
  width: min(100%, 250px);
  margin-inline: auto;
  justify-content: flex-start;
}

/* ── Columns that hold still between tabs ────────────────────────────────────

   A table sizes its columns to whatever is in them, so every stage produced a
   different layout: Intake sent has "No case" where Approved has "Family
   petition I-130", and three buttons where Declined has one. Switching tabs
   slid every column sideways, which reads as the page rebuilding itself rather
   than as a filter being applied.

   Fixed layout with declared widths means the grid is a property of the table,
   not of the rows in it. The tabs now change only which rows are shown.

   Percentages rather than pixels so it still adapts to the window; the two
   ends are fixed because a checkbox and a row of buttons need what they need
   regardless of how wide the card is. */
#admin-directory-card .portal-table { table-layout: fixed; }
#admin-directory-card .portal-table th:nth-child(1) { width: 48px; }
#admin-directory-card .portal-table th:nth-child(2) { width: 22%; }
#admin-directory-card .portal-table th:nth-child(3) { width: 13%; }
#admin-directory-card .portal-table th:nth-child(4) { width: 15%; }
#admin-directory-card .portal-table th:nth-child(5) { width: 12%; }
#admin-directory-card .portal-table th:nth-child(6) { width: 330px; }

#admin-matters-card .portal-table { table-layout: fixed; }
#admin-matters-card .portal-table th:nth-child(1) { width: 23%; }
#admin-matters-card .portal-table th:nth-child(2) { width: 20%; }
#admin-matters-card .portal-table th:nth-child(3) { width: 13%; }
#admin-matters-card .portal-table th:nth-child(4) { width: 17%; }
#admin-matters-card .portal-table th:nth-child(5) { width: 14%; }
#admin-matters-card .portal-table th:nth-child(6) { width: 13%; }

#admin-matters-card .matter-client-row { background: var(--portal-cream); }
#admin-matters-card .matter-client-row > td { border-top: 1px solid var(--dashboard-border); }
.matter-client-toggle { display: flex; align-items: center; width: 100%; padding: 3px 0; border: 0; gap: 10px; background: transparent; color: var(--portal-ink); text-align: left; cursor: pointer; }
.matter-client-toggle:focus-visible { outline: 3px solid var(--portal-gold); outline-offset: 4px; border-radius: 4px; }
.matter-client-caret { display: inline-block; flex: none; width: 12px; font-size: 1.4rem; text-align: center; }
.matter-client-toggle[aria-expanded="true"] .matter-client-caret { transform: rotate(90deg); }
.matter-client-identity { display: grid; min-width: 0; gap: 5px; }
.matter-client-identity strong { font-size: 0.86rem; }
.matter-client-identity .portal-meta { margin: 0; overflow-wrap: anywhere; }
#admin-matters-card .matter-child-client { padding-left: 34px; color: var(--portal-muted); font-size: 0.72rem; }
#admin-matters-card .matter-child-row > td { background: var(--portal-surface); }
#admin-matters-card .portal-table-actions button { max-width: 100%; white-space: normal; }
#admin-matters-card .matter-client-row .portal-button { text-transform: none; letter-spacing: 0; padding: 8px 10px; }
#admin-matters-card tr[hidden] { display: none; }

/* Fixed columns cannot grow to fit a long word, so anything that would spill
   wraps instead — an address or a matter title should never push a column. */
.portal-table td { overflow-wrap: anywhere; }

/* ── The same two tables, as lists of cards ──────────────────────────────────

   Below 700px the columns above stop being a discipline and become the whole
   problem. Clients declares a hard 330px for its actions column — wider than
   the entire card at 320px — and .portal-table-actions forbids the row from
   wrapping, so Approve, Decline, Chase and the rest render outside the
   .portal-table-wrap viewport: roughly 280px of sideways scrolling before the
   attorney sees a single control. Matters declares 90% in percentages plus a
   130px sixth column, which forces the table wider than its container and
   turns every 40px cell into a vertical ribbon of broken words.

   Neither is a fit problem that a smaller font would solve — five data columns
   and six buttons do not go into 256px — so both get a different layout. Each
   row becomes a bordered block and each cell a line inside it, which is how a
   list of records reads on a phone anyway. Matters needs nothing else: the
   whole row is already the click target, so no control has to move.

   ORDER MATTERS, and this file has recorded why twice already — above
   .portal-table-actions and again above .portal-stacked-cell. `display: flex`
   on a <td> that is still in table layout drops the cell out of its row and
   draws its border short. So `display: block` goes on the whole table first,
   in this same block; by the time the actions cell is flexed below it is an
   ordinary block box and there is no table layout left to fall out of.

   The header row is taken out of the flow the way .portal-visually-hidden does
   it, not with display: none — but honesty about what that buys: display:
   block on the table strips its table semantics, so the clipped headers stop
   reaching a screen reader as column labels anyway. The data-label captions
   below are what actually name each value in a card, on screen and out loud;
   the clipped thead merely keeps the words in the document. And the last rule
   catches the tables that are NOT converted: a translated label like
   "Последняя активность" has nowhere to go inside a 12% column. */
@media (max-width: 700px) {
  #admin-directory-card .portal-table,
  #admin-directory-card thead,
  #admin-directory-card tbody,
  #admin-directory-card tr,
  #admin-directory-card td,
  #admin-matters-card .portal-table,
  #admin-matters-card thead,
  #admin-matters-card tbody,
  #admin-matters-card tr,
  #admin-matters-card td { display: block; }

  #admin-directory-card thead,
  #admin-matters-card thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  /* Declared widths held the columns still between tabs. With the rows stacked
     there are no columns left for them to hold. */
  #admin-directory-card .portal-table,
  #admin-matters-card .portal-table { table-layout: auto; }
  #admin-directory-card .portal-table th:nth-child(n),
  #admin-matters-card .portal-table th:nth-child(n) { width: auto; }

  #admin-directory-card tbody tr,
  #admin-matters-card tbody tr {
    margin-bottom: 10px;
    padding: 12px;
    border: 1px solid var(--dashboard-border);
    border-radius: 7px;
  }

  /* The blank rows admin.js pads a short page with exist to hold the table's
     height still between pages. Cards have no height to hold, and a border
     around nothing is seven empty boxes under the real ones. */
  #admin-directory-card tbody tr.portal-row-blank { display: none; }

  /* The card edge is the separator now, so the per-cell rule underneath it is
     one line too many. */
  #admin-directory-card td,
  #admin-matters-card td { padding: 4px 0; border-bottom: 0; }

  /* Four bare values on consecutive lines is a card only its author can read.
     The row builders stamp each cell with its column's (already translated)
     heading, and it becomes the caption over the value — skipping the name
     cell, which introduces itself. Generated content is in the accessibility
     tree, so this is also what a screen reader gets in place of the columns
     the block conversion dissolved. */
  #admin-directory-card td[data-label]::before,
  #admin-matters-card td[data-label]::before {
    content: attr(data-label);
    display: block;
    color: var(--portal-muted);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }

  /* The chevron said "the row opens" without being a control. As a card the
     whole bordered block is still the target, but a lone › on its own final
     line reads as a button that lost its label — the affordance costs more
     than it says. */
  #admin-matters-card td.portal-table-actions { display: block; padding-top: 12px; }
  #admin-matters-card td.portal-table-actions::before { display: none; }
  #admin-matters-card .matter-client-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px 16px; margin-bottom: 0; }
  #admin-matters-card .matter-client-row > td { border: 0; text-align: left; }
  #admin-matters-card .matter-client-row > td:first-child { grid-column: 1 / -1; }
  #admin-matters-card .matter-client-row > td:nth-child(4),
  #admin-matters-card .matter-client-row > td:nth-child(5) { display: none; }
  #admin-matters-card .matter-client-row > td:last-child { grid-column: 1 / -1; text-align: left; }
  #admin-matters-card .matter-child-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px 16px; margin: 0 0 0 14px; border-top: 0; border-left: 3px solid var(--portal-rule); border-radius: 0 0 7px 7px; }
  #admin-matters-card .matter-child-row > td { text-align: left; }
  #admin-matters-card .matter-child-row > td:first-child,
  #admin-matters-card .matter-child-row > td:nth-child(2),
  #admin-matters-card .matter-child-row > td:last-child { grid-column: 1 / -1; }
  #admin-matters-card .matter-child-row > td:nth-child(2) { font-weight: 600; }
  #admin-matters-card .matter-child-row > td[data-label]::before { font-weight: 600; }
  #admin-matters-card .matter-client-row:not(:first-child) { margin-top: 16px; }
  #admin-matters-card .matter-child-client { padding-left: 0; }

  /* Safe to flex only here, several rules after the cell stopped being a cell. */
  #admin-directory-card td.portal-table-actions {
    display: flex;
    margin-top: 10px;
    flex-wrap: wrap;
    gap: 8px;
    text-align: left;
    white-space: normal;
  }

  /* Spacing comes from the gap above; the margin was for inline children in a
     cell that no longer exists. The 106px floor on Approve and Chase was sized
     for a column, not for a card. */
  #admin-directory-card .portal-table-actions > * + * { margin-left: 0; }
  #admin-directory-card .portal-table-actions .portal-button { flex: 1 1 auto; min-width: 0; }

  .portal-table th { white-space: normal; overflow-wrap: anywhere; }
}

/* Inside the client drawer, a record sits on a panel that is already dark, so
   the same surface as the page behind it would make the card edges vanish.
   One step up, which is what "raised" means on a dark ground. */
:root[data-theme="dusk"] .client-detail-panel .portal-list-item,
:root[data-theme="dusk"] #client-detail .portal-list-item {
  background: #262231;
  border-color: #3a3446;
}


/* Delete stays red in every theme.

   The Dusk rule that makes a primary button amber has three class components
   and this one had two, so it won — the delete control has been rendering
   amber on a dark screen, which is the colour of "go ahead" rather than of
   "this cannot be undone". Matched specificity puts that right.

   The same red as the Decline button, so the two destructive-looking actions in
   this console are recognisably the same family. */
:root[data-theme="dusk"] .portal-button.portal-button-danger {
  border-color: #a34042;
  background: #a34042;
  color: #fdeceb;
}

:root[data-theme="dusk"] .portal-button.portal-button-danger:hover:not(:disabled) {
  border-color: #b84c4e;
  background: #b84c4e;
}

:root[data-theme="warm"] .portal-button.portal-button-danger {
  border-color: #97262c;
  background: #97262c;
  color: #fdf4f3;
}

/* A stranger's message, in the record drawer. Pre-wrap because they typed
   paragraphs and the office should read them as written. */
.portal-request-message {
  margin: 14px 0 0;
  padding: 14px 16px;
  border-left: 2px solid var(--portal-gold);
  border-radius: 0 4px 4px 0;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font-size: 0.9rem;
  line-height: 1.65;
  white-space: pre-wrap;
}

/* A timestamp cell: date on top, time under it.

   The date leads because that is what the column is scanned for; the time is
   supporting detail at caption weight. Neither line may wrap — the split is
   deliberate, so the browser must not add one of its own.

   Aimed at timestamps specifically. It was written for every stacked cell,
   which caught the request "About" cell too: a long subject like "Spousal &
   Family Petitions (I-130)" could then not break, so it ran straight out of its
   column and under the one beside it. */
.portal-table td.portal-timestamp > div,
.portal-table td.portal-timestamp > span { white-space: nowrap; }

/* ==========================================================================
   Case dashboard
   ==========================================================================

   The second view of the Cases tab, not a layer over it. It was a centred modal
   at first, which was wrong twice over: a matter is what you came to this tab
   to read rather than an interruption to dismiss, and a fixed box has to invent
   its own scrollbar for content the page could simply have scrolled.

   So it occupies the tab. No position, no shadow, no backdrop — it is a section
   of the page, and the page scrolls it. */
.case-detail {
  display: block;
}

/* A way back, leading and unmistakable, before the matter's own name. */
.portal-back-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 14px;
  padding: 6px 12px 6px 9px;
  border: 1px solid var(--portal-rule);
  border-radius: 999px;
  background: var(--portal-surface);
  color: var(--portal-link);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.portal-back-link:hover { border-color: var(--portal-link); }
.portal-back-link:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

.case-detail-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  /* Once the buttons beside the title can shrink, they should be allowed to
     drop below it instead of shrinking past legibility. Never fires above
     640px — there is slack in the row — but it covers the band between. */
  flex-wrap: wrap;
  gap: 20px;
  margin-bottom: 22px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--portal-rule);
}

.case-detail-head h2 { margin: 4px 0 2px; font-size: 1.55rem; }
.case-detail-head .portal-meta { margin: 0; }

/* A flex item will not shrink below the widest thing inside it, and the matter
   title is a client's own words — one long unbroken word held this column open
   and pushed the status line off the end of the row instead of wrapping. */
.case-detail-head-main { min-width: 0; }

/*
  The five panels, stacked, with the tab strip above them.

  A grid so the space between them belongs to the container rather than to the
  panels. Once the strip is wired, four of the five carry `hidden` and generate
  no box at all — so they cost no gap, and whichever panel is on screen sits
  the same distance under the strip as any other. Margins on the panels could
  not manage that: the first panel's top margin would still be there with the
  panel above it hidden, and every tab would sit at a different height.
*/
.case-detail-body {
  display: grid;
  gap: 18px;
  align-content: start;
  padding: 0;
}

/* At a glance: the four figures that answer "is this moving" before any prose.
   The gap below belongs to .case-panel now — this grid is a panel child, and a
   margin of its own would leave the stats sitting further from what follows
   them than any two other things on any other tab. */
.case-stats {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}

/*
  Each tile is a <button> — every figure here is the short form of something on
  another tab, so pressing it goes there.

  Which is what the last three declarations are for, and they are not decoration:
  a button does not inherit the page's font or its colour. Without `color` the
  tiles would print in the browser's own ButtonText, which is near-black in every
  theme — including dusk, where the surface behind it is nearly black too, and
  the number would be legible only by tilting the screen.
*/
.case-stat {
  padding: 13px 15px;
  border: 1px solid var(--portal-rule);
  border-radius: 6px;
  background: var(--portal-surface);
  text-align: center;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.case-stat strong { display: block; font-size: 1.25rem; line-height: 1.2; }
.case-stat span { display: block; margin-top: 4px; color: var(--portal-muted); font-size: 0.74rem; }

/* Overdue, or silent for a month. Colour rather than a sentence, because the
   point is to be noticed on the way past. */
.case-stat.is-warn { border-color: #c08a3e; background: #fbf0dc; }
.case-stat.is-warn strong { color: #8a5d10; }
:root[data-theme="dusk"] .case-stat.is-warn { border-color: #8a6a2c; background: #3a2e18; }
:root[data-theme="dusk"] .case-stat.is-warn strong { color: #e3c07f; }

/*
  A panel of the matter's tab strip.

  This replaced the two-by-two grid of cards. That grid was an answer to the
  page being a screen and a half of full-width cards; the tabs are a better
  one, and with only one card in most panels a second column would have had
  nothing to put in it.

  The gap is all this rule is really for — the cards inside bring their own
  padding. But `> :empty` earns its place beside it: several panels ship
  containers that a later render fills, and an empty grid item is still an
  item, so an unfilled slot would hold a row gap open above the card below it
  and read as a missing row rather than as no row.
*/
.case-panel {
  display: grid;
  gap: 18px;
  align-content: start;
}

.case-panel > :empty { display: none; }

/* Silent, the way .settings-panel is. A panel takes focus so the strip can put
   the reader inside what they just selected; a ring around a whole panel reads
   as something being wrong with it rather than as having arrived. */
.case-panel:focus { outline: none; }

/*
  A heading row for a panel whose content is a bare list rather than a card.

  Updates and Messages both need it. Their lists have to stay direct children of
  the panel so that `.case-panel > :empty` can take one out of the flow on a
  matter nobody has posted about yet, which rules out wrapping them in a card
  with a header of its own — so the header stands beside the list instead of
  around it, and on Updates it carries the button that opens the post dialog.
*/
.case-panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.case-panel-head h3 { margin: 0; }
.case-panel-head .portal-eyebrow { margin-bottom: 6px; font-size: 0.58rem; }

/* Forms still out, under the ones that came back. Heading and list go together
   because a heading over an empty list announces a section that is not there —
   and on a matter with nothing outstanding, which is most of them, that is the
   only thing it would ever say. */

/* What the office is still waiting on (048). Hidden when nothing is owed, by
   the same trick the forms list uses: a heading reading "still waiting on"
   over an empty list is a small worry delivered on every visit. */
.case-documents-owed:has(> div:empty) { display: none; }
.case-documents-owed h4 { margin: 18px 0 12px; font-size: 0.95rem; }

/* ── The matter checklist (055) ─────────────────────────────────────────────
   What the matter needs, above what has arrived. Grouped by state — still
   needed, then the conditional ones, then what is in — so the count at the top
   and the eye reading down agree about what is outstanding. */
/*
  The cabinet and the checklist, side by side.

  Two halves of one question — what has arrived, and what is still wanted — and
  stacked they were read one after the other rather than against each other. The
  cabinet takes the width because it is the thing being worked through; the
  checklist is a reference and takes a column.
*/
.case-documents-shell {
  display: grid;
  /* Three parts to two, not a fixed 22rem column. At 22rem the checklist's
     labels wrapped a word to a line and its three actions did not fit beside
     them; at two fifths of the card a line is a line. */
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  align-items: start;
  gap: 26px;
}

.case-documents-main { min-width: 0; }

/* The button that adds to the cabinet, over the cabinet. */
.case-documents-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }

/*
  The checklist runs the page's own length.

  It was pinned under the topbar and capped at the window's height, scrolling
  inside itself, so the two halves could be read against each other while the
  cabinet was long. The cost was a second scrollbar inside the first, and a
  list of thirty lines whose bottom third was reached by scrolling a box that
  did not look like one. One scrollbar, the browser's, and the whole list on
  the page.
*/
.case-matter-checklist {
  min-width: 0;
  padding: 16px 18px;
  border: 1px solid var(--dashboard-border);
  border-radius: 8px;
  background: var(--portal-cream);
}

.case-matter-checklist .dashboard-card-header { margin-bottom: 10px; }

/*
  One column again when there is no room for two.

  The checklist goes back on top, which is where it was and where it belongs on
  a narrow screen: it is the shorter of the two and the one that says what to do
  next. `order` rather than moving it in the markup, so the reading order for a
  screen reader stays the one the page is written in.
*/
@media (max-width: 1100px) {
  .case-documents-shell { grid-template-columns: minmax(0, 1fr); }

  .case-matter-checklist {
    order: -1;
    margin-bottom: 4px;
  }
}

.checklist-section { margin-bottom: 14px; }
.checklist-section h5 {
  margin: 0 0 6px;
  color: var(--portal-muted);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.checklist-list { margin: 0; padding: 0; list-style: none; }
/* .admin-console .portal-list caps a list at 470px and scrolls it, which is
   right for a table of matters and wrong here: the checklist's whole point is
   to be read top to bottom against the cabinet, and a box scrolling inside a
   page that scrolls is the thing this panel has just stopped doing. */
.admin-console .checklist-list { max-height: none; overflow: visible; }

/*
  The line's words on the left, its buttons on the right.

  This was a row once, and went back to two stacked rows because the checklist
  was a 22rem column and "Ask the client", "Mark done" and "Remove" as three
  underlined words measured about 250px of it: the label was left fifty pixels
  and came out one word per line. Two things have changed since. The column is
  two fifths of the card rather than 22rem, and the actions are one small
  button and two 32px pictures — under 170px together, and they do not grow
  with the language.

  So a row again, but one that knows when it does not fit: the words claim at
  least 14rem and the actions may wrap under them, keeping their right edge, on
  the one line in a hundred whose label is longer than the room. Everything
  else reads across, which is how a checklist is read.
*/
.checklist-item {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px 14px;
  padding: 12px 0;
  border-bottom: 1px solid var(--dashboard-border);
}
.checklist-item:last-child { border-bottom: 0; }
.checklist-item-main { flex: 1 1 14rem; min-width: 0; }
.checklist-item-main strong {
  display: block;
  font-size: 0.9rem;
  line-height: 1.35;
  color: var(--portal-link);
}

/* Who and what kind, in the cabinet's words, on one quiet line under the label. */
.checklist-item-meta {
  display: block;
  margin-top: 2px;
  color: var(--portal-muted);
  font-size: 0.72rem;
}
.checklist-item-note { margin: 4px 0 0; font-size: 0.78rem; line-height: 1.5; }
.checklist-item-done {
  margin: 4px 0 0;
  color: var(--portal-gold);
  font-size: 0.74rem;
  font-weight: 700;
}

/* A ticked line steps back — it is done, and the eye should pass over it to the
   ones that are not. The label keeps its weight; everything else greys. */
.checklist-item.is-done .checklist-item-main strong { color: var(--portal-muted); font-weight: 400; }

/* Beside the words, hard right; margin-left: auto keeps that edge when the row
   wraps and the actions land on a line of their own. */
.checklist-item-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  margin-left: auto;
}
.checklist-item-actions .portal-link-button { font-size: 0.74rem; }
/* The one worded button, sized to the pictures beside it. Overrides the small
   variant's 36px floor, which is set for buttons that stand alone in a row. */
.checklist-item-actions .portal-button.portal-button-small { min-height: 32px; padding: 6px 12px; font-size: 0.7rem; white-space: nowrap; }

/* A square with one picture in it. Tokens only: the console has four themes.
   The bin reads red, because it is the one action here that cannot be undone
   from this list. */
.checklist-icon-button {
  display: grid;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  place-items: center;
  padding: 0;
  border: 1px solid var(--dashboard-border);
  border-radius: 6px;
  background: var(--portal-surface);
  color: var(--portal-link);
  cursor: pointer;
}
.checklist-icon-button svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.checklist-icon-button:hover { border-color: var(--portal-gold); color: var(--portal-gold); }
.checklist-icon-button.is-danger { color: var(--portal-danger); }
.checklist-icon-button.is-danger:hover { border-color: var(--portal-danger); background: color-mix(in srgb, var(--portal-danger) 10%, var(--portal-surface)); color: var(--portal-danger); }
.checklist-icon-button:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

.checklist-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
}
.checklist-empty { padding: 4px 0; }
.checklist-empty .portal-muted { margin: 0 0 12px; }

/* The one answer on the Add dialog that changes what the count means.

   "Every matter of this kind needs it" and the sentence explaining it were two
   loose elements sitting between the last field and the buttons — the quietest
   thing on the form, and the only one that decides whether a line ever reads as
   missing. Boxed together they are one decision with its reason attached, which
   is what they always were.

   Same inset as .doc-preview-pane-body: the console's way of setting a block
   apart inside a dialog without giving it a heading it does not need. */
.checklist-line-flag {
  padding: 12px 14px;
  border: 1px solid var(--dashboard-border);
  border-radius: 8px;
  background: var(--dashboard-bg);
}

/* .portal-check carries a -4px top margin to close the gap the form's 18px grid
   leaves under a field. Inside the box that gap is the padding, so the pull-up
   would lift the tick off its own top edge. */
.checklist-line-flag .portal-check { margin-top: 0; }

/* Indented to the checkbox's text, not its box, so the reason lines up under
   the words it is about — 17px of control plus the 10px .portal-check gap. */
.checklist-line-flag .portal-field-note { margin: 8px 0 0 27px; }

/* ── The matter timeline (history view) ─────────────────────────────────────
   Each entry is a card in the list, with a left stripe coloured by what kind of
   thing happened — so the office's own posts stand apart from the documents and
   correspondence around them without a legend to decode. The rail above the
   panels stays the process view; this is the history. */
.case-timeline .case-timeline-item {
  border-left-width: 3px;
  border-left-color: var(--portal-muted);
}
.case-timeline-item.is-update { border-left-color: var(--portal-navy); }
.case-timeline-item.is-document,
.case-timeline-item.is-form,
.case-timeline-item.is-ask { border-left-color: var(--portal-gold); }
.case-timeline-item.is-opened,
.case-timeline-item.is-status { border-left-color: var(--portal-link); }

.case-timeline-when {
  display: block;
  color: var(--portal-muted);
  font-size: 0.72rem;
}
.case-timeline-item > strong {
  display: block;
  margin-top: 3px;
  font-size: 0.9rem;
  color: var(--portal-link);
}
.case-timeline-tag {
  display: inline-block;
  margin-top: 8px;
  padding: 1px 9px;
  border: 1px solid var(--portal-navy);
  border-radius: 99px;
  color: var(--portal-navy);
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.case-timeline-actions {
  display: flex;
  gap: 16px;
  margin-top: 8px;
}
.case-timeline-actions .portal-link-button { font-size: 0.74rem; }

/* The file button on a client's ask, while its upload is in flight. */
.portal-button.is-busy { opacity: 0.6; pointer-events: none; }




/* A caveat attached to one field — quieter than the label, tied to it by
   proximity rather than by a line of its own. */
.portal-field-note {
  margin: 5px 0 0;
  color: var(--portal-muted);
  font-size: 0.76rem;
  line-height: 1.5;
}

/* What a dialog is about to do, said before the button is pressed. Carries the
   gold rule the console uses elsewhere for "read this one". */
.portal-inline-note {
  margin: 4px 0 0;
  padding: 11px 14px;
  border-left: 3px solid var(--portal-gold);
  border-radius: 0 4px 4px 0;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font-size: 0.82rem;
  line-height: 1.6;
}

/* Something the reader needs to notice before they act on the record around
   it. Amber rather than red: a client giving a second address is a fact to
   know, not a fault to fix. */
.portal-inline-warning {
  margin: 0 0 14px;
  padding: 11px 14px;
  border: 1px solid #c08a3e;
  border-left-width: 3px;
  border-radius: 0 4px 4px 0;
  background: #fbf0dc;
  color: #6d4a12;
  font-size: 0.84rem;
  line-height: 1.6;
}

:root[data-theme="dusk"] .portal-inline-warning {
  border-color: #8a6a2c;
  background: #3a2e18;
  color: #e3c07f;
}

/* The "Change email address" control, which sits under the field it unlocks
   rather than beside it — beside, it competed with the input for the same row
   and read as a second thing to fill in. */
.intake-change-field {
  margin-top: 6px;
  font-size: 0.82rem;
}

/* The calendar search ships read-only to keep the browser's password manager
   out of it, and is unlocked on focus. It must not look unavailable in the
   meantime — a search box greyed out until clicked reads as broken. */
#admin-calendar-search[readonly] {
  background: inherit;
  color: inherit;
  cursor: text;
  opacity: 1;
}

/* A row that opens something says so on approach. */
.portal-row-openable { cursor: pointer; }
.portal-row-openable:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: -2px; }
.portal-row-chevron { color: var(--portal-muted); font-size: 1.1rem; }
.portal-row-openable:hover .portal-row-chevron { color: var(--portal-link); }

/* What an enquiry was about, standing in for a case that does not exist yet.
   Plain weight, not the case title's — it is the client's description of their
   problem, not a matter the firm has opened. */
.portal-enquiry-topic {
  display: block;
  color: var(--portal-ink);
  font-size: 0.84rem;
  line-height: 1.45;
}

/* The usual sequence for the route this matter is taking. Numbered, because
   the order is the information. */
.case-route { margin: 0; padding-left: 20px; color: var(--portal-ink); }
.case-route li { padding: 3px 0; font-size: 0.86rem; line-height: 1.5; }

/* ==========================================================================
   Case progress
   ==========================================================================

   The road the matter is on, across the top of the dashboard. It replaced six
   figures — days open, updates, documents, messages — which were true and were
   trivia: the documents and messages have their own panels, and none of them
   answered the question somebody opens a case to ask, which is "where are we". */
/* No margin of its own. The 22px it used to carry here was the space between
   the rail and the cards under it; the rail now has a panel to itself and the
   gap belongs to .case-detail-body, so keeping it would only hang 22px off the
   bottom of the Timeline tab. */
.case-progress { margin: 0; }

.attorney-stage-dialog { width: min(480px, calc(100vw - 32px)); max-height: calc(100dvh - 32px); overflow: auto; box-sizing: border-box; padding: 26px; border: 1px solid var(--portal-rule); border-top: 4px solid var(--portal-gold); border-radius: 6px; background: var(--portal-surface); color: var(--portal-ink); }
.attorney-stage-dialog::backdrop { background: rgb(0 0 0 / 55%); }
.attorney-stage-dialog h2 { margin-top: 0; font-family: Georgia, serif; }
.attorney-stage-dialog p { line-height: 1.6; }
.attorney-stage-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
/*
  Nothing drawn, nothing taking up room.

  It used to get this from `.case-panel > :empty` for free, being a panel
  child. It sits above the panels now so that every tab shows it, which puts it
  outside that rule and into its own — and .case-detail-body is a grid with an
  18px gap, so an empty section there is a visible hole rather than nothing.
*/
.case-progress:empty { display: none; }

.case-progress-rail {
  display: grid;
  gap: 0;
  grid-auto-columns: 1fr;
  grid-auto-flow: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The connecting line, drawn behind the markers rather than between them: a
   border on each step would break at every gap and read as a dashed rule. */
.case-progress-step { position: relative; text-align: center; }

.case-progress-step::before {
  position: absolute;
  z-index: 0;
  top: 15px;
  left: 0;
  width: 100%;
  height: 2px;
  background: var(--portal-rule);
  content: "";
}

.case-progress-step:first-child::before { left: 50%; width: 50%; }
.case-progress-step:last-child::before { width: 50%; }
.case-progress-step.is-done::before,
.case-progress-step.is-current::before { background: var(--portal-gold); }

.case-progress-button {
  position: relative;
  z-index: 1;
  display: flex;
  width: 100%;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--portal-muted);
  font: inherit;
  cursor: pointer;
}

.case-progress-marker {
  display: grid;
  width: 32px;
  height: 32px;
  border: 2px solid var(--portal-rule);
  border-radius: 50%;
  background: var(--portal-surface);
  font-size: 0.82rem;
  font-weight: 700;
  place-items: center;
}

.case-progress-label { font-size: 0.72rem; line-height: 1.35; max-width: 15ch; }

.case-progress-step.is-done .case-progress-marker {
  border-color: var(--portal-gold);
  background: var(--portal-gold);
  color: #1f1a10;
}

/* The one the eye should land on: filled, ringed, and the only label in ink. */
.case-progress-step.is-current .case-progress-marker {
  border-color: var(--portal-gold);
  background: var(--portal-surface);
  box-shadow: 0 0 0 4px rgba(198, 159, 85, 0.24);
  color: var(--portal-gold);
}

.case-progress-step.is-current .case-progress-button {
  color: var(--portal-ink);
  font-weight: 600;
}

.case-progress-button:hover .case-progress-marker { border-color: var(--portal-gold); }
.case-progress-button:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 3px; }

.case-progress-caption {
  margin: 14px 0 0;
  color: var(--portal-muted);
  font-size: 0.78rem;
  line-height: 1.5;
  text-align: center;
}

/* Stacked on a narrow screen: a six-column rail on a phone is six unreadable
   columns. The line runs down the left instead of across the top. */
@media (max-width: 700px) {
  .case-progress-rail { grid-auto-flow: row; gap: 2px; }
  .case-progress-step::before { top: 0; left: 15px; width: 2px; height: 100%; }
  .case-progress-step:first-child::before { top: 50%; height: 50%; left: 15px; width: 2px; }
  /* The width has to be restated, not only the height. `:last-child::before`
     up in the wide block sets width: 50% to stop the line running off the end
     of the row, and this rule has the same specificity — so leaving width out
     here does not fall back to the 2px above it, it keeps the 50%. The last
     step drew a grey slab across half its width instead of a line down to it.
     Invisible in the console, which is rarely read on a phone, and the first
     thing you see on the client's own rail. */
  .case-progress-step:last-child::before { width: 2px; height: 50%; }
  .case-progress-button { flex-direction: row; gap: 12px; text-align: left; }
  .case-progress-label { max-width: none; font-size: 0.82rem; }
  .case-progress-caption { text-align: left; }
}

/* The receipt number, doubling as a way into the USCIS status page. Underlined
   so it reads as a link in a list of plain facts, where nothing else is one. */
.case-receipt-link {
  color: var(--portal-link);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.case-receipt-link:hover { text-decoration-thickness: 2px; }
.case-receipt-link:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

/* ==========================================================================
   Which tab is open, before the JavaScript runs
   ==========================================================================

   The markup ships with Overview open, and admin.js switches to whatever the
   address names — but it is a module, so it runs after the first paint. That
   made every refresh on another tab flash Clients first.

   portal/tab-boot.js writes the answer onto <html> in the head, and these rules
   apply it at first paint. Once admin.js has run it sets `hidden` and
   `is-active` itself and these stop mattering; they only have to be right for
   the first frame. */
:root[data-boot-tab]:not([data-boot-tab="overview"]) #panel-overview { display: none; }

/*
  `!important`, and not lightly.

  Every panel but Overview ships with the `hidden` attribute, and this stylesheet
  enforces that with `[hidden] { display: none !important }` — deliberately, so
  that nothing accidentally reveals something meant to be closed. A plain
  `display: block` here loses to it however specific the selector is, which is
  what the first-paint test caught: the default was correctly hidden and Cases never
  appeared, leaving a blank console for one frame.

  Matching the `!important` and outranking it on specificity is the honest way
  to win that argument. It applies only for the single frame before admin.js
  removes the attribute itself.
*/
:root[data-boot-tab="clients"] #panel-clients,
:root[data-boot-tab="cases"] #panel-cases,
:root[data-boot-tab="calendar"] #panel-calendar,
:root[data-boot-tab="documents"] #panel-documents,
:root[data-boot-tab="forms"] #panel-forms,
:root[data-boot-tab="messages"] #panel-messages,
:root[data-boot-tab="blog"] #panel-blog,
:root[data-boot-tab="settings"] #panel-settings { display: block !important; }

/* The rail highlight, which flashed with the panel. Neutralised on Overview and
   applied to the named tab, using the same three declarations as .is-active so
   the two cannot drift apart visually. */
/* The three values are copied from `.admin-tab` above, not guessed: a resting
   tab is transparent with 0.65 white, and anything else here would show as a
   one-frame colour shift instead of the one-frame layout shift it replaced. */
:root[data-boot-tab]:not([data-boot-tab="overview"]) #tab-overview {
  background: transparent;
  box-shadow: none;
  color: rgba(255, 255, 255, 0.65);
}

:root[data-boot-tab]:not([data-boot-tab="clients"]) #tab-clients .admin-tab-count {
  background: rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.82);
}

:root[data-boot-tab="cases"] #tab-cases,
:root[data-boot-tab="calendar"] #tab-calendar,
:root[data-boot-tab="documents"] #tab-documents,
:root[data-boot-tab="messages"] #tab-messages,
:root[data-boot-tab="blog"] #tab-blog,
:root[data-boot-tab="settings"] #tab-settings {
  background: rgba(255, 255, 255, 0.09);
  box-shadow: inset 3px 0 #d9ba7b;
  color: var(--portal-white);
}

/* The already-happened half of the timeline. Facts, not controls: no pointer,
   no hover, and a date under each because "when" is the whole of what they say. */
.case-progress-button.is-static { cursor: default; }

.case-progress-date {
  color: var(--portal-muted);
  font-size: 0.66rem;
  line-height: 1.3;
}

/* A history step that has not arrived yet — a consultation still to come. It
   sits in the past half of the rail but must not read as done. */
.case-progress-step.is-ahead .case-progress-marker {
  border-style: dashed;
  color: var(--portal-muted);
}

.case-progress-step.is-ahead .case-progress-date { color: var(--portal-gold); }

/* The join between what happened and what is planned. A hairline rather than a
   heading: it separates the two halves without spending a row on saying so. */
.case-progress-step.is-history + .case-progress-step:not(.is-history):not(.is-adder)::after {
  position: absolute;
  top: 4px;
  left: 0;
  width: 1px;
  height: 24px;
  background: var(--portal-rule);
  content: "";
}

@media (max-width: 700px) {
  .case-progress-step.is-history + .case-progress-step:not(.is-history):not(.is-adder)::after {
    top: 0;
    left: 0;
    width: 100%;
    height: 1px;
  }
}

/* A landmark the office added, rather than one derived from the record. It is a
   control — whether it is done is somebody's decision — so it gets a pointer,
   and a small remove affordance that stays out of the way until hovered. */
.case-progress-step.is-mine { position: relative; }
.case-progress-step.is-mine .case-progress-button { cursor: pointer; }
.case-progress-step.is-mine .case-progress-label { font-style: italic; }

/*
  Above the cell, or a mouse never reaches them.

  The step cell is `position: relative; z-index: 1` and fills the whole width
  of its list item. These two sit on top of it, absolutely positioned, and at
  the default z-index they lost — every real click at the × or the paperclip
  landed on the cell behind, which quietly toggled the step done instead.

  It survived testing because a scripted .click() dispatches straight at the
  element and skips hit-testing entirely. Only a pointer finds this.
*/
.case-step-remove {
  position: absolute;
  z-index: 2;
  top: -4px;
  right: 6px;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  opacity: 0;
  background: var(--portal-surface);
  color: var(--portal-muted);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  transition: opacity 120ms ease;
}

.case-progress-step.is-mine:hover .case-step-remove,
.case-step-remove:focus-visible { opacity: 1; }
.case-step-remove:hover { color: #97262c; }

/* The kind, in small caps above the label (030). Coloured to match the
   calendar, so "Court" on a rail and a court date on the Calendar tab are the
   same colour rather than two unrelated conventions. */
.case-progress-kind {
  color: var(--portal-muted);
  font-size: 0.56rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.case-progress-step[data-kind="form"] .case-progress-kind { color: #9a7430; }
.case-progress-step[data-kind="meeting"] .case-progress-kind { color: #315b9b; }
.case-progress-step[data-kind="court"] .case-progress-kind { color: #6a4a9c; }

/* The office's own steps are italic to separate them from the derived history.
   A tagged one already reads as different, so it stays upright — two signals
   for one distinction is one too many. */
.case-progress-step[data-kind="form"] .case-progress-label,
.case-progress-step[data-kind="meeting"] .case-progress-label,
.case-progress-step[data-kind="court"] .case-progress-label { font-style: normal; }

/* The signed copy. It mirrors the remove control on the opposite corner but
   stays visible rather than waiting for a hover: an attachment is a fact about
   the step, not an action parked next to it. */
.case-step-doc {
  position: absolute;
  z-index: 2;
  top: -4px;
  left: 6px;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--portal-surface);
  font-size: 0.7rem;
  line-height: 1;
  cursor: pointer;
}

.case-step-doc:hover { background: #f2ede3; }
.case-step-doc:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }
.case-step-doc[disabled] { opacity: 0.5; cursor: default; }

/* The way into a returned questionnaire, in the same corner as the signed copy.

   It carries .case-step-doc as well, so it is already placed. When a step has
   both — a form answered online and then signed on paper — the second one moves
   over rather than sitting on top of the first. Written as a sibling rule
   because the DOM order is fixed by the code that builds the step: answers,
   then the attachment. */
.case-step-answers + .case-step-doc { left: 30px; }

/*
  On touch, the corner controls come out of the corner.

  The × is held at opacity: 0 until the step is hovered, and a phone has no
  hover and no :focus-visible to offer either — so it is not a small target, it
  is an invisible one, sitting hit-testable on top of a cell that marks the step
  done when it is pressed by mistake. The paperclip beside it is the same 20px
  square, visible but barely.

  Both go back into the step's own flow at a thumb's size. That needs the step
  to be a flex container to put them anywhere: they are siblings of the cell,
  which is a plain <li> child at width: 100%, so the cell keeps its line and the
  controls take the one under it — where margin-left: auto can push Remove to
  the far end, away from the two that open things. Scoped to .is-mine, which is
  exactly the set of steps that carry these buttons (cases.js adds both on the
  same !derived test).
*/
@media (hover: none) and (pointer: coarse) {
  .case-progress-step.is-mine { display: flex; flex-wrap: wrap; }

  .case-step-remove {
    position: static;
    width: 44px;
    height: 44px;
    margin-left: auto;
    opacity: 1;
  }

  .case-step-doc {
    position: static;
    width: 44px;
    height: 44px;
  }
}

/* Where a form actually is, beside the date on the rail.

   Two states and two colours, both quieter than the label they follow: this is
   a fact about the step rather than the step itself, and a bright chip here
   would outrank the thing it is describing. */
.case-step-sent {
  font-size: 0.58rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.case-step-sent[data-state="sent"] { color: #9a7430; }
.case-step-sent[data-state="returned"],
.case-step-sent[data-state="ready"],
.case-step-sent[data-state="filed"] { color: #2f6b45; }

/* ---------------------------------------------------------------------------
   Add a step: the kind, asked first

   Four radios rather than a select. The choice is not a value being filled in,
   it decides what the rest of the dialog asks for — and a select would hide
   three of the four options behind a click, which is the wrong shape for the
   first and most consequential question on the form.
   --------------------------------------------------------------------------- */
.case-step-kinds {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin: 0;
  padding: 0;
  border: 0;
}

.case-step-kinds legend {
  grid-column: 1 / -1;
  float: left;
  width: 100%;
  padding: 0;
  margin-bottom: 8px;
  color: var(--portal-muted);
  font-size: 0.72rem;
}

.case-step-kind {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 2px 8px;
  padding: 10px 12px;
  border: 1px solid var(--portal-rule);
  border-radius: 6px;
  cursor: pointer;
}

.case-step-kind input { grid-row: span 2; margin-top: 3px; }
.case-step-kind-name { font-size: 0.82rem; font-weight: 600; }

.case-step-kind-note {
  grid-column: 2;
  color: var(--portal-muted);
  font-size: 0.68rem;
  line-height: 1.35;
}

.case-step-kind:hover { border-color: var(--portal-gold); }
.case-step-kind:has(input:checked) { border-color: var(--portal-navy); background: #faf7f1; }
.case-step-kind:has(input:focus-visible) { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

/* One kind's fields at a time. `hidden` does the hiding; this only decides how
   the fields that are showing sit together. */
.case-step-group { display: grid; gap: 12px; }

/* The offer to send the questions, and the sentence explaining it.

   Tighter than the gap between fields, because the note is about the checkbox
   above it rather than a field of its own — the standard 12px made them read as
   two separate things. */
.case-step-send { display: grid; gap: 4px; }
.case-step-send .portal-field-note { margin: 0; }

@media (max-width: 560px) {
  .case-step-kinds { grid-template-columns: minmax(0, 1fr); }
}

/* The way to add one, at the end of the rail. Dashed, so it reads as a slot
   waiting to be filled rather than as another step that has happened. */
.case-progress-step.is-adder .case-progress-marker {
  border-style: dashed;
  border-color: var(--portal-rule);
  background: transparent;
  color: var(--portal-muted);
  font-weight: 400;
}

/* The line runs into it, because it is the next place on the rail rather than
   a control parked next to one. Grey like any step still ahead. */
.case-progress-step.is-adder::before { background: var(--portal-rule); }
.case-step-add:hover .case-progress-marker { border-color: var(--portal-gold); color: var(--portal-gold); }


/* ==========================================================================
   Update this matter
   ==========================================================================

   One card, three groups, in the order somebody works: what the file IS, where
   it STANDS, and what to ADD to its timeline. They were two cards at opposite
   ends of the page, which made a single act — recording what moved — feel like
   two errands. */
.case-edit-group {
  margin: 0 0 4px;
  padding: 0;
  border: 0;
  border-top: 1px solid var(--portal-rule);
  padding-top: 14px;
}

/* The legend carries the grouping on its own, so it is styled as a quiet label
   rather than a heading competing with the card title above it. */
.case-edit-group > legend {
  padding: 0 8px 0 0;
  color: var(--portal-muted);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.case-edit-group .portal-field { margin-top: 12px; }
.case-edit-group .portal-form-row { margin-top: 12px; }

/* Each group saves on its own, and its button sits with it rather than at the
   far bottom of the card where it would look like it saved everything. */
.case-edit-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 16px;
}

.case-edit-card .portal-status:empty { display: none; }



/* The end of the rail. Square rather than round so the last marker reads as a
   stop, and the line does not run past it. */
.case-progress-step.is-closing .case-progress-marker {
  border-radius: 4px;
  font-size: 0.7rem;
}

.case-progress-step.is-closing.is-ahead .case-progress-marker {
  border-style: solid;
  border-color: var(--portal-rule);
  color: var(--portal-muted);
}

/* Closed is the one state on this rail that is an ending rather than progress,
   so it is green — the same green the Approved stage and answered deadlines
   use — rather than the gold that means "moving". */
.case-progress-step.is-closing.is-done .case-progress-marker {
  border-color: #1c6039;
  background: #1c6039;
  color: #eaf3ed;
}

.case-progress-step.is-closing.is-done::before { background: #1c6039; }
.case-progress-step.is-closing .case-progress-button:not(.is-static):hover .case-progress-marker {
  border-color: #1c6039;
  color: #1c6039;
}

/* ---------------------------------------------------------------------------
   Add a step: the form picker and its upload
   --------------------------------------------------------------------------- */

/* Bigger than the fields around it, on purpose — and measured against them
   rather than guessed. The dialog's inputs are 1rem; the first attempt at
   "bigger" was 0.95rem, which is smaller. This box is a search over forty-five
   forms rather than a value being filled in, and the list is what you read
   while you type, so both are set above the surrounding fields. */
#case-step-form-name {
  font-size: 1.15rem;
  padding-top: 11px;
  padding-bottom: 11px;
}

#case-step-form-list .portal-combobox-option strong { font-size: 1rem; font-weight: 500; }
#case-step-form-list { max-height: 360px; width: max(100%, 420px); }

/* The button and the name of whatever was chosen, on one line. */
.case-step-upload {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.case-step-filename {
  color: var(--portal-muted);
  font-size: 0.74rem;
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
   Grouped combobox: headings and a number column
   --------------------------------------------------------------------------- */

/* The heading stays put while its own section scrolls past, so you always know
   which part of the list you are looking at. */
.portal-combobox-group {
  position: sticky;
  top: -4px;
  z-index: 1;
  padding: 8px 10px 4px;
  background: var(--portal-surface);
  color: var(--portal-muted);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.portal-combobox-group + .portal-combobox-option { border-top: 0; }

/* Number and title as two columns, so the numbers line up and the list can be
   read down its left edge instead of sentence by sentence. */
.portal-combobox-option.has-badge {
  display: grid;
  grid-template-columns: 5.25rem minmax(0, 1fr);
  align-items: baseline;
  gap: 10px;
}

.portal-combobox-badge {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--portal-link);
  white-space: nowrap;
}

.portal-combobox-option.has-badge strong { min-width: 0; }

@media (max-width: 560px) {
  .portal-combobox-option.has-badge { grid-template-columns: 4.25rem minmax(0, 1fr); }
  #case-step-form-list { width: 100%; }
}


/* ---------------------------------------------------------------------------
   Add a step: one size, whatever is in it
   --------------------------------------------------------------------------- */

/*
  A fixed height, not a max.

  The dialog was sized by its contents, so it changed shape underneath you:
  646px on "Something else", 702px on "Form to sign", and another 18px taller
  the moment the form list opened. Every one of those is the box moving while
  you are reading it, and the buttons at the bottom moving with it.

  Sized to the tallest of the four kinds, so switching never resizes it and the
  dropdown opens inside the space that is already there. The shorter kinds carry
  a little room at the bottom, which is the cheaper of the two costs.
*/
#case-step-modal {
  display: flex;
  height: min(92vh, 832px);
  max-height: min(92vh, 832px);
  flex-direction: column;
  /* The body scrolls, not the dialog — otherwise the header scrolls away and
     the body never gets a height to fill. */
  overflow: hidden;
}

/*
  The buttons sit at the bottom of the dialog, not at the end of the fields.

  Making the box taller on its own does nothing for the form list: the actions
  follow the last field, so the extra height lands underneath them and the list
  still opens across them. Pinned to the bottom instead, the height the dialog
  gained becomes room between the search box and the buttons — which is exactly
  where the list needs it.

  It also puts Cancel and Add step in the same place for all four kinds, rather
  than wherever that kind's fields happened to end.
*/
/* The body has to be a column for the form to fill it: `min-height: 100%` on
   the form alone resolved against a flex-sized parent and came out as auto,
   which left the buttons floating in the middle of a taller dialog. */
#case-step-modal .portal-modal-body {
  display: flex;
  flex-direction: column;
}

#case-step-form {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 18px;
}

#case-step-form .portal-modal-actions { margin-top: auto; }

/* Fits between the search box and the buttons at the dialog's new height. */
#case-step-form-list { max-height: 260px; }

/* All four kind cards the same height, whether their description wraps to one
   line or two — otherwise the second row sits higher than the first. */
.case-step-kinds { grid-auto-rows: 1fr; }

/* The upload action at the foot of the form list. Sticky, so it stays reachable
   however far down the catalogue you have scrolled, and ruled off from the
   options above so it does not read as one of them. */
.portal-combobox-footer {
  position: sticky;
  bottom: -4px;
  padding: 4px;
  border-top: 1px solid var(--portal-rule);
  background: var(--portal-surface);
}

.portal-combobox-upload {
  display: block;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--portal-link);
  font: inherit;
  font-size: 0.86rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.portal-combobox-upload::before { content: "＋ "; }
.portal-combobox-upload:hover { background: #f2ede3; }

/* The complete button and its date, on one line above the save row. */
.case-step-edit-state {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* Remove sits left, away from Cancel and Save, so the destructive one is not
   next to the one you press without looking. */
#case-step-edit-remove { margin-right: auto; color: var(--portal-danger); }

/* Sized like the add dialog so opening either does not resize the screen. */
#case-step-edit-modal { max-height: min(92vh, 620px); }

/* The two choices a step offers, side by side and equally weighted — one is not
   more likely than the other, so neither is made small. */
.case-step-choices {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.case-step-choices .portal-button { width: 100%; }

/*
  What to do with one thing in the cabinet.

  Borrows the step chooser's two-across grid, because it is the same question
  asked about a different object and a second layout for it would be a second
  thing to recognise.
*/
#doc-choice-heading { overflow-wrap: anywhere; }

/*
  Two choices or three, across the width either way.

  The step chooser it borrows from always has exactly two, so it can be a fixed
  pair of columns. This one grows a Filed on… on a form that has come back, and
  a fixed pair would put the third on a row of its own looking like the odd one
  out rather than the middle one.
*/
#doc-choice-choices {
  grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
}

/*
  And on a phone, one per row.

  At 130px the three needed 414px of grid and a large phone offers about 300,
  so they wrapped two-and-one — Delete alone on the second row, which is the
  shape the rule above exists to prevent. 92px fits three across from 480px
  up; below that they stack, which is a list rather than an odd one out.
*/
@media (max-width: 479px) {
  #doc-choice-choices {
    grid-template-columns: minmax(0, 1fr);
  }
}

/*
  The lesser of the two, coloured but not filled: it opens a warning, it does
  not delete anything. The filled one under the warning is the one that does.

  Qualified with :root to outweigh the theme. Dusk restyles every secondary
  button at (0,4,0) and this was (0,3,0), so under that theme Delete lost its
  red and came out identical to the Filed on… beside it — two buttons the same
  colour, one of which takes the form off the matter. portal-button-danger had
  the same trap and was fixed the same way; this is the case that was missed.
*/
:root .case-step-choices .portal-button.portal-button-secondary.is-danger {
  color: var(--portal-danger);
  border-color: color-mix(in srgb, var(--portal-danger) 45%, transparent);
}

/*
  The warning, in the space the two choices were in.

  Bordered and tinted rather than a bare paragraph: it has replaced the thing
  that was there a moment ago, and the change has to be visible to somebody who
  was about to press where Delete used to be.
*/
.doc-choice-warning {
  display: grid;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid color-mix(in srgb, var(--portal-danger) 35%, transparent);
  border-radius: 8px;
  background: color-mix(in srgb, var(--portal-danger) 7%, transparent);
}

.doc-choice-warning[hidden] { display: none; }
.doc-choice-warning p { margin: 0; font-size: 0.9rem; line-height: 1.5; }

/* Taller than the step chooser it borrows from: two lines of choices where
   that one has one, and a sentence that must not have to scroll to be read. */
#doc-choice-modal { max-height: min(88vh, 480px); }

/* ==========================================================================
   Add-a-document chooser: full-width option buttons, a title and a line of
   what it does. Used both for the two ways in (request / send a form) and, a
   step later, for the list of forms to send. One look for "press this to go
   there", so the second screen reads as more of the first. See documents.js.
   ========================================================================== */
/* Wider and taller than a bare two-button chooser: the choices carry a line of
   what they do, and the second view is the whole library, which wants room to
   list several forms without the box becoming a letterbox. */
#doc-add-modal {
  width: min(720px, calc(100% - 32px));
  max-height: min(90vh, 720px);
}

.doc-add-choices { display: grid; gap: 12px; }

.doc-add-option {
  display: grid;
  gap: 4px;
  width: 100%;
  padding: 15px 17px;
  border: 1px solid var(--dashboard-border);
  border-radius: 9px;
  background: var(--portal-surface);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.doc-add-option strong { color: var(--portal-link); font-size: 1.02rem; }
.doc-add-option span { color: var(--portal-muted); font-size: 0.86rem; line-height: 1.45; }

.doc-add-option:hover { border-color: var(--portal-navy); background: rgba(198, 159, 85, 0.06); }
.doc-add-option:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

#doc-add-back,
#doc-add-about-back,
#doc-add-confirm-back,
#document-request-back { margin-bottom: 4px; }

/* The ask dialog's Back sits above its form, so it needs the gap the chooser's
   own views get from their grid. */
#document-request-back { display: block; margin-bottom: 12px; }

#doc-add-forms .portal-field { margin: 12px 0; }
#doc-add-form-none { margin-top: 12px; }

/* The form being sent, named at the top of the confirm. The only heading of its
   kind in this body, so it is sized here rather than inheriting a page rule. */
#doc-add-confirm-what {
  margin: 10px 0 6px;
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.15rem;
  font-weight: 400;
}

#doc-add-confirm .portal-modal-actions { margin-top: 18px; }

/* ==========================================================================
   The questionnaire preview, in a window of its own.

   Wider and taller than an ordinary dialog because it holds a whole form —
   the questions carry their own numbering and help text, and a narrow column
   wraps every one of them into three lines. Above the chooser (30/31) so the
   two backdrops cannot disagree about which dialog is being dimmed.
   ========================================================================== */
#doc-preview-backdrop { z-index: 40; }

#doc-preview-modal {
  z-index: 41;
  /* Two panes need two readable columns: a federal form is laid out for paper
     and the questions carry their own numbering and help text. */
  width: min(1320px, calc(100% - 32px));
  max-height: min(94vh, 1000px);
}

#doc-preview-modal .portal-modal-body { padding-top: 8px; }
#doc-preview-modal .portal-status:empty { display: none; }

/* The two panes. The body itself stops scrolling — each pane does instead, so
   the form can be at page four while the questions are at question one. */
.doc-preview-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 18px;
  overflow: hidden;
}

.doc-preview-pane {
  display: flex;
  min-width: 0;
  flex-direction: column;
  overflow: hidden;
}

/*
  The pane's own caption, and only that.

  Written as a descendant it reached every h4 inside the pane as well — and the
  questionnaire uses h4 for the heading over a run of boxes, which on the I-485
  is a two-hundred-character instruction. Set in ten-pixel capitals it was
  unreadable. The child combinator keeps this to the two captions it was for.
*/
.doc-preview-pane > h4 {
  margin: 0 0 8px;
  color: var(--portal-muted);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

/*
  Recessed, not another white card.

  The window is a surface, the panes were the same surface, and the pages inside
  them are white paper — three whites stacked, which reads as one flat sheet with
  lines drawn on it. The panes sit on the page ground instead, the way a document
  viewer puts paper on a desk: the paper is then the only white in the window and
  is what the eye finds. --portal-cream is a theme token, so under dusk the desk
  goes dark and the paper stays paper without a second rule.
*/
.doc-preview-pane-body {
  min-height: 0;
  flex: 1 1 auto;
  overflow-y: auto;
  border: 1px solid var(--dashboard-border);
  border-radius: 8px;
  padding: 12px 14px;
  background: var(--portal-cream);
}

/* The pages, stacked down the pane the way a document reads. */
#doc-preview-pdf { display: grid; gap: 10px; }

/* Paper on the desk: white because the form is, lifted so its edge is findable
   against a dark ground as well as a pale one. */
.doc-preview-page {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--dashboard-border);
  border-radius: 3px;
  background: #fff;
  box-shadow: 0 2px 8px rgba(16, 38, 83, 0.14);
}

/* One column on anything narrow: two half-width columns of a federal form is
   neither of them readable. */
@media (max-width: 900px) {
  .doc-preview-split { grid-template-columns: minmax(0, 1fr); overflow-y: auto; }
  .doc-preview-pane { overflow: visible; }
  .doc-preview-pane-body { overflow: visible; }
}

/* The sentence that stands where the buttons were, saying what the press does.
   Bordered and tinted so the swap is visible to somebody who was about to
   press where Add Form used to be — the same move doc-choice's warning makes. */
.doc-add-armed {
  display: grid;
  margin-top: 14px;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--dashboard-border);
  border-radius: 8px;
  background: rgba(198, 159, 85, 0.07);
}

.doc-add-armed[hidden] { display: none; }
.doc-add-armed p { margin: 0; font-size: 0.9rem; line-height: 1.55; }

/* Disabled controls read as unavailable rather than as broken: this is a
   picture of a form, not a form. Same rule the send dialog's preview uses. */
#doc-add-preview .client-question input:disabled,
#doc-add-preview .client-question select:disabled,
#doc-add-preview .client-question textarea:disabled {
  opacity: 1;
  background: var(--portal-sheet, rgba(0, 0, 0, 0.02));
  cursor: default;
}

/* And the same for taking somebody on: a heading, an email, a picker, a line
   of help and two buttons do not fit the 340px a two-button chooser needs. */
#take-on-modal { max-height: min(88vh, 460px); }

/* A chooser is two buttons, not a form; it should not stand as tall as one. */
.portal-modal.is-compact {
  height: auto;
  max-height: min(80vh, 340px);
}

#case-step-edit-modal .portal-modal-head p { color: var(--portal-muted); font-size: 0.74rem; }

/* ---------------------------------------------------------------------------
   The form library
   --------------------------------------------------------------------------- */

.form-row-head {
  display: grid;
  grid-template-columns: 5.25rem minmax(0, 1fr);
  align-items: baseline;
  gap: 10px;
}

.form-row-number {
  color: var(--portal-link);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.form-row-title { font-size: 0.92rem; }
.form-row-facts { margin-top: 4px; }

/* A form the checker could not verify is marked on the row itself, not only in
   the banner — the banner says which, the row says where. */
.form-row[data-status="outdated"],
.form-row[data-status="error"] { border-left: 3px solid var(--portal-danger); padding-left: 11px; }
.form-row[data-status="outdated"] .form-row-facts,
.form-row[data-status="error"] .form-row-facts { color: var(--portal-danger); }

/* Not watched is a statement of fact, not a problem: EOIR and State Department
   forms are published elsewhere and were never claimed to be checked. */
.form-row[data-status="unchecked"] .form-row-number { color: var(--portal-muted); }

@media (max-width: 560px) {
  .form-row-head { grid-template-columns: minmax(0, 1fr); }
}

/* Two buttons in the library header rather than one. */
.form-library-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------------------------------------------------------------------------
   The form editor
   --------------------------------------------------------------------------- */

/*
   Wide, because this is data entry over hundreds of fields rather than a
   confirmation. It still scrolls internally so the header and controls stay put.

   The height used to stop at 860px however tall the screen was, which is what
   made a whole page impossible to see: a letter sheet fitted to that box comes
   out under a fifth of the window on a desktop monitor, and the room to fix it
   was sitting unused underneath. 95vh, and the fit does the rest.
*/
/*
  1240 was the width when the page had the dialog to itself.

  The questions column takes 340 of it and the controls another 208, which left
  the sheet about 640px — a US Letter page printed at three quarters of the size
  USCIS prints it, and the 8pt in Part 3 unreadable. 1680 gives the page back
  what the column took and then some; the vw cap means a laptop still gets
  whatever it has, and the page column is the 1fr, so every pixel added here
  goes to the form rather than to the furniture.

  The form editor is the only thing wearing is-wide, so this widens one dialog.
*/
.portal-modal.is-wide {
  display: flex;
  width: min(96vw, 1680px);
  height: 95vh;
  max-height: 95vh;
  /*
    No padding of its own, and this is the single largest thing that was making
    a fitted page small: the ordinary .portal-modal above sets 26px top and
    bottom, the console dialog below never unsets it, and the head and the body
    both carry their own padding anyway. So the dialog was inset twice — 52px
    of height, more than the head and the body padding put together, spent on a
    margin around a margin.
  */
  padding: 0;
  flex-direction: column;
  overflow: hidden;
}

/*
  The head of this dialog, on one line.

  Every other modal stacks its title, its subject and its tally, and can afford
  to: it is asking one question and then closing. This one is a window onto a
  letter sheet, and what governs how big that sheet can be drawn is height —
  "Whole page" fits the smaller of the two measurements, and on any screen
  wider than it is tall that is always the height. The stack spent about
  seventy pixels of it on three things that read perfectly well side by side,
  and every one of those pixels came out of the form.

  Wrapped rather than squeezed: on a narrow window a long form name and a long
  client name take a second line instead of crushing the picker between them.
*/
#form-editor-modal .portal-modal-head {
  /* The same inset across as the body under it, so the form name lines up with
     the first control and the Close with the right edge of the page box. The
     dialog has no padding of its own to line either of them up with. */
  padding: 10px clamp(18px, 3vw, 26px);
  align-items: center;
}

#form-editor-modal .portal-modal-head > div {
  display: flex;
  min-width: 0;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
}

/* The margins that held the stack apart are what the row's own gap is now. */
#form-editor-modal .form-editor-who { margin: 0; }
#form-editor-modal .portal-modal-head p { margin: 0; }

/*
  A column, and stretched rather than centred: a stack of buttons each as wide
  as its own words is a ragged edge down the middle of the dialog, and the eye
  reads the ragged edge instead of the labels.

  No longer sticky. It was sticky because it scrolled with the body it sat on
  top of; it is its own column now and goes nowhere. It keeps its own scrollbar
  for the same reason the pages have one — on a short window, ten controls are
  taller than the room for them.
*/
.form-editor-controls {
  display: flex;
  overflow-y: auto;
  padding-right: 16px;
  border-right: 1px solid var(--portal-rule);
  background: var(--portal-surface);
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
}

/* Not "flex: 1 1 220px". Down a column that basis is a height, and the filter
   grew to fill whatever the buttons left over. */
.form-editor-controls input[type="search"] { flex: none; }

/*
  What you do when you are finished, held to the bottom of the rail.

  Everything above it changes how the page is drawn — bigger, smaller, whole
  page, the list. These two change the form. Pushed apart so the difference is
  visible without reading the labels, and so the press that files a client's
  answers is not the one directly under "Page width".
*/
.form-editor-finish {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid var(--portal-rule);
}

/* The lifecycle presses sit above Save, stacked like everything else here
   rather than in the row they wore in the header. */
.form-editor-finish .form-editor-lifecycle {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  margin: 0;
  font-size: 0.8rem;
}

.form-editor-finish .form-editor-lifecycle:empty { display: none; }
.form-editor-finish .form-editor-lifecycle button { text-align: center; }

/*
  Show, over the box that holds a sealed identifier.

  Small, and inside the widget's own rectangle at the right-hand end, because
  the thing it belongs to is one box on a page of two hundred and a control
  anywhere else would need explaining. The annotation layer positions the
  section; this rides in it.
*/
.annotationLayer section.is-sealed { overflow: visible; }

.form-editor-eye {
  position: absolute;
  top: 50%;
  right: 2px;
  z-index: 2;
  transform: translateY(-50%);
  padding: 1px 5px;
  border: 1px solid color-mix(in srgb, var(--portal-navy) 30%, transparent);
  border-radius: 3px;
  background: var(--portal-white);
  color: var(--portal-navy);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1.4;
  cursor: pointer;
}

.form-editor-eye:hover { background: color-mix(in srgb, var(--portal-navy) 8%, #fff); }
.form-editor-eye:disabled { opacity: 0.5; cursor: default; }

/*
  The filter and the unanswered toggle belong to the list.

  On the page there is nothing hidden to filter and nothing to count down to, so
  leaving them there would be two controls that appear to do nothing.
*/
.form-editor-controls[data-view="form"] .for-list { display: none; }

/*
  Nothing that writes is on the bar while View is up.

  Save, both fills and the Form/List pair all change what the form says, and
  View exists precisely so a form can be read without that being possible — a
  Save answers sitting over a page nobody can type into is an offer to do
  something there is no way to have done.

  Download and "Save to their documents" stay. Both only read what is on
  screen, and both are things somebody in View has come to do.
*/

/* And nothing about how big a page is drawn while the list is up, which is a
   column of text: the buttons would be there, they would light up, and nothing
   on screen would change. */
.form-editor-controls[data-view="list"] .for-form { display: none; }

/*
  The zoom, in the same pill the view switches wear.

  Two steppers, the percentage they are stepping, and the two fits. The fits
  light up like a chosen tab because they are a state — a form stays fitted
  until somebody says otherwise — while the steppers are just presses. Neither
  is lit when the editor opens, which is the truth: it opens at a size of its
  own rather than at either fit.
*/
.form-editor-zoom {
  display: flex;
  overflow: hidden;
  align-items: center;
  border: 1px solid var(--portal-rule);
  border-radius: 999px;
}

.form-editor-zoom button {
  padding: 6px 14px;
  border: 0;
  background: transparent;
  color: var(--portal-muted);
  cursor: pointer;
  flex: none;
  font: inherit;
  font-size: 0.95rem;
  line-height: 1;
}

/* The percentage takes what the two steppers leave. Lining figures, so a press
   of + does not shuffle the buttons either side of it along by a pixel per
   digit. */
#form-editor-zoom-level {
  flex: 1 1 auto;
  color: var(--portal-muted);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.form-editor-views {
  display: flex;
  overflow: hidden;
  border: 1px solid var(--portal-rule);
  border-radius: 999px;
}

/* Halves, so a two-word label and a one-word label either side of the same
   switch do not make it lopsided. */
.form-editor-views button {
  padding: 6px 10px;
  border: 0;
  background: transparent;
  color: var(--portal-muted);
  cursor: pointer;
  flex: 1 1 0;
  font: inherit;
  font-size: 0.85rem;
}

.form-editor-views button.is-on {
  background: var(--portal-ink);
  color: var(--portal-surface);
}

/*
  The controls down the side, the form beside them.

  They were a bar across the top, which is the one place a form editor cannot
  afford to spend room: what is short on a filing is height, the page is longer
  than it is wide, and three rows of wrapped buttons above it came out of the
  same budget as the form. Down the side they cost width, which a letter sheet
  in a dialog this wide has to spare.

  Two columns and two rows: the controls hold the left of both, the status line
  sits over the form, and the pages and the field list share the cell under it —
  they are never both on screen, so they never both claim it.

  The pages scroll, not the dialog. The observer that decides when to draw a
  page measures against that box, so it has to be the thing that actually
  scrolls — against a box the height of all 24 pages every page is always "in
  view" and they would all draw at once.
*/
#form-editor-modal .portal-modal-body {
  display: grid;
  overflow: hidden;
  /* Tighter above and below than a modal body normally is, for the same reason
     the head is: this is the page's height budget, not decoration. */
  padding: 10px clamp(18px, 3vw, 26px) 12px;
  grid-template-columns: 208px minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  column-gap: 18px;
}

#form-editor-modal .portal-modal-body > .form-editor-controls {
  grid-row: 1 / span 2;
  grid-column: 1;
}

#form-editor-modal .portal-modal-body > #form-editor-status {
  grid-row: 1;
  grid-column: 2;
}

/*
  Gone when it has nothing to say.

  It is a grid row of its own, so an empty one is a line and a half of the
  page's height spent on a blank. It comes back the moment there is a message
  to put in it, which is the only time it is worth the room — and a warning
  that these answers were typed against an older edition is worth the room.
*/
#form-editor-modal .portal-modal-body > #form-editor-status:empty { display: none; }

.form-editor-pages,
#form-editor-fields {
  grid-row: 2;
  grid-column: 2;
  min-height: 0;
  overflow-y: auto;
}

.form-editor-pages {
  display: flex;
  padding: 2px 0 10px;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  background: var(--portal-sunk, rgba(0, 0, 0, 0.04));
}

/*
  The variables pdf.js positions its widgets with. --total-scale-factor is what
  every annotation's CSS is written against; the rounding pair keeps a widget
  from landing half a pixel off the box it belongs in.
*/
.form-page {
  --user-unit: 1;
  --total-scale-factor: calc(var(--scale-factor) * var(--user-unit));
  --scale-round-x: 1px;
  --scale-round-y: 1px;
  --hcm-highlight-filter: none;

  position: relative;
  /*
    Never shrunk. These are flex items with an explicit height in pixels, and a
    flex column's default is to squash its children to fit — which turned a
    letter-size page into a 220px strip with the whole form crushed into it.
  */
  flex: none;
  direction: ltr;
  box-shadow: 0 1px 10px rgb(0 0 0 / 0.18);
}

/*
  A bar again when there is no width to give away.

  208px out of a phone is a third of the screen, and the page beside it would be
  too narrow to read a form on. Below this the controls go back over the top and
  wrap, which costs height — but on a screen this size the height was going to
  be spent scrolling either way.
*/
@media (max-width: 900px) {
  #form-editor-modal .portal-modal-body {
    display: flex;
    flex-direction: column;
  }

  .form-editor-controls {
    padding: 0 0 12px;
    border-right: 0;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }

  .form-editor-controls input[type="search"] { flex: 1 1 220px; }

  .form-editor-pages,
  #form-editor-fields {
    flex: 1 1 auto;
  }
}

.form-page.is-broken { display: grid; padding: 20px; place-items: center; }

.form-page-error {
  margin: 0;
  color: var(--portal-danger, #a1231f);
  font-size: 0.85rem;
  text-align: center;
}

/*
  A ticked box shows an X.

  pdf.js draws nothing when a box is checked — it leaves the mark to the PDF's
  own appearance stream, which is only rendered on save or print. On screen the
  box just went on looking empty, so there was no way to tell what you had
  ticked, which on a form where the boxes are mutually exclusive is the one
  thing you need to see.

  An X rather than a tick because that is what USCIS asks for on paper, and
  because it is what the saved PDF will show.

  The selector carries an extra pseudo-class so it outweighs the vendored
  rule that paints the unticked tint — that stylesheet is appended to the head
  at run time and would otherwise win on order.
*/
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked,
.annotationLayer .buttonWidgetAnnotation.radioButton input:checked {
  background-image: url("data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'><path d='M2.2 2.2 L7.8 7.8 M7.8 2.2 L2.2 7.8' stroke='%23111' stroke-width='1.5' stroke-linecap='round'/></svg>");
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
}

/*
  A box this office unlocked, marked so it does not look like the rest.

  USCIS locks these until its own script opens them; the editor opens them
  itself — see unlock() in form-page-view.js — and a faint edge is the honest
  signal that this one was not offered to you by the form.
*/
.annotationLayer .textWidgetAnnotation input[data-was-locked],
.annotationLayer .choiceWidgetAnnotation select[data-was-locked] {
  outline: 1px dashed rgb(0 90 255 / 0.45);
  outline-offset: -1px;
}

/*
  A signature line, and why it will not take a name.

  Every other box on the page can be typed into, so one that refuses reads as
  broken unless it says otherwise. The blue fill that means "you can write here"
  is replaced with a flat grey that means the opposite, and hovering explains.
*/
.annotationLayer section[data-ink-signature] {
  cursor: not-allowed;
}

.annotationLayer section[data-ink-signature] :is(input, textarea) {
  background-image: none;
  background-color: rgb(87 83 78 / 0.11);
  cursor: not-allowed;
}

/*
  The note sits above the box and escapes it — the section is positioned but
  does not clip, so a tooltip wider than a signature line still reads.

  `width: max-content` with a cap, because these lines are wide and a note the
  width of the box would be one word per line.
*/
.annotationLayer section[data-ink-signature]::after {
  content: attr(data-ink-signature);
  position: absolute;
  z-index: 6;
  bottom: calc(100% + 6px);
  left: 0;
  width: max-content;
  max-width: min(280px, 60vw);
  padding: 7px 10px;
  border-radius: 6px;
  background: rgb(24 24 27 / 0.95);
  color: #fff;
  font: 400 0.72rem/1.4 system-ui, -apple-system, sans-serif;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
}

.annotationLayer section[data-ink-signature]:hover::after {
  opacity: 1;
}

/*
  A pointer is not the only way to arrive at a box. The section carries the tab
  stop because pdf.js locks these with `disabled`, and a disabled control cannot
  be focused at all.
*/
.annotationLayer section[data-ink-signature]:focus-visible::after,
.annotationLayer section[data-ink-signature]:focus-within::after {
  opacity: 1;
}

.annotationLayer section[data-ink-signature]:focus-visible {
  outline: 2px solid var(--portal-gold, #b08d3f);
  outline-offset: 1px;
}

@media (prefers-reduced-motion: reduce) {
  .annotationLayer section[data-ink-signature]::after { transition: none; }
}

.form-page canvas {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
}

.form-page-number {
  position: absolute;
  right: 8px;
  bottom: 6px;
  z-index: 3;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgb(0 0 0 / 0.55);
  color: #fff;
  font-size: 0.7rem;
  pointer-events: none;
}

/*
  Who the form is for, under its number.

  Given a width rather than left to fill the header: the combobox is an input,
  and an input stretched across a wide dialog reads as the main thing on it,
  which the questions below are.
*/
/*
  Which of the two the dialog is, in a word.

  The View/Edit switch is gone from the controls: the card that opened the form
  had already chosen — View on a document, Edit on a form being filled in — and
  a switch beside it that could disagree with the press that got you here was
  one more thing to read and one more way to end up in the wrong one. So the
  head states it instead of offering it.
*/

.form-editor-who {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 2px;
}

.form-editor-who label {
  color: var(--portal-muted);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.form-editor-who .portal-combobox { width: min(320px, 60vw); }

/*
  What a box holds, next to what it is called.

  Quiet until it is set: unmapped is the correct state for almost every field,
  so a page of 423 rows should not read as 423 things left undone.
*/
.form-editor-fact {
  max-width: 190px;
  padding: 2px 6px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--portal-muted);
  font-size: 0.72rem;
}

.form-editor-fact:hover,
.form-editor-fact:focus { border-color: var(--portal-rule); }

.form-editor-fact.is-set {
  border-color: var(--portal-rule);
  color: var(--portal-ink);
}

/*
  A suggestion is not a mapping.

  It reads the field's name, which cannot tell Part 1's family name from Part
  3's — so it has to look unlike something a person decided.
*/
.form-editor-fact[data-suggested] {
  color: var(--portal-muted);
  font-style: italic;
  opacity: 0.75;
}

/*
  And the press that turns one into the other.

  It only exists beside a suggestion, and removes itself once the mapping is
  saved, so the row goes quiet again the moment somebody has decided.
*/
.form-editor-fact-use {
  flex: 0 0 auto;
  padding: 2px 6px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/*
  The same press, for a whole part, sitting in its heading.

  Pushed to the right so the heading still reads as a heading — the part's name
  is what somebody is scanning for, and a button in the middle of that line
  would be read before it.
*/
.form-editor-use-all {
  float: right;
  margin-left: 12px;
  padding: 1px 8px;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/*
  Except where the part is not the client's.

  The attorney, preparer and interpreter blocks carry boxes named exactly like
  the client's, so this one is worth a second look before it is pressed. Amber
  rather than red: it is allowed, and frequently right.
*/
.form-editor-use-all.is-careful {
  color: #8a6320;
  text-decoration-style: dotted;
}

.form-editor-group { margin-bottom: 22px; }

/*
  The part heading sticks to the top of the list, and to nothing else.

  It used to stick 52px down, which was the height of the controls bar when
  the bar was sticky across the top of the body the list scrolled inside — the
  heading had to clear it. The bar went down the side and the list became its
  own scroll container, and 52px stopped meaning anything: sticky holds an
  element at its offset even when it has not been scrolled to yet, so the very
  first heading was pushed 52px into its own rows and sat on top of them.
*/
.form-editor-group h3 {
  position: sticky;
  top: 0;
  margin: 0 0 10px;
  padding: 6px 0;
  border-bottom: 1px solid var(--portal-rule);
  background: var(--portal-surface);
  color: var(--portal-muted);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.form-editor-field {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: 6px 14px;
  padding: 7px 0;
  border-bottom: 1px solid var(--portal-rule);
}

.form-editor-field-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.form-editor-field label { font-size: 0.86rem; overflow-wrap: anywhere; }

/* An unnamed field shows its own key, which is readable but is not a label —
   so it is set apart rather than passed off as one. */
.form-editor-field label.is-unnamed {
  color: var(--portal-muted);
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 0.76rem;
}

.form-editor-rename { font-size: 0.68rem; white-space: nowrap; }

/* The raw key, always visible: it is what the answer is stored against and what
   somebody will search USCIS's own instructions for. */
.form-editor-key {
  grid-column: 1 / -1;
  color: var(--portal-muted);
  font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
  font-size: 0.62rem;
}

.form-editor-field input[type="checkbox"] { justify-self: start; width: 18px; height: 18px; }
/*
  The question a run of boxes belongs to, in the form editor.

  Said once above them rather than repeated onto every row. Indented under it
  so the rows read as its boxes; the rows keep their own controls, because in
  the editor each box still has a value, a name and a fact of its own.
*/
.form-editor-asked {
  margin: 18px 0 6px;
  color: var(--portal-link);
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.45;
}

.form-editor-field.is-box { padding-left: 18px; }

.form-row-fill { margin-left: 10px; }

@media (max-width: 700px) {
  .form-editor-field { grid-template-columns: minmax(0, 1fr); }
}

/* The parts of a form, listed before it is sent to a client.

   One row per part, each a tick and a count, because the decision being made is
   "is this reasonable to send" and the count is what answers it. The parts that
   are never anybody's are hidden rather than shown unticked — an interpreter's
   certification is not a choice somebody needs to make. */
.form-send-parts {
  display: grid;
  gap: 2px;
  margin-bottom: 12px;
}

.form-send-part {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 6px;
  cursor: pointer;
}

.form-send-part:hover { background: rgba(198, 159, 85, 0.08); }

.form-send-part input { justify-self: start; width: 16px; height: 16px; }

.form-send-part-name {
  /* The heading is USCIS's own and can run long. It wraps rather than being cut,
     because "Part 9 — General Eligibility and Inadmissibility Grounds" truncated
     to "Part 9 — General Eligibility" reads as something milder than it is. */
  overflow-wrap: anywhere;
}

.form-send-part-count { white-space: nowrap; }

/* The questions a client is asked for one form.

   A column, one question per row, with the label above the box rather than
   beside it: USCIS's own wording runs long — "4. If you selected a category
   listed above in Item Numbers 3.a. - 3.g. …" — and a label column wide enough
   for that leaves no room for the answer. */
/* ==========================================================================
   A questionnaire, in its parts.

   A federal form is three hundred questions and used to arrive as one page of
   them. Nobody sits down to answer three hundred questions; they answer the
   part about their family, and come back to the part about their employment.
   So the form opens as a list of its parts, each saying how much is left, and
   only one is ever on screen. See renderTopics in client-forms.js.
   ========================================================================== */
.client-topics {
  display: grid;
  margin: 4px 0 8px;
  gap: 10px;
}

.client-topic {
  display: grid;
  width: 100%;
  padding: 16px 18px;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px 16px;
  align-items: baseline;
  border: 1px solid var(--dashboard-border);
  border-radius: 9px;
  background: var(--portal-surface);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.client-topic:hover { border-color: var(--portal-navy); background: rgba(198, 159, 85, 0.05); }
.client-topic:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

.client-topic strong {
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.06rem;
  font-weight: 400;
  line-height: 1.35;
}

.client-topic-state {
  color: var(--portal-muted);
  font-size: 0.8rem;
  white-space: nowrap;
}

/* A part that is finished says so quietly and stops asking for attention. */
.client-topic[data-done="true"] strong { color: var(--portal-muted); }
.client-topic[data-done="true"] .client-topic-state { color: var(--portal-success, #246b45); }

.client-topic-bar {
  display: block;
  overflow: hidden;
  height: 4px;
  grid-column: 1 / -1;
  border-radius: 99px;
  background: var(--dashboard-border);
}

.client-topic-bar > span {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: var(--portal-gold);
  transition: width 0.2s ease;
}

.client-topic[data-done="true"] .client-topic-bar > span { background: var(--portal-success, #246b45); }

/* The way back and the way on, under the questions of one part. */
.client-topic-nav {
  display: flex;
  flex-wrap: wrap;
  margin-top: 18px;
  padding-top: 18px;
  gap: 12px;
  justify-content: space-between;
  border-top: 1px solid var(--dashboard-border);
}

.client-question-group {
  margin: 0 0 28px;
}

/*
  The part of the form this run of questions belongs to.

  In the house serif, like every other heading in the portal. It was left at the
  browser's bold Arial, so the one page a client spends an hour on was the one
  page whose headings did not look like the practice's.
*/
.client-question-group h3 {
  margin: 0 0 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--portal-line, rgba(0, 0, 0, 0.1));
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.22rem;
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.3;
}

/*
  One question, with room around it.

  A federal form runs to three hundred boxes and they used to arrive as an
  undivided column: ten pixels between one question and the next, the same ten
  between a question and its own answer, so nothing said where one ended. On a
  page somebody is working down for an hour, that is the difference between a
  list of questions and a wall of text. Each question is now its own block, set
  apart by a hairline, and the parts inside it sit closer to each other than to
  anything outside it.
*/
/*
  `hidden` must win the argument with the display rules below, or an address
  a client's Yes has excused stays on the page with the attribute set.
*/
.client-question[hidden],
.client-question-of[hidden],
.client-question-help[hidden] {
  display: none;
}

.client-question {
  display: grid;
  /* One column, stated. Left implicit the track was `auto`, which is sized to
     its contents — and a native <select> reports its widest option as its
     min-content width. A USCIS category label therefore held the row open and
     carried the answer past the edge of the page with nothing to scroll. */
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
  padding: 16px 0;
}

/* Between questions, never above the first: a rule under the heading that
   introduces them would separate them from what they belong to. */
.client-question + .client-question,
.client-question + .client-question-group,
.client-question-group + .client-question,
.client-question-line + .client-question,
.client-question + .client-question-line {
  border-top: 1px solid var(--dashboard-border);
}

/*
  A name on one line: first, middle, last.

  Three boxes stacked down the page is three questions where there is one, and
  the form's own order puts the surname first, which nobody says aloud. The
  boxes are placed by `order` rather than drawn out of sequence, so the DOM
  keeps the paper's order and tabbing and a screen reader still follow it.

  Wrapping rather than squeezing: a middle-name box at 90px is worse than a
  second line, so they fall to one per line when there is no room for three.
*/
.client-question-line {
  display: flex;
  flex-wrap: wrap;
  gap: 0 18px;
  /*
    Stretch, so every member is as tall as the tallest — which is what lets
    the answer boxes land on one shared baseline below. `start` left each
    member its own height, and a question wearing a help note pushed its box
    lower than its neighbours': three boxes on one line at three heights.
  */
  align-items: stretch;
}

/*
  Inside a shared line the answer sits at the bottom, whatever is stacked
  above it. The label and its help take the top; the auto margin takes the
  slack; the boxes align with each other the way the paper's do.
*/
.client-question-line > .client-question {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.client-question-line > .client-question > input,
.client-question-line > .client-question > select,
.client-question-line > .client-question > .client-question-unit {
  margin-top: auto;
}

/*
  Three across wherever three will fit.

  The basis is what each box asks for before the line is shared out, so it also
  decides when the line gives up and wraps. At 12rem three boxes wanted more
  than the 596px column the office's preview gives them and fell to two lines —
  which is the wrap working, but on the commonest row on every form. 8rem fits
  three in a column of about 460px and still grows to fill a wide one.
*/
.client-question-line > .client-question {
  flex: 1 1 8rem;
  min-width: 0;
  /* The rule belongs between rows, not between the parts of one name. */
  border-top: 0;
}

/* Each box takes its share of the line rather than stopping at the 460px a
   question on its own is capped to. */
.client-question-line > .client-question input[type="text"],
.client-question-line > .client-question select {
  max-width: 100%;
}

/*
  The question and the answer speak in different faces.

  Georgia is the voice this site asks in — every heading, the dashboard, the
  letterhead feel — and Arial is the plain hand things are filled in with. So
  the question is set in the serif and the answer stays in the sans: at a
  glance a client can tell what the office is asking from what they themselves
  have written, the same way a printed form is distinct from the ink on it.

  Weight comes down with the change: Georgia carries authority at 400 where
  Arial needed 600, and hundreds of bold serif questions in a row would shout.
*/
/*
  A measure, because some of these questions are very long and must stay so.

  168 questions across the forms run past 160 characters and one reaches 405:
  the inadmissibility and good-moral-character questions, the lists of
  offences, the parole eligibility statements. Every one of them is USCIS's
  own wording and a paraphrase would change what is being sworn to, so they
  are not shortened — but a 405-character line set across a 900px column is
  four hundred characters nobody finishes. Capped at the measure typography
  has used for centuries, which costs the meaning nothing.

  The help line under them already had this; the question itself did not.
*/
.client-question label {
  display: block;
  max-width: 68ch;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.02rem;
  font-weight: 400;
  color: var(--portal-link);
  line-height: 1.45;
  overflow-wrap: anywhere;
}

/* The track above lets the row give way; this lets its contents give way with
   it. A grid item's automatic minimum size is its min-content width, and for a
   select that is the longest option in the list — so without this the box wins
   the argument with the track. The intake's own fields got the same treatment
   in the ≤560px net; .client-question was never in that list. */
.client-question > * { min-width: 0; }

/*
  The answer boxes, dressed like the rest of the portal.

  They were never styled at all: width and nothing else, so a client filling in
  a federal form met the browser's own 1990s control — a two-pixel inset border,
  square corners, twenty-one pixels tall, no focus ring beyond whatever the
  browser draws. Every other box in this portal is 46px with a soft border and a
  gold focus, and the one screen where somebody types for an hour was the one
  screen that looked unfinished.

  The same rule as .portal-field rather than a second look invented here: the
  intake and the questionnaire are the same act, and they should not read as two
  different products.
*/
.client-question input[type="text"],
.client-question select,
.client-question textarea {
  /* The answer's own face — pinned, not inherited, so the serif above can
     never leak into what the client types. See the label rule. */
  font-family: Arial, sans-serif;
  width: 100%;
  max-width: min(460px, 100%);
  min-height: 46px;
  padding: 11px 13px;
  border: 1px solid var(--portal-rule);
  border-radius: 4px;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font: inherit;
}

/*
  An answer box the size of the answer.

  Above, every text box is drawn up to 460px because nothing knew what any of
  them held — so a ZIP code, a date of birth and a street address were all the
  same size, and the two that hold five characters looked like the one that
  holds forty. Which questions get which size is decided in the library, by
  answerWidth, so the client's page and the office's preview cannot disagree;
  anything it does not recognise keeps the full width.

  Written as `.client-question[data-answer="…"] input[type="text"]` — one
  class, two attributes and an element — so it beats the rule above it rather
  than relying on coming after it. That is the same trap the A-Number boxes
  fell into twice.
*/
.client-question[data-answer="tiny"] input[type="text"] { max-width: 6rem; }
.client-question[data-answer="date"] input[type="text"] { max-width: 10rem; }
.client-question[data-answer="code"] input[type="text"] { max-width: 14rem; }
.client-question[data-answer="short"] input[type="text"] { max-width: 18rem; }

/*
  Except on a line. A city, a state and a ZIP printed as one row are shared
  out by the grid that draws it, and a ZIP shrunk inside its own column would
  only make the row ragged. Two classes and two attributes, so this wins over
  the four rules above wherever both apply.
*/
.client-question-line > .client-question[data-answer] input[type="text"] {
  max-width: 100%;
}

.client-question input[type="text"]:focus,
.client-question select:focus,
.client-question textarea:focus {
  border-color: var(--portal-gold);
  outline: 3px solid rgba(198, 159, 85, 0.16);
}


.client-question input[type="checkbox"] {
  justify-self: start;
  width: 18px;
  height: 18px;
}

/* An answer that came from the client's own file, marked so they know why a
   question they have not seen is already filled in — and that changing it is
   allowed rather than a mistake. */
.client-question[data-from-file] input[type="text"],
.client-question[data-from-file] select {
  border-color: var(--portal-gold, #c69f55);
}

/*
  Yes and No as two buttons, not a dropdown.

  Half the choices on these forms are Yes and No, and a dropdown is three
  actions for a two-way question — open it, find the word, let it close — which
  on a phone opens a modal wheel over the form. See choiceButtons in dom.js;
  the radio inside each label is what makes it a real radiogroup, and it is
  hidden rather than removed so the keyboard and screen readers still have it.
*/
.client-choice {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}

.client-choice-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 5.5rem;
  min-height: 40px;
  padding: 8px 18px;
  border: 1px solid var(--portal-rule);
  border-radius: 999px;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font-family: Arial, sans-serif;
  font-size: 0.95rem;
  cursor: pointer;
  user-select: none;
}

/* The input itself is off the page, not display:none — a hidden input is not
   focusable, and this one has to be. */
.client-choice-button input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.client-choice-button:hover { border-color: var(--portal-gold, #c69f55); }

/* Chosen. Filled rather than outlined, so which one is picked is readable at a
   glance and not a difference of one pixel of border. */
.client-choice-button:has(input:checked) {
  border-color: var(--portal-link, #1b2a4a);
  background: var(--portal-link, #1b2a4a);
  color: #fff;
  font-weight: 600;
}

/* The focus ring belongs on the pill, because the input it is on is off the
   page. */
.client-choice-button:has(input:focus-visible) {
  outline: 3px solid rgba(198, 159, 85, 0.35);
  outline-offset: 2px;
}

.client-choice[data-disabled="true"] .client-choice-button {
  cursor: default;
  opacity: 1;
  background: rgba(16, 20, 28, 0.04);
}

.client-choice[data-disabled="true"] .client-choice-button:has(input:checked) {
  background: var(--portal-link, #1b2a4a);
  color: #fff;
}

.client-question-known {
  margin: 0;
  color: var(--portal-gold, #c69f55);
}

/*
  Said under a box answered in another alphabet.

  The questions are asked in the client's language and a federal form is filed
  in English, so this is the one place the two cannot both be served. It is a
  note and not an error: an answer nobody can write in Latin letters should
  still be written, and the office will have it translated. Quiet for the same
  reason — nothing here is wrong, there is simply a step left to do.
*/
.client-question-script {
  margin: 4px 0 0;
  color: var(--portal-gold, #c69f55);
}

.client-question-script[hidden] { display: none; }

/* And the office's copy, beside the answer it belongs to. */
.form-editor-script {
  align-self: center;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(198, 159, 85, 0.14);
  color: var(--portal-gold, #c69f55);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.form-editor-script[hidden] { display: none; }

/*
  The office explaining a federal question, under the question.

  Quieter than the question and plainly not part of it: the words above are
  USCIS's and an answer is checked against them on the paper form, so this must
  never be mistaken for more of the printed wording. Muted, smaller, and set in
  the body face rather than the form's.
*/
/*
  The boxes of one question, under the question.

  Indented so they read as belonging to it rather than as more questions, and
  stacked rather than laid across: the words beside them run from "Spouse" to
  "Brother/Sister" to whole clauses on the I-131, and a row that wraps
  unpredictably makes it hard to see which word goes with which box.
*/
/*
  The item number, beside the question rather than in front of it.

  "12.b." is where the answer goes on the paper. It has to be findable — an
  attorney checks an answer against the printed form by it — and it is not the
  opening words of the question. Tabular figures so a column of them lines up,
  and quiet enough that the eye starts at the question.
*/
/*
  Where the question sits on the paper.

  It earns its place — it is how an answer is checked against the printed form —
  but it is not what is being asked, so it sits in its own narrow column to the
  left rather than running into the first word of the question. Tabular figures
  so "9." and "12.a." line up down the page instead of jittering.
*/
.client-question-item {
  display: inline-block;
  min-width: 3.1em;
  margin-right: 4px;
  color: var(--portal-muted);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
}

/*
  Which of several repeats these questions belong to.

  A form wanting three addresses hangs "(Physical Address 1)" off all nine of
  the first one's boxes. Said once above them, the nine read as one address;
  said nine times, they read as nine unrelated questions that happen to rhyme.
*/
/*
  What a run of boxes belongs to: "Mailing Address", or the whole instruction
  the form prints over a table.

  Sized to be read as a sentence, because on some forms it is one — the I-485's
  other-names heading runs to two hundred characters. It reads as a heading by
  weight and rule rather than by being small and spaced out, which is what makes
  a long one unreadable.
*/
.client-question-of {
  max-width: 72ch;
  margin: 26px 0 4px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--portal-rule);
  color: var(--portal-link);
  /* The heading over a run is a question too — same serif voice as the
     labels below it, a step larger, its underline doing the separating. */
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.08rem;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.45;
}

.client-question-of:first-of-type { margin-top: 8px; }

/*
  Which of apartment, suite or floor, and its number, on one line.

  The picker is sized to its longest word and the number takes what is left —
  a unit number is "304" or "B", never long, and giving it the rest of the row
  would make the two look like unrelated questions that happen to be adjacent.

  They wrap on a narrow screen rather than shrinking, because a select showing
  half of "Apartment" is worse than a second line.
*/
.client-question-unit {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

/*
  A number of a fixed length, drawn the way the paper prints it: one box per
  digit, behind the letter where the form prints one.

  Three of them on these forms — the A-Number (nine, behind an A), a Social
  Security number (nine), a USCIS online account number (twelve) — and little
  boxes rather than one long field for the reason set out at digitBoxes in
  dom.js. They shrink together on a narrow screen rather than wrapping into a
  second row, because a row of nine that becomes five-and-four stops looking
  like an A-Number at all.

  Twelve of them is the widest this gets: at the smallest box the row is about
  250px, which still fits the answer column on a phone.
*/
.client-digits {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 4px;

  /*
    The size lives here, once, because the box needs saying twice — see below.
  */
  --digits-w: clamp(17px, 4.2vw, 22px);
  --digits-h: 28px;
  --digits-type: 0.85rem;
}

/*
  44px was not arbitrary: it is the smallest comfortable touch target, and on a
  phone it is still nearly right. So the box comes down there too — the same
  complaint is true under a thumb — but not as far. The floor there is the
  finger, not the digit.
*/
@media (pointer: coarse) {
  .client-digits {
    --digits-w: clamp(21px, 5.4vw, 26px);
    --digits-h: 38px;
    --digits-type: 0.95rem;
  }
}

.client-digits-letter {
  padding-right: 3px;
  color: var(--portal-link);
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.1rem;
  font-weight: 400;
}

/*
  Sized for the one character it holds.

  These started at 38px wide and 44px tall — the size of a box somebody types a
  word into. Nine of them in a row, for nine single digits, read as a much
  bigger question than it is and took most of the answer column to ask for a
  number nine characters long. A digit is about eight pixels wide, so the box
  is sized to the digit and its border rather than to a text field, and the
  height comes down with it: a tall thin box reads as a field that was cut off
  rather than one meant to hold a single character.

  SAID TWICE, ON PURPOSE

  Every A-Number box on a questionnaire sits inside a .client-question, and the
  rule that dresses an answer box there is

      .client-question input[type="text"] { width: 100%; min-height: 46px;
                                            padding: 11px 13px }

  which is a class, an attribute and an element — more specific than the single
  class this used to be. So the size below was being set and then overruled,
  and the boxes stayed 46px tall and full-width however small the number said,
  which is exactly what they looked like. Measuring the widget on its own gave
  the size it was asked for; the page gave the size it got.

  The second selector out-ranks that rule instead of racing it: two classes and
  an element against its one class, one attribute and one element. The three
  properties it imposes — width, min-height, padding — are reset by name here
  rather than left to be inherited from whichever rule happens to come last.

  SAID A THIRD TIME, FOR THE SAME REASON

  The intake form draws these too now — the A-Number, the USCIS online account
  number and the Social Security number, which are items 1 to 3 of most USCIS
  forms — and its boxes sit inside a .portal-field, whose

      .portal-field input { width: 100%; min-height: 46px; padding: 11px 13px }

  is the same trap in a different card: one class and one element, which beats
  a bare .client-digits-box. The third selector clears it the same way.
*/
.client-digits-box,
.client-question .client-digits input.client-digits-box,
.portal-field .client-digits input.client-digits-box {
  width: var(--digits-w);
  min-width: 0;
  max-width: none;
  height: var(--digits-h);
  min-height: 0;
  padding: 0;
  border: 1px solid var(--portal-rule);
  border-radius: 3px;
  font-family: Arial, sans-serif;
  font-size: var(--digits-type);
  line-height: 1;
  text-align: center;
}

.client-digits-box:focus {
  border-color: var(--portal-gold);
  outline: 3px solid rgba(198, 159, 85, 0.16);
}

.client-digits[data-disabled="true"] .client-digits-box { background: rgba(16, 20, 28, 0.04); }

/*
  A date, drawn the way the paper prints it: MM / DD / YYYY.

  Two boxes of two and one of four, with the slashes the form itself shows
  between them, so the order cannot be read two ways — see dateBoxes for why a
  single field was worse here than anywhere else on the form.

  The same two-selector shape as the A-Number row above, and for the same
  reason: .client-question input[type="text"] is a class, an attribute and an
  element, so a single class here would be set and then thrown away.
*/
.client-date {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-top: 4px;

  --date-h: 34px;
  --date-type: 0.95rem;
}

@media (pointer: coarse) {
  .client-date {
    --date-h: 42px;
    --date-type: 1rem;
  }
}

.client-date-box,
.client-question .client-date input.client-date-box {
  width: 2.6rem;
  min-width: 0;
  max-width: none;
  height: var(--date-h);
  min-height: 0;
  padding: 0 4px;
  border: 1px solid var(--portal-rule);
  border-radius: 4px;
  font-family: Arial, sans-serif;
  font-size: var(--date-type);
  line-height: 1;
  text-align: center;
}

/* The year holds four digits and says so. */
.client-date-box[data-part="year"],
.client-question .client-date input.client-date-box[data-part="year"] { width: 4rem; }

.client-date-box::placeholder { color: var(--portal-muted, #8a8f98); letter-spacing: 0.02em; }

.client-date-box:focus,
.client-question .client-date input.client-date-box:focus {
  border-color: var(--portal-gold);
  outline: 3px solid rgba(198, 159, 85, 0.16);
}

.client-date-slash {
  color: var(--portal-muted, #8a8f98);
  font-family: Arial, sans-serif;
}

.client-date[data-disabled="true"] .client-date-box { background: rgba(16, 20, 28, 0.04); }

.client-question-unit select { flex: 0 1 11rem; }
/*
  No grow. A unit number is "304" or "B", and the comment above has said so
  all along — but `flex: 1` let the box take the rest of the row anyway, so
  the shortest answer on the form had the longest box on the page.
*/
.client-question-unit input { flex: 0 1 7rem; min-width: 6rem; width: auto; }

.client-question-boxes {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 6px;
  padding-left: 2px;
}

/*
  The whole line is the target, not the twelve-pixel square.

  `align-items: start` rather than centre because a long choice wraps to two
  lines and a box centred against those sits halfway down the sentence it
  belongs to.
*/
.client-question-box {
  display: flex;
  align-items: start;
  gap: 9px;
  cursor: pointer;
  font-weight: 400;
}

.client-question-box input {
  margin: 3px 0 0;
  flex: none;
}

/*
  The office's line under the question, set in from it.

  Indented and ruled so it reads as a note about the question rather than as a
  second question: on a long form the two were the same weight in the same
  column, and the eye had to work out which was being asked.
*/
.client-question-help {
  max-width: 62ch;
  margin: 1px 0 2px;
  padding-left: 10px;
  border-left: 2px solid var(--dashboard-border);
  color: var(--portal-muted);
  font-size: 0.82rem;
  line-height: 1.55;
}

/*
  The same line, but about the block rather than one box.

  It sits under the heading and above the run it explains, so it needs the
  breathing room a row's own help does not: hard against the heading, with the
  first question clear of it.
*/
.client-question-help.is-over {
  margin: -2px 0 10px;
}

/* .portal-form-actions is the same row on the intake page (client-intake.html
   at :60 and :108) and no stylesheet has ever matched it — its buttons fell
   back to inline flow, so two 44px targets sat edge to edge with no gap
   between them. Same shape, same rule, rather than a second copy of it. */
.client-forms-actions,
.portal-form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 16px 0;
}

/*
  The way back: between the header and the form, and nothing more than that.

  This took several tries and every wrong one shared a cause — giving the
  button somewhere of its own. Fixed at the top left it drew over the
  navigation column; sticky in the content it became a white band across the
  page; in the navigation column it was inside the header. So it is given
  nothing: no background, no border, no row to itself. It sits above the form
  title, at the size of the text around it, and reads as a way out rather
  than as a piece of furniture.

  Wide enough to press, by padding rather than by a box: the target is 44px
  tall while the ink is a line of text.
*/
.client-wayback {
  display: inline-flex;
  margin: 0 0 6px -6px;
  padding: 10px 6px;
  align-items: center;
  gap: 7px;
  border: 0;
  background: none;
  color: var(--portal-link);
  font-family: Arial, sans-serif;
  font-size: 0.84rem;
  font-weight: 700;
  cursor: pointer;
  letter-spacing: 0.01em;
}

.client-wayback:hover,
.client-wayback:focus-visible { color: var(--portal-gold); text-decoration: underline; text-underline-offset: 3px; }

.client-wayback[hidden] { display: none; }

/*
  Wide screens: into the gutter, and pinned there.

  Between the navigation column and the questions there is empty margin —
  measured, not guessed: 56px up to 1700px wide, then 107px at 1800 and 167px
  at 1920, because the reading column stops growing at 1440 and the rest
  becomes margin. From 1800 the button fits in that gap with room to spare,
  which is the one place on this page that is neither the header, nor the
  navigation, nor the questions.

  Below that the gutter is 56px and a button in it would sit on the
  questions, so it stays in the flow above the form's title. That is the
  whole rule: it is pinned where there is somewhere to pin it to, and in the
  flow where there is not.
*/
@media (min-width: 1800px) {
  .client-wayback {
    position: fixed;
    /* Just past the navigation column, under the header. */
    left: calc(258px + 14px);
    top: calc(72px + 22px);
    z-index: 6;
    margin: 0;
  }
}

/*
  A form being answered is the whole page.

  The case page carries a heading — "Case center / My case", and a sentence
  about statuses and important dates — and a strip of three tabs above the
  panel. Both are how somebody moves around the portal, and neither helps
  somebody in the middle of a federal form: together they push the first
  question most of a screen down, and the strip offers to leave a
  half-answered questionnaire with one press.

  So they go while a form is open, and come back when it closes. The way out
  is the Back button, which is on screen the whole time and goes up one level
  at a time — into the list of topics, then out to the list of forms.

  Set from client-forms.js; see showOnlyTheForm for why it is a class on the
  body rather than that module reaching into two elements it does not own.

  Direct children of the main column, and only those. The open form has a
  heading of its own with the same class — it is the thing that names the
  questionnaire — and it sits four levels down inside the Forms panel. A rule
  written on the class alone hid that as well, so a client answering a form
  was looking at a page that no longer said which form it was.
*/
body.has-open-form #dashboard-main > .dashboard-page-heading,
body.has-open-form #dashboard-main > #client-matter-picker,
body.has-open-form #dashboard-main > .portal-subtabs-row {
  display: none;
}

.client-form-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 6px 16px;
}

.client-form-row p { grid-column: 1; margin: 0; }
.client-form-row button { grid-column: 2; grid-row: 1 / span 2; }

/* The preview of what a client will be asked, inside the send dialog.

   Folded away by default: the decision on that screen is which parts to send,
   and 124 questions unfolded under it would bury the thing being decided. The
   controls inside are disabled — this is for looking, not for answering on
   somebody's behalf. */
.form-send-preview-wrap {
  margin-top: 14px;
  border-top: 1px solid var(--portal-line, rgba(0, 0, 0, 0.1));
  padding-top: 10px;
}

.form-send-preview-wrap > summary {
  cursor: pointer;
  font-weight: 500;
}

.form-send-preview-wrap[open] > summary { margin-bottom: 12px; }

/* Disabled controls in the preview read as unavailable rather than as broken:
   this is a picture of a form, not a form. */
.form-send-preview-wrap .client-question input:disabled,
.form-send-preview-wrap .client-question select:disabled {
  opacity: 1;
  background: var(--portal-sheet, rgba(0, 0, 0, 0.02));
  cursor: default;
}

/* Said when the page is older than the site.

   At the very top, across the width, and quiet: it is information, not an
   error. Nobody is interrupted and nothing reloads on its own — the console
   has no draft recovery worth the name, and taking the page away from somebody
   mid-sentence to save them a stale stylesheet is the worse trade. */
.portal-stale {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 8px 16px;
  background: var(--portal-gold, #c69f55);
  color: var(--portal-navy-deep, #12243d);
  font-size: 0.8rem;
  font-weight: 600;
}

.portal-stale .portal-link-button {
  color: inherit;
  text-decoration: underline;
}

/* ==========================================================================
   A submitted intake, printed back as the form it was.

   Paper proportions on purpose: a fixed reading width, generous margins and a
   serif for the headings, so it reads as a document rather than as a panel of
   the console that happens to have answers in it. See pages/admin/intake-sheet.js.
   ========================================================================== */
.intake-sheet {
  position: fixed;
  inset: 0;
  z-index: 60;
  overflow-y: auto;
  padding: 26px 16px 60px;
  background: rgba(16, 20, 28, 0.55);
}

.intake-sheet[hidden] { display: none; }

/* The controls float above the page and never print. Wrapped, because the row
   is five controls long once corrections are open and none of them shortens. */
.intake-sheet-bar {
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  width: min(820px, 100%);
  margin: 0 auto 14px;
}

.intake-sheet-bar .portal-link-button { color: #fff; }

.intake-sheet-paper {
  width: min(820px, 100%);
  margin: 0 auto;
  padding: 46px 54px 40px;
  background: #fff;
  color: #172033;
  border-radius: 3px;
  box-shadow: 0 18px 48px rgba(10, 14, 22, 0.35);
}

.intake-sheet-head {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  justify-content: space-between;
  padding-bottom: 18px;
  margin-bottom: 26px;
  border-bottom: 2px solid #102653;
}

.intake-sheet-head h2 {
  margin: 4px 0 0;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.5rem;
  color: #102653;
}

.intake-sheet-who { text-align: right; font-size: 0.86rem; }
.intake-sheet-who strong { display: block; font-size: 1rem; }
.intake-sheet-who span { color: #5e6674; }

.intake-sheet-block { margin-bottom: 26px; break-inside: avoid; }

.intake-sheet-block h3 {
  margin: 0 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid #e3ddd2;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.02rem;
  color: #102653;
}

.intake-sheet-block dl { margin: 0; }

/*
  Question left, answer right, on one line where there is room.

  The question column is fixed rather than sized to content: a form reads down
  its left edge, and a ragged one reads as a table of unrelated pairs.
*/
.intake-sheet-row {
  display: grid;
  grid-template-columns: minmax(0, 15em) minmax(0, 1fr);
  gap: 10px 18px;
  padding: 5px 0;
  border-bottom: 1px dotted #ece7dd;
  break-inside: avoid;
}

.intake-sheet-row dt { color: #5e6674; font-size: 0.84rem; }
.intake-sheet-row dd { margin: 0; font-size: 0.92rem; white-space: pre-wrap; }
.intake-sheet-row dd.is-blank { color: #b3b8c2; }

/*
  Corrections.

  The sheet stays a sheet while it is being edited: the boxes take the width of
  the answer column and nothing else moves, so the document a person was reading
  a moment ago is still recognisably the same page. A form that reflows when you
  press Edit loses the reader's place in forty questions.
*/
.intake-sheet-body.is-editing .intake-sheet-row { padding: 3px 0; }

.intake-sheet-row dd input[type="text"],
.intake-sheet-row dd input[type="date"],
.intake-sheet-row dd select,
.intake-sheet-row dd textarea {
  width: 100%;
  padding: 4px 7px;
  font: inherit;
  font-size: 0.9rem;
  color: #172033;
  background: #fffdf9;
  border: 1px solid #d8d2c6;
  border-radius: 3px;
}

.intake-sheet-row dd textarea { resize: vertical; }

.intake-sheet-row dd :is(input, select, textarea):focus-visible {
  outline: 2px solid #102653;
  outline-offset: 1px;
}

.intake-sheet-boxes {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
}

.intake-sheet-boxes label {
  display: inline-flex;
  gap: 5px;
  align-items: center;
  font-size: 0.88rem;
}

/*
  What the client themselves put there, under anything the office has moved.

  Small and grey, but never hidden and never dropped from the print: an intake
  is a record of what somebody stated, and a corrected answer that does not show
  the original is a rewritten statement.
*/
.intake-sheet-was,
.intake-sheet-sealed {
  display: block;
  margin-top: 3px;
  color: #8a6d3b;
  font-size: 0.78rem;
  white-space: normal;
}

.intake-sheet-sealed { color: #8b8f98; font-style: italic; }

#intake-sheet-corrected { display: block; color: #8a6d3b; }
.intake-sheet-bar .portal-status { margin-right: auto; color: #fff; }

.intake-sheet-foot {
  margin-top: 28px;
  padding-top: 14px;
  border-top: 1px solid #e3ddd2;
  color: #5e6674;
  font-size: 0.78rem;
}

@media (max-width: 620px) {
  .intake-sheet-paper { padding: 28px 22px 26px; }
  .intake-sheet-row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .intake-sheet-head { flex-direction: column; }
  .intake-sheet-who { text-align: left; }

  /* Huddled right, "Correct the answers" and "Print or save as PDF" want about
     440px between them and the overlay offers 288px — so each collapsed into a
     narrow tower of one-word lines. Two-up instead: the status line takes the
     top of the bar to itself, and the buttons share the rows under it at half
     each, which is a grid rather than a queue that did not fit. */
  /* flex-start, said plainly: the base rule packs the bar right, and the
     half-width buttons below should fill their rows from the left. (stretch
     would compute to the same thing here, but only by falling back.) */
  .intake-sheet-bar { justify-content: flex-start; }
  .intake-sheet-bar .portal-status { flex: 1 1 100%; margin-right: 0; }
  .intake-sheet-bar .portal-button { flex: 1 1 45%; justify-content: center; }
}

/*
  Printing: the sheet, and nothing else.

  The console is a fixed-position application — a rail, a topbar, panels that
  scroll inside themselves — and printing it without this gives one page of
  navigation. Everything is hidden and the sheet is lifted out of its overlay
  into the flow of the page, so the browser paginates it as an ordinary
  document. "Save as PDF" is a destination in the same dialog on every platform,
  which is why the button says both.
*/
@media print {
  body > *:not(.intake-sheet) { display: none !important; }

  .intake-sheet {
    position: static;
    overflow: visible;
    padding: 0;
    background: transparent;
  }

  .intake-sheet-bar { display: none; }

  .intake-sheet-paper {
    width: auto;
    max-width: none;
    padding: 0;
    box-shadow: none;
    border-radius: 0;
  }

  .intake-sheet-block { page-break-inside: avoid; }
}


/* ============================================================
   The cabinet, as people rather than as a list of files.

   The cards are the Overview's — same classes, same three stripes, same fold —
   because they are the same people two tabs apart, and a family that is a
   navy card on one screen and a grey rule on the other is two things to learn
   about one matter.

   Only the inside differs: the Overview opens onto facts about somebody, and
   this opens onto their papers, sectioned by kind of paper.
   ============================================================ */
.case-documents-people {
  gap: 10px;
  margin-top: 8px;
}

/* ============================================================
   The papers themselves, several to a row.

   One card per line spent the whole width on a card whose content is a form
   number and a date, and made a person with six papers six screens tall.
   Auto-fill rather than a fixed count: the drawer is 720px at its widest and
   narrower on a laptop with the rail out, so the number of columns should
   follow the room rather than a guess about it.

   A 220px card gives names and descriptions room to breathe. The minimum
   shrinks with its container so nested attachments also fit on a phone.
   ============================================================ */
.case-person-detail .portal-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr));
  gap: 10px;
  /*
    One height per row, which is grid's default and the right one here.

    The first version let each card be its own height, on the reasoning that a
    wrapped file name should not stretch its neighbours. On the page that reads
    as a ragged edge: "Consultation intake" wraps to two lines and the I-485
    beside it stops short, leaving a step between two boxes that are the same
    kind of thing. The extra few pixels under the short one cost nothing.
  */
}

/* First heading in an opened card sits under the fold line, not away from it. */
.case-person-detail > .case-person-group:first-child {
  margin-top: 4px;
}


/* The About picker on the timeline's step dialog, under its send checkbox. */
.case-step-about {
  display: flex;
  align-items: center;
  margin-top: 10px;
  gap: 8px;
  font-size: 0.82rem;
}

.case-step-about select {
  min-width: 0;
  flex: 1;
  padding: 6px 8px;
  border: 1px solid var(--portal-rule);
  border-radius: 4px;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font: inherit;
  font-size: 0.82rem;
}

/* ============================================================
   A compact card: what it is, what state it is in, when.

   Forms, intake answers and files share the same three rows: name,
   description space, then date and badge. A missing description keeps its
   space so the footer stays aligned across different kinds of document.
   ============================================================ */
.portal-list-item.is-compact .portal-card-open {
  display: grid;
  height: 100%;
  min-height: 136px;
  padding: 14px;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: minmax(2.6em, auto) minmax(2.5em, 1fr) auto;
  align-items: start;
  gap: 6px 8px;
}

.portal-list-item.is-compact { min-width: 0; }
.is-compact .doc-card-top { display: contents; }
.is-compact .doc-card-name {
  display: -webkit-box;
  grid-column: 1 / -1;
  grid-row: 1;
  overflow: hidden;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  font-weight: 400;
}
.is-compact .doc-card-sub { grid-column: 1 / -1; grid-row: 2; margin: 0; }
.is-compact .doc-card-when { grid-column: 1; grid-row: 3; align-self: center; overflow-wrap: anywhere; }
.is-compact .doc-state { grid-column: 2; grid-row: 3; align-self: center; justify-self: end; max-width: 100%; white-space: normal; text-align: center; }

.doc-card-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.doc-card-name {
  min-width: 0;
  font-family: Georgia, serif;
  font-size: 0.9rem;
  line-height: 1.3;
  /* A file name has no spaces to break at and must not push the grid wider. */
  overflow-wrap: anywhere;
}

.doc-card-when {
  color: var(--portal-muted);
  font-size: 0.72rem;
}

/*
  What the form is, under the number it is known by.

  "I-130" is what the office scans a list for and it is not what the document
  is; the title says that. Quieter than the number and louder than the date,
  because it is read second of the three. Two lines at most — the titles run
  to "Application to Register Permanent Residence" and a card three lines deep
  in a grid of them is a wall.
*/
.doc-card-sub {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
  margin-top: 1px;
  color: var(--portal-ink);
  font-size: 0.78rem;
  line-height: 1.25;
  opacity: 0.72;
}

.doc-card-sub[hidden] { display: none; }

/* ============================================================
   The state pill.

   Colour and a word, never colour alone. Four states, and only one of them
   means nothing is owed: gold is waiting on somebody else, navy is waiting on
   this office, green is checked, grey is done. A person's card answers "is
   anything outstanding here" before it is read.

   `file` is the odd one out — it is an extension rather than a state, on an
   uploaded document — and it is deliberately the quietest of the five.
   ============================================================ */
.doc-state {
  flex: none;
  padding: 2px 7px;
  border: 1px solid transparent;
  border-radius: 99px;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Nothing has gone out. Quietest of the five on purpose: every other state on
   this card is something in motion, and this one is the absence of it. Muted
   rather than coloured, so it reads as "not started" rather than as a fifth
   thing happening — and drawn at all, because an unstyled pill is the only
   colourless one on a card whose whole claim is state as colour as well as
   word. */
.doc-state[data-state="draft"] {
  border-color: var(--portal-rule);
  background: transparent;
  color: var(--portal-muted);
}

.doc-state[data-state="sent"] {
  border-color: color-mix(in srgb, var(--portal-gold) 55%, transparent);
  background: color-mix(in srgb, var(--portal-gold) 16%, transparent);
  color: color-mix(in srgb, var(--portal-gold) 72%, var(--portal-ink));
}

.doc-state[data-state="returned"] {
  border-color: color-mix(in srgb, var(--portal-link) 40%, transparent);
  background: color-mix(in srgb, var(--portal-link) 12%, transparent);
  color: var(--portal-link);
}

.doc-state[data-state="ready"] {
  border-color: color-mix(in srgb, var(--portal-success) 45%, transparent);
  background: color-mix(in srgb, var(--portal-success) 12%, transparent);
  color: var(--portal-success);
}

.doc-state[data-state="filed"],
.doc-state[data-state="file"] {
  border-color: var(--portal-rule);
  color: var(--portal-muted);
}

/* The waiting mark sits at the far edge of a shut card, where the eye runs
   down a column of them looking for it. */
.case-person-head > .doc-state {
  margin-left: auto;
  align-self: center;
}

/* The note and the added date on a person's card: about the record, not about
   the person, so they are ruled off from the fact groups above them. */
.case-person-detail .case-person-tail {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--portal-rule);
}


/* Reviewed, Filed on… and Unsend, in the viewer's header beside the mode tag. */
.form-editor-lifecycle {
  display: inline-flex;
  margin-left: 10px;
  gap: 12px;
  font-size: 0.78rem;
}

/*
  The one that takes the form away, coloured as such.

  Beside Reviewed and Filed on… — which move a form forward — sits the one that
  removes it, and three identical navy links do not say which is which. Bold
  once it is armed, the same tell the document cards use, so the state of a
  press already made is visible and not only in the word.
*/
.form-editor-lifecycle .is-danger { color: var(--portal-danger); }
.form-editor-lifecycle .is-danger[data-armed="true"] { font-weight: 700; }

/* ============================================================
   The questionnaire beside the form.

   A third column, and only when it has something in it — the grid falls back
   to two the moment the panel is hidden, so editing gets the whole width back
   rather than a 340px gap where the questions were.

   It scrolls on its own. The page beside it is already its own scroller and
   the two are read independently: checking box 10.a against "Whose name is on
   the mailbox?" means moving one without losing the other.
   ============================================================ */
/*
  Four columns, and the last one is yours.

  How much of each you want depends on what you are doing: checking a name
  against box 4.a wants the page wide, reading a part through wants the
  questions wide. So the width is a variable the grip writes to and this reads
  — see the grip in form-editor.js — and it is remembered, because somebody who
  has set it once has said what they want.

  Clamped in the CSS as well as in the drag: the stored number comes back from
  a previous session and a window that has since been made narrower must not be
  handed a column wider than itself.
*/
#form-editor-modal .portal-modal-body:has(> #form-editor-questions:not([hidden])) {
  grid-template-columns: 208px minmax(0, 1fr) 14px clamp(240px, var(--questions-w, 340px), 62%);
}

.form-editor-questions {
  overflow-y: auto;
  min-width: 0;
  padding-left: 18px;
  border-left: 1px solid var(--portal-rule);
  grid-row: 1 / span 2;
  grid-column: 4;
}

/*
  The line between the two, and it has to look like something you can take
  hold of.

  Drawn as a border it was invisible — indistinguishable from the rule that had
  always been there, so nothing said the split could move. It is two things
  now: a full-height line down the middle, and a handle at the height of the
  eye, which is the part that says grab me. Both go gold under the pointer and
  stay gold for the length of the drag.
*/
.form-editor-grip {
  position: relative;
  grid-row: 1 / span 2;
  grid-column: 3;
  cursor: col-resize;
  touch-action: none;
}

.form-editor-grip[hidden] { display: none; }

/* The line. Two pixels, so it reads as a seam rather than as a hairline
   somebody drew by accident. */
.form-editor-grip::before {
  content: "";
  position: absolute;
  inset: 0 calc(50% - 1px);
  background: var(--portal-rule);
  transition: background 0.12s ease;
}

/*
  The handle, a third of the way down rather than halfway: the column beside it
  scrolls, and a mark in the exact middle of a tall panel reads as part of what
  is in it. Up here it belongs to the edge.
*/
.form-editor-grip::after {
  content: "";
  position: absolute;
  top: 33%;
  left: 50%;
  width: 8px;
  height: 42px;
  transform: translate(-50%, -50%);
  border: 1px solid var(--portal-rule);
  border-radius: 999px;
  background: var(--portal-surface);
  box-shadow: 0 1px 3px rgba(16, 20, 28, 0.12);
  transition:
    background 0.12s ease,
    border-color 0.12s ease;
}

.form-editor-grip:hover::before,
.form-editor-grip:focus-visible::before,
.form-editor-grip[data-dragging="true"]::before {
  background: var(--portal-gold, #c69f55);
}

.form-editor-grip:hover::after,
.form-editor-grip:focus-visible::after,
.form-editor-grip[data-dragging="true"]::after {
  border-color: var(--portal-gold, #c69f55);
  background: var(--portal-gold, #c69f55);
}

.form-editor-grip:focus-visible {
  outline: 2px solid var(--portal-gold, #c69f55);
  outline-offset: 1px;
}

/*
  While dragging, the pointer owns the whole dialog.

  Without this the drag dies the moment it crosses the PDF, which selects text
  and swallows the move, and the cursor flickers between the two.
*/
#form-editor-modal[data-resizing="true"] {
  cursor: col-resize;
  user-select: none;
}

#form-editor-modal[data-resizing="true"] .portal-modal-body * { pointer-events: none; }
#form-editor-modal[data-resizing="true"] .form-editor-grip { pointer-events: auto; }

.form-editor-questions-head {
  margin: 0 0 12px;
  font-size: 0.9rem;
}

/*
  One part at a time, the way the client meets it.

  A client's form opens at a list of its topics, each saying how much is left,
  and only one is on screen at once. The office reading the answers back had
  one continuous column of every part — a hundred and seven questions down a
  strip beside the page on an I-130. Closed, these are an index of the form;
  the one being read is the one that is open.
*/
.form-editor-part {
  margin: 0 0 8px;
  border: 1px solid var(--portal-rule);
  border-radius: 8px;
  background: var(--portal-surface);
}

.form-editor-part > summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  cursor: pointer;
  list-style: none;
}

/* The default disclosure triangle, replaced with one that turns. */
.form-editor-part > summary::-webkit-details-marker { display: none; }

.form-editor-part > summary::after {
  content: "▸";
  flex: none;
  color: var(--portal-muted);
  transition: transform 0.12s ease;
}

.form-editor-part[open] > summary::after { transform: rotate(90deg); }

.form-editor-part > summary:hover { background: var(--portal-cream); }

.form-editor-part-name {
  font-family: Georgia, "Times New Roman", serif;
  font-size: 0.92rem;
  line-height: 1.3;
}

/* How much is left, in the client's own words. Green once there is nothing. */
.form-editor-part-count {
  flex: none;
  color: var(--portal-muted);
  font-size: 0.72rem;
  white-space: nowrap;
}

.form-editor-part-count[data-done="true"] { color: var(--portal-green, #2f6f4f); }

/* The part's own heading is the summary now, so the block inside carries only
   its questions. */
.form-editor-part > .client-question-group {
  margin: 0;
  padding: 0 12px 12px;
}

/* Answered here as well as on the page. The disabled rule stays for the case
   that is still read-only, and keeps the page's own ink rather than the
   browser's grey — a column of greyed-out boxes reads as broken. */
.form-editor-questions input:disabled,
.form-editor-questions select:disabled,
.form-editor-questions textarea:disabled {
  opacity: 1;
  background: var(--portal-cream);
  color: var(--portal-ink);
}

/* Narrow windows keep the page and drop the column: 1240px is the widest this
   dialog ever is, and below about a laptop there is not room for three. */
@media (max-width: 1100px) {
  #form-editor-modal .portal-modal-body:has(> #form-editor-questions:not([hidden])) {
    grid-template-columns: 208px minmax(0, 1fr);
  }

  .form-editor-questions { display: none; }
}

/* ==========================================================================
   The Team tab (080)
   ==========================================================================
   Nothing here duplicates something above. The table, the cards, the pills and
   the row buttons are all the console's own; what is new is a definition list
   whose values are sentences rather than values, and a role picker sitting
   inside a table cell.
*/

.admin-role-notes { display: grid; margin: 0; gap: 14px; }
.admin-role-notes > div + div { padding-top: 14px; border-top: 1px solid var(--dashboard-border); }

.admin-role-notes dt {
  color: var(--portal-muted);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Left, not right. .settings-facts sets a short value against its label down
   the right-hand edge; a paragraph set that way is unreadable, and these are
   paragraphs. */
.admin-role-notes dd {
  margin: 5px 0 0;
  color: var(--portal-ink);
  font-size: 0.86rem;
  line-height: 1.55;
  text-align: left;
}

/* The role picker in a row. Sized to its content so a column of them lines up
   with the text either side rather than stretching to the cell. */
.admin-team-role {
  min-width: 132px;
  font-size: 0.82rem;
}

/* What somebody has done in thirty days: a few small numbers that have to read
   as one answer, so they sit on their own line rather than wrapping into the
   date beside them. */
.admin-team-work {
  display: block;
  color: var(--portal-muted);
  font-size: 0.78rem;
  line-height: 1.5;
}

/* A colleague whose access is held. The row stays legible — it is still the
   record of who filed what — but it must not read as somebody who can be asked
   to do something today. */
.admin-team-row.is-suspended .portal-identity-text strong { color: var(--portal-muted); }
/* ==========================================================================
   The client's Overview
   ==========================================================================

   Approved layout: DASHBOARD_LAYOUT_BASELINE.md.
   Keep the equal-height desktop card rows, static status label, route note
   and decorative empty-state icons together here. Shared welcome and Secure
   portal badge styles live in the dashboard section above.

   Surface colors use theme tokens; status tones have explicit theme variants.
   Responsive rules sit beside the components they adapt.
   ========================================================================== */

/* Searches what the page already holds. Held out of the topbar below 900px:
   that row is pinned to 72px and three other things measure from it, and below
   900 it is already carrying a Menu button, a bell, a refresh, an avatar and
   the two selects portal.js prepends. */
.overview-search {
  flex: 1 1 190px;
  max-width: 340px;
  margin-right: auto;
  margin-left: 22px;
}

.overview-search input {
  width: 100%;
  min-height: 38px;
  padding: 8px 15px;
  border: 1px solid var(--dashboard-border);
  border-radius: 999px;
  background: var(--portal-surface);
  color: var(--portal-ink);
  font: inherit;
  font-size: 0.82rem;
}

.overview-search input:focus {
  border-color: var(--portal-gold);
  outline: 3px solid rgba(198, 159, 85, 0.16);
}

/* Three columns and two rows, the rows levelled.

   The cards were three stacked columns, each its own height: a client owed six
   documents against one owed none makes cards of different heights, and a span
   grid leaves a hole under the shorter. The other reading of that hole is a
   ragged row — "Documents needed", "Coming up" and "Messages" each ending on a
   different line. So the wrappers stay in the markup and stop being boxes
   (display: contents), each card is pinned to its wrapper's column, and the
   grid's rows do the levelling: a row is as tall as its tallest card and the
   others stretch to meet it. Dense placement, so a card lands in the first
   free row of its own column rather than below the previous column's last. */
.overview-matter-context { display: flex; align-items: end; flex-wrap: wrap; gap: 12px 24px; margin: 0 0 18px; min-width: 0; }
.overview-matter-context[hidden], .overview-next-action[hidden] { display: none; }
.overview-matter-context > div { flex: 1 1 320px; min-width: 0; max-width: 620px; }
.overview-matter-context label { display: block; margin-bottom: 7px; font-weight: 600; font-size: 0.88rem; color: var(--portal-ink); }
.overview-matter-context select { display: block; width: 100%; min-width: 0; min-height: 46px; padding: 10px 32px 10px 12px; border: 1px solid var(--portal-rule); border-radius: 8px; background: var(--portal-surface); color: var(--portal-ink); font: inherit; text-overflow: ellipsis; }
.overview-matter-context select:focus-visible { outline: 3px solid var(--portal-gold); outline-offset: 3px; }
.overview-matter-context p { margin: 0 0 10px; overflow-wrap: anywhere; }
.overview-next-action { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 24px; margin-bottom: 18px; padding: 18px 22px; border-left: 3px solid var(--portal-gold); border-radius: 8px; background: var(--portal-surface); color: var(--portal-ink); }
.overview-next-action > div { flex: 1 1 220px; min-width: 0; }
.overview-next-action h2 { margin: 0 0 6px; font-size: 0.94rem; }
.overview-next-action p { margin: 0; font-size: 0.88rem; line-height: 1.6; overflow-wrap: anywhere; }
.overview-grid {
  display: grid;
  align-items: stretch;
  gap: 22px;
  grid-auto-flow: row dense;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.overview-column { display: contents; }
.overview-column > .dashboard-card { min-width: 0; }
.overview-column:nth-child(1) > .dashboard-card { grid-column: 1; }
.overview-column:nth-child(2) > .dashboard-card { grid-column: 2; }
.overview-column:nth-child(3) > .dashboard-card { grid-column: 3; }

/* The same ladder the rest of the dashboard uses. Two columns cannot level a
   row of three, so the wrappers go back to being stacks. */
@media (max-width: 1120px) {
  .overview-grid { align-items: start; grid-auto-flow: row; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .overview-column { display: grid; align-content: start; gap: 22px; min-width: 0; }
  .overview-column:nth-child(n) > .dashboard-card { grid-column: auto; }
}

@media (max-width: 900px) {
  .overview-search { display: none; }
}

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

/* ── The matter card ─────────────────────────────────────────────────────── */

/* Nothing said, nothing taking up room. These three lines are each absent in a
   situation that really happens — no route chosen, no receipt recorded, one
   matter rather than three — and an empty paragraph inside a 22px grid gap is a
   visible hole rather than nothing. */
.overview-case-type:empty,
.overview-case-more:empty,
.overview-case-receipt:empty,
.overview-status-caption:empty { display: none; }

.overview-case-type { margin: 4px 0 0; font-size: 0.82rem; }

.overview-case-receipt {
  margin: 0;
  justify-content: flex-end;
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.overview-case-more { margin: 0; font-size: 0.72rem; }

/* The matter card stands on its own row above the three columns, and says the
   status once, large.

   A six-step rail stood here, and the card was full-width for its sake: six
   step labels that are sentences need the whole page to be read. The rail is
   gone — it drew the whole road for one lit node, and on most matters lit
   none — so what is left is the office's own status word as a pill, the step
   count beside it when the office has set one, and the note saying what that
   means for this kind of matter, with the way through to the full record.
   Stacked in one column with a single gap, so the pieces read top to bottom
   and a hidden piece leaves no hole. */
.overview-case { margin-bottom: 22px; }
.overview-case-body { display: flex; flex-direction: column; gap: 18px; }

.overview-status { display: flex; flex-direction: column; gap: 9px; }
.overview-status[hidden] { display: none; }
.overview-status-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }

/* One colour per status word, the reading the console gives its caseload rail:
   blue to start, gold while work goes on, green once filed, grey when closed,
   red where the office stopped. Text and dot in the tone, the field a tint of
   it over the card, so every theme draws the pill from its own surface. */
.overview-status-badge {
  --tone: var(--portal-muted);
  display: inline-flex;
  margin: 0;
  padding: 7px 12px;
  align-items: center;
  gap: 10px;
  border: 0;
  border-radius: 6px;
  background: color-mix(in srgb, var(--tone) 11%, var(--portal-surface));
  color: var(--tone);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
}
.overview-status-badge i { flex: none; width: 11px; height: 11px; border-radius: 50%; background: var(--tone); }
.overview-status-badge[data-tone="start"]   { --tone: #2f5d8f; }
.overview-status-badge[data-tone="work"]    { --tone: #8a5d10; }
.overview-status-badge[data-tone="filed"]   { --tone: #1f6b3f; }
.overview-status-badge[data-tone="closed"],
.overview-status-badge[data-tone="other"]   { --tone: #5e6674; }
.overview-status-badge[data-tone="stopped"] { --tone: #a3282f; }
:root[data-theme="warm"] .overview-status-badge[data-tone="start"]   { --tone: #2b5580; }
:root[data-theme="warm"] .overview-status-badge[data-tone="work"]    { --tone: #7d5410; }
:root[data-theme="warm"] .overview-status-badge[data-tone="filed"]   { --tone: #1c6039; }
:root[data-theme="warm"] .overview-status-badge[data-tone="closed"],
:root[data-theme="warm"] .overview-status-badge[data-tone="other"]   { --tone: #6b6152; }
:root[data-theme="warm"] .overview-status-badge[data-tone="stopped"] { --tone: #97262c; }
:root[data-theme="dusk"] .overview-status-badge[data-tone="start"]   { --tone: #7fb0e0; }
:root[data-theme="dusk"] .overview-status-badge[data-tone="work"]    { --tone: #e3c07f; }
:root[data-theme="dusk"] .overview-status-badge[data-tone="filed"]   { --tone: #7fc3a8; }
:root[data-theme="dusk"] .overview-status-badge[data-tone="closed"],
:root[data-theme="dusk"] .overview-status-badge[data-tone="other"]   { --tone: #a69d92; }
:root[data-theme="dusk"] .overview-status-badge[data-tone="stopped"] { --tone: #e2908c; }

.overview-status-caption { margin: 0; font-size: 0.74rem; }

/* What the status means for this kind of matter, said in words under the pill,
   with the way through to the full record. */
.overview-note {
  display: grid;
  align-items: center;
  margin-top: 0;
  padding: 15px 17px;
  border: 1px solid var(--dashboard-border);
  border-left: 3px solid var(--portal-gold);
  border-radius: 6px;
  background: color-mix(in srgb, var(--portal-gold) 7%, var(--portal-surface));
  gap: 14px;
  grid-template-columns: auto minmax(0, 1fr) auto;
}

/* Restated because the rule above sets a display of its own, and an author
   display beats the user agent's [hidden]. */
.overview-note[hidden] { display: none; }

.overview-note-mark {
  display: grid;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--portal-gold) 24%, var(--portal-surface));
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 0.9rem;
  font-weight: 700;
  place-items: center;
}

.overview-note > div > strong { display: block; color: var(--portal-link); font-size: 0.82rem; }
.overview-note p strong { font-weight: 700; }
.overview-note p { margin: 4px 0 0; color: var(--portal-muted); font-size: 0.74rem; line-height: 1.5; }

@media (max-width: 640px) {
  .overview-note { grid-template-columns: auto minmax(0, 1fr); }
  .overview-note > a { width: 100%; grid-column: 1 / -1; }
}

/* ── Proportions ─────────────────────────────────────────────────────────── */

/* The sentence and the percentage on one line above the bar, because "four of
   seven" and "57%" are the same fact said two ways and reading them apart makes
   somebody check one against the other. */
.overview-progress-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 0 0 9px;
  color: var(--portal-muted);
  font-size: 0.74rem;
  gap: 12px;
}

.overview-progress-line strong {
  color: var(--portal-link);
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
}

.overview-bar { margin-bottom: 17px; }

/* .client-topic-bar animates its width and was written inside a component that
   never redraws while somebody is watching. Here it moves on every refresh. */
@media (prefers-reduced-motion: reduce) {
  .client-topic-bar > span { transition: none; }
}

/* Lists grow with their content. Desktop rows stretch to the tallest card;
   narrower layouts use column stacks. Keep outstanding tasks visible without
   an inner scrollbar or a fixed card height. */
.overview-list { min-width: 0; }

.overview-list > .portal-empty { padding: 18px 0; text-align: center; }
.overview-empty-icon {
  display: block;
  width: 30px;
  height: 30px;
  margin: 0 auto 10px;
  color: var(--portal-muted);
  opacity: 0.35;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The questionnaire's own name, at the weight a row title takes rather than the
   1.15rem the case list gives its matters. These sit three to a card. */
.overview-form h3 { margin: 0; font-size: 0.84rem; }
.overview-form .portal-meta { margin: 5px 0 0; font-size: 0.7rem; }

/* ── What is coming ──────────────────────────────────────────────────────── */

.overview-appointment {
  display: grid;
  padding: 0 0 15px;
  border-bottom: 1px solid var(--dashboard-border);
  gap: 5px 13px;
  grid-template-columns: auto minmax(0, 1fr);
}

.overview-appointment:last-child { padding-bottom: 0; border-bottom: 0; }

/* The date as a tear-off, which is how somebody scanning a column of these
   finds the one they are looking for without reading any of them. Decoration:
   the full date is written out beside it for anybody being read to. */
.overview-date-chip {
  display: grid;
  width: 46px;
  padding: 6px 0;
  align-content: center;
  border: 1px solid var(--dashboard-border);
  border-radius: 6px;
  background: color-mix(in srgb, var(--portal-gold) 9%, var(--portal-surface));
  justify-items: center;
}

.overview-date-chip small {
  color: var(--portal-gold);
  font-size: 0.55rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.overview-date-chip strong {
  color: var(--portal-link);
  font-family: Georgia, serif;
  font-size: 1.15rem;
  font-weight: 400;
  line-height: 1.15;
}

.overview-appointment > div > strong { display: block; color: var(--portal-link); font-size: 0.8rem; }
.overview-appointment .portal-meta { margin: 3px 0 0; font-size: 0.68rem; }
.overview-join { grid-column: 2; }
.overview-join a { font-size: 0.7rem; font-weight: 700; }

/* ── The conversation ────────────────────────────────────────────────────── */

.overview-message {
  display: grid;
  padding: 0 0 14px;
  border-bottom: 1px solid var(--dashboard-border);
  gap: 12px;
  grid-template-columns: auto minmax(0, 1fr);
}

.overview-message:last-child { padding-bottom: 0; border-bottom: 0; }
.overview-message strong { display: block; color: var(--portal-link); font-size: 0.78rem; }
.overview-message-preview { margin: 3px 0 0; color: var(--portal-muted); font-size: 0.72rem; line-height: 1.5; }
.overview-message .portal-meta { margin: 4px 0 0; font-size: 0.62rem; }

/* One message nobody has read yet. A dot rather than a bolder weight, because
   the preview under it is already the lightest thing on the card and taking the
   contrast out of it to make room would cost more than the mark is worth. */
.overview-message.is-unread strong::after {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: 7px;
  border-radius: 50%;
  background: var(--portal-gold);
  content: "";
  vertical-align: middle;
}

/* ── What has been provided ──────────────────────────────────────────────── */

/* An ask that has been answered stays on the list. It is the record of what was
   sent, and taking it off the moment the file arrives leaves a client wondering
   whether it landed — but it must not read as still owed, so it loses the gold
   edge .is-needed gives it. */
.overview-ask.is-answered { border-color: var(--dashboard-border); box-shadow: none; }

.overview-ask-done {
  color: var(--portal-success);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.overview-ask-done::before { margin-right: 5px; content: "\2713"; }

/* ── Shortcuts ───────────────────────────────────────────────────────────── */

.overview-actions {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.overview-actions > a {
  display: grid;
  padding: 15px 9px;
  align-content: start;
  border: 1px solid var(--dashboard-border);
  border-radius: 7px;
  background: var(--portal-surface);
  color: var(--portal-link);
  gap: 7px;
  justify-items: center;
  text-align: center;
  text-decoration: none;
}

.overview-actions > a:hover {
  border-color: var(--portal-gold);
  background: color-mix(in srgb, var(--portal-gold) 8%, var(--portal-surface));
}

.overview-actions > a:focus-visible { outline: 2px solid var(--portal-gold); outline-offset: 2px; }

/* Gold and link, not --dashboard-blue.

   .dashboard-quick-actions gets away with #244b91 on #f3f5f8 because both are
   literals: the square stays light at night and the dark blue on it stays
   readable. Mixing the same blue into var(--portal-surface) does not — under
   Dusk the surface is #1d1a22 and the result was a near-black glyph on a
   near-black tile. Measured on the rendered page, which is the only place that
   class of mistake shows up. Both tokens below move with the theme. */
.overview-actions > a > span {
  display: grid;
  width: 34px;
  height: 34px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--portal-gold) 20%, var(--portal-surface));
  color: var(--portal-link);
  font-size: 0.9rem;
  place-items: center;
}

.overview-actions strong { font-size: 0.7rem; }
.overview-actions small { color: var(--portal-muted); font-size: 0.6rem; line-height: 1.4; }

@media (max-width: 640px) {
  .overview-actions > a { padding: 13px 7px; }
  .overview-actions small { display: none; }
}

/* ==========================================================================
   The Blog tab (082)
   ==========================================================================

   Two views of one panel, like Cases: the list, or the post being written. The
   editor is a two-column shell — what is typed on the left, what a reader will
   see on the right — and it collapses to one column before the two get narrow
   enough that neither is comfortable to write in. */

/* The address under a post's title in the list. Tabular and quiet: it is how
   somebody recognises a post they are looking for, not a thing to read. */
.admin-blog-address {
  display: block;
  margin-top: 3px;
  color: var(--portal-muted);
  font-size: 0.64rem;
  letter-spacing: 0.01em;
}

/* A draft is not a lesser row — it is usually the one being worked on — so it
   is marked rather than dimmed. */
#admin-blog-list tr.is-draft td:first-child {
  box-shadow: inset 2px 0 var(--portal-gold);
}

.blog-editor-shell {
  display: grid;
  align-items: start;
  gap: 20px;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
  margin-top: 16px;
}

.blog-editor-form {
  padding: 20px;
  border: 1px solid var(--dashboard-border);
  border-radius: 8px;
  background: var(--portal-surface);
  box-shadow: 0 6px 22px rgba(16, 38, 83, 0.035);
}

/* The body box is the reason this tab exists, so it gets the room. A serif at a
   generous measure, because what is being typed is prose and it is read back on
   the right in a serif too — two faces would make the preview look like a
   different document. */
#blog-body {
  min-height: 320px;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 0.86rem;
  line-height: 1.7;
  resize: vertical;
}

#blog-title { font-family: Georgia, "Times New Roman", serif; font-size: 1rem; }

/* The address, shown as an address. */
#blog-slug,
#blog-address-preview {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.76rem;
}

/* Its own row rather than .portal-table-actions alone: that class is laid out
   as a table cell and computes display:block here, so gap and the auto margin
   below did nothing and the buttons sat in one inline run. */
.blog-editor-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--dashboard-border);
}

.blog-editor-actions #blog-delete { margin-left: auto; }

/* How to write it: open on demand, and never a page somebody has to leave the
   editor to read. */
.blog-format { margin-top: 12px; }

.blog-format summary {
  color: var(--portal-link);
  cursor: pointer;
  font-size: 0.68rem;
}

.blog-format dl {
  display: grid;
  gap: 6px;
  margin: 10px 0 0;
  padding: 12px 14px;
  border: 1px solid var(--dashboard-border);
  border-radius: 7px;
  background: color-mix(in srgb, var(--portal-cream) 55%, var(--portal-surface));
}

.blog-format dl > div { display: grid; align-items: baseline; gap: 4px 12px; grid-template-columns: minmax(120px, auto) 1fr; }
.blog-format dt { color: var(--portal-ink); font-size: 0.66rem; }
.blog-format dd { margin: 0; color: var(--portal-muted); font-size: 0.66rem; }
.blog-format code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.64rem; }
.blog-format > p { margin: 8px 0 0; color: var(--portal-muted); font-size: 0.64rem; }

.blog-preview-pane {
  position: sticky;
  top: 16px;
  padding: 20px;
  border: 1px solid var(--dashboard-border);
  border-radius: 8px;
  background: color-mix(in srgb, var(--portal-cream) 60%, var(--portal-surface));
}

/* The post itself, in the console and on the public site. The two rulesets are
   separate files with separate palettes; what they share is the shape — a
   serif measure, headings that are lighter than the body is dark, and lists
   that indent rather than hang. */
.post-body { color: var(--portal-ink); font-family: Georgia, "Times New Roman", serif; }
.post-body h2 { margin: 22px 0 8px; color: var(--portal-link); font-size: 1.05rem; font-weight: 400; }
.post-body h2:first-child { margin-top: 0; }
.post-body h3 { margin: 18px 0 6px; color: var(--portal-link); font-size: 0.9rem; font-weight: 700; }
.post-body p { margin: 0 0 12px; font-size: 0.82rem; line-height: 1.75; }
.post-body ul,
.post-body ol { margin: 0 0 12px; padding-left: 20px; }
.post-body li { margin-bottom: 6px; font-size: 0.82rem; line-height: 1.7; }
.post-body blockquote {
  margin: 0 0 14px;
  padding: 2px 0 2px 14px;
  border-left: 3px solid var(--portal-gold);
  color: var(--portal-muted);
  font-size: 0.82rem;
  font-style: italic;
  line-height: 1.7;
}
.post-body hr { margin: 20px 0; border: 0; border-top: 1px solid var(--dashboard-border); }
.post-body a { color: var(--portal-link); }
.post-body .post-standfirst { color: var(--portal-muted); font-size: 0.86rem; font-style: italic; }

/* An empty preview says why rather than sitting blank, which reads as broken. */
.post-body:empty::before {
  content: "Nothing written yet.";
  color: var(--portal-muted);
  font-family: inherit;
  font-size: 0.72rem;
  font-style: italic;
}

@media (max-width: 1100px) {
  .blog-editor-shell { grid-template-columns: minmax(0, 1fr); }
  /* Unstuck once it is below the writing rather than beside it: a sticky pane
     in a single column follows you down the page over the thing you are typing. */
  .blog-preview-pane { position: static; }
}

@media (max-width: 640px) {
  .blog-editor-form,
  .blog-preview-pane { padding: 15px; }
  .blog-editor-actions #blog-delete { margin-left: 0; }
}
.private-document-viewer { width: min(1100px, calc(100vw - 24px)); max-width: none; height: min(900px, calc(100dvh - 24px)); max-height: calc(100dvh - 24px); box-sizing: border-box; border: 1px solid var(--portal-line, #ddd); border-radius: 14px; padding: 18px; color: var(--portal-ink, #13294b); background: var(--portal-surface, #fff); }
.private-document-viewer[open] { display: flex; flex-direction: column; gap: 12px; }
.private-document-viewer::backdrop { background: rgb(8 20 42 / 65%); }
.private-document-viewer header, .private-document-viewer footer, .private-document-controls { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.private-document-viewer h2 { margin: 0; font-size: 1.15rem; overflow-wrap: anywhere; flex: 1; min-width: 0; }
.private-document-viewer .portal-status { margin: 0; }
.private-document-body { flex: 1; min-height: 0; overflow: auto; background: var(--portal-bg, #f4f1ea); border-radius: 8px; padding: 12px; }
.private-document-body canvas, .private-document-body img { display: block; max-width: 100%; height: auto; margin: auto; }
.private-document-body pre { white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; font-size: 1rem; }
.private-document-controls[hidden], .private-document-viewer [hidden] { display: none; }

/* Attorney Overview: grouped data, immediate actions, and a private ledger. */
:root:not([data-theme="dusk"]) .admin-console {
  --portal-cream: #f9f8f6;
  --dashboard-bg: #f9f8f6;
  --portal-surface: #fff;
}
.admin-console .case-panel > .dashboard-card {
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.08);
}
#mpanel-overview .case-person-detail {
  display: grid;
  max-width: none;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 16px;
  padding: 20px;
  background: var(--portal-cream);
}
#mpanel-overview .case-person-detail[hidden] { display: none; }
.case-data-card {
  min-width: 0;
  padding: 22px;
  border: 1px solid var(--dashboard-border);
  border-radius: 10px;
  background: var(--portal-surface);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.07);
}
.case-data-card h4 { margin: 0 0 20px; font-size: 1rem; font-weight: 650; color: var(--portal-ink); }
#mpanel-overview .case-data-card .settings-facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px 24px;
}
#mpanel-overview .case-data-card .settings-facts > div {
  display: block;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
#mpanel-overview .case-data-card .settings-facts dt {
  color: var(--portal-muted);
  font-size: 0.68rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.065em;
  line-height: 1.5;
}
#mpanel-overview .case-data-card .settings-facts dd {
  margin: 5px 0 0;
  color: var(--portal-ink);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.5;
  text-align: left;
  overflow-wrap: anywhere;
}
#mpanel-overview .case-person-detail > :not(.case-data-card) { grid-column: 1 / -1; }
.case-quick-actions {
  position: sticky;
  top: 82px;
  z-index: 8;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 14px;
  border: 1px solid var(--dashboard-border);
  border-radius: 10px;
  background: var(--portal-surface);
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);
}
.case-quick-label { margin-right: auto; color: var(--portal-muted); font-size: 0.76rem; }
.case-quick-actions .portal-button { display: inline-flex; align-items: center; gap: 8px; min-height: 40px; }
.case-quick-actions svg, .case-health svg {
  width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.case-health h3, .case-work-records h3 { margin: 0 0 18px; font-size: 1.12rem; font-weight: 650; }
.case-health h4, .case-work-records h4 { margin: 0 0 14px; font-size: 0.84rem; font-weight: 650; }
.case-health h4 { display: flex; align-items: center; gap: 8px; }
.case-health svg { color: #a56b12; }
.case-health-columns { display: grid; grid-template-columns: minmax(0, 2fr) minmax(210px, 1fr); gap: 24px; }
.case-deadline-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.case-deadline-list li { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 14px; padding: 10px 12px; border-left: 3px solid var(--portal-rule); border-radius: 4px; background: var(--portal-cream); font-size: 0.84rem; }
.case-deadline-list li strong { flex: 1 1 200px; overflow-wrap: anywhere; }
.case-deadline-list .is-urgent { border-color: #ba821f; }
.case-deadline-list .is-overdue { border-color: var(--portal-danger); }
.case-deadline-list .is-overdue time { color: var(--portal-danger); font-weight: 650; }
.case-triage { border-left: 1px solid var(--dashboard-border); padding-left: 24px; }
.case-triage-pill { display: inline-block; width: fit-content; border-radius: 999px; padding: 5px 10px; background: var(--portal-cream); color: var(--portal-muted); font-size: 0.72rem; font-weight: 650; }
.case-triage-pill.is-ready { color: var(--portal-success); background: color-mix(in srgb, var(--portal-success) 10%, var(--portal-surface)); }
.case-triage-pill.is-waiting, .is-overdue .case-triage-pill { color: var(--portal-danger); background: color-mix(in srgb, var(--portal-danger) 10%, var(--portal-surface)); }
.case-triage label { display: grid; gap: 7px; margin-top: 16px; font-size: 0.72rem; color: var(--portal-muted); }
.case-triage select { width: 100%; min-height: 38px; padding: 7px; color: var(--portal-ink); background: var(--portal-surface); border: 1px solid var(--dashboard-border); border-radius: 5px; }
.case-work-records > .portal-muted { margin: -8px 0 20px; }
.case-billing-totals { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0 0 20px; gap: 12px; }
.case-billing-totals > div { padding: 16px; background: var(--portal-cream); border-radius: 8px; }
.case-billing-totals dt { color: var(--portal-muted); font-size: 0.72rem; }
.case-billing-totals dd { margin: 6px 0 0; color: var(--portal-ink); font-size: 1.22rem; font-weight: 650; font-variant-numeric: tabular-nums; }
.case-work-list { list-style: none; padding: 0; margin: 0; }
.case-work-list li { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 0; border-top: 1px solid var(--dashboard-border); }
.case-work-list li > div { min-width: 0; overflow-wrap: anywhere; }
.case-work-list strong { font-size: 0.88rem; }
.case-work-list .portal-meta { margin: 6px 0 0; }
.case-private-notes { margin-top: 0; }
.case-note-copy { margin: 0 0 8px; white-space: pre-wrap; line-height: 1.6; font-size: 0.9rem; }
.case-work-dialog { width: min(560px, calc(100% - 24px)); max-height: calc(100dvh - 32px); overflow: auto; padding: 26px; border: 1px solid var(--dashboard-border); border-radius: 14px; background: var(--portal-surface); color: var(--portal-ink); box-shadow: 0 12px 48px rgb(0 0 0 / 0.2); }
.case-work-dialog::backdrop { background: rgb(10 24 53 / 0.48); }
.case-work-dialog h2 { margin: 0 0 8px; font-size: 1.35rem; font-weight: 650; }
.case-work-dialog form > label, .case-time-fields > label { display: grid; gap: 7px; }
.case-work-dialog label:has(input[type="checkbox"]) { display: flex; align-items: center; gap: 10px; }
.case-work-dialog input[type="checkbox"] { width: 18px; min-height: 18px; }
.case-work-dialog [hidden] { display: none; }
.case-time-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.case-time-preview { font-size: 1rem; font-weight: 650; }
.case-work-dialog :focus-visible, .case-quick-actions :focus-visible, .case-triage :focus-visible { outline: 3px solid var(--portal-gold); outline-offset: 3px; }
@media (min-width: 1600px) {
  #mpanel-overview .case-data-card .settings-facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 1100px) {
  #mpanel-overview .case-person-detail { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .case-quick-actions { top: 78px; gap: 6px; padding: 8px; }
  .case-quick-label { display: none; }
  .case-quick-actions .portal-button { flex: 1; justify-content: center; padding: 8px 5px; font-size: 0.7rem; letter-spacing: 0; text-transform: none; }
  .case-quick-actions svg { width: 15px; height: 15px; }
  .case-health-columns { grid-template-columns: minmax(0, 1fr); }
  .case-triage { padding: 18px 0 0; border-left: 0; border-top: 1px solid var(--dashboard-border); }
  #mpanel-overview .case-person-detail { padding: 12px; }
  .case-data-card { padding: 16px; }
  #mpanel-overview .case-data-card .settings-facts { gap: 18px 14px; }
  .case-billing-totals { gap: 6px; }
  .case-billing-totals > div { padding: 10px; }
  .case-billing-totals dd { font-size: 1rem; overflow-wrap: anywhere; }
  .case-work-list li { align-items: start; flex-direction: column; }
}
@media (max-width: 420px) {
  #mpanel-overview .case-data-card .settings-facts, .case-time-fields { grid-template-columns: minmax(0, 1fr); }
}
