/*
 * ===========================================================================
 * BetterVue design system
 * ===========================================================================
 *
 * One stylesheet for every surface: candidate, staff, platform operator, and the
 * error pages. There is no second sheet and no per-page <style> block, because the
 * drift between them is what made the product look like several products.
 *
 * Four rules hold the whole thing together.
 *
 * 1. LOGICAL PROPERTIES ONLY. `inline-start`/`inline-end`, never `left`/`right`.
 *    NFR-I18N-2 requires RTL at page and block level, and the Arabic layout here is
 *    the *same* stylesheet under `dir="rtl"` — there is no mirrored sheet to fall out
 *    of sync, which is how the legacy system accumulated RTL defects. The one
 *    deliberate exception is `.ltr-field`: email, phone, URL, number and code values
 *    stay left-to-right inside an RTL page, because mirroring them corrupts the value
 *    a person is reading.
 *
 * 2. TOKENS, NOT LITERALS. Components reference custom properties. Dark mode
 *    redefines the tokens and nothing else, so a component cannot be legible in one
 *    theme and unreadable in the other.
 *
 * 3. FOCUS IS ALWAYS VISIBLE. Every interactive element gets the same focus ring from
 *    one rule. The previous sheet styled no focus state at all, which made the whole
 *    application keyboard-hostile regardless of how the markup was structured.
 *
 * 4. THE BRAND COLOUR IS UNTRUSTED. `--brand-primary` comes from tenant settings
 *    (FR-ORG-2, a validated hex). It is used for accent and emphasis, never as the
 *    only carrier of meaning, and never as a text colour on an unknown ground — a
 *    tenant picking pale yellow must not be able to make their own staff's UI
 *    unreadable. Text on brand-coloured fills uses `--brand-on-primary`.
 */

/* ---------------------------------------------------------------------------
 * 0. Typefaces
 * ---------------------------------------------------------------------------
 *
 * The tokens below have named Inter and IBM Plex Sans Arabic since this stylesheet
 * was written, but nothing ever fetched them: there was no `@font-face` anywhere in
 * the project and no stylesheet link in `base.html`, so `document.fonts` was empty on
 * every page and both stacks fell through to whatever the device happened to have.
 * The product had a typographic identity on paper and none on screen.
 *
 * Self-hosted rather than linked from a font CDN, for three reasons that all matter
 * here: a candidate page must render on a slow mobile connection without a second DNS
 * lookup and TLS handshake to a third party; a third-party font request discloses the
 * visitor's IP and the page they are on to that party, which is not something a
 * candidate consented to (FR-APPLY-6 is about their CV, but the principle is the
 * same); and the files are then covered by the same `CompressedManifestStaticFilesStorage`
 * hashing and long-lived caching as the rest of `static/`.
 *
 * `font-display: swap` throughout. The alternative, `block`, hides text for up to three
 * seconds — on the one page where a candidate is deciding whether to bother finishing,
 * text that is briefly in the fallback face beats text that is briefly absent.
 *
 * Inter ships as a variable font over the 400-700 range, so the Latin stack is two
 * files (latin, latin-ext) rather than eight. IBM Plex Sans Arabic has no variable
 * build, so each weight this sheet actually uses is loaded as its own file, split by
 * script so an English page never downloads the Arabic glyphs and vice versa.
 */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/inter-latin-var.65850a373e25.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/inter-latin-ext-var.d54db44de5cc.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/plex-ar-arabic-400.c094b91f8db8.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FEFF, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EEFF;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/plex-ar-latin-400.eadb12e578b8.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/plex-ar-arabic-500.f9ee976ee855.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FEFF, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EEFF;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/plex-ar-latin-500.9023ba1d47ca.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/plex-ar-arabic-600.c0ebb1ff010f.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FEFF, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EEFF;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/plex-ar-latin-600.6271bfe4060f.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/plex-ar-arabic-700.89e0f9b275a9.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FEFF, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EEFF;
}
@font-face {
  font-family: "IBM Plex Sans Arabic";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/plex-ar-latin-700.c6a7b569b1c3.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------------------------------------------------------------------------
 * 1. Tokens
 * ------------------------------------------------------------------------- */

:root {
  /* Light by default for every viewer, regardless of the operating system's
     appearance setting. `[data-theme="dark"]` below is the opt-in. */
  color-scheme: light;

  /* Brand. Overridden per-organization by an inline custom property in base.html. */
  --brand-primary: #269b55;
  --brand-on-primary: #ffffff;

  /* Neutrals carry a slight green bias so they sit with the default brand rather
     than reading as an unrelated grey. */
  --color-bg: #ffffff;
  --color-surface: #f7f9f8;
  --color-surface-raised: #ffffff;
  --color-surface-sunken: #eef2f0;
  --color-border: #dbe2df;
  --color-border-strong: #b9c4bf;

  --color-text: #131a17;
  --color-text-muted: #5a6763;
  --color-text-faint: #85938d;

  /* Semantic colours are independent of the brand: a tenant changing their colour
     must not change what "error" looks like. */
  --color-danger: #b3261e;
  --color-danger-bg: #fdeceb;
  --color-warning: #8a6100;
  --color-warning-bg: #fdf3e0;
  --color-success: #1c6b3f;
  --color-success-bg: #e7f4ec;
  --color-info: #1b4f7a;
  --color-info-bg: #e8f1f8;

  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;

  --shadow-sm: 0 1px 2px rgb(19 26 23 / 6%), 0 1px 3px rgb(19 26 23 / 4%);
  --shadow: 0 2px 4px rgb(19 26 23 / 6%), 0 6px 16px rgb(19 26 23 / 8%);
  --shadow-lg: 0 8px 24px rgb(19 26 23 / 10%), 0 2px 6px rgb(19 26 23 / 6%);

  --font-latin: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-arabic: "IBM Plex Sans Arabic", "Noto Naskh Arabic", "Segoe UI", system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, monospace;

  /* A type scale, so headings relate to each other instead of being picked ad hoc. */
  --text-xs: 0.75rem;
  --text-sm: 0.8125rem;
  --text-base: 0.9375rem;
  --text-md: 1.0625rem;
  --text-lg: 1.375rem;
  --text-xl: 1.75rem;
  --text-2xl: 2.25rem;

  --content-width: 76rem;
  --prose-width: 42rem;
  --header-height: 3.5rem;
}

/* The dark palette is retained but is **opt-in only**.
 *
 * It used to apply automatically from `prefers-color-scheme`, so an operator whose
 * machine is in dark mode saw a dark product while everyone else saw a light one — and
 * neither of them chose it. The product owner asked for a light background, so light is
 * what every viewer gets.
 *
 * Nothing is thrown away: the tokens below still define a complete, contrast-checked
 * dark set, and `<html data-theme="dark">` switches to it. That is what a future explicit
 * theme control would set, so adding one is a template change rather than a redesign.
 */
:root[data-theme="dark"] {
    --color-bg: #0f1412;
    --color-surface: #161d1a;
    --color-surface-raised: #1c2421;
    --color-surface-sunken: #111815;
    --color-border: #2a3531;
    --color-border-strong: #3d4b46;

    --color-text: #e9eeec;
    --color-text-muted: #a3b0ab;
    --color-text-faint: #7b8884;

    /* Semantic hues are lifted for contrast on a dark ground; the light-mode values
       fail WCAG AA against #0f1412. Grounds are darkened rather than tinted white. */
    --color-danger: #ff8a80;
    --color-danger-bg: #2e1614;
    --color-warning: #f0b429;
    --color-warning-bg: #2b2211;
    --color-success: #5ed699;
    --color-success-bg: #10281c;
    --color-info: #7fb8e8;
    --color-info-bg: #12222e;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
    --shadow: 0 2px 4px rgb(0 0 0 / 40%), 0 6px 16px rgb(0 0 0 / 32%);
    --shadow-lg: 0 8px 24px rgb(0 0 0 / 48%);
}

/* ---------------------------------------------------------------------------
 * 2. Reset and document
 * ------------------------------------------------------------------------- */

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

html {
  font-family: var(--font-latin);
  font-size: 16px;
  color: var(--color-text);
  background: var(--color-bg);
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
}

/* Arabic needs a taller line and a script-appropriate stack to stay legible at the
   same optical size (NFR-I18N-1). */
html[lang="ar"] {
  font-family: var(--font-arabic);
  line-height: 1.8;
}

body {
  margin: 0;
  font-size: var(--text-base);
  min-block-size: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
}

h1, h2, h3, h4 {
  margin: 0 0 var(--space-3);
  line-height: 1.25;
  font-weight: 650;
  text-wrap: balance;
  letter-spacing: -0.011em;
}
h1 { font-size: var(--text-xl); }
h2 { font-size: var(--text-lg); }
h3 { font-size: var(--text-md); }
h4 { font-size: var(--text-base); }

html[lang="ar"] h1,
html[lang="ar"] h2,
html[lang="ar"] h3,
html[lang="ar"] h4 { letter-spacing: normal; }

p { margin: 0 0 var(--space-4); }
p:last-child { margin-block-end: 0; }

a { color: var(--color-text); text-underline-offset: 0.18em; }
a:hover { text-decoration-thickness: 2px; }

hr {
  border: 0;
  border-block-start: 1px solid var(--color-border);
  margin-block: var(--space-5);
}

code, kbd, samp, pre { font-family: var(--font-mono); font-size: 0.9em; }

img { max-inline-size: 100%; block-size: auto; }

/* One focus ring for the entire application. `:focus-visible` so a mouse click does
   not leave a ring behind, while every keyboard path shows one. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------------------
 * 3. Direction control (NFR-I18N-2)
 * ------------------------------------------------------------------------- */

.ltr-field,
input[type="email"],
input[type="tel"],
input[type="url"],
input[type="number"],
input[type="password"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
code, kbd, samp, pre,
.token, .monospace, .tabular {
  direction: ltr;
  text-align: start;
  unicode-bidi: isolate;
}

/* Candidate answers and model output may be in either language; isolation keeps a
   mixed Arabic/English transcript correct per block (FR-REPORT-5, NFR-I18N-2). */
.auto-dir { unicode-bidi: isolate; }

.tabular, .table td.numeric, .table th.numeric { font-variant-numeric: tabular-nums; }

/* Values with no spaces in them: an address, a calendar UID, a storage key, a token.

   These are the things that force a page sideways. `qa.admin.qade060d@bettervue-qa.invalid`
   in a 368px card is one unbreakable word, and the card cannot shrink below it — which is
   how a participants cell ended up clipped mid-address on the meeting page.

   `anywhere` rather than `break-all`, because `break-all` would also chop ordinary prose
   mid-word. And `min-inline-size: 0` because a flex or grid child refuses to shrink below
   its content by default, so the wrapping never gets a chance to happen. */
.token,
.wrap-anywhere,
.table td .token,
[data-wrap="anywhere"] {
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

/* Deliberately NOT applied to every table cell.

   That was the first attempt and the screenshot showed it was worse than the problem: in
   a 368px card the notifications table stopped scrolling and started shattering instead,
   breaking `basel123496@gmail.com` across three lines as `basel123 / 496@g / mail.com`,
   and wrapping "Hiring manager asked for times" to four. `anywhere` will break a token at
   *any* character once the container is narrow enough, which is the `break-all`-on-prose
   failure the guidance names.

   A table in a narrow container should scroll — `.table-scroll` already does that — and
   the class goes on the values that genuinely have nowhere else to break. */
.table td .token, .table td code { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------
 * 4. Utilities
 * ------------------------------------------------------------------------- */

.visually-hidden {
  position: absolute;
  inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute;
  inset-inline-start: var(--space-3);
  inset-block-start: -100%;
  z-index: 200;
  background: var(--brand-primary);
  color: var(--brand-on-primary);
  padding: var(--space-2) var(--space-4);
  border-radius: 0 0 var(--radius) var(--radius);
  font-weight: 600;
  text-decoration: none;
}
.skip-link:focus { inset-block-start: 0; }

.muted { color: var(--color-text-muted); }
.faint { color: var(--color-text-faint); }
.text-sm { font-size: var(--text-sm); }
.text-xs { font-size: var(--text-xs); }
.stack > * + * { margin-block-start: var(--space-4); }
.stack-sm > * + * { margin-block-start: var(--space-2); }
.row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.row-end { margin-inline-start: auto; }
.prose { max-inline-size: var(--prose-width); }

/* ---------------------------------------------------------------------------
 * 5. Application shell
 *
 * Two shells share this stylesheet:
 *
 *   .staff-shell      sidebar + content, direction follows the operator's choice
 *   .candidate-shell  centred single column with a plain header, direction-aware
 *
 * The sidebar is a <details>. On desktop the disclosure is irrelevant — the nav is
 * always shown and the summary acts as a brand plate. Below 960px the summary becomes
 * the toggle and the nav collapses. One element, one behaviour, no script.
 * ------------------------------------------------------------------------- */

.shell { display: flex; min-block-size: 100vh; align-items: stretch; }

.shell__main {
  flex: 1;
  min-inline-size: 0;   /* lets a wide table shrink instead of pushing the sidebar */
  display: flex;
  flex-direction: column;
}

/* The staff shell follows the document direction, which the operator chooses (see
   `apps/core/language_policy.py`). It used to pin `direction: ltr` here, from when staff
   pages were English-only.

   `text-align: start` stays, and is the load-bearing half: it is what keeps text aligned
   to the reading edge in *either* direction, so nothing needs a mirrored rule. */
.staff-shell { text-align: start; }

.sidebar {
  inline-size: 16rem;
  flex: none;
  background: var(--color-surface);
  border-inline-end: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  position: sticky;
  inset-block-start: 0;
  block-size: 100vh;
  overflow-y: auto;
}

.sidebar__toggle {
  list-style: none;
  cursor: default;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4);
  border-block-end: 1px solid var(--color-border);
  min-block-size: 3.5rem;
}
.sidebar__toggle::-webkit-details-marker { display: none; }
.sidebar__toggle-icon { display: none; margin-inline-start: auto; font-size: 1.15rem; }

.sidebar__brand { display: flex; align-items: center; gap: var(--space-3); min-inline-size: 0; }
.sidebar__logo { block-size: 26px; inline-size: auto; border-radius: var(--radius-sm); }
.sidebar__mark {
  inline-size: 28px; block-size: 28px; flex: none;
  border-radius: var(--radius-sm);
  background: var(--brand-primary);
  color: var(--brand-on-primary);
  display: grid; place-items: center;
  font-size: var(--text-sm); font-weight: 700;
}
.sidebar__name {
  font-weight: 650;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidebar__nav { padding: var(--space-4) var(--space-3); flex: 1; }

.sidebar__group {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--color-text-faint);
  font-weight: 650;
  margin: var(--space-5) var(--space-3) var(--space-2);
}
.sidebar__nav > .sidebar__group:first-child { margin-block-start: 0; }
.sidebar__group--platform {
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-border);
}

.sidebar__link {
  display: block;
  padding: var(--space-3) var(--space-3);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--color-text-muted);
  font-weight: 550;
  margin-block-end: 2px;
}
.sidebar__link:hover { background: var(--color-surface-sunken); color: var(--color-text); }
.sidebar__link[aria-current="page"] {
  background: color-mix(in srgb, var(--brand-primary) 12%, transparent);
  color: var(--color-text);
  font-weight: 650;
  box-shadow: inset 3px 0 0 var(--brand-primary);
}

.sidebar__footer {
  padding: var(--space-4);
  border-block-start: 1px solid var(--color-border);
}
.sidebar__user { display: flex; flex-direction: column; gap: 2px; margin-block-end: var(--space-3); }
.sidebar__user strong { font-size: var(--text-sm); }
.sidebar__user span { font-size: var(--text-xs); color: var(--color-text-muted); }
.sidebar__signout {
  inline-size: 100%;
  text-align: start;
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--color-danger);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
}
.sidebar__signout:hover { background: var(--color-danger-bg); }

/* ---- candidate shell ---- */

/* The ground the form sits on: a light tone rather than pure white, so the white card
   above it reads as a surface rather than as the page itself. */
.candidate-shell { background: var(--color-surface); }

.candidate-header {
  /* Two stops of the tenant's own colour. `color-mix` darkens one end without needing a
     second token — the brand supplies one colour and this derives the rest, so a tenant
     cannot end up with a gradient whose halves disagree. */
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--brand-primary) 82%, #000) 0%,
    var(--brand-primary) 55%,
    color-mix(in srgb, var(--brand-primary) 88%, #fff) 100%
  );
  color: var(--brand-on-primary);
  border-block-end: 0;
}
/* The organization's name, and nothing else. An earlier version of this rule said
   `a:not(.button)` and caught the language switch too, whose unselected option has a
   white background — the label went white on white and disappeared. */
.candidate-header .candidate-header__name { color: inherit; }

.candidate-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-inline-size: 46rem;
  margin-inline: auto;
  padding: var(--space-4) var(--space-5);
  min-block-size: 3.5rem;
}
.candidate-header__logo { block-size: 28px; inline-size: auto; border-radius: var(--radius-sm); }
.candidate-header__name { font-weight: 650; }

/* ---- the hero, on the same green field -----------------------------------------
   What this page is, said once and large. Centred because it is a title rather than a
   row of controls, and because the design centres it. */
.candidate-hero {
  max-inline-size: 46rem;
  margin-inline: auto;
  padding: var(--space-3) var(--space-5) var(--space-7);
  text-align: center;
}
.candidate-hero__eyebrow {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  opacity: 0.85;
}
.candidate-hero__title {
  margin: 0;
  font-size: var(--text-2xl, 1.75rem);
  line-height: 1.25;
  color: inherit;
}
.candidate-hero__subtitle {
  margin: var(--space-2) 0 0;
  opacity: 0.9;
  font-size: var(--text-sm);
}

/* The form lifts off the green onto its own surface, overlapping the join so the two
   read as one panel rather than two stacked bands. */
.candidate-shell .site-main { position: relative; }
.candidate-shell--hero .site-main { margin-block-start: calc(var(--space-6) * -1); }

.site-main {
  flex: 1;
  inline-size: 100%;
  max-inline-size: var(--content-width);
  margin-inline: auto;
  padding: var(--space-6) var(--space-5) var(--space-7);
}
.candidate-shell .site-main { max-inline-size: 46rem; }
.site-main--narrow { max-inline-size: 44rem; }

.site-footer {
  border-block-start: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.site-footer__inner {
  max-inline-size: 46rem;
  margin-inline: auto;
  padding: var(--space-5);
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  align-items: center;
}
.site-footer__note { margin: 0; }

/* ---------------------------------------------------------------------------
 * 6. Page header (title + actions + breadcrumb)
 * ------------------------------------------------------------------------- */

.page-header { margin-block-end: var(--space-5); }
.page-header__top {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.page-header__titles { flex: 1 1 20rem; min-inline-size: 0; }
.page-header h1 { margin-block-end: var(--space-1); }
.page-header__lede { color: var(--color-text-muted); margin: 0; max-inline-size: var(--prose-width); }
.page-header__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  margin: 0 0 var(--space-3);
  padding: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.breadcrumb li { display: flex; gap: var(--space-2); align-items: center; }
.breadcrumb li + li::before { content: "/"; color: var(--color-text-faint); }
.breadcrumb a { color: var(--color-text-muted); }

/* Sub-navigation for a single record (job: overview / matches / sourcing / schedule). */
.tabs {
  display: flex;
  gap: var(--space-1);
  border-block-end: 1px solid var(--color-border);
  margin-block-end: var(--space-5);
  overflow-x: auto;
  scrollbar-width: thin;
}
.tabs__link {
  padding: var(--space-3) var(--space-4);
  text-decoration: none;
  color: var(--color-text-muted);
  font-weight: 550;
  white-space: nowrap;
  border-block-end: 2px solid transparent;
  margin-block-end: -1px;
}
.tabs__link:hover { color: var(--color-text); }
.tabs__link[aria-current="page"] {
  color: var(--color-text);
  border-block-end-color: var(--brand-primary);
}

/* ---------------------------------------------------------------------------
 * 7. Surfaces
 * ------------------------------------------------------------------------- */

.card {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-5);
  box-shadow: var(--shadow-sm);
}
.card--flush { padding: 0; overflow: hidden; }
.card--quiet { box-shadow: none; background: var(--color-surface); }

.card__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-4) var(--space-5);
  border-block-end: 1px solid var(--color-border);
}
.card__header h2, .card__header h3 { margin: 0; }
.card__body { padding: var(--space-5); }
.card__footer {
  padding: var(--space-4) var(--space-5);
  border-block-start: 1px solid var(--color-border);
  background: var(--color-surface);
}

.grid { display: grid; gap: var(--space-4); }
/* A grid item's automatic minimum size is its *content*, so an item holding anything
   intrinsically wide — a table, a long unbroken string — refuses to shrink into its
   track and drags the page sideways with it. `minmax(18rem, 1fr)` sets the track's
   minimum; this sets the item's, which is a different thing and the one that was
   missing. Measured on the meeting page at 390px: 46px of horizontal page scroll,
   traced by bisection to `.grid--cards > .card > .table-scroll > .table` — the scroll
   container was already correct and was being widened from above. */
.grid > * { min-inline-size: 0; }
/* Metrics are counted in sixes here — six pipeline stages on the dashboard, six on a job
   — and `auto-fit` does not know that. At 1440px it fitted five columns of 13rem and put
   the sixth on a row of its own, so "Interviews completed" sat alone under five siblings
   looking like a different kind of thing (D-007).

   Explicit column counts, and only divisors of six, so a six-card group always fills its
   last row. Six columns need about 88rem of content width before they stop being cramped,
   which is wider than a 1440px window has, so three is what a laptop gets. A group of two
   or eight still lays out sensibly: the worst case is a final row of two, which reads as a
   pair rather than as an orphan. */
.grid--metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 40rem) {
  .grid--metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 88rem) {
  .grid--metrics { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
/* Cards are as tall as what is in them.

   A grid stretches its items to the tallest in the row by default, which is right when the
   items are comparable — six metric tiles should line up. These are not comparable: the
   meeting page puts "Nobody has suggested a time." beside a table of eight rows, and the
   stretch gave that one sentence a 668px box of white space. Three of those in a column
   read as a page that has failed to load rather than one with little to say.

   `start` also stops one long card holding its whole row open, which is what made a single
   over-full card on that page cost ~1200px across its neighbours. */
.grid--cards { grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); align-items: start; }
.grid--halves { grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr)); }

/* A form grid is exactly two columns, not `auto-fit`. `auto-fit` on a wide screen gave
   the organization form three enormous columns and paired fields that have nothing to do
   with each other — "Timezone" beside "Organization name". Two columns keeps related
   fields adjacent and each one a sane reading width. */
.grid--form {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4) var(--space-5);
  align-items: start;
}
.grid--form > .field { margin-block-end: 0; }
/* A field that should never share a row. */
.grid--form > .field--full { grid-column: 1 / -1; }

/* A metric that is also a link to the query behind it (FR-DASH-2). */
.metric {
  display: block;
  text-decoration: none;
  color: inherit;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-5);
  box-shadow: var(--shadow-sm);
  transition: box-shadow 120ms ease, border-color 120ms ease, transform 120ms ease;
}
a.metric:hover {
  border-color: var(--color-border-strong);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
.metric__value {
  font-size: var(--text-2xl);
  font-weight: 680;
  line-height: 1.1;
  margin: 0 0 var(--space-2);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.metric__value--unavailable {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--color-text-muted);
  letter-spacing: normal;
}
.metric__label { margin: 0; color: var(--color-text-muted); font-size: var(--text-sm); }

/* Definition-style key/value display used on detail pages. */
.datalist { margin: 0; display: grid; gap: var(--space-3) var(--space-5); }
.datalist--two { grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.datalist dt {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-faint);
  font-weight: 650;
  margin-block-end: var(--space-1);
}
html[lang="ar"] .datalist dt { text-transform: none; letter-spacing: normal; }
.datalist dd { margin: 0; }

/* ---------------------------------------------------------------------------
 * 8. Buttons
 * ------------------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-5);
  /* The same height as an input or a select, so a button beside a field lines up with it
     instead of sitting 6px short. */
  min-block-size: var(--control-h);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  /* `--brand-field`, not `--brand-primary`: white on the raw default brand is 3.56:1, so
     every primary button in the product had a label under the AA floor. The token darkens
     the tenant's own colour rather than replacing it. */
  background: var(--brand-field);
  color: var(--brand-on-primary);
  font-size: var(--control-text);
  font: inherit;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  text-decoration: none;
  text-align: center;
  transition: filter 120ms ease, background 120ms ease;
}
.button:hover { filter: brightness(0.94); }
.button:active { filter: brightness(0.88); }
.button:disabled, .button[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  filter: none;
}

.button--secondary {
  background: var(--color-surface-raised);
  color: var(--color-text);
  border-color: var(--color-border-strong);
}
.button--secondary:hover { background: var(--color-surface-sunken); filter: none; }

.button--ghost {
  background: transparent;
  color: var(--color-text);
  border-color: transparent;
}
.button--ghost:hover { background: var(--color-surface-sunken); filter: none; }

.button--danger { background: var(--color-danger); color: #fff; }

.button--small { padding: var(--space-2) var(--space-3); min-block-size: 2rem; font-size: var(--text-sm); }

/* On a touch screen the small button stops being small.

   `--small` exists for density in tables and toolbars, which is a pointer concern. A
   finger still needs a 44px target, and `pointer: coarse` is the honest test for that —
   narrow-viewport media queries also catch a resized desktop window, where the mouse has
   not changed size. */
@media (pointer: coarse) {
  .button--small { min-block-size: 2.75rem; padding-inline: var(--space-4); }
}
.button--block { inline-size: 100%; }
/* The one action a candidate is meant to take on a page. Used by "Start AI Interview". */
.button--large {
  padding: var(--space-3) var(--space-6);
  min-block-size: 3rem;
  font-size: var(--text-lg);
}

.button-group { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }

/* A form whose only job is one button (POST actions in tables and toolbars). */
.inline-form { display: inline; margin: 0; }

/* ---------------------------------------------------------------------------
 * 9. Forms
 * ------------------------------------------------------------------------- */

.form { max-inline-size: var(--prose-width); }
.form--wide { max-inline-size: none; }

.field { margin-block-end: var(--space-4); }
.field__label { display: block; font-weight: 600; margin-block-end: var(--space-2); }
.field__required { color: var(--color-danger); margin-inline-start: 0.15em; }
.field__hint { color: var(--color-text-muted); font-size: var(--text-sm); margin-block-start: var(--space-2); }
.field__error {
  color: var(--color-danger);
  font-size: var(--text-sm);
  margin-block-start: var(--space-2);
  font-weight: 550;
}

/* A native <select> draws its arrow on the *inline-end* edge, which follows the
   element's own `direction`. Inheriting `rtl` on a staff page therefore moved every
   dropdown arrow to the left of the control while the surrounding layout stayed LTR —
   one of the direction defects reported on the organizations page. The staff shell is
   LTR at the root now, and this states it on the control too, so a stray `dir` on an
   ancestor cannot move it again.

   The custom arrow also replaces the platform-specific native one, so the control looks
   the same on macOS, Windows and Android instead of three different heights. */
select,
select.input {
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%235a6763' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  padding-inline-end: var(--space-6);
}
/* An Arabic page gets a genuinely mirrored control: the whole layout is RTL, so the
   arrow belongs on the left and the padding on the other side. Applies to both shells
   now that a staff page can be Arabic too — it was scoped to `.candidate-shell` when it
   was the only one that could be. */
html[dir="rtl"] select,
[dir="rtl"] select {
  background-position: left var(--space-3) center;
  padding-inline-end: var(--space-3);
  padding-inline-start: var(--space-6);
}
/* …except one whose *value* is technical, which stays LTR in either direction. */
html[dir="rtl"] .field--ltr select,
[dir="rtl"] .field--ltr select {
  direction: ltr;
  background-position: right var(--space-3) center;
  padding-inline-start: var(--space-3);
  padding-inline-end: var(--space-6);
}

/* `field--ltr` marks a field whose *value* is technical — a slug, an email address, a
   timezone identifier, a date. NFR-I18N-2 requires these to stay left-to-right even
   inside an RTL page, because mirroring them corrupts the value being read. The type
   selectors above cover most of them; this covers the rest, such as a slug in a plain
   text input. */
.field--ltr input,
.field--ltr select,
.field--ltr textarea {
  direction: ltr;
  text-align: start;
  unicode-bidi: isolate;
}

/* Trailing punctuation on a mixed-script line resolves against the paragraph direction,
   so an English sentence inside an RTL block renders its full stop at the start of the
   line. Isolating each label and hint keeps the punctuation with its own sentence. */
.field__label, .field__hint, .field__error, .page-header__lede, .empty-state__body {
  unicode-bidi: isolate;
}

/* Every control that takes typed or chosen input. The exclusions are the ones that are
   not text boxes: checkboxes and radios have their own shape, buttons are buttons, and a
   file/range/colour control is a native widget we would only make worse by boxing. */
:where(
  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="file"]):not([type="range"]):not([type="color"]),
  select,
  textarea
),
.input {
  padding: var(--space-3);
  /* 44px. The web criterion is 24x24 (WCAG 2.2 AA) and this clears it, but a date or
     time box is tapped with a thumb on a phone and 40px was not comfortable. */
  min-block-size: 2.75rem;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-text);
  font: inherit;
  /* A date input reports an intrinsic width from its own locale format and will happily
     exceed a narrow card. It may shrink; it may not overflow. */
  max-inline-size: 100%;
}

/* Width is layout, so it stays with the wrapper. A field fills its column; a control
   loose in a toolbar or a table cell keeps its intrinsic size. */
.field input:not([type="checkbox"]):not([type="radio"]),
.field select,
.field textarea {
  inline-size: 100%;
}

textarea, textarea.input { min-block-size: 7rem; resize: vertical; line-height: 1.6; }

/* Native date and time widgets place their own spin/picker buttons inside the box. With
   the padding above they sit against the edge, so the inline padding comes off that side
   and the widget gets its own room. */
input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"] {
  padding-inline-end: var(--space-2);
}

:where(input, textarea)::placeholder { color: var(--color-text-faint); }

:where(input, select, textarea):hover:not(:disabled) { border-color: var(--color-text-faint); }

:where(input, select, textarea):disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-muted);
  cursor: not-allowed;
  /* Disabled is a state, not a decoration: it reads as unavailable without dropping
     below the contrast a reader needs to know what the control was for. */
  opacity: 0.7;
}

/* A file input, which was excluded above and then looked it.

   It is excluded from the box treatment because the element is two things — a button and
   a filename label — and boxing the pair gives a 44px outline around a 20px button. So the
   *button half* is styled instead, through `::file-selector-button`, to match the
   secondary button it sits beside. The filename stays plain text, which is what it is.

   The wrapper still gets a height so it lines up with the fields either side of it, and
   `::file-selector-button` is the standard now that Chromium, Firefox and Safari all
   support it — the old `-webkit-` pseudo-element is kept for older WebKit. */
input[type="file"] {
  font: inherit;
  color: var(--color-text-muted);
  min-block-size: 2.75rem;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-inline-size: 100%;
  /* Chromium paints its own light fill behind the whole control, which showed as a grey
     band across the field row once the button beside it was styled. The button carries
     the surface now; the rest is just a filename. */
  background: transparent;
  border: 0;
  padding: 0;
}
input[type="file"]::file-selector-button,
input[type="file"]::-webkit-file-upload-button {
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  padding: var(--space-2) var(--space-4);
  min-block-size: 2.25rem;
  margin-inline-end: var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
}
input[type="file"]:hover::file-selector-button { border-color: var(--color-text-faint); }
input[type="file"]:disabled::file-selector-button { cursor: not-allowed; opacity: 0.7; }

/* Read-only is not disabled. The value matters and is meant to be read and copied; only
   editing is closed. Telling them apart is `read-only-distinction`. */
:where(input, textarea):read-only:not(:disabled) {
  background: var(--color-surface-sunken);
  cursor: default;
}

/* Invalid, from three directions: the server's own class, an explicit ARIA state, and
   the browser's constraint validation — `:user-invalid` rather than `:invalid` so a
   required field is not flagged red before it has been touched. */
.field--invalid input, .field--invalid select, .field--invalid textarea,
:where(input, select, textarea)[aria-invalid="true"],
:where(input, select, textarea):user-invalid {
  border-color: var(--color-danger);
  /* Not colour alone (`color-not-only`): the border thickens, so the state survives a
     monochrome display and colour-blind vision. */
  box-shadow: inset 0 0 0 1px var(--color-danger);
}

.field--check { display: flex; align-items: flex-start; gap: var(--space-3); }
.field--check input { margin-block-start: 0.2em; inline-size: 1rem; block-size: 1rem; flex: none; }
.field--check .field__label { margin: 0; font-weight: 500; }

.fieldset {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-5);
  margin: 0 0 var(--space-5);
}
.fieldset > legend {
  padding-inline: var(--space-2);
  font-weight: 650;
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
}
html[lang="ar"] .fieldset > legend { text-transform: none; letter-spacing: normal; }

.form__actions {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: center;
  margin-block-start: var(--space-5);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--color-border);
}

/* Several buttons that belong together, each in its own POST form.

   Not `.form__actions`: that is a form's footer and brings a divider and a large top
   margin with it. This is a row of sibling actions inside a block — on the meeting page,
   the three things you can do about the hiring manager's link — where a rule above them
   would separate them from the state that explains why you would press any of them.

   Wraps, so three buttons in a 18rem card become three rows rather than an overflow. */
.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}
.button-row > form { margin: 0; }

/* ---- searchable select --------------------------------------------------------
   A filter box above a long list. See `static/js/searchable-select.js` for why 195
   options need one; this is only its appearance. Without JavaScript the search box is
   never inserted and what remains is an ordinary select, which is the point. */
.searchable { display: flex; flex-direction: column; gap: var(--space-2); }
.searchable__search { inline-size: 100%; }
.searchable__count { margin: 0; }
/* While filtering, the script opens the list to show matches. It needs a height it can
   scroll rather than one that pushes the rest of the form down the page. */
.searchable select[size]:not([size="0"]) { max-block-size: 14rem; overflow-y: auto; }

/* A fieldset used only to group inputs, with none of a fieldset's own furniture.

   `<fieldset>`/`<legend>` is the right markup for a set of radios — it names the group,
   and a `<label>` cannot, which is the defect the candidate reschedule form had. But the
   browser's default border and inset padding draw a box the design does not have. This
   strips the chrome and keeps the semantics. */
.fieldset--bare {
  border: 0;
  padding: 0;
  margin-inline: 0;
  min-inline-size: 0;
}
.fieldset--bare > legend { padding: 0; }

/* ---- choice pair --------------------------------------------------------------
   Two mutually exclusive answers, shown as two buttons rather than two radio dots.

   Radios are correct semantics and stay underneath: the inputs are still radios, still
   keyboard-operable, still announced as a group. This restyles their labels. Two options
   is the case where a radio pair costs more vertical room than it earns, and the design
   asks for a segmented control. */
.choice-pair {
  display: grid;
  /* 7rem, not 10: the design puts both answers on one row on a phone, and at 390px a
     10rem minimum could not fit two columns plus the gap, so they stacked — which reads
     as two separate buttons rather than one either/or choice. */
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: var(--space-3);
}
.choice-pair input[type="radio"] {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  opacity: 0;
  pointer-events: none;
}
.choice-pair label {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-block-size: 2.75rem;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  cursor: pointer;
  font-weight: 500;
}
.choice-pair label:hover { border-color: var(--color-text-faint); }
.choice-pair input[type="radio"]:checked + label {
  border-color: var(--brand-primary);
  background: var(--color-success-bg, var(--color-surface));
  font-weight: 650;
}
/* The ring belongs on the label, because the input it describes is visually hidden and a
   focus ring nobody can see is not a focus ring. */
.choice-pair input[type="radio"]:focus-visible + label {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

/* ---- amount and its unit ------------------------------------------------------
   A salary and the currency it is quoted in are one answer, so they sit on one row.
   "20000" without "SAR" is not a smaller answer, it is an unusable one. */
.amount {
  display: grid;
  grid-template-columns: 1fr minmax(7.5rem, auto);
  gap: var(--space-3);
  align-items: start;
}
.amount > * { min-inline-size: 0; }
@media (max-width: 22rem) {
  /* Below this the two halves each become too narrow to read their own content. */
  .amount { grid-template-columns: 1fr; }
}

/* ---- two fields on one row ----------------------------------------------------
   Country and city, which are read together. Stacks early: two 11rem columns on a
   390px phone leaves neither label room to sit above its control. */
.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0 var(--space-4);
}

/* ---- flat sections ------------------------------------------------------------
   For a page about one record rather than a list of them. See the note in
   `templates/scheduling/meeting_detail.html` for why the meeting stopped using cards. */
.sections {
  display: flex;
  flex-direction: column;
  /* Wide enough for a form row, narrow enough that a line of prose stays readable at
     1440px. The page shell is 76rem; an unbounded single column would run a hint about
     calendar UIDs across the full width of a desktop monitor. */
  max-inline-size: 54rem;
}

.section {
  /* The container the summary list measures itself against, now that no card wraps it. */
  container-type: inline-size;
  padding-block: var(--space-6);
  border-block-start: 1px solid var(--color-border);
}
.section:first-child { padding-block-start: 0; border-block-start: 0; }
.section:last-child { padding-block-end: 0; }
.section > :first-child { margin-block-start: 0; }
.section > h2 { margin-block-end: var(--space-4); }

/* A section whose whole content is one short sentence — "Nobody has suggested a time."
   — does not need the vertical room a form section does. */
.section--brief { padding-block: var(--space-5); }

/* Two sections that belong side by side on a wide screen and stack below it: the
   summary and the single action that applies to it. Not a card grid — no equal heights,
   no borders, just two columns of the same flow. */
.section__split {
  display: grid;
  gap: var(--space-5) var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  align-items: start;
}

/* ---- summary list -------------------------------------------------------------
   Facts about one thing: time, duration, participants, location.

   These were `<table>`s, and a table is the wrong instrument for them. A table wants to
   size its columns to content, so a 368px card holding a 44-character address produced a
   label column squeezed to two words per line and a value column clipped mid-address —
   and `table-layout` cannot fix it because there is no relationship *between rows* to
   line up. A definition list has no column to fight over.

   Two columns where there is room; stacked below 30rem, where a label above its value is
   more readable than either being 12 characters wide. */
.summary {
  display: grid;
  grid-template-columns: minmax(7rem, auto) 1fr;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  align-items: baseline;
}
.summary > dt {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 500;
}
.summary > dd {
  margin: 0;
  min-inline-size: 0;
  /* Addresses, UIDs and joining links live here. */
  overflow-wrap: anywhere;
}
.summary > dd:not(:last-child) { margin-block-end: var(--space-1); }

/* Whatever follows a summary is a different thought — a hint about the list, an action
   on it — and needs air. Without this the invitation hint sat directly under the last
   participant's address and read as part of the participants row. */
.summary + p,
.summary + .button-row,
.summary + form { margin-block-start: var(--space-4); }

@container (max-width: 30rem) {
  .summary { grid-template-columns: 1fr; gap: var(--space-1); }
  .summary > dt { margin-block-start: var(--space-3); }
  .summary > dt:first-child { margin-block-start: 0; }
}
/* Containers only query an element that declares itself one. The card is the box whose
   width actually decides whether two columns fit — the viewport is not, because the same
   card is one of three on a desktop and full width on a phone. */
.card { container-type: inline-size; }

/* A list of people, one per line, each with what they are. An email is a single
   unbreakable token and belongs on its own line rather than inline after a name. */
.people { margin: 0; padding: 0; list-style: none; }
.people > li { margin-block-end: var(--space-2); min-inline-size: 0; }
.people > li:last-child { margin-block-end: 0; }
.people__role {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
html[lang="ar"] .people__role { text-transform: none; letter-spacing: normal; }
.people__who { overflow-wrap: anywhere; }

/* ---- technical detail, folded away ---------------------------------------------
   Identifiers an operator needs twice a year and a recruiter never. `progressive-
   disclosure`: they stay one click away and stop competing with the time and place. */
.details {
  margin-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-border);
  padding-block-start: var(--space-3);
}
.details > summary {
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-weight: 500;
  /* A disclosure triangle is a control: it needs a comfortable target, not a 16px glyph. */
  padding-block: var(--space-2);
  min-block-size: 2.75rem;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* `display: flex` on a <summary> removes the native marker in Chromium, so the control
     rendered as a line of grey text with nothing to say it could be opened — which is how
     it looked in the first screenshot of it. The marker is drawn back explicitly, and it
     turns when the section opens so the state is visible rather than implied. */
  list-style: none;
}
.details > summary::-webkit-details-marker { display: none; }
.details > summary::before {
  content: "";
  inline-size: 0.5rem;
  block-size: 0.5rem;
  flex: none;
  border-inline-end: 2px solid currentColor;
  border-block-end: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}
html[dir="rtl"] .details > summary::before { transform: rotate(-135deg); }
.details[open] > summary::before { transform: rotate(45deg); }
html[dir="rtl"] .details[open] > summary::before { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
  .details > summary::before { transition: none; }
}
.details > summary:hover { color: var(--color-text); }

/* The same disclosure used inline — inside a filter bar, say — where the rule above it
   and the block spacing would separate it from the controls it belongs with. It keeps
   the marker and the target size, which are the parts that matter. */
.details--inline {
  margin-block-start: 0;
  border-block-start: 0;
  padding-block-start: 0;
  flex: 1 1 100%;
}
.details[open] > summary { margin-block-end: var(--space-3); }

/* A filter/search bar above a table. */
.toolbar {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
  align-items: flex-end;
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  margin-block-end: var(--space-4);
}
.toolbar .field { margin: 0; flex: 1 1 12rem; min-inline-size: 0; }

/* ---------------------------------------------------------------------------
 * 10. Feedback: messages, banners, badges
 * ------------------------------------------------------------------------- */

.messages {
  list-style: none;
  margin: 0;
  padding: var(--space-4) var(--space-5) 0;
  max-inline-size: var(--content-width);
  margin-inline: auto;
  inline-size: 100%;
}

.message, .banner {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-4);
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  border-inline-start-width: 4px;
  background: var(--color-surface);
  margin-block-end: var(--space-3);
}
.message:last-child { margin-block-end: 0; }
.banner { margin-block-end: var(--space-5); }
.banner > *, .message > * { margin-block-end: 0; }

.message--error, .banner--error {
  border-color: var(--color-danger);
  background: var(--color-danger-bg);
  color: var(--color-danger);
}
.message--warning, .banner--warning {
  border-color: var(--color-warning);
  background: var(--color-warning-bg);
  color: var(--color-warning);
}
.message--success, .banner--success {
  border-color: var(--color-success);
  background: var(--color-success-bg);
  color: var(--color-success);
}
.message--info, .banner--info {
  border-color: var(--color-info);
  background: var(--color-info-bg);
  color: var(--color-info);
}

/* A message that carries a block of content rather than one line.

   `.message` is a flex *row*, which is right for its usual job: an icon beside a sentence.
   Put a heading, two paragraphs and two forms inside one and every child becomes a column
   — on the meeting page that produced five columns between 53px and 140px wide, each
   breaking to one word per line, with the last form spilling out of the card entirely.

   The row behaviour is still the default, because it is still the common case. This is
   for the ones that hold real content. */
.message--stacked {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
}
.message--stacked > form { margin-block: 0; }
/* The explanatory line under an action belongs to that action, not to the next one. */
.message--stacked > .muted { margin-block-start: calc(var(--space-2) * -1); }

/* FR-EVAL-4 / FR-REPORT-4: an insufficient-evidence result must be prominent and must
   never be mistaken for a score. */
.banner--insufficient-evidence {
  border-color: var(--color-warning);
  background: var(--color-warning-bg);
  color: var(--color-warning);
  font-weight: 600;
}

/* FR-EVAL-12: every result states that it is decision support requiring human review. */
.advisory-notice {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  border-inline-start: 3px solid var(--brand-primary);
  padding-inline-start: var(--space-4);
  margin-block: var(--space-4);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.15rem var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 650;
  line-height: 1.6;
  background: var(--color-surface-sunken);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
  white-space: nowrap;
}
.badge--success { background: var(--color-success-bg); color: var(--color-success); border-color: currentcolor; }
.badge--warning { background: var(--color-warning-bg); color: var(--color-warning); border-color: currentcolor; }
.badge--danger  { background: var(--color-danger-bg);  color: var(--color-danger);  border-color: currentcolor; }
.badge--info    { background: var(--color-info-bg);    color: var(--color-info);    border-color: currentcolor; }

/* State is never carried by colour alone: a dot precedes the text so the badge is
   still readable in monochrome and to anyone who cannot distinguish the hues. */
.badge::before {
  content: "";
  inline-size: 0.45em; block-size: 0.45em;
  border-radius: 50%;
  background: currentcolor;
  flex: none;
}
.badge--plain::before { display: none; }

/* ---------------------------------------------------------------------------
 * 11. Tables
 * ------------------------------------------------------------------------- */

.table-scroll {
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface-raised);
  /* `overflow-x: auto` alone does not make a wide table scroll: a grid or flex child
     has an automatic minimum size of its content, so the container grows to fit the
     table and pushes the whole page sideways instead of scrolling inside its own box.
     Measured on the meeting page at 390px: 136px of horizontal page overflow, and still
     46px after the table was wrapped — because the wrapper itself was being widened.
     `min-inline-size: 0` is what lets it shrink and scroll, and it belongs here rather
     than on any one page so every table in the product inherits the fix. */
  min-inline-size: 0;
  max-inline-size: 100%;
  /* `overflow-x: auto` clips what is *painted*, but the wide table still contributes to
     the scrollable overflow area its ancestors report, so the page itself stayed
     horizontally scrollable — 46px on the meeting page at 390px, after the wrapper and
     the grid item were both already correct. `contain: paint` is the containment
     boundary that stops that, and it is the right assertion for a scroll container:
     nothing inside it is meant to paint outside it. Established empirically — of
     `overflow-x: hidden`, `min-inline-size: 0` on main, and this, only this one worked. */
  contain: paint;
}
.table-scroll .table { border: 0; margin: 0; }

.table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--text-base);
}
.table th, .table td {
  text-align: start;
  padding: var(--space-3) var(--space-4);
  border-block-end: 1px solid var(--color-border);
  vertical-align: top;
}
.table thead th {
  background: var(--color-surface);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  font-weight: 650;
  white-space: nowrap;
  position: sticky;
  inset-block-start: 0;
}
html[lang="ar"] .table thead th { text-transform: none; letter-spacing: normal; }
.table tbody tr:last-child td { border-block-end: 0; }
.table tbody tr:hover { background: var(--color-surface); }
.table td.actions { white-space: nowrap; }

/* Below the breakpoint a wide table becomes a list of labelled rows. The label comes
   from `data-label` in the markup, so no column is silently dropped on a phone. */
@media (max-width: 720px) {
  .table--stacked thead { display: none; }
  .table--stacked, .table--stacked tbody, .table--stacked tr, .table--stacked td { display: block; }
  .table--stacked tr {
    border-block-end: 1px solid var(--color-border);
    padding: var(--space-3) var(--space-4);
  }
  .table--stacked tr:last-child { border-block-end: 0; }
  .table--stacked td { border: 0; padding: var(--space-2) 0; }
  .table--stacked td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-text-faint);
    font-weight: 650;
    margin-block-end: var(--space-1);
  }
  html[lang="ar"] .table--stacked td[data-label]::before {
    text-transform: none; letter-spacing: normal;
  }

  /* `td.actions` is `white-space: nowrap` so a row's buttons stay on one line on a
     desktop table. In a stacked row that same rule stops them wrapping, and two inline
     forms — a role select plus two buttons — ran 116px past the viewport, scrolling the
     whole document sideways. Found by driving a real browser at 390px; a status-code
     check cannot see it. */
  .table--stacked td.actions {
    white-space: normal;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
  }
  .table--stacked td.actions .inline-form { display: inline-flex; gap: var(--space-2); }
  .table--stacked td.actions select { inline-size: auto; max-inline-size: 100%; }

  /* A stacked table has no horizontal extent, so its wrapper does not need to scroll.
     Scoped with `:has()` deliberately: an unscoped `overflow-x: visible` here would also
     hit the tables that do NOT stack — the audit log's seven forensic columns — and a
     wide table in a non-scrolling wrapper overflows the page body itself, which is the
     one thing a responsive layout must never do. */
  .table-scroll:has(.table--stacked) { overflow-x: visible; }
}

.pagination {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-block-start: var(--space-4);
}
.pagination__status {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
/* Arrows are written as HTML entities in the markup and would point the wrong way in
   RTL; the glyph is mirrored here rather than duplicated in every template. */
html[dir="rtl"] .pagination__arrow { display: inline-block; transform: scaleX(-1); }

/* ---------------------------------------------------------------------------
 * 12. Empty, loading and error states
 * ------------------------------------------------------------------------- */

/* An empty state must say what is missing, why, and what to do next. A bare "No
   results" leaves the operator unable to tell an empty list from a broken page —
   which is precisely how an empty plan catalogue surfaced as an ORM exception. */
.empty-state {
  text-align: center;
  padding: var(--space-7) var(--space-5);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-surface);
}
.empty-state__icon {
  inline-size: 2.75rem; block-size: 2.75rem;
  margin-inline: auto;
  margin-block-end: var(--space-4);
  border-radius: 50%;
  background: var(--color-surface-sunken);
  color: var(--color-text-faint);
  display: grid; place-items: center;
  font-size: var(--text-lg);
}
.empty-state__title { margin-block-end: var(--space-2); font-size: var(--text-md); }
.empty-state__body {
  color: var(--color-text-muted);
  max-inline-size: 34rem;
  margin-inline: auto;
  margin-block-end: var(--space-5);
}
.empty-state__body:last-child { margin-block-end: 0; }
.empty-state--inline { padding: var(--space-5); text-align: start; }

/* Work that is running server-side. Announced via `role="status"` in the markup, so
   the spinner is decoration and the text carries the meaning. */
.loading { display: inline-flex; align-items: center; gap: var(--space-3); color: var(--color-text-muted); }
.spinner {
  inline-size: 1rem; block-size: 1rem;
  border: 2px solid var(--color-border-strong);
  border-block-start-color: var(--brand-primary);
  border-radius: 50%;
  animation: spin 700ms linear infinite;
  flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* A spinner with its own label, for a wait the candidate is watching. `aria-live` on
   the container is what announces it; the spinner itself is decorative. */
.progress-indicator {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface-sunken);
  color: var(--color-text-muted);
}
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 3s; }
}

/* The candidate's own match score. Large because it is the answer they came back for,
   and `tabular-nums` so it does not reflow as the number changes. */
.score-badge {
  display: inline-flex;
  align-items: baseline;
  gap: 1px;
  font-size: var(--text-2xl);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--brand-primary);
  /* A score is a number: it stays LTR on an Arabic page (NFR-I18N-2). */
  direction: ltr;
  unicode-bidi: isolate;
}
.score-badge__max { font-size: var(--text-base); font-weight: 400; color: var(--color-text-muted); }
.score-badge__label { margin: 0; font-weight: 600; }

.skeleton {
  background: linear-gradient(
    90deg,
    var(--color-surface-sunken) 25%,
    var(--color-surface) 50%,
    var(--color-surface-sunken) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
  border-radius: var(--radius-sm);
  block-size: 1em;
}
@keyframes shimmer { to { background-position: -200% 0; } }

/* Whole-page error and terminal states (404, 500, expired link, closed job). */
.state-page {
  max-inline-size: 34rem;
  margin-inline: auto;
  text-align: center;
  padding-block: var(--space-7);
}
.state-page__code {
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--color-text-faint);
  font-variant-numeric: tabular-nums;
  margin-block-end: var(--space-3);
}
.state-page__body { color: var(--color-text-muted); margin-block-end: var(--space-5); }

/* ---------------------------------------------------------------------------
 * 12b. Authentication pages
 * ------------------------------------------------------------------------- */

/* Sign-in, password reset and invitation acceptance share one centred layout so a
   person moving between them is never unsure they are still in the same product. */
.auth { max-inline-size: 26rem; margin-inline: auto; padding-block: var(--space-6); }
.auth__card { background: var(--color-surface-raised); }
.auth__lede { color: var(--color-text-muted); margin-block-end: var(--space-5); }
.auth__footer {
  margin-block-start: var(--space-5);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-border);
  font-size: var(--text-sm);
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
}
.auth-shell { background: var(--color-surface); }

/* ---------------------------------------------------------------------------
 * 13. Candidate journey
 * ------------------------------------------------------------------------- */

/* Candidate pages carry no navigation and should read as a document, so they get a
   calmer ground and a single centred column. */
.candidate-shell { background: var(--color-surface); }
.candidate-shell .site-main { max-inline-size: 46rem; }

.choice-list { display: grid; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }
.choice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-surface-raised);
  text-decoration: none;
  color: inherit;
  font-weight: 600;
  font-size: var(--text-md);
}
.choice:hover { border-color: var(--brand-primary); box-shadow: var(--shadow); }
.choice__arrow { color: var(--color-text-faint); flex: none; }

/* The language gate (D-009). Each option carries its own prompt, in its own language and
   direction, so the Arabic sentence sits against the Arabic button instead of being
   stranded at the far side of the page from it. `min-inline-size: 0` because the body is
   a flex child and a long organization name would otherwise refuse to wrap. */
/* A two-option question does not need the full width of a desktop screen; at 1440px the
   choices stretched to something like 950px each, which reads as a layout with content
   missing from it rather than as a deliberate pair. */
.language-gate { max-inline-size: 34rem; }
.language-gate__heading { display: flex; flex-direction: column; gap: var(--space-1); }
.language-gate__org { margin: 0; color: var(--color-text-muted); font-size: var(--text-md); }
/* The two halves of one bilingual question. Laid out as a pair rather than two stacked
   paragraphs: the Arabic one is `dir="rtl"` and on its own it aligned to the far edge,
   leaving it looking like a stray sentence instead of the other half of the English one.
   They wrap to two rows on a narrow screen, still as a group. */
.language-gate__prompts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  align-items: baseline;
}
.language-gate__prompts p { margin: 0; color: var(--color-text-muted); min-inline-size: 0; }

/* Progress through a multi-step candidate flow. */
/* `.steps` was retired by the rollout. It was the confirmation page's own progress
   indicator, duplicating `.rail` above with different class names because it sat on the
   page rather than on the brand field. `.rail--on-brand` is that difference now. */

/* ---------------------------------------------------------------------------
 * 14. Interview
 * ------------------------------------------------------------------------- */

.interview {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-inline-size: 48rem;
  margin-inline: auto;
}
.interview__meta { color: var(--color-text-muted); font-size: var(--text-sm); }

.transcript {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  min-block-size: 16rem;
}
/*
 * Before the first question there is no transcript, and a 16rem bordered box holding
 * nothing is worse than no box: it pushes the only thing the candidate can act on off
 * the fold and, when starting fails, frames the failure with a large blank area. The
 * height exists to stop the panel jumping as bubbles arrive, which is a reason to
 * reserve space once there is something in it, not before.
 *
 * `:empty` is exact here because the bubbles are element children built in
 * `static/js/interview.js` — the div is genuinely empty until the first one lands, and
 * the template leaves no whitespace inside it.
 */
.transcript:empty {
  min-block-size: 0;
  padding: 0;
  border: 0;
  background: none;
}

/* `.bubble--<role>` is built in `static/js/interview.js` from the message role, so these
   class names are an interface with that file, not a free choice. Roles are
   interviewer / candidate / system. */
.bubble {
  padding: var(--space-4);
  border-radius: var(--radius);
  max-inline-size: 90%;
  unicode-bidi: isolate;
  white-space: pre-wrap;
  line-height: 1.65;
}
.bubble--interviewer {
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  align-self: flex-start;
  border-start-start-radius: var(--radius-sm);
}
.bubble--candidate {
  background: color-mix(in srgb, var(--brand-primary) 14%, var(--color-surface-raised));
  border: 1px solid color-mix(in srgb, var(--brand-primary) 32%, transparent);
  align-self: flex-end;
  border-end-end-radius: var(--radius-sm);
}
.bubble--system {
  background: transparent;
  border-inline-start: 3px solid var(--brand-primary);
  align-self: stretch;
  max-inline-size: 100%;
  border-radius: 0;
  padding-block: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* The composer stays within reach on a phone, where the transcript can be long. */
.composer {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  position: sticky;
  inset-block-end: 0;
  background: var(--color-bg);
  padding-block: var(--space-4);
  border-block-start: 1px solid var(--color-border);
}
.composer textarea { min-block-size: 6rem; }
.composer__actions {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
}
.typing { color: var(--color-text-muted); font-style: italic; }

/* An answer already sent but not yet acknowledged by the server. Held at reduced
   opacity so the candidate can see it landed without being told it is confirmed. */
.bubble--pending { opacity: 0.62; }

/* The interviewer's "thinking" bubble, in the transcript rather than beside it. A turn
   against a reasoning model runs 15-90 seconds here, so this is the normal state of the
   page for most of an interview, not a brief flicker. */
.bubble--typing {
  display: flex;
  gap: 5px;
  align-items: center;
  min-block-size: 1rem;
  padding-block: var(--space-4);
}
.bubble--typing span {
  inline-size: 7px;
  block-size: 7px;
  border-radius: 50%;
  background: var(--color-text-muted);
  animation: typing-dot 1.25s ease-in-out infinite;
}
.bubble--typing span:nth-child(2) { animation-delay: 0.18s; }
.bubble--typing span:nth-child(3) { animation-delay: 0.36s; }
@keyframes typing-dot {
  0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
  30%           { opacity: 1;    transform: translateY(-3px); }
}
@media (prefers-reduced-motion: reduce) {
  .bubble--typing span { animation: none; opacity: 0.55; }
}

/* The bar carrying the language switch on a signed-out staff page, which has no
   sidebar to put it in. Aligned to the far end so it reads as chrome rather than as
   part of the sign-in card. */
.shell__topbar {
  display: flex;
  justify-content: flex-end;
  padding: var(--space-3) var(--space-5) 0;
}
.shell__topbar .language-switch {
  flex-direction: row;
  align-items: center;
  gap: var(--space-3);
  padding: 0;
  border: 0;
}

/* The interface language switch in the sidebar. Two segmented buttons, because there
   are two languages and the current one has to stay visible — a control that shows only
   the alternative leaves you unable to tell which language you are in. */
.language-switch {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-block-start: 1px solid var(--color-border);
}
.language-switch__label {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.language-switch__options {
  display: flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.language-switch__option {
  flex: 1;
  padding: var(--space-2) var(--space-3);
  border: 0;
  background: var(--color-surface-raised);
  color: var(--color-text-muted);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  /* Each label is in its own script; isolation stops one reordering the other. */
  unicode-bidi: isolate;
}
.language-switch__option + .language-switch__option {
  border-inline-start: 1px solid var(--color-border);
}
.language-switch__option:hover { background: var(--color-surface-sunken); }
.language-switch__option.is-current {
  background: var(--brand-primary);
  color: #fff;
  font-weight: 600;
  cursor: default;
}
.language-switch__option:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: -2px;
}

/*
 * The candidate switcher, which is a different control from the staff one above.
 *
 * `.language-switch` is built for the staff sidebar: a column, with an uppercase label
 * stacked over a `.language-switch__options` row, and a rule along its top edge
 * separating it from the nav above. The candidate template has no label and no options
 * wrapper — its two links are direct children — so it inherited `flex-direction:
 * column` and rendered as one language above the other, under a stray horizontal rule,
 * in the middle of the page header. It had no rules of its own at all.
 *
 * Here it is what it should be: a compact inline pair sitting in the header beside the
 * organization name. Logical properties throughout, so it needs no RTL override.
 */
.language-switch--candidate {
  flex-direction: row;
  align-items: center;
  gap: 0;
  padding: 0;
  border-block-start: 0;
  margin-inline-start: auto;
}
.language-switch--candidate .language-switch__option {
  flex: 0 0 auto;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: 0;
  text-decoration: none;
  white-space: nowrap;
}
.language-switch--candidate .language-switch__option:first-child {
  border-start-start-radius: var(--radius-sm);
  border-end-start-radius: var(--radius-sm);
}
.language-switch--candidate .language-switch__option:last-child {
  border-start-end-radius: var(--radius-sm);
  border-end-end-radius: var(--radius-sm);
}
/* One shared border between the two, rather than two abutting ones. */
.language-switch--candidate .language-switch__option + .language-switch__option {
  border-inline-start: 0;
}

/* The same switch, on the brand-coloured header.

   Both options sit on a colour the tenant chose, so every value here is derived from
   `--brand-on-primary` rather than assuming white-on-green: the unselected option is a
   wash of it, the selected one is solid with the brand colour showing through as its
   text. A tenant who sets a pale brand gets a readable control instead of an invisible
   one, which is principle 4 applied to a control rather than to body text. */
.candidate-header .language-switch--candidate .language-switch__option {
  background: color-mix(in srgb, var(--brand-on-primary) 16%, transparent);
  border-color: color-mix(in srgb, var(--brand-on-primary) 38%, transparent);
  color: var(--brand-on-primary);
}
.candidate-header .language-switch--candidate .language-switch__option:hover {
  background: color-mix(in srgb, var(--brand-on-primary) 28%, transparent);
}
.candidate-header .language-switch--candidate .language-switch__option.is-current {
  background: var(--brand-on-primary);
  color: var(--brand-primary);
  font-weight: 650;
}
.candidate-header .language-switch--candidate .language-switch__option:focus-visible {
  outline: 2px solid var(--brand-on-primary);
  outline-offset: 2px;
}

/* Staff transcript review: numbered question/answer pairs.
   Deliberately *not* the candidate's chat bubbles. A recruiter scans a transcript for
   what was asked and what was said; alternating alignment makes the eye re-find the
   margin on every turn, and nothing in a bubble log says which question is which. */
.qa-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  counter-reset: qa;
}
.qa {
  padding-inline-start: var(--space-4);
  border-inline-start: 3px solid var(--color-border);
}
.qa--preamble {
  border-inline-start-color: var(--color-warning, var(--color-border-strong));
  margin-block-end: var(--space-5);
}
.qa__label {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.qa__label--answer { margin-block-start: var(--space-4); }
.qa__text {
  margin: 0;
  /* Each message carries its own direction; isolation stops an Arabic answer from
     re-ordering the English label above it (NFR-I18N-2). */
  unicode-bidi: isolate;
  white-space: pre-wrap;
  line-height: 1.65;
}
.qa__text--question { font-weight: 600; }
.qa__closing {
  margin-block-start: var(--space-5);
  padding-inline-start: var(--space-4);
  border-inline-start: 3px solid var(--brand-primary);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  unicode-bidi: isolate;
}

/* A disabled composer must read as "not yet", not as broken. */
.composer textarea:disabled {
  background: var(--color-surface-sunken);
  cursor: not-allowed;
}
.language-choice { display: grid; gap: var(--space-3); }

[hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
 * 15. Report and evaluation
 * ------------------------------------------------------------------------- */

.score-list { display: grid; gap: var(--space-4); }
.score {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-2) var(--space-4);
  align-items: baseline;
}
.score__name { font-weight: 600; }
.score__value { font-variant-numeric: tabular-nums; font-weight: 650; }
.score__weight { font-size: var(--text-xs); color: var(--color-text-faint); }
.score__meter {
  grid-column: 1 / -1;
  block-size: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-sunken);
  overflow: hidden;
}
.score__fill { block-size: 100%; background: var(--brand-primary); border-radius: inherit; }
.score__evidence {
  grid-column: 1 / -1;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.recommendation {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius);
  font-weight: 650;
  border: 1px solid currentcolor;
}
.recommendation--strong_recommend, .recommendation--recommend {
  background: var(--color-success-bg); color: var(--color-success);
}
.recommendation--neutral { background: var(--color-surface-sunken); color: var(--color-text-muted); }
.recommendation--not_recommend, .recommendation--strong_reject {
  background: var(--color-danger-bg); color: var(--color-danger);
}

/* The assessment report, shared by the staff view and the external token view so the
   request owner and the recruiter read the same document.

   These lived in a per-page <style> block in both templates, with `#b00020` written as
   a literal three times. That hex is unreadable on the dark ground — it was the one
   colour in the product that ignored the token system, and it was on the banner whose
   whole job is to be noticed. */
.report__ai-notice {
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}
.report__banner {
  border: 2px solid var(--color-danger);
  background: var(--color-danger-bg);
  color: var(--color-danger);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  font-weight: 650;
}
.report__unavailable { color: var(--color-text-muted); }
.report__gate-flag { color: var(--color-danger); font-weight: 600; }
.report__transcript { list-style: none; padding: 0; margin: 0; }
.report__turn { margin-block-end: var(--space-4); }
.report__turn-role {
  font-weight: 650;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-faint);
  display: block;
  margin-block-end: var(--space-2);
}
html[lang="ar"] .report__turn-role { text-transform: none; letter-spacing: normal; }

/* ---------------------------------------------------------------------------
 * 15b. Records: one suggestion, brief or meeting presented for a decision
 * ------------------------------------------------------------------------- */

/* Used by matching results, sourcing briefs and scheduled meetings. All three are
   "here is a thing, and here is what you can do about it", so they share one shape
   rather than each inventing a card. */
.record {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-5);
  margin-block-end: var(--space-4);
  background: var(--color-surface-raised);
}
/* A record already acted on stays visible — "respect previous outcomes" means showing
   the decision, not hiding the row — but recedes so pending work reads first. */
.record--acted { background: var(--color-surface); border-style: dashed; }
.record--acted .record__headline { color: var(--color-text-muted); }

.record__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-block-end: var(--space-3);
}
.record__headline { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.record__headline strong { font-size: var(--text-md); }
.record__score {
  font-size: var(--text-lg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.record__score small { font-weight: 500; color: var(--color-text-muted); font-size: var(--text-sm); }
.record__meta { color: var(--color-text-muted); font-size: var(--text-sm); margin-block-start: var(--space-3); }
.record__actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-border);
}
.record__actions .field { margin: 0; flex: 1 1 12rem; min-inline-size: 0; }

/* Evidence for and against, side by side, so a reviewer weighs them rather than
   reading a conclusion (SRS §16.1: "SHALL show why the candidate matches"). */
.evidence-columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--space-4); }
.evidence-columns h4 { font-size: var(--text-sm); color: var(--color-text-muted); margin-block-end: var(--space-2); }
.evidence-columns ul { margin: 0; padding-inline-start: 1.15rem; }
.evidence-columns li { margin-block-end: var(--space-1); }

/* A block of literal text meant to be read or copied verbatim — a Boolean search, a
   generated original, a token. Always LTR via `.ltr-field`'s rule on `code`, since
   operators and brackets are LTR structure even when the terms are Arabic. */
.code-block {
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
}
.code-block code {
  display: block;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: var(--text-sm);
  line-height: 1.7;
}

/* Filter chips above a result list. */
.chips { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-block: var(--space-4); }
.chip {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface-raised);
  text-decoration: none;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: 550;
}
/* A chip carrying a label and a count. `.chip` is `inline-flex`, which discards the
   whitespace between its children, so a label beside a number reads as "Approval1"
   without an explicit gap. A modifier rather than a change to `.chip`, because every
   other chip in the product has a single child and does not want the extra space. */
.chip--counted { gap: var(--space-2); }
.chip:hover { background: var(--color-surface-sunken); color: var(--color-text); }
.chip[aria-current="true"] {
  background: var(--brand-primary);
  color: var(--brand-on-primary);
  border-color: transparent;
}

/* Compact metrics used for a pool/summary strip, smaller than dashboard cards. */
.stat-strip { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-block: var(--space-4); }
.stat {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  min-inline-size: 9rem;
}
.stat__value { display: block; font-size: var(--text-lg); font-weight: 700; font-variant-numeric: tabular-nums; }
.stat__label { color: var(--color-text-muted); font-size: var(--text-sm); }

/* ---------------------------------------------------------------------------
 * 16. Job dashboard cards (FR-JOB-3)
 *
 * Scoped entirely to `.job-grid` / `.job-card`. Nothing here selects a shared component
 * (`.badge`, `.button`, `.card`), so the dashboard's spacing decisions cannot leak into
 * a page that never asked for them.
 *
 * The spacing scale, fixed once and used throughout:
 *   --job-card-pad     24px   the card's own padding
 *   --job-card-gap     16px   between the card's six sections
 *   --job-card-gap-sm   8px   between a label and its value
 * ------------------------------------------------------------------------- */

.job-grid {
  --job-card-pad: 1.5rem;
  --job-card-gap: 1rem;
  --job-card-gap-sm: 0.5rem;

  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--job-card-gap);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Three columns on a wide screen, two on a tablet, one on a phone — explicit counts
   rather than `auto-fit`, because the card has a designed width and `auto-fit` would
   give a very wide monitor four narrow ones whose metric row wraps. The breakpoints
   account for the 16rem sidebar, present above 960px and a disclosure below it. */
@media (min-width: 600px) {
  .job-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1140px) {
  .job-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* -- the compact notices above the grid ----------------------------------------- */

/* One line, not a full-width warning band. These report conditions worth knowing about
   (an unrecognised stage, closures held back by the cutoff) on a page that may list
   hundreds of requisitions, and the previous banner treatment made a one-record
   condition the loudest thing on the screen. */
.job-grid__notice {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--job-card-gap-sm);
  margin: 0 0 var(--job-card-gap);
  padding: var(--job-card-gap-sm) var(--space-3);
  border: 1px solid var(--color-warning);
  border-radius: var(--radius);
  background: var(--color-warning-bg);
  color: var(--color-warning);
  font-size: var(--text-sm);
  line-height: 1.5;
}

.job-grid__notice--info {
  border-color: var(--color-info);
  background: var(--color-info-bg);
  color: var(--color-info);
}

.job-grid__notice-icon {
  flex: none;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border-radius: 50%;
  border: 1px solid currentcolor;
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1.15rem;
  text-align: center;
  align-self: center;
}

.job-grid__notice-details { display: inline; }

.job-grid__notice-details > summary {
  display: inline;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.job-grid__notice-details[open] > summary { font-weight: 600; }

/* The raw provider values, shown on request and never mapped for the reader. */
.job-grid__notice-raw {
  display: block;
  margin-block-start: var(--job-card-gap-sm);
}

.job-grid__notice-raw code {
  background: var(--color-surface-raised);
  border: 1px solid currentcolor;
  border-radius: var(--radius-sm);
  padding: 0 var(--space-2);
  unicode-bidi: isolate;
}

.job-grid__notice-hint {
  display: block;
  margin-block-start: 2px;
  font-size: var(--text-xs);
  opacity: 0.85;
}

/* -- the card ------------------------------------------------------------------- */

/* A column of six sections in a fixed order. `min-content` rows everywhere except the
   metrics, which take the slack, so the footers of three cards in a row land on the same
   baseline without any card being given a fixed height that could clip its content. */
.job-card {
  display: grid;
  grid-template-rows: min-content min-content min-content 1fr min-content;
  gap: var(--job-card-gap);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--job-card-pad);
  min-inline-size: 0;
}

.job-card:hover { box-shadow: var(--shadow); }

/* 1. identity ------------------------------------------------------------------- */

.job-card__identity { min-inline-size: 0; }

/* Exactly two lines, always. Reserved with `min-block-size` and capped with the line
   clamp, so a one-line title and a three-line one occupy the same space and every card
   in a row starts its badges at the same height. The full text stays available through
   the link's `title` and on the job page. */
.job-card__title {
  margin: 0;
  font-size: var(--text-md);
  line-height: 1.35;
  min-block-size: calc(2 * 1.35em);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.job-card__title a { color: var(--color-text); text-decoration: none; }
.job-card__title a:hover { color: var(--brand-primary); text-decoration: underline; }

.job-card__ref {
  display: flex;
  align-items: center;
  gap: var(--job-card-gap-sm);
  flex-wrap: wrap;
  margin: var(--job-card-gap-sm) 0 0;
  min-block-size: 1.25rem;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* An identifier from another system: mono, and isolated so an Arabic page cannot
   reorder "EMP: 0845". */
.job-card__ref-code {
  font-family: var(--font-mono);
  unicode-bidi: isolate;
}

.job-card__source {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  padding: 0 var(--space-2);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

/* 2. badges --------------------------------------------------------------------- */

/* Their own row, below the title and never beside it. Sharing a row with the title
   meant a two-line title pushed its badges down while its neighbours' stayed put, and
   nothing lined up across a row of three cards. */
.job-card__badges {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--job-card-gap-sm);
  min-block-size: 1.5rem;
}

/* The source name inside the stage badge: present, and quieter than the stage itself,
   which is the fact the reader is actually after. */
.job-card__badge-source {
  font-size: var(--text-xs);
  font-weight: 500;
  opacity: 0.75;
}

.job-card__badge-source::after {
  content: "\00b7";
  margin-inline-start: 0.25em;
}

/* 3. metadata ------------------------------------------------------------------- */

/* Two columns, each cell a label stacked over its value, both starting at the same
   inline edge. Not `justify-content: space-between` on a row: at card width that leaves
   a lake between "Department" and its value, and the eye crosses it for every field. */
.job-card__meta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--job-card-gap) var(--job-card-gap);
  margin: 0;
}

.job-card__meta-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
  text-align: start;
}

.job-card__meta dt {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-faint);
  line-height: 1.3;
}

.job-card__meta dd {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--color-text);
  overflow-wrap: anywhere;
}

/* "Nobody is assigned yet" is a state a recruiter can act on, and is styled as one —
   distinct from an em dash, which means the value is simply unavailable. */
.job-card__unassigned {
  color: var(--color-text-faint);
  font-style: italic;
}

/* 4. metrics -------------------------------------------------------------------- */

/* Four equal cells, one number size and one label size across all of them. Sunken
   rather than raised: these are the card's readings, and a second set of raised boxes
   inside a raised card flattens the hierarchy. `align-self: start` keeps the panel
   against the metadata while the grid row above absorbs any extra height. */
.job-card__metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--job-card-gap-sm);
  align-self: start;
  inline-size: 100%;
  background: var(--color-surface-sunken);
  border-radius: var(--radius);
  padding: var(--space-3) var(--job-card-gap-sm);
}

.job-card__metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: center;
  min-inline-size: 0;
}

.job-card__metric-value {
  font-size: var(--text-md);
  font-weight: 700;
  line-height: 1.2;
  /* `font-variant-numeric` directly, rather than the shared `.tabular` class, which also
     carries `direction: ltr; text-align: start`. That pair is right for a code or an
     input and wrong here: `text-align: start` overrode the cell's centring and left every
     figure hard against the inline edge of its column. */
  font-variant-numeric: tabular-nums;
}

/* An unavailable reading is deliberately quieter than a real one, so scanning the grid
   never mistakes an em dash for a measurement. */
.job-card__metric-value--unavailable { color: var(--color-text-faint); font-weight: 500; }

.job-card__metric-label {
  font-size: var(--text-xs);
  line-height: 1.3;
  color: var(--color-text-muted);
}

/* 5 + 6. dates and actions ------------------------------------------------------ */

.job-card__foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--job-card-gap);
  flex-wrap: wrap;
  border-block-start: 1px solid var(--color-border);
  padding-block-start: var(--job-card-gap);
}

.job-card__dates {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.job-card__date {
  display: flex;
  align-items: baseline;
  gap: var(--job-card-gap-sm);
  margin: 0;
  font-size: var(--text-xs);
  line-height: 1.4;
}

.job-card__date-label { color: var(--color-text-faint); }

/* `<bdi>` with its own `dir=auto`, and deliberately *not* the shared `.tabular` class.
   Django's Arabic date format embeds RLM marks — "10‏/09‏/2026" — which lay out correctly
   in an RTL context and scramble to "102026/09/" the moment something forces the run to
   LTR, which is exactly what `.tabular` does. Isolation from the surrounding label is
   wanted; a fixed direction is not. */
.job-card__date-value {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* Money is Latin-scripted in both interfaces ("10,000 SAR"), so `<bdi>`'s auto detection
   settles it to LTR on its own and the figure never reorders. */
.job-card__money { font-variant-numeric: tabular-nums; }

/* The provenance pill: the source, and the record's own id, which is the thing a person
   quotes when they ask T2 about a requisition. */
.job-card__source-id {
  font-family: var(--font-mono);
  opacity: 0.8;
}

/* The import date answers "when did we last hear from Farabi", which matters only when
   something looks wrong. The request date is the one a recruiter reasons about. */
.job-card__date--secondary { opacity: 0.7; }

.job-card__actions {
  display: flex;
  gap: var(--job-card-gap-sm);
  flex-wrap: wrap;
}

@media (max-width: 560px) {
  .job-grid { --job-card-pad: 1rem; }
  /* Two by two rather than four across: four metrics in a 300px row leaves each label
     narrower than the word "Applicants". */
  .job-card__metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .job-card__actions { inline-size: 100%; }
  .job-card__actions .button { flex: 1 1 auto; }
}

/* ---------------------------------------------------------------------------
 * 17. Responsive
 * ------------------------------------------------------------------------- */

/* Below this width the sidebar stops being a column and becomes a disclosure at the
   top of the page. The <details> summary, inert on desktop, becomes the toggle. */
@media (max-width: 960px) {
  :root { --content-width: 100%; }

  .shell { flex-direction: column; }

  .sidebar {
    inline-size: 100%;
    block-size: auto;
    position: static;
    border-inline-end: 0;
    border-block-end: 1px solid var(--color-border);
    overflow: visible;
  }
  .sidebar__toggle { cursor: pointer; }
  .sidebar__toggle-icon { display: block; }
  .sidebar__toggle:hover { background: var(--color-surface-sunken); }

  /* Collapsed by default on a phone: the content is what the person came for. */
  .sidebar:not([open]) .sidebar__nav,
  .sidebar:not([open]) .sidebar__footer { display: none; }

  .sidebar__nav { padding: var(--space-3); }
  .sidebar__link { padding: var(--space-3) var(--space-4); }

  .site-main { padding: var(--space-5) var(--space-4) var(--space-6); }
  .page-header__actions { inline-size: 100%; }
  .page-header__actions .button { flex: 1 1 auto; }
  .grid--halves, .grid--form { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  :root { --text-2xl: 1.875rem; --text-xl: 1.5rem; }
  .site-main { padding: var(--space-4) var(--space-3) var(--space-6); }
  .card { padding: var(--space-4); }
  .card__header, .card__body, .card__footer { padding: var(--space-4); }
  .form__actions { flex-direction: column; align-items: stretch; }
  .form__actions .button { inline-size: 100%; }
  .empty-state { padding: var(--space-5) var(--space-4); }
  .toolbar { padding: var(--space-3); }
  .toolbar .field { flex-basis: 100%; }
}

/* Touch targets stay at least 44px on coarse pointers regardless of viewport. */
@media (pointer: coarse) {
  .button, .sidebar__link, .tabs__link, .chip { min-block-size: 2.75rem; }
}

@media print {
  .site-header, .site-footer, .skip-link, .page-header__actions, .tabs { display: none !important; }
  .site-main { max-inline-size: none; padding: 0; }
  .card { box-shadow: none; border-color: #ccc; }
}

/* ===========================================================================
 * 21. The candidate application sheet
 * ===========================================================================
 *
 * The reference implementation of the redesign. Everything here is scoped to
 * `.candidate-shell` (and mostly to `.candidate-shell--hero`) on purpose: the shared
 * design system is meant to reach the staff pages too, but only once this page has been
 * approved, and a token changed in section 1 would reach them today.
 *
 * WHAT THIS FIXES, IN THE ORDER IT WAS FOUND
 *
 * 1. `--brand-on-primary` is `#ffffff` and `--brand-primary` is `#269b55`, and white on
 *    that green is **3.56:1** — below the 4.5:1 that WCAG AA asks of body text. Every
 *    white word on the header, and the label on every primary button, was under the
 *    line. The header gradient made it worse: its lightest stop was the brand mixed
 *    *towards white*, at which point the ratio is 3.02:1. `--brand-field` below derives
 *    a darker ground for white text, and `--brand-ink` derives a darker ink for brand-
 *    coloured text on a light ground, so both directions clear AA for the default brand.
 *
 * 2. The unselected language option was white text on a 16%-white wash over that green —
 *    **2.86:1**, under even the 3:1 asked of a user-interface component. It is the
 *    reason a Latin word appeared to float over the Arabic progress steps rather than
 *    reading as the button it is. Adding *more* white to the fill makes this worse, not
 *    better, so the fill is gone and the pill is carried by a border instead.
 *
 * 3. The section legends were uppercase, 13px, and muted, while the field labels beneath
 *    them were 15px and near-black. A section ranked below its own contents. Sections are
 *    now headings, in ink, in sentence case.
 *
 * 4. Three bordered boxes became one sheet with ruled sections.
 */

/* ROLLOUT. These began scoped to `.candidate-shell` while the reference page was under
   review. They are now `:root`, and the rules that reference them are unscoped with them,
   so the staff and platform pages get the same controls, the same contrast and the same
   components as the page that was approved. */
:root {
  /* Grounds derived from the tenant's colour rather than hard-coded, so an organization
     that sets its own brand gets the same relationships rather than a green page with
     one repainted button. */
  --brand-field: color-mix(in srgb, var(--brand-primary) 82%, #04140c);
  --brand-field-deep: color-mix(in srgb, var(--brand-primary) 58%, #04140c);
  --brand-ink: color-mix(in srgb, var(--brand-primary) 70%, #04140c);

  /* 46px. The brief asks for 44-48; 44 was the old floor and read as slightly tight
     beside 16px text, and 48 starts to look like a touch-first kiosk on a desktop.
     Staff controls were 49px and 51px depending on the control, so this makes them
     consistent as well as smaller. */
  --control-h: 2.875rem;

  /* 16px inside a control, on every page. The candidate reason was iOS zooming a focused
     input whose text is under 16px; the staff reason is that 15px text in a 46px box
     reads as a control someone forgot to finish. Body copy keeps the denser scale — this
     token is only ever applied to controls. */
  --control-text: 1rem;
}

/* ---- the sheet and its sections ------------------------------------------------ */

/* Wider than the 46rem the other candidate pages use, because the sections below put
   their heading beside their fields rather than above them. Still nowhere near the
   76rem the staff shell allows: no single control here is ever stretched. */
.candidate-shell--hero .site-main,
.candidate-shell--hero .candidate-header__inner,
.candidate-shell--hero .candidate-hero { max-inline-size: 60rem; }

.sheet {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  font-size: var(--control-text);
}

.sheet__form { max-inline-size: none; margin: 0; }

/*
 * A section: its name and note in one column, its fields in the other.
 *
 * Below 60rem the two columns become one and the heading sits above its fields, which is
 * both the phone layout and the tablet layout — at 768px a 14rem label column would
 * leave the fields narrower than they are on a large phone.
 */
.formsec {
  display: grid;
  gap: var(--space-4) var(--space-6);
  padding: var(--space-6);
  border-block-end: 1px solid var(--color-border);
}
.formsec--last { border-block-end: 0; }

@media (min-width: 60rem) {
  .formsec {
    grid-template-columns: 14rem minmax(0, 1fr);
    padding: var(--space-6) var(--space-7);
  }
}

.formsec__head { min-inline-size: 0; }
.formsec__title {
  margin: 0;
  /* Larger and darker than the labels it governs, which is the whole point. */
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--color-text);
  /* Not uppercase. An uppercase Latin legend has no Arabic equivalent, so the two
     languages were being given different typographic treatments of the same heading. */
  text-transform: none;
  letter-spacing: -0.006em;
}
html[lang="ar"] .formsec__title { letter-spacing: normal; }

.formsec__note {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  text-wrap: pretty;
}
.formsec__prose {
  margin: 0 0 var(--space-4);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  max-inline-size: 60ch;
}
.formsec__body { min-inline-size: 0; }
.formsec__body > .field:last-child,
.formsec__body > *:last-child { margin-block-end: 0; }

/* ---- controls ------------------------------------------------------------------- */

:where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea),
.input,
.combobox__trigger {
  min-block-size: var(--control-h);
  font-size: var(--control-text);
  border-radius: var(--radius);
  border-color: var(--color-border-strong);
}
.field__label {
  font-size: 0.9375rem;
  font-weight: 550;
  margin-block-end: var(--space-2);
}
.field { margin-block-end: var(--space-4); }
.field__hint { font-size: var(--text-sm); }

/* A field whose answer is short. A city name or a salary does not become easier to read
   by being 44rem wide, and a row of full-width boxes is most of what makes a form look
   like a database screen. */
@media (min-width: 45rem) {
  .field--half { max-inline-size: 22rem; }
}

/* ---- the searchable combobox ----------------------------------------------------
 *
 * One control per question. The old markup put a search box and a select on the page at
 * the same time, so "Nationality" was asked twice; `static/js/searchable-select.js`
 * explains what replaced it. These are its clothes.
 *
 * The native `<select>` is `hidden` by the script, so nothing here has to hide it — and
 * if the script never runs, none of these classes exist and the select is simply a
 * select. */
.combobox { position: relative; }

.combobox__trigger {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border-strong);
  background: var(--color-bg);
  color: var(--color-text);
  font: inherit;
  text-align: start;
  cursor: pointer;
}
.combobox__trigger:hover:not(:disabled) { border-color: var(--color-text-faint); }
.combobox__trigger:disabled {
  background: var(--color-surface-sunken);
  color: var(--color-text-muted);
  cursor: not-allowed;
  opacity: 0.7;
}
.combobox__value {
  flex: 1;
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Nothing chosen yet. The resting text is the placeholder row's own label, set in the
   same muted tone a placeholder gets in a text box, so an unanswered combobox and an
   unanswered input look like the same state. */
.combobox.is-empty .combobox__value { color: var(--color-text-faint); }

.combobox__caret {
  flex: none;
  inline-size: 0.6rem;
  block-size: 0.6rem;
  border-inline-end: 1.6px solid var(--color-text-muted);
  border-block-end: 1.6px solid var(--color-text-muted);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 120ms ease;
}
.combobox.is-open .combobox__caret { transform: rotate(225deg) translate(-2px, -2px); }

/* Invalid, from the server. Matches what a rejected text input does, including the
   thickened edge that survives a monochrome display. */
.combobox__trigger[aria-invalid="true"],
.field--invalid .combobox__trigger {
  border-color: var(--color-danger);
  box-shadow: inset 0 0 0 1px var(--color-danger);
}

.combobox__popup {
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(100% + var(--space-1));
  z-index: 40;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.combobox__search { padding: var(--space-2); border-block-end: 1px solid var(--color-border); }
.combobox__input {
  inline-size: 100%;
  min-block-size: 2.5rem;
  border-radius: var(--radius-sm);
}

.combobox__list {
  list-style: none;
  margin: 0;
  padding: var(--space-1);
  /* Tall enough to show that the list continues, short enough that the popup never
     covers the field below it on a phone in landscape. */
  max-block-size: 15rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.combobox__option {
  padding: var(--space-2) var(--space-3);
  min-block-size: 2.5rem;
  display: flex;
  align-items: center;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
/* The keyboard's position in the list. A background tint alone is invisible to someone
   who cannot distinguish it, so the selected row also carries a mark. */
.combobox__option.is-active { background: var(--color-surface-sunken); }
.combobox__option[aria-selected="true"] { font-weight: 600; }
.combobox__option[aria-selected="true"]::after {
  content: "";
  margin-inline-start: auto;
  inline-size: 0.45rem;
  block-size: 0.8rem;
  border-inline-end: 2px solid var(--brand-ink);
  border-block-end: 2px solid var(--brand-ink);
  transform: rotate(45deg);
}
.combobox__option[aria-disabled="true"] { color: var(--color-text-faint); cursor: not-allowed; }

.combobox__empty {
  margin: 0;
  padding: var(--space-4) var(--space-3);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  text-align: center;
}

/* ---- the upload ------------------------------------------------------------------
   The native control is two widgets in one element and cannot be styled into the row of
   inputs above it, so the input is moved off screen — still focusable, still operable by
   Space — and the `<label>` is the visible control. */
.upload { position: relative; }
.upload__input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.upload__box {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-surface);
  cursor: pointer;
}
.upload__box:hover { border-color: var(--brand-ink); background: var(--color-surface-sunken); }
/* The ring belongs on the thing a keyboard user can see, not on the 1px input. */
.upload__input:focus-visible + .upload__box {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}
.upload__icon { flex: none; display: flex; color: var(--brand-ink); }
.upload__text { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.upload__title { font-weight: 550; font-size: 0.9375rem; }
.upload__hint { color: var(--color-text-muted); font-size: var(--text-sm); }
.upload__action {
  margin-inline-start: auto;
  flex: none;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
  font-size: var(--text-sm);
  font-weight: 600;
}
.upload.has-file .upload__box { border-style: solid; border-color: var(--brand-ink); background: var(--color-surface-raised); }
.upload__chosen {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}
@media (max-width: 30rem) {
  /* The button would otherwise squeeze the filename to two characters. */
  .upload__box { flex-wrap: wrap; gap: var(--space-3); }
  .upload__action { margin-inline-start: 0; inline-size: 100%; text-align: center; }
}

/* ---- the optional disclosure ------------------------------------------------------ */
.disclosure { border-block-start: 1px solid var(--color-border); padding-block-start: var(--space-4); }
.disclosure__summary {
  cursor: pointer;
  font-weight: 550;
  font-size: 0.9375rem;
  color: var(--brand-ink);
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-block-size: 2.5rem;
}
.disclosure__summary::-webkit-details-marker { display: none; }
.disclosure__summary::before {
  content: "";
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-inline-end: 1.6px solid currentColor;
  border-block-end: 1.6px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}
[dir="rtl"] .disclosure__summary::before { transform: rotate(135deg); }
.disclosure[open] > .disclosure__summary::before { transform: rotate(45deg); }
.disclosure__body { padding-block-start: var(--space-4); }

/* ---- the error summary ------------------------------------------------------------
   Shown at the top of the form after a rejection, because on a phone the field that was
   refused can be several screens away from the button that was pressed. */
.notice {
  margin: 0;
  padding: var(--space-5) var(--space-6);
  border-block-end: 1px solid var(--color-border);
  border-start-start-radius: var(--radius-lg);
  border-start-end-radius: var(--radius-lg);
}
.notice--error { background: var(--color-danger-bg); }
.notice:focus-visible { outline: 2px solid var(--color-danger); outline-offset: -2px; }
.notice__title { margin: 0 0 var(--space-2); font-size: 1rem; color: var(--color-danger); }
.notice__list { margin: 0; padding-inline-start: var(--space-5); font-size: var(--text-sm); }
.notice__list li + li { margin-block-start: var(--space-1); }
.notice__list a { color: var(--color-danger); }

/* ---- the submit row --------------------------------------------------------------- */
.sheet__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-5) var(--space-6) var(--space-6);
}
@media (min-width: 60rem) {
  .sheet__actions { padding-inline: var(--space-7); }
}
.sheet__required-key { margin: 0; color: var(--color-text-muted); font-size: var(--text-sm); }
.sheet__actions .button { margin-inline-start: auto; }
@media (max-width: 45rem) {
  /* One full-width action on a phone, and the key above it rather than beside it. */
  .sheet__actions { flex-direction: column-reverse; align-items: stretch; }
  .sheet__actions .button { margin-inline-start: 0; inline-size: 100%; }
  .sheet__required-key { text-align: center; }
}

/* The rollout moved this correction onto `.button` itself, where it reaches the staff and
   platform pages too. Nothing is scoped to the candidate shell here any more. */

/* ===========================================================================
 * 22. The candidate hero, its progress rail, and the switch that sits on it
 * =========================================================================== */

/*
 * The header gradient, darkened so white text on it clears AA.
 *
 * The previous stops ran from `brand mixed with black` up to `brand mixed with **white**`.
 * That last stop is the problem: it lightens the ground under white text to a 3.02:1
 * ratio. Both ends now move towards the same dark, so the gradient still reads as one
 * colour with depth in it, and the lightest point of it is 4.75:1 against white.
 */
.candidate-shell .candidate-header {
  background: linear-gradient(
    135deg,
    var(--brand-field-deep) 0%,
    var(--brand-field) 58%,
    color-mix(in srgb, var(--brand-primary) 74%, #04140c) 100%
  );
}

.candidate-shell--hero .candidate-hero {
  margin-inline: auto;
  padding: var(--space-5) var(--space-5) var(--space-6);
  /* Start-aligned, not centred. A centred title over a start-aligned form makes the eye
     re-find the margin on the way into the first field, and the hero is the same block
     as the form rather than a banner above it. */
  text-align: start;
}

.candidate-shell--hero .candidate-hero__title {
  margin: 0;
  font-size: 1.75rem;
  font-weight: 650;
  line-height: 1.2;
  color: var(--brand-on-primary);
  text-wrap: balance;
}
@media (min-width: 45rem) {
  .candidate-shell--hero .candidate-hero__title { font-size: 2.125rem; }
}

.candidate-shell--hero .candidate-hero__subtitle {
  margin: var(--space-2) 0 0;
  font-size: var(--text-md);
  /* No `opacity`. Fading white text on a coloured ground is the commonest way a
     "supporting" line falls under the contrast floor; the difference between the title
     and this is size and weight, which costs nothing legible. */
  opacity: 1;
  color: var(--brand-on-primary);
  font-weight: 400;
}

/* ---- the progress rail ------------------------------------------------------------
 *
 * Two steps, on the hero rather than under it.
 *
 * As `.steps` it was the first element inside `<main>`, which `.candidate-shell--hero`
 * pulls up by 2rem to overlap the header — so the two 4px bars, being the very top of
 * that content, rendered underneath the green and could not be seen at any width. Here
 * the rail is part of the header and the overlap moves the *sheet*, which has its own
 * padding and nothing to lose at its edge.
 *
 * `<ol>`, because two steps in a fixed order is one of the few things on this page that
 * genuinely is a sequence.
 */
.rail {
  display: grid;
  /* However many steps the page has: two on the application form, three on the
     confirmation page. Pinned at two, the third one overflowed its row. */
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--space-3);
  list-style: none;
  margin: var(--space-6) 0 0;
  padding: 0;
}
.rail__step { display: flex; flex-direction: column; gap: var(--space-2); min-inline-size: 0; }
/*
 * One progress component, two grounds.
 *
 * There used to be two: `.rail` on the application hero and `.steps` on the confirmation
 * page, describing the same three-step journey in the same product with different markup
 * and different class names. They existed separately only because one sits on the brand
 * colour and the other on the page, which is a difference of four colour values rather
 * than of component.
 *
 * The base is the on-page version; `.rail--on-brand` restates the same four values
 * against `--brand-on-primary`.
 */
.rail__bar {
  block-size: 3px;
  border-radius: var(--radius-pill);
  /* A step not yet reached. Non-text, and the step's name beside it carries the meaning
     at full contrast, so this can be a wash without hiding anything. */
  background: var(--color-border);
}
.rail__step--done .rail__bar,
.rail__step--current .rail__bar { background: var(--brand-field); }
.rail__name {
  font-size: var(--text-sm);
  /* Full strength for every step. Which one you are on is said by the bar above it, by
     `aria-current`, and by the weight below -- not by making the others harder to read. */
  color: var(--color-text);
  font-weight: 400;
}
.rail__step--current .rail__name { font-weight: 650; }

.rail--on-brand .rail__bar { background: color-mix(in srgb, var(--brand-on-primary) 34%, transparent); }
.rail--on-brand .rail__step--done .rail__bar,
.rail--on-brand .rail__step--current .rail__bar { background: var(--brand-on-primary); }
.rail--on-brand .rail__name { color: var(--brand-on-primary); }

/* ---- the language switch, on the same green ---------------------------------------
 *
 * This is the "stray English" the brief asked about, and it was a contrast defect rather
 * than a stray string: the word comes from `settings.LANGUAGES`, which is correct — a
 * language is named in its own language, so "English" stays "English" on an Arabic page.
 * What made it read as loose text instead of a control was that the unselected option
 * was white on a 16%-white wash over the brand green: **2.86:1**, under the 3:1 floor for
 * a user-interface component, let alone the 4.5:1 for its label.
 *
 * Raising the wash makes it worse, not better — every percent of white added to the
 * ground closes the gap with the white text on top of it. So the fill is removed and the
 * pill is drawn with a border instead, which puts its label back on the header ground at
 * 4.75:1.
 */
.candidate-header .language-switch--candidate .language-switch__option {
  background: transparent;
  border-color: color-mix(in srgb, var(--brand-on-primary) 58%, transparent);
  color: var(--brand-on-primary);
  font-weight: 500;
  padding: var(--space-2) var(--space-3);
  min-block-size: 2.25rem;
  display: inline-flex;
  align-items: center;
}
.candidate-header .language-switch--candidate .language-switch__option:hover {
  background: color-mix(in srgb, #04140c 22%, transparent);
}
/* The selected half. Its label is the brand colour on white, which for the default green
   is 3.56:1 — so it uses the darkened `--brand-ink` (5.88:1) rather than the raw brand. */
.candidate-header .language-switch--candidate .language-switch__option.is-current {
  background: var(--brand-on-primary);
  color: var(--brand-ink);
  border-color: var(--brand-on-primary);
  font-weight: 650;
}

/* The sheet lifts off the green, overlapping the join so the two read as one panel. Safe
   now in a way it was not before: what enters the overlap is the sheet's own padded
   edge, not a 4px progress bar. */
.candidate-shell--hero .site-main { margin-block-start: calc(var(--space-5) * -1); }

/* ---- corrections found by looking at the rendered page ---------------------------
 *
 * Each of these was invisible in the markup and obvious in a screenshot, which is why
 * they are grouped rather than folded into the rules above: they are what the design
 * review actually caught.
 */

/*
 * 1. The salary's unit was wider than the salary.
 *
 * `.amount` was `1fr minmax(7.5rem, auto)`, and `auto` resolves to the select's
 * max-content — the width of "SAR — Saudi Riyal". Paired with notice period in a 295px
 * column that left the *number* 78px, so the unit occupied two thirds of the control
 * that exists to hold an amount. The unit is now capped, and the salary is no longer
 * paired with anything: a value-plus-unit composite is not one of the "short related
 * fields" that earns a second column.
 */
.amount { grid-template-columns: minmax(0, 1fr) minmax(0, 12.5rem); }
@media (max-width: 30rem) {
  /* On a phone the pair cannot both be legible on one line, so the unit goes underneath
     rather than squeezing the number to three digits. */
  .amount { grid-template-columns: minmax(0, 1fr); }
}
@media (min-width: 45rem) {
  .field--amount { max-inline-size: 26rem; }
}

/*
 * 2. The combobox caret pointed left in Arabic.
 *
 * It was two borders rotated 45deg, and one of them was `border-inline-end` — which is
 * the *left* edge under `dir="rtl"`, so the chevron rotated into a back-arrow on every
 * Arabic page. A caret is one of the few marks that must not mirror: it points at the
 * list below it, and down is down in both directions. Drawn as an image instead, exactly
 * as the native `<select>` arrow above already is.
 */
.combobox__caret {
  inline-size: 0.75rem;
  block-size: 0.5rem;
  border: 0;
  transform: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%235a6763' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  transition: transform 120ms ease;
}
.combobox.is-open .combobox__caret { transform: rotate(180deg); }

/* The disclosure's own marker has the same problem and the same fix is not available —
   it is a border triangle. It is explicitly rotated for RTL above; this keeps the open
   state pointing up in both directions rather than inheriting the LTR rotation. */
[dir="rtl"] .disclosure[open] > .disclosure__summary::before { transform: rotate(-135deg); }

/*
 * 3. The upload box was dashed, which promises a drop target.
 *
 * Nothing here accepts a dropped file — a `<label>` cannot — so the dashes were offering
 * an interaction that does not exist. Solid, and it reads as the control it is.
 */
.upload__box { border-style: solid; }
.upload.has-file .upload__box { border-inline-start: 3px solid var(--brand-ink); }

/*
 * 4. A section note is a sentence, and a sentence has punctuation.
 *
 * Trailing punctuation resolves against the paragraph direction, so an English note
 * inside an RTL page rendered its full stop at the *start* of the line — ".How the hiring
 * team will reach you". The same isolation the labels and hints already get.
 */
.formsec__title, .formsec__note, .formsec__prose,
.upload__title, .upload__hint, .rail__name { unicode-bidi: isolate; }

/*
 * 5. The controls were three different heights, and two of them depended on the language.
 *
 * `min-block-size: 2.875rem` was never the binding constraint: a control's height is its
 * line box plus its padding, and at `padding: var(--space-3)` that came to 50.8px on an
 * English page. On an Arabic page `html[lang="ar"]` raises the line height from 1.55 to
 * 1.8 — correct for running Arabic text, wrong inside a 46px box — and the same input
 * became 54.8px. So a candidate reading Arabic got visibly taller fields than one reading
 * English, from the same stylesheet, and neither matched the 46px combobox beside them.
 *
 * Fixed rather than minimum, with the block padding removed and the line box pinned: a
 * single-line control is a box of a known height, and the text is centred in it. Textareas
 * and the checkbox keep their own sizing — they are not single-line controls.
 */
:where(
  input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]),
  select
),
.combobox__trigger {
  block-size: var(--control-h);
  padding-block: 0;
  /* `normal`, not a number: a unitless line-height here would inherit into the select's
     option list on some platforms. */
  line-height: normal;
}
textarea { block-size: auto; padding-block: var(--space-3); }

/* The search box inside the combobox popup is a smaller control in a smaller container,
   so it keeps its own height rather than the field height. */
.combobox__input { block-size: 2.5rem; }

/*
 * 6. Vertical rhythm on a phone.
 *
 * The section heading, its note and 48px of side padding all earn their place at 1440px
 * and cost more than they return at 390px, where the heading already sits directly above
 * its own fields. The padding tightens and the sheet gets its width back.
 */
@media (max-width: 45rem) {
  .formsec { padding: var(--space-5) var(--space-4); gap: var(--space-3); }
  .sheet__actions { padding: var(--space-4); }
  .notice { padding: var(--space-4); }
  .upload__box { padding: var(--space-3); gap: var(--space-3); }
  .site-main { padding-inline: var(--space-4); }
}

/*
 * 7. The disclosure marker pointed the wrong way in Arabic, for the same reason the
 *    combobox caret did: it was two logical borders rotated, and `border-inline-end` is
 *    the left edge under `dir="rtl"`. It is now the same direction-neutral chevron,
 *    pointing down when closed and up when open — which is also the clearer convention
 *    for a disclosure inside a form, where a sideways arrow reads as navigation.
 */
.disclosure__summary::before {
  inline-size: 0.75rem;
  block-size: 0.5rem;
  border: 0;
  transform: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%231c6b3f' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5l5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}
[dir="rtl"] .disclosure__summary::before { transform: none; }
.disclosure[open] > .disclosure__summary::before,
[dir="rtl"] .disclosure[open] > .disclosure__summary::before { transform: rotate(180deg); }

/*
 * 8. The stacked layout kept the two-column layout's padding.
 *
 * `.formsec` was padded for the wide case and only tightened below 45rem, so a 768px
 * tablet — stacked, because the section-beside-fields layout needs 60rem — sat in
 * between and carried 32px/48px around a single column. Measured, that made the page
 * 297px taller at 768 than the design it replaced, which is the "unnecessarily stretched
 * vertically" the brief asks to avoid.
 *
 * The generous padding now belongs to the two-column case that earns it, and the stacked
 * case gets its own.
 */
.formsec { padding: var(--space-5); }
@media (min-width: 60rem) {
  .formsec { padding: var(--space-6) var(--space-7); }
}
.sheet__actions { padding: var(--space-5); }
@media (min-width: 60rem) {
  .sheet__actions { padding: var(--space-5) var(--space-7) var(--space-6); }
}

/* The phone's own padding, restated after the rules above.
 *
 * `.formsec { padding: var(--space-5) }` in section 8 is unconditional and sits later in
 * the sheet than the `max-width: 45rem` block in section 6, so at equal specificity it
 * won and gave a 390px screen 24px side padding instead of 16px. Same-specificity
 * selectors fighting over the same property across two sections is exactly the collision
 * that is easy to introduce and invisible until something is measured, so the narrow case
 * is settled last and in one place. */
@media (max-width: 45rem) {
  .formsec { padding: var(--space-4); gap: var(--space-3); }
  .sheet__actions { padding: var(--space-4); }
}

/* ===========================================================================
 * 23. Rollout: the shared system on the staff and platform pages
 * ===========================================================================
 *
 * The reference page is approved, so its tokens and components are `:root` and unscoped
 * above. What is left is the part that could not be settled on one page: how the same
 * system behaves on an operator tool, which has wide cards, dense tables and forms that
 * were never given a width.
 */

/*
 * A field is as wide as the answer it takes, not as wide as the card it sits in.
 *
 * The team page's "Invite a member" form put a 1500px box under the word Email, because
 * `.field input { inline-size: 100% }` and nothing above it had a width. A control that
 * runs the full width of a desktop monitor is the single most administrative-looking
 * thing a form can do, and it also makes the text hard to scan back along.
 *
 * A token rather than a literal, so a layout that has already sized its own columns can
 * opt out in one line instead of fighting this rule.
 */
:root { --field-max: 36rem; }

.field input:not([type="checkbox"]):not([type="radio"]),
.field select,
.field .combobox,
.field .amount { max-inline-size: var(--field-max); }

/* Prose takes the width it is given: a job description or an agenda is written in
   paragraphs and a 36rem box would make a long one a scrolling slit. */
.field textarea { max-inline-size: none; }

/* The candidate sheet sizes its own field column through the section grid, and was
   approved at that width. It opts out rather than being re-capped 14px narrower. */
.formsec__body { --field-max: none; }

/* ---- the dossier layout ------------------------------------------------------------
 *
 * One record with a main column and an aside, for a page that is *about* a single thing
 * and has both a narrative (what happened, who is involved) and machinery (actions that
 * are not the next one, delivery state, identifiers).
 *
 * Named `dossier` rather than `record`: `.record` is already a component — one
 * suggestion, brief or meeting presented for a decision, used by matching, sourcing and the
 * schedule page — and a second meaning for the same class silently drew its border and its
 * padding around this whole page.
 *
 * `.sections` — the previous treatment — is a single capped column, which is right for a
 * page of prose and wrong here: the meeting page put twelve sections and six tables into
 * a 540px strip with 900px of empty space beside it, and ran to 4,234px.
 *
 * Stacks below 64rem, aside last, because on a phone "what is this waiting for" has to
 * come before "here is everything you could do about it".
 */
.dossier { display: grid; gap: var(--space-5); align-items: start; }
@media (min-width: 64rem) {
  .dossier { grid-template-columns: minmax(0, 1fr) 22rem; gap: var(--space-6); }
}
.dossier__main, .dossier__aside { min-inline-size: 0; display: flex; flex-direction: column; gap: var(--space-5); }

/* ---- the standout: what this record is waiting for --------------------------------
 *
 * The one thing on the page that is allowed to be loud. Its left edge is the state, so
 * the answer to "is anything blocked on me" is readable before any word is.
 */
.standout {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-inline-start: 4px solid var(--color-border-strong);
  border-radius: var(--radius);
  padding: var(--space-5);
  box-shadow: var(--shadow-sm);
}
/* Whose move it is. Semantic rather than brand-coloured: "waiting on you" must not change
   meaning because a tenant picked a different green. */
.standout--recruiter { border-inline-start-color: var(--color-warning); }
.standout--hiring_manager, .standout--candidate { border-inline-start-color: var(--color-info); }
.standout--nobody { border-inline-start-color: var(--color-success); }
.standout--system { border-inline-start-color: var(--color-danger); }

.standout__eyebrow {
  margin: 0 0 var(--space-1);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
html[lang="ar"] .standout__eyebrow { text-transform: none; letter-spacing: normal; }
.standout__title { margin: 0 0 var(--space-2); font-size: var(--text-lg); }
.standout__summary { margin: 0 0 var(--space-2); font-size: var(--text-md); }
.standout__next { margin: 0; color: var(--color-text-muted); max-inline-size: 70ch; }

.standout__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--space-4);
  margin: var(--space-5) 0 0;
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--color-border);
}
.standout__facts dt {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-block-end: var(--space-1);
}
html[lang="ar"] .standout__facts dt { text-transform: none; letter-spacing: normal; }
.standout__facts dd { margin: 0; font-weight: 550; }
.standout__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-block-start: var(--space-5); }
.standout__actions:empty { display: none; }
.standout__actions form { margin: 0; }

/* ---- the aside's panels ------------------------------------------------------------
   Quieter than a card: these are the drawer beside the record, not objects to compare. */
.panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
}
.panel__title {
  margin: 0 0 var(--space-3);
  font-size: var(--text-base);
  font-weight: 650;
}
.panel .disclosure { border-block-start: 1px solid var(--color-border); padding-block-start: 0; }
.panel .disclosure:first-of-type { border-block-start: 0; }
.panel .disclosure__summary { min-block-size: 2.75rem; color: var(--color-text); }
.panel .disclosure--danger .disclosure__summary { color: var(--color-danger); }
.panel .field { margin-block-end: var(--space-3); }

/* ---- a plain stacked list of small records ---------------------------------------- */
.stack-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.stack-list > li { padding-block: var(--space-3); border-block-start: 1px solid var(--color-border); }
.stack-list > li:first-child { border-block-start: 0; }
.stack-list > li > p { margin: 0 0 var(--space-1); }
.stack-list > li > p:last-child { margin-block-end: 0; }
.stack-list > li form { margin-block-start: var(--space-2); }
.stack-list--tight > li { padding-block: var(--space-2); }

/* Two small forms in one table cell, stacked rather than fighting for one line. */
.cell-actions { display: flex; flex-direction: column; gap: var(--space-2); }
.cell-actions form { display: flex; gap: var(--space-2); align-items: center; margin: 0; }
.cell-actions input { inline-size: auto; min-inline-size: 0; }

/* A block that holds the action a page is waiting on, inside a section that has others. */
.action-panel {
  margin-block-start: var(--space-4);
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.action-panel > h3 { margin-block-start: 0; }
.action-panel form + form { margin-block-start: var(--space-3); }

/* A slot table's "when" column wrapped to four lines ("Sept. 18, / 2026, / 8:54 / a.m.")
   because the two action forms beside it took most of the row. The date is one value and
   should read as one; the actions column is what gives way. */
.table td:first-child { text-wrap: pretty; }
.cell-actions { min-inline-size: 0; }
.cell-actions input[type="datetime-local"] { max-inline-size: 13rem; }
.cell-actions input[type="text"] { max-inline-size: 11rem; }

/* A disclosure is a block, and what follows it is a new thought. Without this the
   "Interview brief" heading sat directly against the "Add a panel member" summary with no
   gap, so the two read as one control. */
.disclosure + h3,
.disclosure + h2,
.disclosure + p { margin-block-start: var(--space-5); }
.section > .disclosure { margin-block-start: var(--space-4); }

/* A cancelled interview is at rest and is not good news. `--closed` is deliberately not
   `--nobody` (green): the edge says "nothing is blocked on you", and on a cancellation
   that reads as approval of it. */
.standout--closed { border-inline-start-color: var(--color-text-faint); }
.standout--closed .standout__title { color: var(--color-text-muted); }

/* A colour swatch is a swatch, not a text box. Excluded from the control sizing above —
   correctly, it is a native widget — it then inherited `.field input { inline-size: 100% }`
   and rendered as a 700px band of solid green across the settings form. */
.field input[type="color"] {
  inline-size: 4.5rem;
  block-size: var(--control-h);
  padding: var(--space-1);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  cursor: pointer;
}

/* ---- the BetterVue mark ------------------------------------------------------------
 *
 * The supplied lockup sits on its own opaque white plate, so its corners are rounded
 * here as well as baked into the file. Both, deliberately: the CSS radius is what a
 * browser applies at the 26-28px the shell actually renders it at, and the baked one is
 * what survives in an email client or anywhere this file is used without the stylesheet.
 *
 * The plate is white and the staff sidebar is near-white, so without an edge the mark
 * floats on a field only a shade different from itself. A hairline gives it a boundary
 * without turning it into a card.
 */
.sidebar__logo--product,
.candidate-header__logo--product {
  border-radius: var(--radius-sm);
  background: #fff;
}
.sidebar__logo--product {
  border: 1px solid var(--color-border);
}

/* On the brand-coloured candidate header the white plate has all the contrast it needs,
   so it takes no border — an outline there would read as a box drawn around the logo. */
.candidate-header__logo--product { border: 0; }

/* The "Powered by" line: mark, then words, on one baseline. */
.site-footer__note {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.site-footer__logo {
  block-size: 20px;
  inline-size: auto;
  border-radius: 4px;
  background: #fff;
  flex: none;
}

/* The mark on a signed-out staff page. `.shell__topbar` pushes its one child to the far
   end; with two children the logo takes the start and the switch keeps the end. */
.shell__topbar { justify-content: space-between; }
.shell__topbar-logo {
  block-size: 30px;
  inline-size: auto;
  border-radius: var(--radius-sm);
  background: #fff;
  border: 1px solid var(--color-border);
}
