/* ==========================================================================
   OraCareers V2 — Design System
   Light-first. Enterprise recruitment platform.

   Deliberately NOT carried over from legacy: neon cyan/orange/violet on
   near-black, glow effects, gradient card borders, the Oxanium display face.
   Sophistication comes from typography, spacing and hierarchy instead.

   Contents
     1. Tokens
     2. Reset & base
     3. Accessibility
     4. Layout
     5. Typography
     6. Buttons
     7. Forms
     8. Cards
     9. Badges & status
    10. Tables & lists
    11. Navigation
    12. Search & filters
    13. Alerts & empty states
    14. Pagination & breadcrumbs
    15. (removed in the B2.1 closeout: dialog, dropdown, tabs, stat tiles —
        component scaffolding no view ever rendered)
    17. Skeleton
    18. Footer
    19. Utilities
   ========================================================================== */

/* ── 1. Tokens ─────────────────────────────────────────────────────────── */
:root {
  /* Neutrals */
  --gray-0:#ffffff;  --gray-25:#fcfdfe; --gray-50:#f8fafc; --gray-100:#f1f5f9;
  --gray-200:#e2e8f0; --gray-300:#cbd5e1; --gray-400:#94a3b8; --gray-500:#64748b;
  --gray-600:#475569; --gray-700:#334155; --gray-800:#1e293b; --gray-900:#0f172a;

  /* ── Brand ────────────────────────────────────────────────────────────
     The approved OraCareers identity, and the same three values the logo
     files use. Not Oracle red: OraCareers is independent and must not imply
     affiliation.

     These three are the source of truth. The ramps below are built around
     them so the interface and the logo can never drift apart again — before
     B2 the primary button was #173d70 while the wordmark was #2563EB, eight
     pixels apart in the same header. */
  --brand-navy:#0B132B;
  --brand-blue:#2563EB;
  --brand-teal:#14B8A6;

  --primary-50:#eff4ff;  --primary-100:#dbe6fe; --primary-200:#bfd3fe;
  --primary-500:#3b82f6; --primary-600:#2563EB; --primary-700:#1d4ed8;
  --primary-800:#1e3a8a; --primary-900:#0B132B;

  /* Teal — reserved for one meaning: something OraCareers has itself
     verified or approved. Never a general "success".

     --teal-600 is the brand value and is for fills and tints. It measures
     2.49:1 against white, so it must NOT be used for a bare icon or dot on a
     white surface — WCAG 1.4.11 wants 3:1 for that. --teal-mark exists for
     those cases at 4.24:1. */
  --teal-50:#effcf9; --teal-100:#cbf5ed; --teal-200:#99e9db;
  --teal-600:#14B8A6; --teal-mark:#0d8a7d; --teal-800:#0d5d55;

  /* Accent — retired in B2. The orange was a fifth colour that the brand
     does not contain, and every place it was used meant "needs attention",
     which the warning tokens already say. Aliased rather than deleted so
     any stylesheet still naming it degrades to the correct semantic. */
  --accent-50:var(--warning-50); --accent-500:var(--warning-600); --accent-600:var(--warning-800);

  /* Semantic */
  --success-50:#f0fdf4; --success-200:#bbf7d0; --success-600:#15803d; --success-800:#14532d;
  --warning-50:#fffbeb; --warning-200:#fde68a; --warning-600:#b45309; --warning-800:#78350f;
  --danger-50:#fef2f2;  --danger-200:#fecaca;  --danger-600:#b91c1c;  --danger-800:#7f1d1d;
  --info-50:#eff6ff;    --info-200:#bfdbfe;    --info-600:#1d4ed8;    --info-800:#1e3a8a;

  /* Surfaces */
  --bg:var(--gray-50);
  --surface:var(--gray-0);
  --surface-sunken:var(--gray-100);
  --text:var(--gray-900);
  --text-muted:var(--gray-600);
  /* #64748b measured 4.76:1 on white and 4.34:1 on --surface-sunken, which is
     where the segmented control, the picker counts and inline code actually
     sit — so it passed where it was chosen and failed where it was used.
     Set against the DARKER of the two surfaces: 4.92:1 on #f1f5f9, 5.39:1 on
     white. */
  --text-subtle:#5f6b7f;
  --border:var(--gray-200);
  --border-strong:var(--gray-300);

  /* The boundary of a form control is not decoration: WCAG 1.4.11 requires
     3:1 against its background so the control can be perceived at all.
     --border-strong (#cbd5e1) measures 1.48:1 and fails that.

     This was #8894a6, chosen as the lightest grey passing against WHITE, at
     3.07:1 — and 3.07 has no room in it. The Oracle skills pane puts inputs
     and selects on --surface-sunken (#f1f5f9), where the same grey measures
     2.81:1 and fails. Machine-measured across all 88 screens, not spotted by
     eye.

     So the value is chosen against the DARKEST surface a control sits on
     rather than the lightest: 3.50:1 on #f1f5f9 and 3.84:1 on white. Both
     have margin, which the previous value did not, and a token with margin
     survives the next surface someone introduces.

     Used for inputs, selects, textareas and bordered buttons. Row dividers
     keep --border, which is decorative and exempt. */
  --border-control:#75839a;

  /* A greyed figure is still information — "nobody is waiting" is a fact the
     reader has to be able to read. --gray-400 measured 2.56:1 and fails 1.4.3
     even at the 3:1 large-text threshold these figures qualify for. This is
     3.42:1 on white and 3.12:1 on --surface-sunken. */
  --text-quiet:#7d8ca2;

  /* Typography */
  --font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --font-mono:ui-monospace,SFMono-Regular,'Cascadia Mono',Menlo,Consolas,monospace;

  --text-xs:.75rem;   --text-sm:.875rem;  --text-base:1rem;
  --text-lg:1.125rem; --text-xl:1.25rem;  --text-2xl:1.5rem;
  --text-3xl:1.875rem; --text-4xl:2.25rem; --text-5xl:3rem;

  --leading-tight:1.25; --leading-snug:1.4; --leading-normal:1.6; --leading-relaxed:1.75;
  --tracking-tight:-.02em; --tracking-normal:0; --tracking-wide:.06em;

  --weight-normal:400; --weight-medium:500; --weight-semibold:600; --weight-bold:700;

  /* Spacing — 4px base */
  --space-0:0;      --space-1:.25rem; --space-2:.5rem;  --space-3:.75rem;
  --space-4:1rem;   --space-5:1.25rem; --space-6:1.5rem; --space-8:2rem;
  --space-10:2.5rem; --space-12:3rem;  --space-16:4rem;  --space-20:5rem;

  /* Containers */
  --container:1200px;
  --container-wide:1400px;
  --container-narrow:44rem;
  --container-form:28rem;

  /* Radius */
  --radius-sm:4px; --radius:6px; --radius-md:8px; --radius-lg:12px; --radius-full:9999px;

  /* Shadows — restrained; no glow */
  --shadow-xs:0 1px 2px rgb(15 23 42 / .04);
  --shadow-sm:0 1px 3px rgb(15 23 42 / .08), 0 1px 2px rgb(15 23 42 / .04);
  --shadow:0 4px 6px -1px rgb(15 23 42 / .07), 0 2px 4px -2px rgb(15 23 42 / .05);
  --shadow-lg:0 10px 15px -3px rgb(15 23 42 / .08), 0 4px 6px -4px rgb(15 23 42 / .05);

  /* Z-index scale */
  --z-base:0; --z-dropdown:100; --z-sticky:200; --z-overlay:300;
  --z-dialog:400; --z-toast:500;

  /* Transitions */
  --ease:cubic-bezier(.4,0,.2,1);
  --duration-fast:120ms; --duration:180ms; --duration-slow:260ms;
}

/* Breakpoints (documented; media queries use literals since CSS vars
   cannot be used in @media conditions):
     sm 640px · md 768px · lg 1024px · xl 1280px · 2xl 1536px          */

/* ── 2. Reset & base ───────────────────────────────────────────────────── */
*,*::before,*::after { box-sizing:border-box; }
body,h1,h2,h3,h4,p,figure,blockquote,dl,dd,ul,ol { margin:0; }
ul[class],ol[class] { list-style:none; padding:0; }

html { -webkit-text-size-adjust:100%; }

body {
  font-family:var(--font-sans);
  font-size:var(--text-base);
  line-height:var(--leading-normal);
  color:var(--text);
  background:var(--bg);
  -webkit-font-smoothing:antialiased;
  min-height:100vh;
  display:flex;
  flex-direction:column;
}

img,svg,video { max-width:100%; display:block; height:auto; }
input,button,textarea,select { font:inherit; color:inherit; }
a { color:var(--primary-600); text-decoration-thickness:1px; text-underline-offset:2px; }
a:hover { color:var(--primary-800); }
hr { border:0; border-top:1px solid var(--border); margin:var(--space-6) 0; }

/* ── 3. Accessibility ──────────────────────────────────────────────────── */

/* Focus, in four situations rather than one.

   A single blue ring is correct on a light page and invisible in the two
   places it matters most: on a blue filled button, and on the navy portal
   sidebar. Each surface therefore gets a ring that actually contrasts with
   it, and filled controls carry a two-tone ring — white inside, dark halo
   outside — so they read on any background. */
:focus-visible {
  outline:2px solid var(--primary-600);
  outline-offset:2px;
  border-radius:var(--radius-sm);
}
:focus:not(:focus-visible) { outline:none; }

/* Filled controls: the ring moves inside and turns white, with a dark halo
   separating it from whatever the control sits on. */
.btn--primary:focus-visible,
.btn--danger:focus-visible,
.chip.is-active:focus-visible,
.pagination__link[aria-current="page"]:focus-visible {
  outline-color:#fff;
  outline-offset:-4px;
  box-shadow:0 0 0 2px var(--primary-800);
}

/* Dark surfaces — the navy portal sidebar and any navy panel. A blue ring on
   #0B132B is close to unreadable, so it goes white. */
.on-navy :focus-visible,
.portal-side :focus-visible {
  outline-color:#fff;
  box-shadow:0 0 0 4px rgb(11 19 43 / .9);
}

/* Teal trust surfaces read as light, so they keep the blue ring but need the
   halo to separate it from the tint. */
.is-trust :focus-visible { box-shadow:0 0 0 4px var(--teal-50); }

.skip-link {
  position:absolute; left:-9999px; top:var(--space-2); z-index:var(--z-toast);
  background:var(--primary-700); color:#fff;
  padding:var(--space-3) var(--space-5); border-radius:var(--radius);
  font-weight:var(--weight-semibold); text-decoration:none;
}
.skip-link:focus { left:var(--space-2); }

.sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}

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

/* ── Icons ─────────────────────────────────────────────────────────────
   One family, inlined once per page by components/icon_sprite and rendered
   with $view->icon(). Stroke is currentColor, so an icon always takes the
   colour of the text beside it — including the active nav item and the
   navy sidebar the shell stage adds.

   Every icon in this system accompanies a label. None of them carries
   meaning on its own, which is why they are all aria-hidden. */
/* The sprite itself renders nothing; CSP forbids an inline style attribute,
   so its positioning lives here. */
.icon-sprite { position:absolute; width:0; height:0; overflow:hidden; }

.ic { flex:0 0 auto; display:inline-block; vertical-align:-.18em; color:var(--gray-400); }
.side-nav__link > span:not(.side-nav__count) { min-width:0; }
.side-nav__link .ic,
.nav__link .ic,
.mobile-nav__link .ic { color:var(--gray-400); transition:color var(--duration-fast) var(--ease); }
.side-nav__link:hover .ic,
.side-nav__link.is-active .ic { color:var(--primary-600); }

/* ── 4. Layout ─────────────────────────────────────────────────────────── */
.shell {
  width:100%; max-width:var(--container);
  margin-inline:auto; padding-inline:var(--space-4);
}
.shell--narrow { max-width:var(--container-narrow); }
.shell--form   { max-width:var(--container-form); }

.main { flex:1; padding-block:var(--space-8) var(--space-16); }
.section { padding-block:var(--space-10); }
.section + .section { border-top:1px solid var(--border); }

.stack   { display:flex; flex-direction:column; gap:var(--space-4); }
.stack-6 { display:flex; flex-direction:column; gap:var(--space-6); }
.stack-8 { display:flex; flex-direction:column; gap:var(--space-8); }
.row     { display:flex; align-items:center; gap:var(--space-3); flex-wrap:wrap; }
.row--between { justify-content:space-between; }
.row--end { justify-content:flex-end; }

.grid { display:grid; gap:var(--space-5); }
.grid--2 { grid-template-columns:repeat(auto-fit,minmax(18rem,1fr)); }
.grid--3 { grid-template-columns:repeat(auto-fit,minmax(15rem,1fr)); }
.grid--sidebar { grid-template-columns:1fr; }

@media (min-width:1024px) {
  .grid--sidebar { grid-template-columns:17rem 1fr; align-items:start; gap:var(--space-8); }
}

/* ── 5. Typography ─────────────────────────────────────────────────────── */
.eyebrow {
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  letter-spacing:var(--tracking-wide); text-transform:uppercase;
  color:var(--primary-600);
}
.h1 { font-size:clamp(1.875rem,4vw,var(--text-4xl)); font-weight:var(--weight-bold); line-height:var(--leading-tight); letter-spacing:var(--tracking-tight); }
.h2 { font-size:clamp(1.5rem,3vw,var(--text-3xl));  font-weight:var(--weight-bold); line-height:var(--leading-tight); letter-spacing:var(--tracking-tight); }
.h3 { font-size:var(--text-xl);  font-weight:var(--weight-semibold); line-height:var(--leading-snug); }
.h4 { font-size:var(--text-lg);  font-weight:var(--weight-semibold); line-height:var(--leading-snug); }

.lead    { font-size:var(--text-lg); color:var(--text-muted); line-height:var(--leading-relaxed); }
.muted   { color:var(--text-muted); }
.subtle  { color:var(--text-subtle); font-size:var(--text-sm); }
.tiny    { font-size:var(--text-xs); color:var(--text-subtle); }
.prose p { margin-bottom:var(--space-4); }
.prose p:last-child { margin-bottom:0; }
.prose ul { list-style:disc; padding-left:var(--space-5); margin-bottom:var(--space-4); }
.prose li { margin-bottom:var(--space-2); }

/* ── 6. Buttons ────────────────────────────────────────────────────────── */
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:var(--space-2);
  min-height:2.75rem;                      /* 44px touch target */
  padding:var(--space-2) var(--space-5);
  border:1px solid transparent; border-radius:var(--radius);
  font-size:var(--text-sm); font-weight:var(--weight-semibold);
  text-decoration:none; cursor:pointer; white-space:nowrap;
  transition:background-color var(--duration) var(--ease),
             border-color var(--duration) var(--ease),
             color var(--duration) var(--ease);
}
.btn:disabled,.btn[aria-disabled="true"] { opacity:.55; cursor:not-allowed; }

/* The primary button is the brand blue exactly — --primary-600 is #2563EB,
   the same value the logo uses. Hover steps to 700. */
.btn--primary   { background:var(--primary-600); color:#fff; }
.btn--primary:hover:not(:disabled) { background:var(--primary-700); color:#fff; }

.btn--secondary { background:var(--surface); color:var(--gray-800); border-color:var(--border-control); }
.btn--secondary:hover:not(:disabled) { background:var(--gray-50); color:var(--gray-900); }

/* Third tier. This variant was used in 19 places across 10 view files —
   consent, representation, requisitions and partner operations — and was
   never defined, so every one of them rendered as a bare .btn with no
   variant styling at all. It is the quiet action beside a primary and a
   secondary: no fill, no border, weight carried by the label alone. */
.btn--ghost     { background:transparent; color:var(--primary-600); border-color:transparent; }
.btn--ghost:hover:not(:disabled) { background:var(--primary-50); color:var(--primary-700); }

/* .btn--accent is deliberately gone. It was defined but used zero times, and
   it was the only orange in the button system. */

.btn--quiet     { background:transparent; color:var(--text-muted); }
.btn--quiet:hover:not(:disabled) { background:var(--gray-100); color:var(--text); }

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

.btn--sm { min-height:2.25rem; padding:var(--space-1) var(--space-3); font-size:var(--text-xs); }
.btn--lg { min-height:3rem; padding:var(--space-3) var(--space-6); font-size:var(--text-base); }
.btn--block { width:100%; }

.btn-group { display:flex; gap:var(--space-3); flex-wrap:wrap; }

/* ── 7. Forms ──────────────────────────────────────────────────────────── */
.form { display:flex; flex-direction:column; gap:var(--space-5); }
.form-row { display:grid; gap:var(--space-5); grid-template-columns:1fr; }
@media (min-width:640px) { .form-row--2 { grid-template-columns:1fr 1fr; } }
/* Three-up filter rows. `.form-row--3` was used by the applications
   workspace before it existed, so five selects stacked in one column on a
   1440px screen. */
@media (min-width:900px) { .form-row--3 { grid-template-columns:repeat(3,1fr); } }
.field-align-end { align-self:end; padding-bottom:.6rem; }

.field { display:flex; flex-direction:column; gap:var(--space-2); min-width:0; }
.field__label {
  font-size:var(--text-sm); font-weight:var(--weight-semibold); color:var(--gray-700);
}
.field__label .req { color:var(--danger-600); margin-left:2px; }
.field__hint { font-size:var(--text-xs); color:var(--text-subtle); }
.field__error { font-size:var(--text-sm); color:var(--danger-600); font-weight:var(--weight-medium); }

.input,.select,.textarea {
  width:100%; min-height:2.75rem;
  padding:var(--space-2) var(--space-3);
  background:var(--surface); color:var(--text);
  border:1px solid var(--border-control); border-radius:var(--radius);
  transition:border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease);
}
.input::placeholder,.textarea::placeholder { color:var(--gray-400); }
.input:hover,.select:hover,.textarea:hover { border-color:var(--gray-400); }
.input:focus-visible,.select:focus-visible,.textarea:focus-visible { border-color:var(--primary-600); }
.input[aria-invalid="true"],.select[aria-invalid="true"],.textarea[aria-invalid="true"] {
  border-color:var(--danger-600);
}
.textarea { min-height:8rem; resize:vertical; line-height:var(--leading-normal); padding-block:var(--space-3); }

.select {
  appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%2364748b' d='M1.4 0 6 4.6 10.6 0 12 1.4 6 7.4 0 1.4z'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right var(--space-3) center;
  padding-right:var(--space-8);
}

.check {
  display:flex; align-items:flex-start; gap:var(--space-3); cursor:pointer;
  /* The label is the real touch target — the 18px control alone is too small,
     so the wrapper carries the 44px minimum. */
  min-height:2.75rem; padding-block:var(--space-2);
}
.check__box {
  width:1.125rem; height:1.125rem; margin-top:.2rem; flex-shrink:0;
  accent-color:var(--primary-700); cursor:pointer;
}
.check__label { font-size:var(--text-sm); line-height:var(--leading-snug); }

.fieldset { border:0; padding:0; margin:0; }
.fieldset__legend {
  font-size:var(--text-sm); font-weight:var(--weight-semibold);
  color:var(--gray-700); margin-bottom:var(--space-3); padding:0;
}

/* Honeypot — hidden from people, reachable by bots */
.hp { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

/* ── 8. Cards ──────────────────────────────────────────────────────────── */
.card {
  background:var(--surface);
  border-radius:var(--r-card); box-shadow:var(--lift-1);
}
.card__body   { padding:var(--space-6); }
.card__header { padding:var(--space-5) var(--space-6); border-bottom:1px solid var(--border); }
.card__footer { padding:var(--space-4) var(--space-6); border-top:1px solid var(--border); background:var(--gray-25); }
.card--interactive { transition:border-color var(--duration) var(--ease), box-shadow var(--duration) var(--ease); }
.card--interactive:hover { border-color:var(--border-strong); box-shadow:var(--shadow-sm); }

/* Job card */
.job-card { display:flex; flex-direction:column; gap:var(--space-3); padding:var(--space-5); }
.job-card__top { display:flex; gap:var(--space-4); align-items:flex-start; }
.job-card__logo {
  width:3rem; height:3rem; flex-shrink:0; border-radius:var(--radius);
  background:var(--gray-100); border:1px solid var(--border);
  display:grid; place-items:center;
  font-weight:var(--weight-bold); color:var(--text-subtle); font-size:var(--text-sm);
  object-fit:contain;
}
.job-card__title { font-size:var(--text-lg); font-weight:var(--weight-semibold); line-height:var(--leading-snug); }
.job-card__title a { color:var(--text); text-decoration:none; }
.job-card__title a:hover { color:var(--primary-700); text-decoration:underline; }
.job-card__company { font-size:var(--text-sm); color:var(--text-muted); }
.job-card__meta {
  display:flex; flex-wrap:wrap; gap:var(--space-2) var(--space-4);
  font-size:var(--text-sm); color:var(--text-muted);
}
.job-card__meta-item { display:inline-flex; align-items:center; gap:var(--space-1); }
.job-card__skills { display:flex; flex-wrap:wrap; gap:var(--space-2); }
/* Company card */
.company-card { display:flex; gap:var(--space-4); padding:var(--space-5); align-items:flex-start; }
.company-card__logo {
  width:3.5rem; height:3.5rem; flex-shrink:0; border-radius:var(--radius);
  background:var(--gray-100); border:1px solid var(--border);
  display:grid; place-items:center; font-weight:var(--weight-bold);
  color:var(--text-subtle); object-fit:contain;
}

/* Stat tile */
/* A stat tile stacks its label, value and any qualifier. The column direction
   lives on .stat rather than only on .stat--link: the plain <div class="stat">
   tiles hold <span> children, which are inline, so without it the number ran on
   the same line as its label. */
.stat { display:flex; flex-direction:column; gap:2px; padding:var(--space-5); background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg); }
.stat__label { font-size:var(--text-sm); color:var(--text-muted); }
.stat__value { font-size:var(--text-3xl); font-weight:var(--weight-bold); line-height:1.1; margin-top:var(--space-1); letter-spacing:var(--tracking-tight); }

/* ── 9. Badges & status ────────────────────────────────────────────────── */
.badge {
  display:inline-flex; align-items:center; gap:var(--space-1);
  padding:var(--space-1) var(--space-2);
  font-size:var(--text-xs); font-weight:var(--weight-medium);
  border-radius:var(--radius-sm); border:1px solid transparent;
  white-space:nowrap;
}
.badge--neutral { background:var(--gray-100); color:var(--gray-700); border-color:var(--gray-200); }
.badge--skill   { background:var(--primary-50); color:var(--primary-800); border-color:var(--primary-100); }
.badge--required{ background:var(--primary-600); color:#fff; }
.badge--success { background:var(--success-50); color:var(--success-800); border-color:var(--success-200); }
.badge--warning { background:var(--warning-50); color:var(--warning-800); border-color:var(--warning-200); }
.badge--danger  { background:var(--danger-50);  color:var(--danger-800);  border-color:var(--danger-200); }
.badge--info    { background:var(--info-50);    color:var(--info-800);    border-color:var(--info-200); }

/* Status pill — always pairs colour with a text label, never colour alone */
.status { display:inline-flex; align-items:center; gap:var(--space-2); font-size:var(--text-sm); font-weight:var(--weight-medium); }
.status__dot { width:.5rem; height:.5rem; border-radius:var(--radius-full); flex-shrink:0; }
.status--verified .status__dot { background:var(--success-600); }
.status--pending  .status__dot { background:var(--warning-600); }
.status--none     .status__dot { background:var(--gray-400); }

/* ── 10. Tables & lists ────────────────────────────────────────────────── */
/* Two independent causes, both real, both needed.

   (1) `position:relative`. `.sr-only` is `position:absolute`, and an absolutely
   positioned element whose ancestors are all static takes the INITIAL
   CONTAINING BLOCK — so it escapes this scroll container entirely. Its static
   position comes from inside the table, which on a phone is far wider than the
   viewport, so a 1px clipped label sitting in a `<th>` at x=500 stretched the
   DOCUMENT to 500px and made the whole page scroll sideways. Invisible, and the
   only symptom was that the admin dashboard slid horizontally at 375/390/430.

   (2) `contain:paint`. With the labels contained, a wide table still widened
   the page: `overflow-x:auto` alone scrolls the wrapper correctly while the
   document still reports the table full width and scrolls with it — an H1 on
   /admin/partners moved 428px in a 375px viewport, every ancestor measuring
   <= 375 while documentElement.scrollWidth measured 803. `contain:paint` states
   what is already true of a scroll container — it does not paint outside its
   own box — and is the narrowest fix that does not need overflow:hidden on an
   ancestor, which would break sticky positioning.

   Neither subsumes the other: (1) was found by the automated browser UAT on the
   remediation branch, (2) by the responsive sweep on the B2 branch, and each was
   written without sight of the other. The defect predates B2 — /admin/partners
   measures the same 803 on the pre-B2 commit — and reached nine table-heavy
   admin, partner and employer screens at 375, 390 and 430, and /admin/partners
   as far up as 1024. */
.table-wrap {
  position:relative;
  overflow-x:auto; -webkit-overflow-scrolling:touch;
  contain:paint;
  border-radius:var(--r-card); background:var(--surface); box-shadow:var(--lift-1);
}
.table { width:100%; border-collapse:collapse; font-size:var(--text-sm); }
.table th,.table td { padding:var(--space-3) var(--space-4); text-align:left; }
.table thead th {
  background:var(--gray-50); border-bottom:1px solid var(--border);
  font-weight:var(--weight-semibold); color:var(--gray-700);
  font-size:var(--text-xs); text-transform:uppercase; letter-spacing:var(--tracking-wide);
  white-space:nowrap;
}
.table tbody tr + tr { border-top:1px solid var(--border); }
.table tbody tr:hover { background:var(--gray-25); }

/* Definition list used for structured detail (job/company facts) */
.facts { display:grid; gap:var(--space-4); grid-template-columns:1fr; }
@media (min-width:640px) { .facts { grid-template-columns:repeat(2,1fr); } }
.fact__label { font-size:var(--text-xs); text-transform:uppercase; letter-spacing:var(--tracking-wide); color:var(--text-subtle); font-weight:var(--weight-semibold); }
.fact__value { font-size:var(--text-base); margin-top:var(--space-1); }

/* ── 11. Navigation ────────────────────────────────────────────────────── */
.topbar {
  background:var(--surface); border-bottom:1px solid var(--border);
  position:sticky; top:0; z-index:var(--z-sticky);
}
.topbar__inner {
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--space-4); min-height:4rem;
}

/* ── Brand ──────────────────────────────────────────────────────────────
   The supplied logo, sized by HEIGHT only. Width is `auto`, so the artwork's
   own 703:142 ratio decides it and the logo can never be stretched by a
   container. The <img> carries width/height attributes, so the browser
   reserves this box before the file arrives and the header does not jump.

   Sized in rem rather than px so it tracks the user's font size — a logo that
   stays 34px while everything else doubles is a logo that vanishes. */
.brand { display:inline-flex; align-items:center; text-decoration:none; color:var(--text); flex-shrink:0; }
.brand__logo { display:block; height:2rem; width:auto; }

/* Small screens get a slightly smaller mark, but not a tiny one: below about
   1.6rem the teal trend line stops reading as a line. */
@media (max-width:479px) { .brand__logo { height:1.75rem; } }
@media (min-width:1024px) { .brand__logo { height:2.25rem; } }

/* The footer logo is secondary to the footer's content, so it sits one step
   down from the header rather than matching it. */
.footer .brand__logo { height:1.75rem; }

.nav { display:none; }
.nav__list { display:flex; align-items:center; gap:var(--space-1); }
.nav__link {
  display:block; padding:var(--space-2) var(--space-3);
  font-size:var(--text-sm); font-weight:var(--weight-medium);
  color:var(--gray-700); text-decoration:none; border-radius:var(--radius);
}
.nav__link:hover { background:var(--gray-100); color:var(--gray-900); }
.nav__link[aria-current="page"] { color:var(--primary-700); background:var(--primary-50); }

.topbar__actions { display:flex; align-items:center; gap:var(--space-2); }
.topbar__desktop-actions { display:none; }

.nav-toggle {
  display:inline-flex; align-items:center; justify-content:center;
  width:2.75rem; height:2.75rem;
  background:transparent; border:1px solid var(--border-control);
  border-radius:var(--radius); cursor:pointer; color:var(--gray-700);
}
.nav-toggle:hover { background:var(--gray-100); }

/* Mobile drawer */
.mobile-nav {
  display:none;
  border-top:1px solid var(--border);
  background:var(--surface);
  padding:var(--space-4) 0 var(--space-6);
}
.mobile-nav[data-open="true"] { display:block; }
.mobile-nav__list { display:flex; flex-direction:column; gap:var(--space-1); }
.mobile-nav__link {
  display:block; padding:var(--space-3) var(--space-4);
  font-size:var(--text-base); font-weight:var(--weight-medium);
  color:var(--gray-800); text-decoration:none; border-radius:var(--radius);
  min-height:2.75rem;
}
.mobile-nav__link:hover { background:var(--gray-100); }
.mobile-nav__link[aria-current="page"] { color:var(--primary-700); background:var(--primary-50); }
.mobile-nav__actions {
  display:flex; flex-direction:column; gap:var(--space-3);
  margin-top:var(--space-4); padding-top:var(--space-4);
  border-top:1px solid var(--border);
}

@media (min-width:1024px) {
  .nav { display:block; }
  .topbar__desktop-actions { display:flex; align-items:center; gap:var(--space-3); }
  .nav-toggle { display:none; }
  .mobile-nav,.mobile-nav[data-open="true"] { display:none; }
}

/* ── 12. Search & filters ──────────────────────────────────────────────── */
.searchbar { display:grid; gap:var(--space-3); grid-template-columns:1fr; }
@media (min-width:768px) {
  .searchbar { grid-template-columns:2fr 1.5fr auto; align-items:end; }
}
.searchbar--hero { padding:var(--space-5); background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg); box-shadow:var(--shadow-sm); }

.filters { display:flex; flex-direction:column; gap:var(--space-5); }
.filter-group { border-bottom:1px solid var(--border); padding-bottom:var(--space-5); }
.filter-group:last-child { border-bottom:0; padding-bottom:0; }
.filter-group__title {
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  text-transform:uppercase; letter-spacing:var(--tracking-wide);
  color:var(--text-subtle); margin-bottom:var(--space-3);
}
.filter-options { display:flex; flex-direction:column; gap:var(--space-2); }

/* Filters collapse into a disclosure below lg */
.filters-panel { display:none; }
.filters-panel[data-open="true"] { display:block; }
.filters-toggle { display:flex; }
@media (min-width:1024px) {
  .filters-panel,.filters-panel[data-open="true"] { display:block; }
  .filters-toggle { display:none; }
}

.result-count { font-size:var(--text-sm); color:var(--text-muted); }
.chip {
  display:inline-flex; align-items:center; gap:var(--space-2);
  padding:var(--space-1) var(--space-3);
  background:var(--primary-50); color:var(--primary-800);
  border:1px solid var(--primary-100); border-radius:var(--radius-full);
  font-size:var(--text-xs); text-decoration:none;
}
.chip:hover { background:var(--primary-100); color:var(--primary-900); }

/* ── 13. Alerts & empty states ─────────────────────────────────────────── */
.alert {
  padding:var(--space-4);
  border:1px solid var(--border); border-left-width:3px;
  border-radius:var(--radius); font-size:var(--text-sm);
}
.alert + .alert { margin-top:var(--space-3); }
.alert p { margin:0; }
.alert p + p { margin-top:var(--space-2); }
.alert--success { background:var(--success-50); border-color:var(--success-200); border-left-color:var(--success-600); color:var(--success-800); }
.alert--error   { background:var(--danger-50);  border-color:var(--danger-200);  border-left-color:var(--danger-600);  color:var(--danger-800); }
.alert--warning { background:var(--warning-50); border-color:var(--warning-200); border-left-color:var(--warning-600); color:var(--warning-800); }
.alert--info    { background:var(--info-50);    border-color:var(--info-200);    border-left-color:var(--info-600);    color:var(--info-800); }

.empty {
  text-align:center; padding:var(--space-12) var(--space-6);
  background:var(--surface); border:1px dashed var(--border-strong); border-radius:var(--radius-lg);
}
.empty__title { font-size:var(--text-lg); font-weight:var(--weight-semibold); margin-bottom:var(--space-2); }
.empty__text  { color:var(--text-muted); max-width:32rem; margin-inline:auto; margin-bottom:var(--space-5); }

.state { text-align:center; padding-block:var(--space-20); }
.state__code { font-size:var(--text-5xl); font-weight:var(--weight-bold); color:var(--gray-300); line-height:1; }
.state__title { font-size:var(--text-2xl); font-weight:var(--weight-bold); margin-top:var(--space-4); }
.state__msg { color:var(--text-muted); margin:var(--space-3) auto var(--space-6); max-width:32rem; }

/* ── 14. Pagination & breadcrumbs ──────────────────────────────────────── */
.pagination { display:flex; align-items:center; justify-content:center; gap:var(--space-1); flex-wrap:wrap; }
.pagination__link {
  display:inline-flex; align-items:center; justify-content:center;
  min-width:2.5rem; min-height:2.5rem; padding:0 var(--space-2);
  border:1px solid var(--border); border-radius:var(--radius);
  background:var(--surface); color:var(--gray-700);
  font-size:var(--text-sm); text-decoration:none;
}
.pagination__link:hover { background:var(--gray-50); border-color:var(--border-strong); }
.pagination__link[aria-current="page"] { background:var(--primary-700); border-color:var(--primary-700); color:#fff; font-weight:var(--weight-semibold); }
.pagination__ellipsis { padding:0 var(--space-2); color:var(--text-subtle); }

.breadcrumb { font-size:var(--text-sm); }
.breadcrumb__list { display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2); }
.breadcrumb__sep { color:var(--text-subtle); }
.breadcrumb a { color:var(--text-muted); text-decoration:none; }
.breadcrumb a:hover { color:var(--primary-700); text-decoration:underline; }
.breadcrumb [aria-current="page"] { color:var(--text); font-weight:var(--weight-medium); }

/* ── 17. Skeleton ──────────────────────────────────────────────────────── */
.skeleton { background:var(--gray-200); border-radius:var(--radius); animation:pulse 1.6s var(--ease) infinite; }
.skeleton--text { height:.875rem; margin-bottom:var(--space-2); }
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.55; } }

/* ── 18. Footer ────────────────────────────────────────────────────────── */
.footer { background:var(--surface); border-top:1px solid var(--border); padding-block:var(--space-10) var(--space-8); margin-top:auto; }
.footer__grid { display:grid; gap:var(--space-8); grid-template-columns:1fr; }
@media (min-width:640px) { .footer__grid { grid-template-columns:repeat(2,1fr); } }
@media (min-width:1024px) { .footer__grid { grid-template-columns:2fr 1fr 1fr 1fr; } }
.footer__title { font-size:var(--text-xs); font-weight:var(--weight-semibold); text-transform:uppercase; letter-spacing:var(--tracking-wide); color:var(--text-subtle); margin-bottom:var(--space-3); }
.footer__list { display:flex; flex-direction:column; gap:var(--space-2); }
.footer__list a { font-size:var(--text-sm); color:var(--text-muted); text-decoration:none; }
.footer__list a:hover { color:var(--primary-700); text-decoration:underline; }
.footer__bottom {
  margin-top:var(--space-8); padding-top:var(--space-5); border-top:1px solid var(--border);
  display:flex; flex-wrap:wrap; gap:var(--space-3); justify-content:space-between;
  font-size:var(--text-xs); color:var(--text-subtle);
}
.footer__disclaimer { max-width:36rem; }

/* ── 19. Utilities ─────────────────────────────────────────────────────── */
.mt-0{margin-top:0}.mt-2{margin-top:var(--space-2)}.mt-3{margin-top:var(--space-3)}
.mt-4{margin-top:var(--space-4)}.mt-6{margin-top:var(--space-6)}.mt-8{margin-top:var(--space-8)}
.mb-2{margin-bottom:var(--space-2)}.mb-3{margin-bottom:var(--space-3)}
.mb-4{margin-bottom:var(--space-4)}.mb-6{margin-bottom:var(--space-6)}.mb-8{margin-bottom:var(--space-8)}
.text-center{text-align:center}
.flex-1{flex:1}
.hidden{display:none}
.nowrap{white-space:nowrap}
.break{overflow-wrap:anywhere}

/* ── 20. Touch refinements ─────────────────────────────────────────────── */
/* .btn--sm stays compact on desktop, but every control must remain a 44px
   target on touch. Verified: this was the only element under 44px. */
@media (max-width:640px) {
  .btn--sm { min-height:2.75rem; padding-inline:var(--space-4); }
}

/* ══════════════════════════════════════════════════════════════════════════
   21. CANDIDATE PORTAL — Phase F
   Every rule below uses the Phase D tokens. No new colours, no new type sizes.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Account layout ───────────────────────────────────────────────────── */
/* Both grid children need min-width:0.
   A grid item defaults to min-width:auto, so it cannot shrink below its
   content's min-content size. Below 1024px .side-nav becomes a horizontal
   scrolling row; without min-width:0 here its full intrinsic width (~1700px)
   propagates up through the grid and scrolls the whole PAGE sideways instead
   of scrolling inside the nav. The overflow-x on .side-nav alone does nothing,
   because the parent is still being sized by its content. */
.account-layout__side,
.account-layout__main { min-width:0; }
.account-head { padding-bottom:var(--space-2); }

/* Below 1024px .grid--sidebar is a single column, so the nav stacks above the
   content and scrolls horizontally rather than becoming a wall of links. */
.side-nav { display:flex; flex-direction:column; gap:var(--space-6); }
.side-nav__title {
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  text-transform:uppercase; letter-spacing:var(--tracking-wide);
  color:var(--text-subtle); margin-bottom:var(--space-2);
}
.side-nav__list { display:flex; flex-direction:column; gap:2px; }
.side-nav__link {
  /* icon, label, then the count pushed right — space-between would spread all
     three evenly now that there is an icon. */
  display:flex; align-items:center; justify-content:flex-start; gap:var(--space-3);
  padding:var(--space-2) var(--space-3);
  border-radius:var(--radius); border-left:2px solid transparent;
  font-size:var(--text-sm); color:var(--text-muted); text-decoration:none;
  min-height:2.5rem;
}
.side-nav__link:hover { background:var(--gray-50); color:var(--primary-700); }
.side-nav__link.is-active {
  background:var(--primary-50); color:var(--primary-800);
  border-left-color:var(--primary-600); font-weight:var(--weight-semibold);
}
.side-nav__count {
  margin-left:auto;
  display:inline-flex; align-items:center; justify-content:center;
  min-width:1.4rem; height:1.4rem; padding:0 var(--space-1);
  border-radius:var(--radius-full); background:var(--warning-600); color:var(--gray-0);
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
}

@media (max-width:1023px) {
  .side-nav { flex-direction:row; gap:var(--space-5); overflow-x:auto; padding-bottom:var(--space-2); }
  .side-nav__title { display:none; }
  .side-nav__list { flex-direction:row; gap:var(--space-1); }
  /* 44px minimum: this is a horizontally scrolling row of small targets, which
     is the hardest thing on the page to hit accurately on a phone. The desktop
     rule's 2.5rem is fine for a pointer and too small for a thumb. */
  .side-nav__link {
    white-space:nowrap; border-left:0; border-bottom:2px solid transparent;
    min-height:2.75rem;
  }
  .side-nav__link.is-active { border-left:0; border-bottom-color:var(--primary-600); }
}

/* ── Completeness meter ───────────────────────────────────────────────── */
.meter { height:.5rem; background:var(--gray-200); border-radius:var(--radius-full); overflow:hidden; }
.meter__fill { height:100%; background:var(--primary-600); border-radius:var(--radius-full); transition:width var(--duration-slow) var(--ease); }

.checklist { display:flex; flex-direction:column; gap:var(--space-3); }
.checklist__item { display:flex; flex-direction:column; gap:2px; font-size:var(--text-sm); }
.checklist__item--done { color:var(--text-subtle); }
.checklist__item--done::before { content:"✓ "; color:var(--success-600); font-weight:var(--weight-bold); }
.checklist__link { font-weight:var(--weight-semibold); }

.stat--link { text-decoration:none; color:inherit; }
.stat--link:hover { border-color:var(--primary-200); background:var(--primary-50); }

/* ── Evidence chips ───────────────────────────────────────────────────────
   NOT a boolean tick. Each chip is one item of evidence, and several coexist.
   `--independent` marks evidence that did not originate with the candidate;
   only those may use the words "verified" or "assessed".                    */
.evidence { display:flex; flex-wrap:wrap; gap:var(--space-2); }
.evidence__chip {
  display:inline-flex; align-items:baseline; gap:var(--space-2);
  padding:var(--space-1) var(--space-3);
  border:1px solid var(--border); border-radius:var(--radius-full);
  background:var(--gray-50); font-size:var(--text-xs);
}
.evidence__chip--independent {
  background:var(--success-50); border-color:var(--success-200); color:var(--success-800);
}
.evidence__label { font-weight:var(--weight-semibold); }
.evidence__detail { color:var(--text-muted); }
.evidence__chip--independent .evidence__detail { color:var(--success-800); }

/* ── Skill rows and the add form ──────────────────────────────────────── */
.skill-row {
  padding:var(--space-5);
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg);
}
.skill-row--unconfirmed { border-left:3px solid var(--warning-600); }
.skill-row__head { display:flex; justify-content:space-between; gap:var(--space-4); flex-wrap:wrap; }
.skill-row__name {
  font-size:var(--text-base); font-weight:var(--weight-semibold);
  display:flex; align-items:center; gap:var(--space-2); flex-wrap:wrap;
}
.skill-row__evidence { margin-top:var(--space-3); }
.skill-row__edit { margin-top:var(--space-4); }
.skill-row__edit summary { cursor:pointer; color:var(--primary-600); }

.skill-add {
  padding:var(--space-4);
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius);
}
.skill-add__head { margin-bottom:var(--space-3); }
.skill-add__name { font-weight:var(--weight-semibold); }
.skill-add__controls {
  display:flex; flex-wrap:wrap; gap:var(--space-3); align-items:flex-end;
}
.skill-add__controls .field { max-width:9rem; }
.skill-add__controls .check { align-self:center; }

.review-item {
  padding:var(--space-4);
  background:var(--warning-50); border:1px solid var(--warning-200); border-radius:var(--radius);
}

.browse-group { border-top:1px solid var(--border); padding:var(--space-3) 0; }
.browse-group__title { cursor:pointer; font-weight:var(--weight-semibold); font-size:var(--text-sm); }

/* ── Radio cards (privacy options, CV choice) ─────────────────────────── */
.radio-card {
  display:flex; gap:var(--space-3); align-items:flex-start;
  padding:var(--space-4);
  border:1px solid var(--border); border-radius:var(--radius); background:var(--surface);
}
.radio-card:focus-within { border-color:var(--primary-500); box-shadow:0 0 0 3px var(--primary-50); }
.radio-card label { display:flex; flex-direction:column; gap:var(--space-1); cursor:pointer; }
.radio-card__label { font-weight:var(--weight-semibold); font-size:var(--text-sm); display:flex; gap:var(--space-2); align-items:center; flex-wrap:wrap; }
.radio-card__detail { font-size:var(--text-xs); color:var(--text-muted); line-height:var(--leading-relaxed); }

.select--multi { min-height:12rem; padding:var(--space-2); }

/* ── Application timeline ─────────────────────────────────────────────── */
.timeline { list-style:none; padding-left:var(--space-6); position:relative; }
.timeline::before {
  content:""; position:absolute; left:.3125rem; top:.5rem; bottom:.5rem;
  width:2px; background:var(--border);
}
.timeline__item { position:relative; padding-bottom:var(--space-6); }
.timeline__item:last-child { padding-bottom:0; }
.timeline__dot {
  position:absolute; left:calc(-1 * var(--space-6) + .0625rem); top:.375rem;
  width:.75rem; height:.75rem; border-radius:var(--radius-full);
  background:var(--primary-600); border:2px solid var(--surface);
}
.timeline__label { font-weight:var(--weight-semibold); font-size:var(--text-sm); }

.card--unread { border-left:3px solid var(--info-600); }

/* ── Touch targets in the portal ──────────────────────────────────────── */
@media (max-width:640px) {
  .skill-add__controls { flex-direction:column; align-items:stretch; }
  .skill-add__controls .field { max-width:none; }
  .skill-add__controls .btn { width:100%; }
  .skill-row__head .btn { min-height:2.75rem; }
}

/* ══════════════════════════════════════════════════════════════════════════
   22. LAYOUT UTILITIES — the CSP-safe replacement for inline style attributes

   Content-Security-Policy is `style-src 'self'` with no 'unsafe-inline', which
   means the browser BLOCKS every `style=""` attribute. Phase D stated the goal
   plainly — "no page carries an arbitrary hardcoded value" — but Phase E and F
   reintroduced 71 inline declarations across 24 templates, and every one of
   them was silently discarded by the browser.

   The fix is these classes, not 'unsafe-inline'. Weakening the policy to make
   the markup work would trade a real security control for a convenience, and
   an inline-style channel is exactly what CSS-injection exfiltration uses.
   ══════════════════════════════════════════════════════════════════════════ */

.m-0        { margin:0; }
.mx-auto    { margin-inline:auto; }
.min-w-0    { min-width:0; }
.strong     { font-weight:var(--weight-semibold); }
.weight-normal { font-weight:var(--weight-normal); }
.inline-flex   { display:inline-flex; }
.justify-center{ justify-content:center; }
.items-start   { align-items:flex-start; }
.items-end     { align-items:flex-end; }
.border-t      { border-top:1px solid var(--border); }
.color-inherit { color:inherit; }
.plain-link    { color:inherit; text-decoration:none; }

/* Row variants. `.row` already sets display:flex, align-items:center,
   gap:--space-3 and flex-wrap:wrap; these only adjust the gap or alignment. */
.row--gap-4 { gap:var(--space-4); }
.row--gap-5 { gap:var(--space-5); }
.row--endwrap { align-items:flex-end; }

.order-1 { order:1; }
.order-2 { order:2; }

.mw-8  { max-width:8rem; }
.mw-12 { max-width:12rem; }
.mw-14 { max-width:14rem; }
.mw-14-min { min-width:14rem; }
.mw-38 { max-width:38rem; }
.mw-44 { max-width:44rem; }
.mw-46 { max-width:46rem; }

.hero-copy { max-width:46rem; padding-block:var(--space-6) var(--space-8); }
.input--otp { letter-spacing:.4em; font-size:var(--text-lg); }

/* Completeness meter fill.
   The width is data-driven, and a `style="width:N%"` attribute would be blocked
   by the same policy. Percentages are rounded to the nearest 5 and matched by
   attribute selector, so the bar is pure CSS: no inline style, and no
   dependence on JavaScript to render a value the page already states in text. */
.meter__fill[data-pct="0"]   { width:0; }
.meter__fill[data-pct="5"]   { width:5%; }
.meter__fill[data-pct="10"]  { width:10%; }
.meter__fill[data-pct="15"]  { width:15%; }
.meter__fill[data-pct="20"]  { width:20%; }
.meter__fill[data-pct="25"]  { width:25%; }
.meter__fill[data-pct="30"]  { width:30%; }
.meter__fill[data-pct="35"]  { width:35%; }
.meter__fill[data-pct="40"]  { width:40%; }
.meter__fill[data-pct="45"]  { width:45%; }
.meter__fill[data-pct="50"]  { width:50%; }
.meter__fill[data-pct="55"]  { width:55%; }
.meter__fill[data-pct="60"]  { width:60%; }
.meter__fill[data-pct="65"]  { width:65%; }
.meter__fill[data-pct="70"]  { width:70%; }
.meter__fill[data-pct="75"]  { width:75%; }
.meter__fill[data-pct="80"]  { width:80%; }
.meter__fill[data-pct="85"]  { width:85%; }
.meter__fill[data-pct="90"]  { width:90%; }
.meter__fill[data-pct="95"]  { width:95%; }
.meter__fill[data-pct="100"] { width:100%; }

/* ══════════════════════════════════════════════════════════════════════════
   23. PHASE G — EMPLOYER AND ADMIN COMPONENTS

   Same rule as section 22: no inline style attribute anywhere, because the
   Content-Security-Policy is `style-src 'self'` with no 'unsafe-inline'.
   ══════════════════════════════════════════════════════════════════════════ */

/* Company / portal identity block above the sidebar navigation */
.org-badge {
  display:flex; flex-direction:column; gap:var(--space-2);
  padding:var(--space-4);
  background:var(--surface); border:1px solid var(--border);
  border-radius:var(--radius-lg); margin-bottom:var(--space-5);
}
.org-badge__name { font-weight:var(--weight-semibold); line-height:var(--leading-snug); }

/* Horizontal chip row used for pipeline stages and quick filters.
   `.chip` is defined in section 12; only the active state is new. */
.filter-chips { display:flex; flex-wrap:wrap; gap:var(--space-2); }
.chip.is-active {
  background:var(--primary-600); color:#fff; border-color:var(--primary-600);
  font-weight:var(--weight-medium);
}
.chip.is-active:hover { background:var(--primary-700); color:#fff; }

/* Admin list filters: a responsive row of fields ending in a submit button
   that aligns to the bottom of the row rather than the top. */
.filter-bar {
  display:flex; flex-wrap:wrap; gap:var(--space-4); align-items:flex-end;
  padding:var(--space-4);
  background:var(--surface); border:1px solid var(--border); border-radius:var(--radius-lg);
}
.filter-bar .field { flex:1 1 12rem; min-width:0; }
.filter-bar .field--action { flex:0 0 auto; }
.filter-bar .field--check { flex:0 0 auto; align-self:center; }
@media (max-width:640px) {
  /* Stacked, and each field sized to its content. Without resetting flex-grow
     the 1 1 12rem above stretches every field to an equal share of the column
     height, which reads as unexplained blank space between the controls. */
  .filter-bar { flex-direction:column; align-items:stretch; }
  .filter-bar .field { flex:0 0 auto; }
  .filter-bar .btn { width:100%; }
}

/* A short form that reads as one line — a single control and its button, inside
   a card that already has its own surface. Same stacking behaviour as
   `.filter-bar` on narrow screens, without the second panel background. */
.form-inline { display:flex; flex-wrap:wrap; gap:var(--space-4); align-items:flex-end; }
.form-inline .field { flex:0 1 18rem; min-width:0; }
.form-inline .field--action { flex:0 0 auto; }
@media (max-width:640px) {
  .form-inline { flex-direction:column; align-items:stretch; }
  .form-inline .btn { width:100%; }
}

/* Structured job requirement rows (employer job builder) */
.req-row {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-3);
  padding:var(--space-3) 0;
}
.req-row + .req-row { border-top:1px solid var(--border); }
.req-row__name { flex:1 1 12rem; min-width:0; font-weight:var(--weight-medium); }

/* A smaller stat number, for tiles that sit four-across in a dense console */
.stat__value--sm { font-size:var(--text-xl); }

/* Four-across metric grid. Collapses to two, then one. */
.grid--4 { grid-template-columns:repeat(auto-fit,minmax(10rem,1fr)); }

/* Machine-readable values: action names, slugs, JSON diffs */
.code-inline {
  font-family:var(--font-mono);
  font-size:.9em; padding:.1em .35em;
  background:var(--gray-100); border-radius:var(--radius-sm);
  overflow-wrap:anywhere;
}
.code-block {
  font-family:var(--font-mono);
  font-size:var(--text-xs); line-height:var(--leading-relaxed);
  padding:var(--space-3); margin:0;
  background:var(--gray-50); border:1px solid var(--border); border-radius:var(--radius);
  overflow-x:auto; white-space:pre-wrap; overflow-wrap:anywhere;
}

/* Plain prose list, for the "what we do / do not do" panels */
.list { padding-left:var(--space-5); color:var(--text-muted); }
.list li + li { margin-top:var(--space-2); }

/* Permission-matrix ticks. Colour is never the only signal: the cell carries
   an aria-label, and an em dash marks the negative case. */
.tick { color:var(--success-700); font-weight:var(--weight-semibold); }
.table th.is-active,.table td.is-active { background:var(--primary-50); }

/* Widths used by inline search and numeric controls */
.mw-10 { max-width:10rem; }
.mw-20 { max-width:20rem; }
.ml-2  { margin-left:var(--space-2); }

/* ══════════════════════════════════════════════════════════════════════════
   24. PHASE I — MATCH EXPLANATION

   A list of requirements, each with its state, what the profile says, and why.
   Deliberately NOT a chart, a gauge or a progress bar: those all imply a single
   quantity, and the whole point of this phase is that there isn't one.
   ══════════════════════════════════════════════════════════════════════════ */

.match-list { list-style:none; padding:0; margin:0; display:flex; flex-direction:column; gap:var(--space-3); }

.match-row {
  display:flex; gap:var(--space-3); align-items:flex-start;
  padding-bottom:var(--space-3);
}
.match-row + .match-row { border-top:1px solid var(--border); padding-top:var(--space-3); }
.match-row:last-child { padding-bottom:0; }

/* The state badge keeps its width so the requirement names line up and the
   column can be read down without tracking left and right. */
.match-row > .badge:first-child { flex:0 0 6.5rem; justify-content:center; }

.match-row__reason {
  display:block; margin-top:var(--space-1);
  font-size:var(--text-sm); color:var(--text-muted); line-height:var(--leading-normal);
}

@media (max-width:640px) {
  /* Stacked, with the badge above the requirement: at 375px a fixed 6.5rem
     column leaves too little room for the reason, which is the useful part. */
  .match-row { flex-direction:column; gap:var(--space-2); }
  .match-row > .badge:first-child { flex:0 0 auto; align-self:flex-start; }
}

/* ══════════════════════════════════════════════════════════════════════════
   25. ADMIN PORTAL SHELL

   The admin console shares the V2 design system but runs denser: an
   administrator is reading lists, not being introduced to the product.
   ══════════════════════════════════════════════════════════════════════════ */

.admin-crumb { margin-bottom:var(--space-4); }
.admin-crumb__list {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2);
  list-style:none; padding:0; margin:0;
  font-size:var(--text-sm); color:var(--text-muted);
}
.admin-crumb__list li + li::before {
  content:'/'; margin-right:var(--space-2); color:var(--text-subtle);
}
.admin-crumb__list a { color:var(--text-muted); }
.admin-crumb__list a:hover { color:var(--primary-700); }
.admin-crumb__list [aria-current="page"] { color:var(--text); font-weight:var(--weight-medium); }

/* Portal switching and the way out, pinned below the navigation. */
.side-nav__footer {
  margin-top:var(--space-6); padding-top:var(--space-4);
  border-top:1px solid var(--border);
}

/* The topbar portal switcher. The active portal is marked so somebody with
   several does not lose track of which application they are inside. */
.topbar__desktop-actions .btn.is-active {
  background:var(--primary-50); color:var(--primary-800); border-color:var(--primary-100);
}

.mobile-nav__label {
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  text-transform:uppercase; letter-spacing:var(--tracking-wide);
  color:var(--text-subtle); margin:var(--space-4) 0 var(--space-2);
}

@media (max-width:1023px) {
  /* Below the sidebar breakpoint the switcher would be stranded at the very
     bottom of a long scroll, so it sits inline with the rest of the nav. */
  .side-nav__footer { margin-top:var(--space-4); }
}

/* Secondary text inside a table cell — an email under a name, a slug under a
   node, a headline under a candidate. Without this it runs on from the primary
   text and the two read as one sentence. */
.table th .tiny.subtle,
.table td .tiny.subtle { display:block; margin-top:2px; font-weight:var(--weight-normal); }

/* ═══════════════════════════════════════════════════════════════════════
   UI/UX excellence pass — 3 September 2026

   Additive only. No existing token, component or palette value was changed
   by that pass.

   ── Superseded by B2, 4 September 2026 ──────────────────────────────────
   This pass originally reasoned that "the enterprise navy already in use IS
   the interface language, and the brand blue belongs to the logo rather than
   to chrome". The B2 design review overturned that, with product-owner
   approval: the result on screen was a #173d70 primary button sitting eight
   pixels from a #2563EB wordmark in the same header, which read as unfinished
   rather than as restraint.

   The brand ramp is now defined once, at the top of this file, around the
   three approved values. The teal tokens that used to live here have moved
   there too, so there is one definition rather than two.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Attention region ──────────────────────────────────────────────────
   Renders only when something genuinely needs the user; the partial
   returns early otherwise. Deliberately the loudest thing on the page —
   it is competing with six stat cards that were previously louder. */
.attention { margin-bottom: var(--space-8); }

.attention__heading {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  letter-spacing: .04em; text-transform: uppercase;
  color: var(--gray-600); margin: 0 0 var(--space-3);
}

.attention__count {
  display: inline-grid; place-items: center;
  min-width: 1.25rem; height: 1.25rem; padding: 0 .35rem;
  border-radius: var(--radius-full);
  background: var(--accent-500); color: #fff;
  font-size: .6875rem; font-weight: var(--weight-bold); letter-spacing: 0;
}

.attention__list { list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-2); }

.attention__item {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--gray-0);
  border: 1px solid var(--border);
  /* The left edge carries the tone. A 3px rule reads at a glance and does
     not depend on colour alone — the label states the situation in words. */
  border-left: 3px solid var(--primary-600);
  border-radius: var(--radius);
}
.attention__item--warning { border-left-color: var(--accent-500); }

.attention__body { flex: 1 1 auto; min-width: 0; }
.attention__label { margin: 0; font-weight: var(--weight-semibold); color: var(--text); }
.attention__detail { margin: .15rem 0 0; font-size: var(--text-sm); color: var(--gray-600); }
.attention__action { flex: 0 0 auto; }

@media (max-width: 599px) {
  .attention__item { flex-direction: column; align-items: stretch; gap: var(--space-3); }
  .attention__action { width: 100%; text-align: center; }
}

/* ── Status vocabulary ─────────────────────────────────────────────────
   One green was carrying five different claims: Company Verified, Approved
   Partner, confirmed skill, verified certification and active
   representation. They rest on different evidence and must not look
   identical. Every badge pairs its colour with a word, so none of this
   depends on colour perception. */
.badge--verified { background: var(--teal-50); color: var(--teal-800); border-color: var(--teal-200); }
.badge--approved { background: var(--primary-50); color: var(--primary-800); border-color: var(--primary-200); }
.badge--active   { background: var(--success-50); color: var(--success-800); border-color: var(--success-200); }
.badge--expired,
.badge--revoked  { background: var(--gray-100); color: var(--gray-600); border-color: var(--gray-200); }
.badge--suspended{ background: var(--danger-50); color: var(--danger-800); border-color: var(--danger-200); }

/* ── Motion ────────────────────────────────────────────────────────────
   Functional only, on the existing 120/180ms tokens. The stylesheet had
   four transitions in total, so this is filling an absence rather than
   decorating. The reduced-motion block above already neutralises all of
   it — these additions inherit that and need no separate guard. */
.btn { transition: background var(--duration-fast) var(--ease),
                   border-color var(--duration-fast) var(--ease),
                   color var(--duration-fast) var(--ease); }

.card--link, .job-card {
  transition: border-color var(--duration) var(--ease),
              box-shadow var(--duration) var(--ease);
}
.card--link:hover, .job-card:hover { border-color: var(--primary-200); }

.side-nav__link { transition: background var(--duration-fast) var(--ease),
                              color var(--duration-fast) var(--ease); }

/* A disclosure that snaps open loses the reader's place. */
details > summary { transition: color var(--duration-fast) var(--ease); cursor: pointer; }

/* Focus must never be animated away or softened — keyboard users need it
   immediate and unambiguous. */
:focus-visible { transition: none !important; }

/* ── Filter chips ──────────────────────────────────────────────────────
   What is currently narrowing the list, and how to remove it, without
   re-reading every control in the sidebar. */
.filter-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
  margin-bottom: var(--space-4); }
.filter-chips__label { font-size: var(--text-sm); color: var(--gray-600); }

.filter-chip {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .25rem .6rem;
  background: var(--primary-50); color: var(--primary-800);
  border: 1px solid var(--primary-200); border-radius: var(--radius-full);
  font-size: var(--text-sm); text-decoration: none;
  transition: background var(--duration-fast) var(--ease);
}
.filter-chip:hover { background: var(--primary-100); }
.filter-chip__x { font-weight: var(--weight-bold); line-height: 1; opacity: .7; }

/* A zero on an existing `.stat` tile. The tile stays — the figure is still
   true and the link still works — but it stops competing with the tiles that
   have something in them. Applied by the view, so nothing is hidden and the
   number remains readable and selectable. */
.stat--zero { background: var(--gray-25); }
.stat--zero .stat__value { color: var(--text-quiet); }

/* ── Oracle taxonomy picker ────────────────────────────────────────────
   One component, reused by candidate skills, the job editor, job search,
   talent search and partner specializations. Native radio/checkbox inside a
   fieldset, so it is keyboard and screen-reader operable with no ARIA. */
.tax-picker { border: 0; margin: 0; padding: 0; min-width: 0; }
.tax-picker__legend { font-weight: var(--weight-semibold); padding: 0; margin-bottom: var(--space-1); }
.tax-picker__hint { font-size: var(--text-sm); color: var(--gray-600); margin-bottom: var(--space-4); }
.tax-picker__search { margin-bottom: var(--space-4); }
.tax-picker__count { margin-top: var(--space-1); min-height: 1.1em; }
.tax-picker__empty { font-size: var(--text-sm); color: var(--gray-600); padding: var(--space-4); }

.tax-picker__families {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--gray-0);
  /* Bounded height with its own scroll, so a 129-option list cannot push the
     submit button off the page. Tall enough that browsing is comfortable. */
  max-height: 26rem; overflow-y: auto;
}

.tax-family + .tax-family { border-top: 1px solid var(--border); }
.tax-family__summary {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  cursor: pointer; font-weight: var(--weight-medium);
  transition: background var(--duration-fast) var(--ease);
}
.tax-family__summary:hover { background: var(--gray-50); }
.tax-family[open] > .tax-family__summary { background: var(--gray-50); border-bottom: 1px solid var(--border); }
.tax-family__count {
  background: var(--gray-100); border-radius: var(--radius-full);
  padding: .05rem .5rem; font-variant-numeric: tabular-nums;
}
.tax-family__options { padding: var(--space-2); }

.tax-option {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm); cursor: pointer;
  transition: background var(--duration-fast) var(--ease);
}
.tax-option:hover { background: var(--primary-50); }
/* The whole row highlights on selection, so the state is visible without
   relying on the control glyph alone. */
.tax-option:has(.tax-option__input:checked) { background: var(--primary-50); box-shadow: inset 2px 0 0 var(--primary-600); }
.tax-option__input { margin-top: .3rem; flex: 0 0 auto; }
.tax-option__body { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); min-width: 0; }
.tax-option__name { font-weight: var(--weight-medium); }
/* The path above the node. Several nodes are only distinguishable by it. */
.tax-option__context { font-size: var(--text-xs); color: var(--gray-500); }
/* Layout only. Colour comes from the .tier class beside it, so each of the
   six taxonomy levels reads differently in the picker. */
.tax-option__level { flex:0 0 auto; }
.tax-option--disabled { opacity: .55; cursor: not-allowed; }

/* Selected technologies, shown as removable chips beside the picker. */
.tax-selected { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }

@media (max-width: 599px) {
  /* A tall scroll region inside an already-scrolling page is worse on a phone
     than simply letting the list flow. */
  .tax-picker__families { max-height: none; }
  .tax-option { padding: var(--space-3); }
  .tax-option__body { gap: var(--space-1); }
}

/* Skill detail fields — one set, applying to whatever the picker has selected,
   replacing the four controls that used to be repeated per taxonomy node. */
.skill-detail { border: 0; margin: 0; padding: 0; min-width: 0; }
.skill-detail__grid {
  display: grid; gap: var(--space-4); margin-top: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  align-items: end;
}
.skill-detail__check { grid-column: 1 / -1; }
@media (min-width: 768px) { .skill-detail__check { grid-column: auto; padding-bottom: .6rem; } }

/* ── Cross-job applications list ───────────────────────────────────────
   One piece of markup, two layouts. Aligned columns on desktop; a stacked
   card on a phone, where a seven-column ATS table is either a horizontal
   scroll or unreadable. The per-cell labels are hidden on desktop, where the
   column position already says what each value is. */
.app-list { list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--space-2); }

.app-row {
  display: grid; gap: var(--space-2) var(--space-4);
  padding: var(--space-4);
  background: var(--gray-0);
  border: 1px solid var(--border);
  border-left: 3px solid transparent;
  border-radius: var(--radius);
  transition: border-color var(--duration) var(--ease);
}
.app-row:hover { border-color: var(--primary-200); }

/* Nobody has looked at this one yet — the only state that is genuinely
   waiting on the employer. */
.app-row--attention { border-left-color: var(--accent-500); }

.app-row__label {
  display: block;
  font-size: var(--text-xs); letter-spacing: .04em; text-transform: uppercase;
  color: var(--gray-500);
}
.app-row__name { font-weight: var(--weight-semibold); }
.app-row__headline { display: block; font-size: var(--text-sm); color: var(--gray-600); }

/* Below 900px the rows stack and each carries its own visible label, so a
   heading row would be a row of words with nothing under them. The media
   query below turns it on; this has to come FIRST, because both rules set
   `display` at the same specificity and the later one wins. */
.app-head { display: none; }

@media (min-width: 900px) {
  .app-row {
    grid-template-columns: minmax(0, 2.2fr) minmax(0, 1.6fr) 10rem 10rem 6.5rem auto;
    align-items: center;
  }

  /* The per-row label is hidden from the EYE, not from the accessibility tree.
     It used to be `display:none` above 900px, on the reasoning that "the column
     heading is redundant once the values line up". Two problems: it holds for
     two columns and stops holding for six, and `display:none` takes the label
     out of the accessibility tree as well — so a screen reader heard six bare
     values per row with nothing naming them.

     Now the eye gets the heading row above (aria-hidden, because these labels
     already say it) and the screen reader keeps the per-row labels. Neither
     audience hears or sees it twice. */
  .app-row__label {
    position:absolute; width:1px; height:1px; padding:0; margin:-1px;
    overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
  }

  .app-head {
    display: grid;
    gap: var(--space-2) var(--space-4);
    grid-template-columns: minmax(0, 2.2fr) minmax(0, 1.6fr) 10rem 10rem 6.5rem auto;
    /* The 3px transparent left border every row carries, so the headings sit
       over their own columns rather than 3px to the left of them. */
    padding: 0 var(--space-4) var(--space-2);
    border-left: 3px solid transparent;
    font-size: var(--text-xs); font-weight: var(--weight-bold);
    letter-spacing: var(--tracking-wide); text-transform: uppercase;
    color: var(--text-muted);
  }
}

/* Collapsed guidance. Content that is essential on day one and furniture on
   day ninety: it leads while the user is new, then folds to one line. Nothing
   is deleted — the consent rule is the thing a recruiter most needs correct. */
.help-note {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--gray-25); padding: var(--space-3) var(--space-4);
}
.help-note > summary {
  cursor: pointer; font-size: var(--text-sm); font-weight: var(--weight-medium);
  color: var(--gray-700);
}
.help-note > summary:hover { color: var(--text); }
.help-note p { font-size: var(--text-sm); color: var(--gray-600); }

/* The job card no longer has a footer row: the date joins the other facts
   and the duplicate "View role" link is gone. */
.job-card__posted { margin-top:var(--space-3); }

/* ═══════════════════════════════════════════════════════════════════════
   B2 stage 3 — the portal shell

   Signed-in surfaces stop being the marketing site with a sidebar bolted on.
   All four portals already share one wrapper — .account-layout__side inside
   .grid--sidebar — so the navy chrome is one rule rather than four.

   The column paints itself and then paints 100vw further left, which takes
   the navy to the viewport edge without moving the sidebar out of page
   content and rewriting eighty-five views to do it.
   ═══════════════════════════════════════════════════════════════════════ */

.is-portal .grid--sidebar { align-items:stretch; }

.is-portal .account-layout__side {
  background:var(--primary-900);
  padding:var(--space-5) var(--space-4) var(--space-6);
  border-radius:var(--radius-lg);
}

/* The navy column is a panel, not a full-bleed rail.
   B2 draws it running to the viewport edge. Doing that here needs either a
   100vw pseudo-element — which introduces the horizontal overflow the
   responsive assertions exist to prevent — or moving the sidebar out of page
   content and into the layout, which is eighty-five views. The panel is the
   honest version of the design inside the structure that exists; the rail is
   recorded as a known difference rather than quietly dropped. */

/* Section headings inside the navy column. */
.is-portal .account-layout__side .side-nav__title {
  /* 4.10:1 on the navy rail, which fails AA for a heading. 5.04:1 now. */
  color:#7986a3;
}

/* Links: light enough to read on navy at 11:1, with the active row taking the
   teal left bar the design system reserves for "you are here". */
.is-portal .account-layout__side .side-nav__link {
  color:#c2cddf;
  border-left-color:transparent;
}
.is-portal .account-layout__side .side-nav__link .ic { color:#8fa0bd; }

@media (hover:hover) and (pointer:fine) {
  .is-portal .account-layout__side .side-nav__link:hover {
    background:rgb(255 255 255 / .06);
    color:#fff;
  }
  .is-portal .account-layout__side .side-nav__link:hover .ic { color:#fff; }
}

.is-portal .account-layout__side .side-nav__link.is-active,
.is-portal .account-layout__side .side-nav__link[aria-current="page"] {
  background:rgb(37 99 235 / .18);
  border-left-color:var(--teal-600);
  color:#fff;
}
.is-portal .account-layout__side .side-nav__link.is-active .ic,
.is-portal .account-layout__side .side-nav__link[aria-current="page"] .ic { color:var(--teal-600); }

/* The organisation badge at the top of the employer, partner and admin
   sidebars, and the candidate's own name. */
.is-portal .account-layout__side .org-badge {
  border-color:rgb(255 255 255 / .12);
  background:rgb(255 255 255 / .04);
}
.is-portal .account-layout__side .org-badge__name { color:#fff; }
.is-portal .account-layout__side .subtle,
.is-portal .account-layout__side .tiny { color:#8fa0bd; }

/* Status pills sit on navy here, so the light tints they use on white would
   glare. They keep their meaning and lose their fill. */
.is-portal .account-layout__side .status {
  background:rgb(255 255 255 / .08);
  border-color:rgb(255 255 255 / .16);
  color:#c2cddf;
}
.is-portal .account-layout__side .status--verified { color:#7fe3d3; }
.is-portal .account-layout__side .status--pending  { color:#f7cf8b; }

.is-portal .account-layout__side .side-nav__footer {
  border-top-color:rgb(255 255 255 / .1);
  color:#7986a3;
}
.is-portal .account-layout__side .side-nav__footer a { color:#c2cddf; }

/* Focus has to be visible on navy — the blue ring is not. */
.is-portal .account-layout__side :focus-visible {
  outline-color:#fff;
  box-shadow:0 0 0 4px rgb(11 19 43 / .9);
}

/* ── Portal footer ─────────────────────────────────────────────────────
   One compact line. The disclaimer is legal and stays; the nine marketing
   links do not follow an administrator into the console. */
.portal-foot {
  border-top:1px solid var(--border);
  background:var(--surface);
  padding:var(--space-4) 0;
  margin-top:var(--space-10);
}
.portal-foot .shell {
  display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--space-2) var(--space-5);
  font-size:var(--text-xs); color:var(--text-subtle);
}
.portal-foot__disclaimer { flex:1 1 24rem; min-width:0; }
.portal-foot a { color:var(--text-subtle); }

@media (max-width:1023px) {
  /* Below the sidebar breakpoint the column is a normal block above the
     content rather than a rail beside it. */
  .is-portal .account-layout__side { margin-bottom:var(--space-5); }
}

/* ═══════════════════════════════════════════════════════════════════════
   B2 stage 4 — the shared component layer

   The pieces every portal needs, defined once. Nothing here is page-specific
   and nothing here is a second design system: these extend the tokens at the
   top of this file rather than introducing their own.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Action queue ──────────────────────────────────────────────────────
   The attention region, rebuilt. It was three loose boxes each carrying a
   3px coloured left edge — the admonition shape that reads as a docs engine
   rather than a designed component, and one that says nothing when every row
   has it. It is now one bordered group with a header and an icon tile per
   row, so the tone is carried by a glyph and a word instead of a stripe. */
.attention {
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  background:var(--surface);
  box-shadow:var(--shadow-xs);
  overflow:hidden;
  margin-bottom:var(--space-6);
}
.attention__heading {
  display:flex; align-items:center; gap:var(--space-2);
  margin:0; padding:var(--space-3) var(--space-5);
  background:var(--gray-50);
  border-bottom:1px solid var(--border);
  font-size:var(--text-xs); font-weight:var(--weight-bold);
  letter-spacing:var(--tracking-wide); text-transform:uppercase;
  color:var(--text-muted);
}
.attention__count {
  display:inline-flex; align-items:center; justify-content:center;
  min-width:1.25rem; height:1.25rem; padding:0 var(--space-1);
  border-radius:var(--radius-full);
  background:var(--warning-600); color:var(--gray-0);
  font-size:var(--text-xs); font-weight:var(--weight-bold);
}
.attention__list { margin:0; padding:0; list-style:none; }

.attention__item {
  display:flex; align-items:center; gap:var(--space-4);
  padding:var(--space-4) var(--space-5);
  background:var(--surface);
  border:0; border-radius:0;
}
.attention__item + .attention__item { border-top:1px solid var(--border); }

/* The tone tile. Icon plus a bordered tint, so tone never rests on colour
   alone — the label states the situation in words directly beside it. */
.attention__tone {
  flex:0 0 2.25rem; height:2.25rem;
  display:grid; place-items:center;
  border-radius:var(--radius); border:1px solid;
}
.attention__item--action .attention__tone {
  background:var(--primary-50); border-color:var(--primary-200); color:var(--primary-800);
}
.attention__item--warning .attention__tone {
  background:var(--warning-50); border-color:var(--warning-200); color:var(--warning-800);
}
.attention__tone .ic { color:currentColor; }

/* ── Metric strip ──────────────────────────────────────────────────────
   One horizontal band replacing a grid of equal tiles. The leading figure is
   tinted, and a zero is greyed but still present and still linked, because a
   zero is information — "nobody is waiting" — not an absence. */
.mstrip {
  /* Grid rather than flex-wrap: with eight metrics, wrapping left a ragged
     second row of two items and a wide gap. auto-fit fills every row. */
  display:grid; grid-template-columns:repeat(auto-fit, minmax(9rem, 1fr));
  gap:1px; background:var(--border);
  border:1px solid var(--border); border-radius:var(--radius-lg);
  box-shadow:var(--shadow-xs);
  overflow:hidden; margin-bottom:var(--space-6);
}
.mstrip__m {
  min-width:0;
  padding:var(--space-3) var(--space-4);
  background:var(--surface);
  text-decoration:none; color:inherit;
}
a.mstrip__m:hover { background:var(--gray-25); }
.mstrip__label { display:block; margin:0 0 2px; font-size:var(--text-xs); color:var(--text-subtle); }
.mstrip__value {
  display:block; margin:0;
  font-size:var(--text-2xl); font-weight:var(--weight-bold);
  letter-spacing:var(--tracking-tight); line-height:1.15;
  font-variant-numeric:tabular-nums;
}
.mstrip__note { display:block; margin-top:2px; font-size:var(--text-xs); color:var(--text-subtle); }
.mstrip__m--lead { background:linear-gradient(180deg,var(--primary-50),var(--surface)); }
.mstrip__m--lead .mstrip__label { color:var(--primary-800); font-weight:var(--weight-semibold); }
.mstrip__m--lead .mstrip__value { color:var(--primary-900); }
.mstrip__m--zero .mstrip__value { color:var(--text-quiet); font-weight:var(--weight-medium); }
.mstrip__m--risk .mstrip__value { color:var(--danger-600); }

@media (max-width:767px) {
  /* Two per row rather than eight squeezed onto one. */
  .mstrip { grid-template-columns:repeat(2, 1fr); }
}

/* ── Taxonomy tier ─────────────────────────────────────────────────────
   The signature OraCareers device. Six real levels from oracle_taxonomy.level
   — family, product, specialization, module, version, skill — each with its
   own restrained treatment, so the structure of the skills graph is legible
   without reading. Never invented: the partial reads the stored level. */
.tier {
  display:inline-block; vertical-align:1px;
  padding:0 var(--space-2);
  border:1px solid transparent; border-radius:var(--radius-full);
  font-size:10.5px; font-weight:800; line-height:1.65;
  letter-spacing:.07em; text-transform:uppercase;
  white-space:nowrap;
}
.tier--family         { background:var(--primary-50); color:var(--primary-800); border-color:var(--primary-100); }
.tier--product        { background:var(--gray-100);   color:var(--gray-700);    border-color:var(--gray-200); }
.tier--specialization { background:var(--teal-50);    color:var(--teal-800);    border-color:var(--teal-200); }
.tier--module         { background:var(--primary-900); color:var(--gray-0);     border-color:var(--primary-900); }
.tier--version        { background:transparent;       color:var(--text-muted);  border-color:var(--border-control); }
/* A leaf skill is the finest grain in the graph, so it takes the lightest
   treatment — a dashed edge, distinct from the solid grey of a product. */
.tier--skill          { background:transparent;      color:var(--gray-600);    border-color:var(--gray-300); border-style:dashed; }

/* The path a node sits on: Database > High Availability > Data Guard. */
.tax-path { margin:0; font-size:var(--text-xs); color:var(--text-subtle); }
.tax-path b { color:var(--text-muted); font-weight:var(--weight-semibold); }
.tax-path i { font-style:normal; color:var(--gray-400); padding:0 3px; }

/* ── Stat grids become the metric band ─────────────────────────────────
   The employer dashboard's markup was converted to .mstrip outright. The
   candidate, partner and admin dashboards carry 58 .stat elements between
   them across four files, and rewriting all of them by hand is a large
   change for a purely visual result.

   They already share one component inside a .grid, so the band treatment is
   applied structurally instead: a connected row of cells with hairline
   separators rather than detached cards floating on the page ground.

   What this cannot do is grey a zero — CSS cannot read the text — so those
   dashboards keep every figure at full weight until their markup is
   converted the way the employer's was. That is a real remaining difference
   and it is recorded as one. */
.grid:has(> .stat) {
  display:grid;
  gap:1px;
  background:var(--border);
  border:1px solid var(--border);
  border-radius:var(--radius-lg);
  overflow:hidden;
  box-shadow:var(--shadow-xs);
}
.grid:has(> .stat) > .stat {
  background:var(--surface);
  border:0;
  border-radius:0;
  box-shadow:none;
  padding:var(--space-3) var(--space-4);
}
.grid:has(> .stat) > a.stat:hover { background:var(--gray-25); }
.grid:has(> .stat) > .stat .stat__value {
  font-size:var(--text-2xl);
  letter-spacing:var(--tracking-tight);
  font-variant-numeric:tabular-nums;
}

/* ── The sidebar's horizontal scroller actually scrolling ──────────────
   Below 1024px the portal sidebar becomes a horizontally scrolling row of
   nav items, and .side-nav carries overflow-x:auto to contain it. It did not
   contain anything: a flex or grid item defaults to min-width:auto, which
   lets it grow to its content instead of shrinking, so the row pushed the
   whole document wide. /admin/partners measured a 803px scrollWidth in a
   375px viewport.

   This predates B2 — the same measurement on the pre-B2 commit gives the same
   803px — so it is a pre-existing defect that the B2 responsive sweep found
   rather than one it caused. It affected nine table-heavy admin, partner and
   employer screens at 375, 390 and 430, and /admin/partners as far up as
   1024.

   min-width:0 on the chain is the whole fix: it lets the column shrink so the
   scroll container has a width to scroll within. */
@media (max-width:1023px) {
  .account-layout__side,
  .account-layout__main,
  .side-nav { min-width:0; }
  /* `min-width:0` belongs on the SCROLL CONTAINER, not on what it scrolls.
     It was on both, and on the lists it did the opposite of the intent: the
     groups shrank below their content instead of overflowing it, so on a phone
     the five nav groups collapsed to 13px, 72px, 47px, 73px and 26px wide and
     their links printed on top of one another — "Dashboard" and "Profile"
     legibly overlapping in the same 13px box.

     The lists must keep their intrinsic width so that there is something for
     .side-nav to scroll. It still cannot widen the page: .side-nav has
     min-width:0 and overflow-x:auto, which is what the original fix was for. */
  .side-nav__list { flex:0 0 auto; }
}

/* ═══════════════════════════════════════════════════════════════════════
   B2 stage 4 (continued) — the rest of the shared component layer

   Written against the tokens at the top of this file, not the prototype's.
   b2-system.css names its own scale (--line, --s-1, --fs-sm); translating
   rather than pasting keeps one set of tokens in the application, so a change
   to --border still reaches every component.

   Every component below is used by a screen in this same commit range. None
   is speculative.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Permission list ───────────────────────────────────────────────────
   The consent authorisation summary. Replaces a definition table whose rows
   read "Contact details shared / No" — true, and not what a person scanning
   a consent decision needs.

   The mark is never the message. Every row states the permission in words;
   the tick or cross repeats it, and the tick carries a check glyph while the
   cross carries a cross glyph, so the two are distinguishable without colour. */
.perm { display:grid; gap:0; margin:0; }
.perm__item {
  display:flex; align-items:flex-start; gap:var(--space-3);
  padding:var(--space-3) var(--space-5);
  border-bottom:1px solid var(--border);
}
.perm__item:last-child { border-bottom:0; }
.perm__mark {
  flex:0 0 1.375rem; height:1.375rem; margin-top:.0625rem;
  border-radius:var(--radius-full);
  display:grid; place-items:center;
  border:1px solid;
}
.perm__mark svg { width:.75rem; height:.75rem; }
.perm__mark--yes { background:var(--teal-50); border-color:var(--teal-200); color:var(--teal-800); }
.perm__mark--no  { background:var(--surface-sunken); border-color:var(--border-strong); color:var(--text-muted); }
.perm__body b {
  display:block;
  font-size:var(--text-sm); font-weight:var(--weight-semibold);
  color:var(--text);
}
.perm__body span {
  display:block; margin-top:.125rem;
  font-size:var(--text-xs); color:var(--text-muted); line-height:1.55;
}

/* ── Steps ─────────────────────────────────────────────────────────────
   A three-part explainer. Presentation only: it describes a workflow that
   already exists and changes nothing about it. */
.steps {
  display:grid; grid-template-columns:repeat(3,1fr); gap:0;
  border:1px solid var(--border); border-radius:var(--radius-lg);
  overflow:hidden; background:var(--surface); box-shadow:var(--shadow-xs);
  margin:0; padding:0; list-style:none;
}
.step { padding:var(--space-4) var(--space-5); border-right:1px solid var(--border); }
.step:last-child { border-right:0; }
.step__n {
  width:1.5rem; height:1.5rem; border-radius:var(--radius-full);
  background:var(--brand-navy); color:var(--gray-0);
  display:grid; place-items:center;
  font-size:var(--text-xs); font-weight:var(--weight-bold);
  margin-bottom:var(--space-2);
}
.step b { display:block; font-size:var(--text-sm); font-weight:var(--weight-semibold); margin-bottom:.125rem; }
.step p { margin:0; font-size:var(--text-xs); color:var(--text-muted); line-height:1.5; }
@media (max-width:767px) {
  .steps { grid-template-columns:1fr; }
  .step { border-right:0; border-bottom:1px solid var(--border); }
  .step:last-child { border-bottom:0; }
}

/* ── Accordion ─────────────────────────────────────────────────────────
   Built on details/summary, so it opens with no JavaScript, is in the tab
   order for free, responds to Enter and Space, and is announced as a
   disclosure. A div-and-onclick version would need all four rebuilt by hand
   and would be inert if the script failed.

   The webkit details marker is suppressed because the chevron replaces it;
   the chevron rotates on open and holds still under reduced motion. */
.acc {
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface); box-shadow:var(--shadow-xs);
}
.acc + .acc { margin-top:var(--space-3); }
.acc__t {
  display:flex; align-items:center; gap:var(--space-2);
  width:100%; padding:var(--space-3) var(--space-5);
  font-size:var(--text-sm); font-weight:var(--weight-semibold);
  color:var(--text); cursor:pointer; list-style:none;
}
.acc__t::-webkit-details-marker { display:none; }
.acc__chevron {
  margin-left:auto; width:1rem; height:1rem; flex-shrink:0;
  color:var(--text-muted);
  transition:transform var(--duration-fast) var(--ease);
}
.acc[open] > .acc__t .acc__chevron { transform:rotate(180deg); }
.acc__c {
  padding:0 var(--space-5) var(--space-4);
  font-size:var(--text-sm); color:var(--text-muted); line-height:1.6;
}
.acc__c > :first-child { margin-top:0; }
.acc__c > :last-child { margin-bottom:0; }
@media (prefers-reduced-motion:reduce) { .acc__chevron { transition:none; } }

/* ── Segmented control ─────────────────────────────────────────────────
   A filter that shows its alternatives. Rendered as links, because each
   segment is a real URL with its own filtered result — which keeps the back
   button, bookmarking and middle-click working. aria-current="page" marks
   the active one, so the state is announced and not merely tinted. */
.seg {
  display:inline-flex; padding:.1875rem;
  background:var(--surface-sunken);
  border:1px solid var(--border); border-radius:var(--radius-md);
  gap:.1875rem; max-width:100%; overflow-x:auto;
}
.seg__item {
  display:inline-flex; align-items:center; gap:var(--space-2);
  padding:var(--space-2) var(--space-3);
  border-radius:var(--radius-sm);
  font-size:var(--text-sm); font-weight:var(--weight-medium);
  color:var(--text-muted); text-decoration:none; white-space:nowrap;
}
.seg__item:hover { color:var(--text); }
.seg__item[aria-current="page"] {
  background:var(--surface); color:var(--text);
  font-weight:var(--weight-semibold);
  box-shadow:var(--shadow-xs);
}
.seg__count {
  font-size:var(--text-xs); font-variant-numeric:tabular-nums;
  color:var(--text-subtle);
}
.seg__item[aria-current="page"] .seg__count { color:var(--text-muted); }

/* ── Toast ─────────────────────────────────────────────────────────────
   The flash message, given a shape. role="status" and aria-live="polite" so
   it is announced without stealing focus, and it never carries information
   that appears nowhere else on the page. */
.toast-region {
  position:fixed; z-index:var(--z-dialog);
  bottom:var(--space-4); left:50%; transform:translateX(-50%);
  display:grid; gap:var(--space-2);
  width:min(28rem, calc(100vw - 2rem));
}
.toast {
  display:flex; align-items:flex-start; gap:var(--space-3);
  padding:var(--space-3) var(--space-4);
  background:var(--surface);
  border:1px solid var(--border-strong); border-left:3px solid var(--brand-navy);
  border-radius:var(--radius-md); box-shadow:var(--shadow-lg);
  font-size:var(--text-sm);
}
.toast--success { border-left-color:var(--success-600); }
.toast--error   { border-left-color:var(--danger-600); }
.toast__icon { flex:0 0 1.125rem; height:1.125rem; margin-top:.125rem; }
.toast__body { flex:1 1 auto; }
.toast__close {
  flex:0 0 auto; background:none; border:0; padding:var(--space-1);
  color:var(--text-muted); cursor:pointer; border-radius:var(--radius-sm);
}

/* ── Responsive action area ────────────────────────────────────────────
   A row of actions that becomes a column when it no longer fits, rather than
   overflowing the viewport. Buttons go full-width on a phone because a
   half-width button beside another half-width button gives two small targets
   where the guidance asks for 44px.

   Deliberately NOT fixed to the bottom of the screen: a fixed action bar on
   a consent decision covers the text the decision depends on. */
.actionbar { display:flex; flex-wrap:wrap; gap:var(--space-3); align-items:center; }
.actionbar--end { justify-content:flex-end; }
.actionbar > form { margin:0; }
@media (max-width:479px) {
  .actionbar { flex-direction:column; align-items:stretch; }
  .actionbar > .btn,
  .actionbar > form,
  .actionbar > form > .btn { width:100%; }
}

/* ═══════════════════════════════════════════════════════════════════════
   B2 stage 5 — the consent composition

   The screen a candidate uses to decide whether a recruitment partner may
   represent them for one role. It is the highest-stakes screen in the
   product, and the one the design round rebuilt most.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── The two-column consent grid ───────────────────────────────────────
   Information left, decision right. The decision column is sticky on
   desktop so the summary stays beside whatever the reader is reading.

   Below 1024 it collapses to one column and the decision panel moves to
   the END of the document, after the information — deliberately, and it is
   why this is a source-order-neutral grid rather than a float. A decision
   control placed above the text it depends on asks for the answer before
   the question has been read. */
.consent-cols { display:grid; gap:var(--space-8); align-items:start; }
@media (min-width:1024px) {
  .consent-cols { grid-template-columns:minmax(0,1fr) 20.5rem; }
}

/* ── Trust panel ───────────────────────────────────────────────────────
   Teal is the OraCareers trust colour and is used nowhere else on this
   page. It states one specific claim — that this organisation passed the
   partner approval process — and the copy immediately qualifies what that
   does and does not mean. */
.trust {
  border:1px solid var(--teal-200); border-radius:var(--radius-lg);
  background:var(--teal-50); overflow:hidden;
}
.trust__head {
  display:flex; align-items:flex-start; gap:var(--space-4);
  padding:var(--space-5);
}
.trust__mark {
  flex:0 0 2.5rem; height:2.5rem; border-radius:var(--radius-md);
  background:var(--surface); border:1px solid var(--teal-200);
  display:grid; place-items:center; color:var(--teal-mark);
}
.trust__body { min-width:0; }
.trust__name {
  margin:0; font-size:var(--text-lg); font-weight:var(--weight-bold);
  color:var(--brand-navy); text-wrap:balance;
}
.trust__meta {
  margin:var(--space-1) 0 0;
  font-size:var(--text-sm); color:var(--text-muted);
}
.trust__chips { display:flex; flex-wrap:wrap; gap:var(--space-2); margin-top:var(--space-3); }
/* The accordion sits on the tinted ground, so it drops its own surface. */
.trust .acc {
  border:0; border-top:1px solid var(--teal-200); border-radius:0;
  background:transparent; box-shadow:none;
}
.trust .acc__c { color:var(--text); }

/* ── Trust vocabulary ──────────────────────────────────────────────────
   Five meanings, five treatments. Teal is a verification, never a success.
   Every one of these carries its own words; none is colour alone. */
.tchip {
  display:inline-flex; align-items:center; gap:var(--space-1);
  padding:var(--space-1) var(--space-2);
  border-radius:var(--radius-sm); border:1px solid transparent;
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  white-space:nowrap;
}
.tchip svg { flex-shrink:0; }
.tchip--verified { background:var(--teal-50);    color:var(--teal-800);    border-color:var(--teal-200); }
.tchip--approved { background:var(--primary-50); color:var(--primary-800); border-color:var(--primary-100); }
.tchip--active   { background:var(--success-50); color:var(--success-800); border-color:var(--success-200); }
.tchip--pending  { background:var(--warning-50); color:var(--warning-800); border-color:var(--warning-200); }
.tchip--risk     { background:var(--danger-50);  color:var(--danger-800);  border-color:var(--danger-200); }
.tchip--neutral  { background:var(--surface-sunken); color:var(--text-muted); border-color:var(--border); }

/* ── Opportunity summary ───────────────────────────────────────────────
   Facts as icon-led rows rather than a definition list, because the reader
   is scanning for four things (where, what shape, what kind, for whom) and
   a dt/dd pair buries each behind its own label. */
.opp { display:grid; gap:var(--space-3); }
@media (min-width:640px) { .opp { grid-template-columns:repeat(2,minmax(0,1fr)); } }
.opp__i { display:flex; align-items:flex-start; gap:var(--space-3); min-width:0; }
.opp__ic {
  flex:0 0 1.75rem; height:1.75rem; border-radius:var(--radius-sm);
  background:var(--surface-sunken); border:1px solid var(--border);
  display:grid; place-items:center; color:var(--text-muted);
}
.opp__t { display:block; font-size:var(--text-xs); color:var(--text-muted); }
.opp__v { display:block; font-size:var(--text-sm); font-weight:var(--weight-semibold); color:var(--text); }

/* ── Decision panel ────────────────────────────────────────────────────
   The only navy object on the screen, so the eye finds it once. It repeats
   the six facts the decision turns on, so the summary is readable without
   scrolling back.

   Both buttons are the same size and the same weight. Accept is navy and
   Decline is bordered, and they are the same physical target — a consent
   screen that makes one answer easier to give is not collecting consent. */
.decide {
  border:1px solid var(--border-strong); border-radius:var(--radius-lg);
  background:var(--surface); box-shadow:var(--shadow-sm); overflow:hidden;
}
@media (min-width:1024px) { .decide { position:sticky; top:var(--space-5); } }
.decide__h { padding:var(--space-3) var(--space-5); background:var(--brand-navy); color:var(--gray-0); }
.decide__eyebrow {
  display:block; font-size:var(--text-xs); font-weight:var(--weight-bold);
  letter-spacing:var(--tracking-wide); text-transform:uppercase;
  color:#9fb0c9;
}
.decide__h b { display:block; font-size:var(--text-base); font-weight:var(--weight-bold); margin-top:.125rem; }
.decide__rows { margin:0; }
.decide__r {
  display:flex; justify-content:space-between; gap:var(--space-3);
  padding:var(--space-2) var(--space-5);
  border-bottom:1px solid var(--border);
  font-size:var(--text-xs);
}
.decide__r dt { margin:0; color:var(--text-muted); flex:0 0 auto; }
.decide__r dd { margin:0; text-align:right; font-weight:var(--weight-semibold); color:var(--text); min-width:0; }
.decide__act { padding:var(--space-4) var(--space-5); display:grid; gap:var(--space-2); }
.decide__act .btn { width:100%; }
.decide__act form { margin:0; }
.decide__note {
  padding:0 var(--space-5) var(--space-4);
  margin:0; font-size:var(--text-xs); color:var(--text-muted); line-height:1.55;
}

/* ── Taxonomy requirement list ─────────────────────────────────────────
   A job's Oracle requirements, each carrying its tier pill and whether it
   is required, preferred or optional. Wraps rather than truncating: an
   abbreviation nobody can expand is worse than a second line. */
.taxlist { margin:0; padding:0; list-style:none; display:grid; gap:var(--space-2); }
.taxlist__i {
  display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-2);
  padding:var(--space-2) var(--space-3);
  border:1px solid var(--border); border-radius:var(--radius);
  background:var(--surface);
}
.taxlist__n { font-size:var(--text-sm); font-weight:var(--weight-semibold); color:var(--text); }

/* ═══════════════════════════════════════════════════════════════════════
   B2 stage 5 — Oracle skills, the signature screen

   Two panes on desktop: taxonomy discovery on the left, the candidate's own
   Oracle profile in the wide column beside it. The profile is the subject of
   the page, so it gets the width; discovery is the companion.
   ═══════════════════════════════════════════════════════════════════════ */

.skills-cols { display:grid; gap:var(--space-6); align-items:start; }

/* Source order is discovery, then profile — so that with no CSS and no
   JavaScript the form still precedes the list it adds to. The grid puts
   discovery in the narrow first column on desktop, and below 1024 the profile
   is pulled ABOVE it, because on a phone the first thing on the screen should
   be what you already have, not a 129-node catalogue. */
@media (min-width:1024px) {
  .skills-cols { grid-template-columns:21.5rem minmax(0,1fr); }
}
@media (max-width:1023px) {
  .skills-profile   { order:1; }
  .skills-discover  { order:2; }
}

.skills-discover {
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface-sunken); box-shadow:var(--shadow-xs);
  padding:var(--space-5);
  min-width:0;
}
.skills-discover__head {
  display:flex; align-items:center; gap:var(--space-3);
  margin-bottom:var(--space-4);
}
.skills-discover__head .h3 { margin:0; }
.skills-discover__form { min-width:0; }

.skills-profile { min-width:0; }
.skills-profile__head {
  display:flex; align-items:flex-start; justify-content:space-between;
  gap:var(--space-4); flex-wrap:wrap;
}
.skills-profile__head .h2 { margin:0; }
/* The add button belongs to the drawer on small screens and is redundant
   beside a permanently visible pane, so it only appears where the pane is not. */
@media (min-width:1024px) { .skills-profile__add { display:none; } }

/* ── Family group ──────────────────────────────────────────────────────
   Skills grouped under the Oracle family they actually hang from, which is
   the root of the taxonomy path and not the immediate parent. */
.famgroup {
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface); box-shadow:var(--shadow-xs); overflow:hidden;
}
.famgroup__h {
  display:flex; align-items:center; gap:var(--space-3);
  padding:var(--space-3) var(--space-5);
  background:var(--surface-sunken); border-bottom:1px solid var(--border);
}
.famgroup__ic {
  flex:0 0 1.625rem; height:1.625rem; border-radius:var(--radius-sm);
  background:var(--surface); border:1px solid var(--border-strong);
  display:grid; place-items:center; color:var(--primary-800);
}
.famgroup__n { margin:0; font-size:var(--text-sm); font-weight:var(--weight-bold); color:var(--brand-navy); }
.famgroup__c {
  margin-left:auto; font-size:var(--text-xs); color:var(--text-muted);
  font-variant-numeric:tabular-nums;
}
.famgroup__b > .skill-row { border-bottom:1px solid var(--border); border-radius:0; }
.famgroup__b > .skill-row:last-child { border-bottom:0; }

/* ── The stated level, on the right of a skill row ─────────────────────
   Four segments, and the word above them. The segments never carry the
   meaning alone, and an unstated level fills none of them. */
.skill-row__level { text-align:right; white-space:nowrap; }
.skill-row__level b { display:block; font-size:var(--text-sm); font-weight:var(--weight-semibold); }
.skill-row__level > span { display:block; font-size:var(--text-xs); color:var(--text-muted); }

.skill-row__remove { margin-top:var(--space-3); }

/* ── Picker: alias context and the on-profile tick ─────────────────────── */
.tax-option__alias {
  display:block; font-size:var(--text-xs); color:var(--text-subtle);
  font-variant:normal;
}
.tax-option__held {
  display:inline-flex; align-items:center; gap:var(--space-1);
  margin-top:var(--space-1);
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  color:var(--teal-800);
}

/* ── The mobile taxonomy drawer ────────────────────────────────────────
   Below 1024 the discovery pane becomes a full-height sheet. `.is-drawer` is
   set by the script and by nothing else: with no JavaScript the pane stays in
   normal flow below the profile, the button is an ordinary link to it, and
   adding a skill works exactly as it does on desktop. The enhancement is the
   drawer, never the capability.

   While open the script gives it role="dialog" and aria-modal, moves focus in,
   keeps Tab inside it, closes on Escape and restores focus to the button. */
@media (max-width:1023px) {
  /* The sheet's VISIBILITY is the `hidden` attribute and nothing else, and it
     does not slide.

     It did, twice, and both attempts had the same failure. A transition out of
     `transform:translateX(100%)` makes the OFF-SCREEN position the element's
     real style, so a browser that does not run the animation leaves the sheet
     displayed, focus trapped inside it, and sitting entirely beyond the right
     edge of the screen — open to the keyboard and invisible to the eye.
     Rewriting it as an entrance @keyframes did not fix it: a throttled or
     backgrounded tab holds the animation on its `from` keyframe, which is the
     same off-screen transform, so the sheet was measured at left:375 in a
     375px viewport with focus inside it. Both were reproduced here.

     No transform, no animation, no failure mode. The sheet appears where it
     belongs the moment it is unhidden. The slide was decoration; being
     reachable was not. */
  .skills-discover.is-drawer {
    position:fixed; inset:0 0 0 auto;
    z-index:var(--z-dialog);
    width:min(28rem, 100vw);
    max-width:100vw;
    border:0; border-radius:0;
    background:var(--surface);
    box-shadow:var(--shadow-lg);
    overflow-y:auto; -webkit-overflow-scrolling:touch;
    padding:var(--space-5);
  }

  .drawer-scrim {
    position:fixed; inset:0;
    z-index:calc(var(--z-dialog) - 1);
    background:rgb(11 19 43 / .45);
  }
  /* The sheet keeps its close control at the top where a thumb reaches it,
     and it stays put while the list behind it scrolls. */
  .skills-discover.is-drawer .skills-discover__head {
    position:sticky; top:calc(var(--space-5) * -1);
    margin:calc(var(--space-5) * -1) calc(var(--space-5) * -1) var(--space-4);
    padding:var(--space-4) var(--space-5);
    background:var(--surface); border-bottom:1px solid var(--border);
    z-index:1;
  }
}

/* The body does not scroll behind an open sheet. */
.has-drawer-open { overflow:hidden; }

/* ── Icon-only button ──────────────────────────────────────────────────
   Every use of this carries an aria-label, because there is no visible text
   to name it. The target is 44px even though the glyph is 18, which is what
   the touch guidance asks for and what a thumb actually needs. */
.iconbtn {
  display:inline-grid; place-items:center;
  width:2.75rem; height:2.75rem;
  padding:0; border:1px solid transparent; border-radius:var(--radius-md);
  background:transparent; color:var(--text-muted); cursor:pointer;
}
.iconbtn:hover { background:var(--surface-sunken); color:var(--text); }

/* A card that is waiting on the reader. Amber is "needs you", never an error. */
.card--pending { border-color:var(--warning-200); background:var(--warning-50); }

/* ── The hidden attribute beats any display we set ─────────────────────
   `[hidden]` is `display:none` in the UA sheet at the lowest specificity, so
   ANY class that sets `display` silently defeats it. `.iconbtn` did exactly
   that: the drawer close button carried `hidden` on desktop and rendered
   anyway, as a control that closes something not open.

   This is the general fix rather than `.iconbtn[hidden]`, because the next
   component to set `display` would reintroduce it. Scripts here toggle
   `el.hidden`, so this rule is what makes that mean something. */
[hidden] { display:none !important; }

/* ═══════════════════════════════════════════════════════════════════════
   B2 stage 3 (completed) — the utility bar and the full-bleed rail

   Two things stage 3 recorded as known differences from the approved design,
   now closed. A portal page carried the PUBLIC marketing header above a navy
   card, so it read as the brochure with an application dropped into the
   middle of it. It now reads as an application.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── The authenticated utility bar ─────────────────────────────────────
   54px, full width, and it duplicates no sidebar navigation: portal context,
   the portals you may switch to, anything waiting on you, and who you are. */
.pbar {
  position:sticky; top:0; z-index:var(--z-sticky, 30);
  background:var(--surface);
  border-bottom:1px solid var(--border);
}
.pbar__in {
  display:flex; align-items:center; gap:var(--space-4);
  min-height:3.375rem;
  padding-inline:var(--space-4);
}
.pbar__ctx { display:flex; align-items:baseline; gap:var(--space-2); min-width:0; }
.pbar__ctxlabel {
  font-size:var(--text-xs); font-weight:var(--weight-bold);
  letter-spacing:var(--tracking-wide); text-transform:uppercase;
  color:var(--text-subtle);
}
.pbar__ctx b {
  font-size:var(--text-sm); font-weight:var(--weight-bold);
  color:var(--brand-navy); white-space:nowrap;
}
.pbar__switch { display:flex; gap:var(--space-1); min-width:0; overflow-x:auto; }
.pbar__switchlink {
  display:inline-flex; align-items:center; gap:var(--space-2);
  padding:var(--space-2) var(--space-3);
  border-radius:var(--radius-sm);
  font-size:var(--text-sm); color:var(--text-muted);
  text-decoration:none; white-space:nowrap;
}
.pbar__switchlink:hover { background:var(--surface-sunken); color:var(--text); }

.pbar__right { margin-left:auto; display:flex; align-items:center; gap:var(--space-2); }

.pbar__bell {
  position:relative; display:grid; place-items:center;
  width:2.75rem; height:2.75rem;
  border-radius:var(--radius-md); color:var(--text-muted);
}
.pbar__bell:hover { background:var(--surface-sunken); color:var(--text); }
/* Amber is "waiting on you". The number is also in the sidebar row and in the
   link's own aria-label, so this pip is never the only statement of it. */
.pbar__pip {
  position:absolute; top:.375rem; right:.375rem;
  min-width:1.05rem; height:1.05rem; padding:0 .1875rem;
  display:grid; place-items:center;
  border-radius:var(--radius-full);
  background:var(--warning-600); color:var(--gray-0);
  font-size:.625rem; font-weight:var(--weight-bold);
  font-variant-numeric:tabular-nums;
}

.pbar__user { position:relative; }
.pbar__usertoggle {
  display:flex; align-items:center; gap:var(--space-2);
  padding:var(--space-1) var(--space-2) var(--space-1) var(--space-1);
  min-height:2.75rem;
  border:1px solid transparent; border-radius:var(--radius-md);
  background:none; cursor:pointer;
  font:inherit; font-size:var(--text-sm); color:var(--text);
}
.pbar__usertoggle:hover { background:var(--surface-sunken); }
.pbar__avatar {
  flex:0 0 1.875rem; height:1.875rem; border-radius:var(--radius-full);
  display:grid; place-items:center;
  background:var(--brand-navy); color:var(--gray-0);
  font-size:var(--text-xs); font-weight:var(--weight-bold);
}
.pbar__username { font-weight:var(--weight-medium); }
@media (max-width:639px) { .pbar__username { display:none; } }

.pbar__menu {
  position:absolute; right:0; top:calc(100% + .25rem);
  min-width:13rem; padding:var(--space-2);
  background:var(--surface);
  border:1px solid var(--border-strong); border-radius:var(--radius-md);
  box-shadow:var(--shadow-lg); z-index:var(--z-dropdown, 40);
}
.pbar__menuemail {
  margin:0 0 var(--space-2); padding:var(--space-2);
  font-size:var(--text-xs); color:var(--text-muted);
  border-bottom:1px solid var(--border);
  overflow-wrap:anywhere;
}
.pbar__menuitem {
  display:flex; align-items:center; gap:var(--space-2); width:100%;
  padding:var(--space-2) var(--space-3);
  border-radius:var(--radius-sm);
  font-size:var(--text-sm); color:var(--text); text-decoration:none;
  text-align:left;
}
.pbar__menuitem:hover { background:var(--surface-sunken); }
.pbar__menuitem--btn { border:0; background:none; font:inherit; cursor:pointer; }
.pbar__menu form { margin:0; }

/* ── The full-bleed portal rail ────────────────────────────────────────
   Stage 3 left the navy column as a rounded card floating inside the page,
   and recorded two reasons: a 100vw pseudo-element introduces the horizontal
   overflow the responsive assertions exist to prevent, and moving the sidebar
   out of page content means rewriting eighty-five views.

   Neither was necessary. The layout wrapper already exists in exactly four
   files — one shell partial per portal — so marking those four with
   `.portal-shell` and dropping the container's max-width and inline padding
   turns the same grid into a full-width one. No pseudo-element, no 100vw, no
   view rewrite, and the grid's own columns still bound everything, so nothing
   can extend past the viewport.

   Below 1024 the grid is one column and the rail becomes an ordinary block
   again — a fixed navy column on a phone would be most of the screen. */
@media (min-width:1024px) {
  .is-portal .main { padding-block:0; }
  .is-portal .portal-shell { max-width:none; padding-inline:0; }
  .is-portal .portal-shell .grid--sidebar { gap:0; align-items:stretch; }

  .is-portal .account-layout__side {
    border-radius:0;
    padding:var(--space-6) var(--space-4);
    /* The rail scrolls with its own content, not the page's, and holds still
       while the main column moves. `top` clears the utility bar. */
    position:sticky; top:3.375rem;
    max-height:calc(100vh - 3.375rem);
    overflow-y:auto;
  }

  /* The main column carries the reading measure the container used to. */
  .is-portal .account-layout__main {
    padding:var(--space-8) var(--space-8) var(--space-16);
    max-width:calc(var(--container-wide) - 17rem);
  }

  /* The flash region sits above the grid and keeps the old container. */
  .is-portal .main > .shell { padding-block:var(--space-4) 0; }
}

/* The navy has to reach the bottom of the window even when the page is short,
   or the rail stops mid-air above the footer. Painting it on the layout
   itself, behind the column, avoids a pseudo-element entirely. */
@media (min-width:1024px) {
  .is-portal .portal-shell .account-layout {
    min-height:calc(100vh - 3.375rem);
    background:linear-gradient(to right, var(--primary-900) 0 17rem, transparent 17rem);
  }
}

/* ── The rail head ─────────────────────────────────────────────────────
   The identity, at the top of the navy column, where the public header used
   to carry it. Sized here rather than in the brand partial so the same
   artwork can be smaller in a rail than it is in a marketing header. */
.rail-head {
  padding:0 var(--space-2) var(--space-5);
  margin-bottom:var(--space-4);
  border-bottom:1px solid rgb(255 255 255 / .1);
}
.brand--rail .brand__logo { height:1.5rem; width:auto; }

/* ── All clear ─────────────────────────────────────────────────────────
   A cleared operations queue, in one line instead of a whole section. Four
   empty queues used to cost four screens of scrolling to say nothing needed
   doing. Teal because a cleared queue is a verified fact, not a celebration —
   and the words say it with the colour removed. */
.allclear {
  display:flex; align-items:center; gap:var(--space-3);
  margin:0 0 var(--space-2);
  padding:var(--space-3) var(--space-4);
  border:1px solid var(--border); border-radius:var(--radius);
  background:var(--surface);
  font-size:var(--text-sm); color:var(--text-muted);
}
.allclear__mark {
  flex:0 0 1.25rem; height:1.25rem; border-radius:var(--radius-full);
  display:grid; place-items:center;
  background:var(--teal-50); border:1px solid var(--teal-200); color:var(--teal-800);
}
.allclear__link { margin-left:auto; font-size:var(--text-xs); }

/* A group of them reads as one block rather than five stacked cards. */
.allclear-group { margin-top:var(--space-6); }
.allclear-group > .allclear:last-child { margin-bottom:0; }

/* ═══════════════════════════════════════════════════════════════════════
   B2 stage 9 — the public homepage
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Split hero ────────────────────────────────────────────────────────
   Proposition, search and call to action on the left; the taxonomy graph on
   the right. Below 1024 the graph goes UNDER the copy rather than beside it:
   at 375 a three-tier diagram beside a search form is neither. */
.hero {
  display:grid; gap:var(--space-8); align-items:center;
  padding-block:var(--space-10) var(--space-8);
}
@media (min-width:1024px) {
  .hero { grid-template-columns:minmax(0,1fr) minmax(0,26rem); gap:var(--space-12, 3rem); }
}
.hero__copy { min-width:0; }
.hero__note { font-size:var(--text-sm); color:var(--text-muted); }
.hero__graph { min-width:0; }

/* The display size, used here and nowhere else. */
.display {
  font-size:clamp(2rem, 1.2rem + 2.6vw, 2.875rem);
  font-weight:760; line-height:1.08;
  letter-spacing:-.033em;
  color:var(--brand-navy);
  text-wrap:balance;
  margin:0;
}

/* ── The taxonomy graph ────────────────────────────────────────────────
   Deterministic SVG. About 2KB, sharp at any density, themeable, and its
   labels are real text — which matters because they are read out of
   oracle_taxonomy and have to be literally true.

   No inline style attributes anywhere in it: `style-src 'self'` forbids them,
   and an illustration that silently loses its styling under CSP is worse than
   one that never had any. Everything is a class or a presentation attribute. */
.graphpanel {
  margin:0; padding:var(--space-5);
  border:1px solid var(--border); border-radius:var(--radius-lg);
  background:var(--surface-sunken);
  box-shadow:var(--shadow-xs);
}
.graph { display:block; width:100%; height:auto; }
.graphpanel__cap {
  margin-top:var(--space-4);
  font-size:var(--text-xs); color:var(--text-muted); line-height:1.55;
  text-wrap:pretty;
}

.graph__edges path { stroke:var(--border-strong); }

.graph__node rect { fill:var(--surface); stroke:var(--border-strong); stroke-width:1; }
.graph__tier {
  font-size:8px; font-weight:700; letter-spacing:.09em;
  fill:var(--text-subtle); text-anchor:middle;
}
.graph__name {
  font-size:11.5px; font-weight:650;
  fill:var(--brand-navy); text-anchor:middle;
}

/* The same three tints the tier pills use, so the diagram and the pills on a
   real profile are visibly the same vocabulary. */

/* ── Differentiators ───────────────────────────────────────────────────── */
.diffs { gap:var(--space-6) var(--space-8); }
.diff { display:flex; align-items:flex-start; gap:var(--space-4); }
.diff__ic {
  flex:0 0 2.25rem; height:2.25rem; border-radius:var(--radius-md);
  display:grid; place-items:center;
  background:var(--primary-50); border:1px solid var(--primary-100);
  color:var(--primary-800);
}
.diff h3 { margin:0; }

/* ── Split authentication ──────────────────────────────────────────────
   Form on the left, brand panel on the right, and the FORM comes first in
   the document so a keyboard never travels through decoration to reach a
   field. Below 1024 the panel is not rendered at all — it holds no control,
   no link the flow needs and no information the form needs, so dropping it
   costs a phone nothing and buys it the whole screen. */
.auth-body { min-height:100vh; }
.auth { display:grid; min-height:100vh; }
@media (min-width:1024px) {
  .auth { grid-template-columns:minmax(0,1fr) minmax(0,28rem); }
}

.auth__form {
  display:flex; align-items:center; justify-content:center;
  padding:var(--space-8) var(--space-4);
  background:var(--bg);
}
.auth__formin { width:100%; max-width:26rem; }
.auth__brand { text-align:center; margin-bottom:var(--space-8); }

/* The panel. Order 2 on desktop so it sits on the right despite coming
   second in the document, which is where it also belongs visually. */
.auth__vis { display:none; }
@media (min-width:1024px) {
  .auth__vis {
    display:flex; align-items:center; justify-content:center;
    padding:var(--space-8);
    background:var(--brand-navy);
  }
}
.auth__visin { max-width:22rem; text-align:center; }
.brand--auth .brand__logo { height:1.75rem; width:auto; }
.auth__lead {
  margin:var(--space-6) 0 0;
  font-size:var(--text-lg); line-height:1.45; color:#dbe4f0;
  text-wrap:balance;
}
.auth__foot {
  margin:var(--space-5) 0 0;
  font-size:var(--text-xs); letter-spacing:.02em; color:#8fa0bd;
}


/* A skill chip in a list of them: wraps as a group so the name, its tier and
   its qualifier stay together on one line rather than breaking between. */
.job-card__skills { display:flex; flex-wrap:wrap; gap:var(--space-2); }
.job-card__skills .taxlist__i { padding:var(--space-1) var(--space-2); }

/* ═══════════════════════════════════════════════════════════════════════
   B2.1 — the visual refinement pass

   Not a redesign. The architecture, the palette, the icon family, the
   taxonomy and every semantic in the B2 system are unchanged. This section
   retunes surface, elevation, radius and rhythm so the same structure reads
   as a more considered piece of work.

   It comes last in the file deliberately: everything here is an override of
   values set above, kept together so the pass can be read — and reversed —
   as one thing.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Three elevation levels, and no more ────────────────────────────
     The application had a border on almost every container plus a shadow
     scale of four, so a card inside a card inside a panel drew three
     hairlines and three shadows to say one thing.

     Each level is a SHADOW-BORDER: the 1px ring is the first shadow layer
     rather than a real border, which means it costs no layout box, adapts
     to any background through transparency, and can be transitioned. The
     contact and ambient layers underneath are what separate a raised
     surface from a flat one.

       0  flat — sits on the page, ring only
       1  raised — a real object: a card, a queue row, a panel
       2  floating — genuinely above the page: drawer, popover, sticky
                     decision panel, toast

     Anything that is not one of those three gets no shadow at all. */
  --lift-0:
    0 0 0 1px rgb(15 23 42 / .06);
  --lift-1:
    0 0 0 1px rgb(15 23 42 / .06),
    0 1px 2px -1px rgb(15 23 42 / .06),
    0 2px 4px 0 rgb(15 23 42 / .04);
  --lift-1-hover:
    0 0 0 1px rgb(15 23 42 / .09),
    0 1px 2px -1px rgb(15 23 42 / .08),
    0 3px 6px 0 rgb(15 23 42 / .06);
  --lift-2:
    0 0 0 1px rgb(15 23 42 / .08),
    0 4px 8px -2px rgb(15 23 42 / .10),
    0 12px 24px -6px rgb(15 23 42 / .10);

  /* ── Radius, scaled to component size ───────────────────────────────
     A radius that reads as soft on a 400px panel reads as square on a
     20px chip, and the same number on both is what makes a hand-built UI
     look hand-built. Three tiers, and nested elements subtract their own
     padding so the curves stay concentric. */
  --r-chip:6px;     /* chips, badges, tier pills */
  --r-control:8px;  /* inputs, selects, buttons */
  --r-card:12px;    /* cards, queue items, table wrappers */
  --r-panel:16px;   /* large panels, drawers, the hero graph */

  /* ── Section rhythm ─────────────────────────────────────────────────
     A caption belongs to the thing under it, so the gap below a heading
     is SMALLER than the gap between sections. They were both 24px, which
     is why the page read as an undifferentiated stack. */
  --gap-heading:.75rem;
  --gap-section:2.5rem;

  /* Marketing pages get a wider type ramp than portals do. A display size
     on an operations console wastes the row it sits in. */
  --measure:68ch;
}

/* ── Cards stop being boxes with lines around them ──────────────────── */
.card {
  border:0;
  border-radius:var(--r-card);
  box-shadow:var(--lift-1);
}
.card__header { border-bottom-color:var(--gray-100); }
.card__footer { border-top-color:var(--gray-100); }
.card--interactive { transition:box-shadow var(--duration) var(--ease); }
.card--interactive:hover { box-shadow:var(--lift-1-hover); }

/* A card that is only there to group things inside another card is not a
   card. It keeps the grouping and loses the box. */
.card .card,
.card__body > .card {
  border:0; box-shadow:none; background:transparent;
  border-radius:0; padding:0;
}

/* ── Everything that was a bordered rectangle ───────────────────────── */
.table-wrap,
.famgroup,
.steps,
.acc,
.attention,
.taxlist__i,
.allclear,
.graphpanel,
.decide,
.skills-discover {
  border:0;
  box-shadow:var(--lift-1);
}
.table-wrap,
.famgroup,
.steps,
.acc,
.attention,
.decide { border-radius:var(--r-card); }
.graphpanel,
.skills-discover { border-radius:var(--r-panel); }
.taxlist__i,
.allclear { border-radius:var(--r-control); }

/* ── The same list, flowed ─────────────────────────────────────────────
   A taxonomy row earns its full width on the consent screen, where it holds
   a name, a level, whether it is required, and a minimum number of years —
   four facts a reader compares down the column. A partner specialism is a
   name and a level. Five of those in five full-width rows is a stack of
   boxes drawn around two words each, which is the shape B2.1 spent the rest
   of the pass removing. Same component and same tier pill; flowed. */
.taxlist--inline { display:flex; flex-wrap:wrap; gap:var(--space-2); }

/* The decision panel and the drawer are the only things genuinely floating
   above the page, so they are the only things at level 2. */
.decide { box-shadow:var(--lift-2); }
.skills-discover.is-drawer { box-shadow:var(--lift-2); border-radius:0; }
.toast { border:0; box-shadow:var(--lift-2); border-radius:var(--r-control); }

/* Dividers inside a raised surface are quieter than the surface's own ring:
   a full-strength line inside a card competes with the card's edge. */
.famgroup__h,
.perm__item,
.attention__item + .attention__item,
.step,
.decide__r,
.table thead th,
.table tbody td { border-color:var(--gray-100); }

/* ── Controls ───────────────────────────────────────────────────────── */
.input, .select, .textarea { border-radius:var(--r-control); }
.btn { border-radius:var(--r-control); }
.chip, .tchip, .tier, .badge { border-radius:var(--r-chip); }
.seg { border-radius:var(--r-control); border:0; box-shadow:var(--lift-0); }
/* Concentric: the track has 3px of padding, so the thumb is 8 − 3. */
.seg__item { border-radius:calc(var(--r-control) - 3px); }
.seg__item[aria-current="page"] { box-shadow:var(--lift-0); }

/* ── Section captions ───────────────────────────────────────────────── */
/* A section label is a caption for what follows, not a competing headline.
   These were bold 16px anchors above surfaces that already group their own
   content, so the page had two levels of heading doing one job. */
.label,
.side-nav__title,
.attention__heading,
.app-head,
.fact__label {
  font-size:var(--text-xs);
  font-weight:var(--weight-semibold);
  letter-spacing:.06em;
  text-transform:uppercase;
}

.section + .section { border-top:0; }
.section { padding-block:var(--gap-section); }

/* ── Prose measure ──────────────────────────────────────────────────── */
/* Body copy that runs the full width of a 1440px page loses the reader
   between rows. Portals are exempt: an operational row is scanned, not
   read, and capping it would leave a column of empty space. */
.lead, .prose p, .muted { max-width:var(--measure); }
.is-portal .lead { max-width:none; }

/* ── B2.1 · the hero ────────────────────────────────────────────────────
   The graph is the page's argument, so it gets the width it needs and a
   surface of its own. The tint is a wash and a hairline grid, not an
   effect: it should read as considered drafting, not as a crypto landing
   page. Nothing here is decorative enough to survive being noticed. */
.hero { padding-block:var(--space-12, 3rem) var(--space-10); }
@media (min-width:1024px) {
  .hero { grid-template-columns:minmax(0,1fr) minmax(0,28rem); align-items:center; }
}

.graphpanel {
  padding:var(--space-6);
  background:var(--surface);
  overflow:hidden;
}
/* Concentric: the panel has 24px of padding, so the graph inside is
   16 − 24 → clamped to square rather than forced to a wrong curve. */
.graph { border-radius:0; }

.graphpanel__cap {
  margin-top:var(--space-5);
  padding-top:var(--space-4);
  border-top:1px solid var(--gray-100);
  font-size:var(--text-xs); color:var(--text-muted); line-height:1.6;
  text-wrap:pretty;
}
.graphpanel__cap strong { color:var(--text); font-weight:var(--weight-semibold); }

/* The sourcing line. Darker than the caption around it and slightly heavier,
   because it is a statement of fact about the picture rather than more prose
   about the product — but NOT a heading and NOT its own paragraph, which would
   turn one caption into two competing blocks under an already busy figure. */
.graphpanel__src {
  color:var(--text); font-weight:var(--weight-medium);
}

/* The wash: blue at the top fading to nothing, with a teal breath in it.
   Stops are classes because gradient stops cannot take a CSS variable in
   an attribute, and a `style` attribute is forbidden by CSP. */
.gr-tint-a { stop-color:#2563EB; stop-opacity:.055; }
.gr-tint-b { stop-color:#14B8A6; stop-opacity:.02; }
.graph__grid { stroke:rgb(15 23 42 / .05); }

.graph__edges path { stroke:var(--gray-300); }
/* The one path that carries meaning: the match. Dashed so it reads as a
   relationship rather than containment, and teal because teal is the trust
   colour and a match is the platform's claim. */
.graph__edge--match {
  stroke:var(--teal-600); stroke-width:1.75;
  stroke-dasharray:5 4;
  animation:graph-flow 2.4s linear infinite;
}
@keyframes graph-flow { to { stroke-dashoffset:-18; } }
@media (prefers-reduced-motion:reduce) {
  .graph__edge--match { animation:none; }
}

.graph__node rect { fill:var(--surface); stroke:var(--gray-300); stroke-width:1; }
.graph__node--root rect { fill:var(--brand-navy); stroke:var(--brand-navy); }
.graph__node--family rect { fill:var(--surface); stroke:var(--primary-200, #bfdbfe); }
.graph__node--exact rect { fill:var(--primary-50); stroke:var(--primary-100); }
.graph__node--match rect { fill:var(--teal-50); stroke:var(--teal-200); }

.graph__root {
  font-size:14px; font-weight:700; fill:#fff; text-anchor:middle;
  letter-spacing:-.01em;
}
.graph__tier {
  font-size:7.5px; font-weight:700; letter-spacing:.1em;
  fill:var(--text-subtle); text-anchor:middle;
}
.graph__tier--match { fill:var(--teal-800); }
.graph__name {
  font-size:11.5px; font-weight:640;
  fill:var(--brand-navy); text-anchor:middle;
}
.graph__name--match { fill:var(--teal-800); }
.graph__foot {
  font-size:9px; letter-spacing:.04em;
  fill:var(--text-subtle); text-anchor:middle;
}
/* Family labels can be two lines; 10.5px keeps the longest inside its box. */

/* The graph is the page's argument, so it is sized to be read rather than
   noticed. At 28rem with 10px labels it rendered as a thumbnail of a diagram;
   the column is wider and the type inside it is set at sizes that survive the
   scale-down to the column's real width. */
@media (min-width:1024px) {
  .hero { grid-template-columns:minmax(0,1fr) minmax(0,30rem); }
}
.graph__root { font-size:16px; }
.graph__tier { font-size:8.5px; }
.graph__name { font-size:12.5px; }
.graph__name--fam { font-size:12px; }
.graph__foot { font-size:10px; }

/* ── B2.1 · homepage rhythm ─────────────────────────────────────────────
   The lower page was six sections of near-identical weight, each a stack of
   white rectangles on one background. Nothing told the eye where one idea
   ended and the next began, so the page read as a single long column.

   Rhythm now comes from the SURFACE, not from more boxes:

     hero        page ground
     journeys    two cards, the only cards above the fold
     latest      page ground, open
     technology  tinted band
     different   navy band — the one place the page raises its voice
     final CTA   page ground, open, centred

   A band is a full-width background with an ordinary .shell inside it, so it
   costs no 100vw, no pseudo-element and no horizontal overflow — which the
   responsive assertions exist to prevent. */
.band { width:100%; }
.band--tint { background:var(--surface-sunken); }
.band--navy { background:var(--brand-navy); }

.band__h { color:#fff; }
.band__lead { color:#c2cddf; font-size:var(--text-lg); max-width:var(--measure); }

/* ── Technology chips ───────────────────────────────────────────────────
   Ten families as chips rather than ten identical cards. The eight with no
   roles said so in eight bordered boxes; now they are simply quieter, and
   the ones that have roles carry the count — which is the difference the
   reader came to find. */
.famchips { display:flex; flex-wrap:wrap; gap:var(--space-3); }
.famchip {
  display:inline-flex; align-items:center; gap:var(--space-2);
  padding:var(--space-3) var(--space-4);
  border-radius:var(--r-control);
  background:var(--surface);
  box-shadow:var(--lift-0);
  font-size:var(--text-sm); font-weight:var(--weight-medium);
  color:var(--text); text-decoration:none;
  transition:box-shadow var(--duration) var(--ease), color var(--duration) var(--ease);
}
@media (hover:hover) and (pointer:fine) {
  .famchip:hover { box-shadow:var(--lift-1-hover); color:var(--primary-800); }
}
.famchip__ic { color:var(--primary-800); flex-shrink:0; }
.famchip--quiet { color:var(--text-muted); }
.famchip--quiet .famchip__ic { color:var(--text-subtle); }
.famchip__c {
  font-size:var(--text-xs); font-weight:var(--weight-semibold);
  color:var(--primary-800);
  padding-left:var(--space-2);
  border-left:1px solid var(--gray-200);
  font-variant-numeric:tabular-nums;
}

/* ── Differentiators, on navy ───────────────────────────────────────────
   Each was six to eight lines of prose. A visitor scans four headlines and
   stops at the one that matters to them, so each item is now a headline, one
   sentence, and a concrete example rendered in the product's OWN vocabulary
   — the tier pills, the four match states, the permission chips. The example
   is the proof the paragraph used to spend four lines describing. */
.diffs {
  display:grid; gap:var(--space-8) var(--space-8);
  grid-template-columns:1fr;
}
@media (min-width:768px)  { .diffs { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (min-width:1280px) { .diffs { grid-template-columns:repeat(4,minmax(0,1fr)); } }

.diff { display:block; }
.diff__ic {
  display:grid; place-items:center;
  width:2.5rem; height:2.5rem; margin-bottom:var(--space-4);
  border-radius:var(--r-control);
  background:rgb(255 255 255 / .07);
  box-shadow:inset 0 0 0 1px rgb(255 255 255 / .1);
  color:var(--teal-600);
}
.diff__h {
  margin:0 0 var(--space-2);
  font-size:var(--text-base); font-weight:var(--weight-bold);
  color:#fff; text-wrap:balance;
}
.diff__p {
  margin:0; font-size:var(--text-sm); line-height:1.6;
  color:#c2cddf; max-width:34ch;
}
.diff__eg {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2);
  margin:var(--space-4) 0 0;
}
/* 4.10:1 on navy fails AA even for a separator that carries meaning in a
   sequence. 5.04:1, the same value the sidebar captions use. */
.diff__arrow { color:#7986a3; font-size:var(--text-xs); }

/* The pills keep their own meaning on navy, but their light tints would
   glare against it, so they lose the fill and keep the words. */
.band--navy .tier,
.band--navy .tchip {
  background:rgb(255 255 255 / .08);
  border-color:rgb(255 255 255 / .16);
  color:#dbe4f0;
}
.band--navy .tchip--verified { color:#7fe3d3; border-color:rgb(20 184 166 / .4); }
.band--navy .tchip--active   { color:#9fe6bd; }
.band--navy .tchip--pending  { color:#f7cf8b; }
.band--navy .tchip--approved { color:#a8c6ff; }

/* The closing call to action: open, centred, and the only place on the page
   where prose is centred — which is what makes it read as a conclusion. */
.cta { text-align:center; padding-block:var(--space-16, 4rem); }
.cta__lead { max-width:44ch; margin-inline:auto; }
.cta .btn-group { display:flex; flex-wrap:wrap; gap:var(--space-3); justify-content:center; }

/* ── B2.1 · the taxonomy explorer ───────────────────────────────────────
   The left pane read as a conventional form panel: a label, a box, a list.
   It is the product's signature interaction, so it now reads as an explorer:
   a search field that looks like one, and results with real anatomy. */

/* A field with its glyph inside it, so it reads as a search box before it is
   focused. The icon is aria-hidden and positioned, not floated, so the input
   keeps its full width and its own focus ring. */
.searchfield { position:relative; }
.searchfield__ic {
  position:absolute; left:var(--space-3); top:50%;
  transform:translateY(-50%);
  display:grid; place-items:center;
  color:var(--text-subtle); pointer-events:none;
}
.searchfield__input { padding-left:2.375rem; }

.tax-picker__families {
  border:0; border-radius:var(--r-card);
  box-shadow:var(--lift-0);
  overflow:hidden;
}
.tax-family + .tax-family { border-top-color:var(--gray-100); }
.tax-family__summary { font-size:var(--text-sm); font-weight:var(--weight-semibold); }
.tax-family[open] > .tax-family__summary { border-bottom-color:var(--gray-100); }
/* A disclosure needs to say it is one. The marker is suppressed by the
   summary's own list-style, so the chevron is drawn here and rotates on open
   — CSS only, and it holds still under reduced motion. */
.tax-family__summary::after {
  content:""; flex:0 0 auto;
  width:.5rem; height:.5rem; margin-left:var(--space-2);
  border-right:1.5px solid var(--text-subtle);
  border-bottom:1.5px solid var(--text-subtle);
  transform:rotate(45deg) translate(-2px,-2px);
  transition:transform var(--duration-fast) var(--ease);
}
.tax-family[open] > .tax-family__summary::after {
  transform:rotate(-135deg) translate(-2px,-2px);
}
@media (prefers-reduced-motion:reduce) {
  .tax-family__summary::after { transition:none; }
}

/* ── The result row ─────────────────────────────────────────────────────
   Grid rather than a wrap, so name, path, tier and state each hold a fixed
   position and a column of results can be scanned down rather than read
   across. */
.tax-option {
  display:grid;
  grid-template-columns:auto auto minmax(0,1fr);
  align-items:start; gap:var(--space-2) var(--space-3);
  padding:var(--space-3);
  border-radius:var(--r-control);
}
.tax-option__glyph {
  display:grid; place-items:center;
  width:1.5rem; height:1.5rem; margin-top:-1px;
  border-radius:var(--radius-sm);
  background:var(--surface-sunken);
  color:var(--text-subtle);
  flex-shrink:0;
}
.tax-option__body { display:block; min-width:0; }
.tax-option__top {
  display:flex; align-items:center; gap:var(--space-2);
  flex-wrap:wrap;
}
.tax-option__name {
  font-size:var(--text-sm); font-weight:var(--weight-semibold);
  color:var(--text);
}
.tax-option__level { margin-left:auto; flex:0 0 auto; }
.tax-option__context {
  display:block; margin-top:.125rem;
  font-size:var(--text-xs); color:var(--text-muted);
}
.tax-option__foot {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-3);
  margin-top:var(--space-1);
}
.tax-option__alias { display:inline; margin:0; }

@media (hover:hover) and (pointer:fine) {
  .tax-option:hover:not(.tax-option--disabled) { background:var(--primary-50); }
  .tax-option:hover:not(.tax-option--disabled) .tax-option__glyph {
    background:var(--primary-100); color:var(--primary-800);
  }
}
.tax-option:has(.tax-option__input:checked) {
  background:var(--primary-50);
  box-shadow:inset 2px 0 0 var(--primary-600);
}
.tax-option:has(.tax-option__input:checked) .tax-option__glyph {
  background:var(--primary-600); color:#fff;
}
/* Already held: quieted, but the tick and the words say why rather than
   leaving a greyed row to be guessed at. */
.tax-option--disabled { opacity:1; cursor:not-allowed; }
.tax-option--disabled .tax-option__name,
.tax-option--disabled .tax-option__context { color:var(--text-muted); }
.tax-option--disabled .tax-option__glyph { background:var(--teal-50); color:var(--teal-mark); }

/* ── B2.1 · My Oracle profile ───────────────────────────────────────────
   An expertise map rather than stacked database rows. The family group keeps
   its header but gains an identity; the skills inside it lose their borders,
   because a bordered row inside a bordered group inside a bordered column is
   three boxes drawn to say one thing. */
.famgroup { overflow:hidden; }
.famgroup__h {
  padding:var(--space-4) var(--space-5);
  background:linear-gradient(180deg, var(--surface-sunken), var(--surface));
  border-bottom:1px solid var(--gray-100);
}
.famgroup__ic {
  flex:0 0 1.875rem; height:1.875rem;
  border-radius:var(--r-chip);
  background:var(--surface);
  box-shadow:var(--lift-0);
  color:var(--primary-800);
}
.famgroup__n { font-size:var(--text-sm); letter-spacing:-.005em; }
.famgroup__c {
  padding:.0625rem .4375rem;
  border-radius:var(--radius-full);
  background:var(--surface-sunken);
  color:var(--text-muted);
}

.skill-row {
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  gap:var(--space-2) var(--space-4);
  padding:var(--space-4) var(--space-5);
  border:0; border-radius:0; background:transparent; box-shadow:none;
}
.famgroup__b > .skill-row + .skill-row { border-top:1px solid var(--gray-100); }
@media (hover:hover) and (pointer:fine) {
  .famgroup__b > .skill-row:hover { background:var(--gray-25); }
}
.skill-row__head { display:contents; }
.skill-row__name {
  display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-2);
  margin:0; font-size:var(--text-sm); font-weight:var(--weight-semibold);
  color:var(--text);
}
.tax-path {
  margin:.1875rem 0 0;
  font-size:var(--text-xs); color:var(--text-muted);
}

/* The evidence line. One line, middot-separated, and only the facts the
   candidate actually stated. */
.skill-row__level { text-align:right; white-space:nowrap; align-self:start; }
/* The four proficiency segments are gone and the level is now the first item
   in a one-line list of stated facts — but the rule that set that word on its
   own line above the segments survived them. It was breaking "Expert · 9
   years" into "Expert" and a second line opening on the separator, which
   reads as a fragment. The word goes back inline with the facts it leads. */
.skill-row__level b { display:inline; }
.skill-row__ev { font-size:var(--text-sm); color:var(--text-muted); }
.skill-row__ev b { color:var(--text); font-weight:var(--weight-semibold); }
/* --gray-300 measured 1.48:1. A middot separating two facts is read, so it
   is set at --text-subtle like the facts around it. */
.skill-row__dot { color:var(--text-subtle); padding-inline:.125rem; }
.skill-row__none { font-size:var(--text-sm); color:var(--text-subtle); font-style:normal; }

/* Evidence chips and the edit disclosure sit under both columns. */
.skill-row__evidence,
.skill-row__edit { grid-column:1 / -1; }
.skill-row__evidence:empty { display:none; }
.skill-row__edit > summary { color:var(--text-muted); }
@media (hover:hover) and (pointer:fine) {
  .skill-row__edit > summary:hover { color:var(--primary-700); }
}

/* Unconfirmed is a state of the row, said in a chip and tinted very lightly —
   not a coloured left edge, which is the admonition shape the audit removed
   everywhere else. */
.skill-row--unconfirmed { background:var(--warning-50); border-left:0; }

/* The discovery pane is a companion, not a form. Sunken ground so the profile
   beside it reads as the subject. */
.skills-discover { background:var(--surface-sunken); }
.skills-discover .tax-picker__families,
.skills-discover .searchfield__input { background:var(--surface); }
.skills-discover__head .h3 { font-size:var(--text-base); }

@media (max-width:1023px) {
  .skill-row { grid-template-columns:minmax(0,1fr); }
  .skill-row__level { text-align:left; }
}

/* The family list scrolls, and it has to LOOK like it scrolls. At a 26rem cap
   the tenth family was sliced through the middle by the panel edge, which
   reads as a rendering fault rather than as "there is more below". The cap is
   now a multiple of the row height so a partial row is a visibly partial row,
   and a soft mask at the bottom edge says the list continues.

   mask-image is progressive: a browser without it simply gets the list. */
.tax-picker__families {
  max-height:27.5rem;
  -webkit-mask-image:linear-gradient(180deg,#000 calc(100% - 1.5rem),transparent);
  mask-image:linear-gradient(180deg,#000 calc(100% - 1.5rem),transparent);
}
.tax-picker__families:not(:hover) { scrollbar-width:thin; }
@media (max-width:599px) {
  .tax-picker__families { -webkit-mask-image:none; mask-image:none; }
}

/* ── B2.1 · consent ─────────────────────────────────────────────────────
   The screen is long and has to be. What it can do is let a reader process
   WHO, WHAT ROLE, WHAT IS SHARED and HOW LONG before any of the detail. */

/* The request strip. Navy-tinted rather than navy: the decision panel is the
   only fully navy object on this screen, and two would compete. */
.reqstrip {
  padding:var(--space-5) var(--space-6);
  border-radius:var(--r-card);
  background:linear-gradient(180deg, var(--primary-50), var(--surface));
  box-shadow:var(--lift-1);
  margin-bottom:var(--space-6);
}
.reqstrip__ask { margin:0; font-size:var(--text-sm); color:var(--text-muted); }
.reqstrip__ask b { color:var(--text); font-weight:var(--weight-semibold); }
.reqstrip__role {
  margin:var(--space-1) 0 0;
  font-size:var(--text-2xl, 1.5rem); font-weight:var(--weight-bold);
  line-height:1.2; letter-spacing:-.02em;
  color:var(--brand-navy); text-wrap:balance;
}
.reqstrip__facts {
  display:flex; flex-wrap:wrap; gap:var(--space-2) var(--space-5);
  margin:var(--space-4) 0 0; padding:0; list-style:none;
}
.reqstrip__facts li {
  display:inline-flex; align-items:center; gap:var(--space-2);
  font-size:var(--text-xs); color:var(--text-muted);
}
.reqstrip__facts .ic { color:var(--primary-800); flex-shrink:0; }

/* Permission group captions. */
.perm__group {
  margin:0; padding:var(--space-3) var(--space-5) var(--space-2);
  font-size:var(--text-xs); font-weight:var(--weight-bold);
  letter-spacing:.06em; text-transform:uppercase;
  color:var(--text-muted);
  border-top:1px solid var(--gray-100);
}
.card__header + .perm__group { border-top:0; }

/* ── Selection tiles ────────────────────────────────────────────────────
   Every tile is a <label> wrapping the SAME native radio or checkbox. The
   browser keeps arrow-key movement in the radio group, Space to toggle, the
   roving tab stop and the accessible name; nothing here reimplements any of
   that. The control stays visible inside the tile rather than being hidden,
   so the selected state never rests on the tint alone. */
.tiles { display:flex; flex-wrap:wrap; gap:var(--space-3); }
.tiles--compact .tile { flex:0 0 auto; min-width:5.5rem; }
.tile {
  position:relative;
  display:flex; align-items:center; gap:var(--space-3);
  padding:var(--space-3) var(--space-4);
  border-radius:var(--r-control);
  background:var(--surface);
  box-shadow:var(--lift-0);
  cursor:pointer;
  transition:box-shadow var(--duration-fast) var(--ease), background var(--duration-fast) var(--ease);
}
.tile--row { flex:1 1 18rem; align-items:flex-start; }
.tile__in { flex:0 0 auto; margin:0; accent-color:var(--primary-600); }
.tile--row .tile__in { margin-top:.1875rem; }
.tile__ic {
  display:grid; place-items:center;
  width:1.75rem; height:1.75rem; flex:0 0 auto;
  border-radius:var(--radius-sm);
  background:var(--surface-sunken); color:var(--text-muted);
}
.tile__body { min-width:0; }
.tile__body b {
  display:block; font-size:var(--text-sm); font-weight:var(--weight-semibold);
  color:var(--text);
}
.tile__body > span {
  display:block; margin-top:.125rem;
  font-size:var(--text-xs); color:var(--text-muted); line-height:1.5;
}
.tile__n { font-size:var(--text-lg); font-weight:var(--weight-bold); color:var(--text); }
.tile__u { font-size:var(--text-xs); color:var(--text-muted); }
.tiles--compact .tile__body { display:flex; align-items:baseline; gap:.25rem; }

@media (hover:hover) and (pointer:fine) {
  .tile:hover { box-shadow:var(--lift-1-hover); }
}
.tile:has(.tile__in:checked) {
  background:var(--primary-50);
  box-shadow:0 0 0 1.5px var(--primary-600);
}
.tile:has(.tile__in:checked) .tile__ic { background:var(--primary-100); color:var(--primary-800); }
/* The focus ring belongs to the tile, because the input inside it is small
   and its own ring would be lost against the tile's edge. */
.tile:has(.tile__in:focus-visible) {
  outline:2px solid var(--primary-600); outline-offset:2px;
}

/* ── B2.1 · auth panel ──────────────────────────────────────────────────
   Three facts about the product rather than a motif. The panel is decoration
   in the structural sense — it holds no control and the form does not need it
   — but decoration that says something true is better than decoration that
   says nothing. */
.auth__facts {
  display:grid; gap:var(--space-5);
  margin:var(--space-8) 0 0; padding:0; list-style:none;
  text-align:left;
}
.auth__facts li { display:flex; align-items:flex-start; gap:var(--space-3); }
.auth__fic {
  display:grid; place-items:center;
  width:2rem; height:2rem; flex:0 0 auto;
  border-radius:var(--r-chip);
  background:rgb(255 255 255 / .07);
  box-shadow:inset 0 0 0 1px rgb(255 255 255 / .1);
  color:var(--teal-600);
}
.auth__facts b {
  display:block; font-size:var(--text-sm); font-weight:var(--weight-semibold);
  color:#fff; margin-bottom:.125rem;
}
.auth__facts li > span:last-child {
  font-size:var(--text-xs); color:#9fb0c9; line-height:1.5;
}
.auth__foot { margin-top:var(--space-8); }

/* ── B2.1 · portal shell polish ─────────────────────────────────────────
   Fit and finish, not a rebuild. The rail keeps its width and its colour. */

/* Nav rows: 36px with a 10px radius, one label weight, and selection carried
   by surface and the teal bar rather than by a weight jump — a bold active
   row makes the whole list look unevenly set. */
.is-portal .account-layout__side .side-nav__link {
  min-height:2.25rem;
  border-radius:var(--r-control);
  margin-inline:calc(var(--space-2) * -1);
  padding-inline:var(--space-3);
  font-weight:var(--weight-medium);
  transition:background var(--duration-fast) var(--ease), color var(--duration-fast) var(--ease);
}
.is-portal .account-layout__side .side-nav__link.is-active,
.is-portal .account-layout__side .side-nav__link[aria-current="page"] {
  font-weight:var(--weight-medium);
  border-radius:var(--r-control);
}
/* Groups need more air between them than their rows need inside them: that
   difference is what makes the grouping legible rather than the list simply
   loose. */
.is-portal .account-layout__side .side-nav__title {
  margin-top:var(--space-6); margin-bottom:var(--space-2);
  text-transform:none; letter-spacing:.01em;
  font-size:var(--text-xs);
}
.is-portal .account-layout__side .side-nav__list + .side-nav__title { margin-top:var(--space-6); }
.is-portal .account-layout__side .side-nav__count {
  min-width:1.25rem; padding:0 .3125rem;
  border-radius:var(--radius-full);
  font-variant-numeric:tabular-nums;
}

/* The utility bar: a little more air, and hover states that say a thing is
   pressable before it is pressed. */
.pbar__in { min-height:3.5rem; gap:var(--space-5); }
.pbar__ctx b { letter-spacing:-.005em; }
.pbar__switchlink,
.pbar__bell,
.pbar__usertoggle { transition:background var(--duration-fast) var(--ease), color var(--duration-fast) var(--ease); }
.pbar__usertoggle { border-radius:var(--r-control); }
.pbar__avatar { box-shadow:inset 0 1px 0 rgb(255 255 255 / .12); }
.pbar__menu { border:0; box-shadow:var(--lift-2); border-radius:var(--r-card); }
.pbar__menuitem { border-radius:calc(var(--r-card) - 8px); }

/* ── Microinteractions ──────────────────────────────────────────────────
   120–200ms, and every one of them either confirms a press or says that
   something is pressable. Nothing moves layout, nothing animates on load, and
   the reduced-motion block at the top of this file already disables the lot. */
.attention__item { transition:background var(--duration-fast) var(--ease); }
@media (hover:hover) and (pointer:fine) {
  .attention__item:hover { background:var(--gray-25); }
  .app-row:hover { background:var(--gray-25); }
  .allclear:hover { box-shadow:var(--lift-1-hover); }
  .mstrip__m:hover { background:var(--gray-25); }
}
.app-row { transition:background var(--duration-fast) var(--ease); }
.allclear { transition:box-shadow var(--duration-fast) var(--ease); }
.mstrip__m { transition:background var(--duration-fast) var(--ease); }

/* A pressed button should look pressed. 1px is enough and costs no layout,
   because transform does not reflow. */
.btn { transition:background var(--duration-fast) var(--ease), box-shadow var(--duration-fast) var(--ease), transform var(--duration-fast) var(--ease); }
.btn:active:not(:disabled) { transform:translateY(1px); }

/* A form that has been submitted says so, rather than sitting still while the
   server works. Applied by the script on submit; without it the button simply
   stays as it was, which is what it did before. */
.btn[data-busy] { opacity:.72; cursor:progress; }

/* ── Empty states ───────────────────────────────────────────────────────
   Concise: a glyph, a title, one sentence, one action. The dashed box that
   used to hold them was the largest object on several screens. */
.empty {
  border:0; border-radius:var(--r-card);
  background:var(--surface); box-shadow:var(--lift-0);
  padding:var(--space-8) var(--space-6);
}
/* ── B2.1 · the metric band, wrapping evenly ────────────────────────────
   auto-fit fills each row but leaves whatever is left over on a ragged last
   row: eight metrics in a 1440px column gave seven and then one, beside a
   wide grey gap. A fixed column count per breakpoint wraps to a rectangle.

   B2.1 claimed here that the counts were chosen so that the common band
   lengths all divide evenly. Six and seven do not divide by four, and the
   partner band holds six: the claim was untested and the closeout review
   found the two empty cells it left. What follows the rule replaces it. */
.mstrip {
  grid-template-columns:repeat(2, minmax(0,1fr));
  background:var(--surface);
  border:0; border-radius:var(--r-card);
  box-shadow:var(--lift-1);
}

/* ── The band's separators are drawn by the tiles, not by the gap ───────
   The gap trick — 1px of gap over a grey container — draws a clean grid only
   while the tiles fill every cell. The partner band holds six, and at four
   columns the last row leaves two empty cells with the container's grey
   showing through them: a phantom tile that reads as a rendering fault
   rather than as absence. The employer band varies with permissions and can
   land on six or seven, so the ragged count is not one page's accident.

   So each tile paints its own left and top hairline instead. The tiles in
   the first column and the first row paint theirs one pixel outside the
   band, where `overflow:hidden` removes it, which leaves internal lines
   only — and leaves any unfilled tail the same colour as a tile, at every
   count and every breakpoint, with no counting rule in the stylesheet. */
.mstrip { gap:0; }
.mstrip__m { box-shadow:-1px 0 0 var(--gray-200), 0 -1px 0 var(--gray-200); }
@media (min-width:640px)  { .mstrip { grid-template-columns:repeat(3, minmax(0,1fr)); } }
@media (min-width:1024px) { .mstrip { grid-template-columns:repeat(4, minmax(0,1fr)); } }
.mstrip__m { padding:var(--space-4) var(--space-5); }
.mstrip__value { letter-spacing:-.02em; }

/* ── B2.1 · dashboard: main plus a contextual column ────────────────────
   The wide column carries the work; the narrow one carries what is true
   about the account. Below 1024 it is one column and the aside falls to the
   bottom, which is where those blocks were before. */
.dash-cols { display:grid; gap:var(--space-8); align-items:start; }
@media (min-width:1024px) {
  .dash-cols { grid-template-columns:minmax(0,1fr) 20rem; }
}
.dash-cols__main { min-width:0; }
.dash-cols__main > .section:first-child { padding-top:0; }
.dash-cols__side { min-width:0; }
.dash-cols__acts { display:grid; gap:var(--space-2); }

/* The rail scrolls when the portal has more sections than the window is tall,
   and it has to look like it scrolls. Without this the last nav row is sliced
   through the middle by the sticky boundary, which reads as a clipped layout
   rather than as "there is more below". Same soft mask as the taxonomy list.

   Progressive: a browser without mask-image simply gets the scroll. */
@media (min-width:1024px) {
  .is-portal .account-layout__side {
    -webkit-mask-image:linear-gradient(180deg,#000 calc(100% - 2rem),transparent);
    mask-image:linear-gradient(180deg,#000 calc(100% - 2rem),transparent);
    padding-bottom:var(--space-10);
  }
}

/* ── B2.1 · admin recents ───────────────────────────────────────────────
   Two feeds and one table, grouped so the block reads as one region of
   "what has been happening" rather than three separate screens of table.
   The audit log keeps its own full-width section below: it is genuinely
   tabular and it is the one an administrator reads down a column. */
.admin-recent { display:grid; gap:var(--space-6); }
@media (min-width:1280px) { .admin-recent { grid-template-columns:repeat(2, minmax(0,1fr)); } }
.admin-recent > .section { padding-block:0; }
.admin-recent > .section:last-child:nth-child(odd) { grid-column:1 / -1; }

/* A feed row: who, what, and when. An event is read one at a time, so it is
   a row of facts rather than a four-column grid holding three of them. */
.feed { margin:0; padding:0; list-style:none; border-radius:var(--r-card); box-shadow:var(--lift-1); overflow:hidden; background:var(--surface); }
.feed__i {
  display:flex; align-items:center; gap:var(--space-3);
  padding:var(--space-3) var(--space-4);
  flex-wrap:wrap;
}
.feed__i + .feed__i { border-top:1px solid var(--gray-100); }
@media (hover:hover) and (pointer:fine) { .feed__i:hover { background:var(--gray-25); } }
.feed__av {
  flex:0 0 1.875rem; height:1.875rem; border-radius:var(--radius-full);
  display:grid; place-items:center;
  background:var(--surface-sunken); color:var(--text-muted);
  font-size:var(--text-xs); font-weight:var(--weight-bold);
}
.feed__b { min-width:0; flex:1 1 10rem; }
.feed__b b { display:block; font-size:var(--text-sm); font-weight:var(--weight-semibold); color:var(--text); }
.feed__b > span { display:block; font-size:var(--text-xs); color:var(--text-muted); overflow-wrap:anywhere; }
.feed__meta { display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-2); margin-left:auto; }
.feed__when { font-size:var(--text-xs); color:var(--text-subtle); white-space:nowrap; }

/* The four differentiators are peers in one row, so their example rows have to
   land on one baseline. Their sentences are different lengths, so without this
   each chip row sat at its own height and the row read as four unrelated
   blocks rather than one comparison. A flex column with the example pushed
   down aligns them without forcing the sentences to be the same length. */
.diff { display:flex; flex-direction:column; height:100%; }
.diff__eg { margin-top:auto; padding-top:var(--space-4); }

/* ── B2.1 · consent on a phone ──────────────────────────────────────────
   The screen is long by necessity and was getting longer: the request strip
   added height at the top, which is a good trade for comprehension and a bad
   one for scrolling if nothing else gives. So the gaps give.

   Nothing is hidden and nothing is collapsed that was open. What changes is
   density: the section rhythm tightens, requirement rows and permission rows
   lose a step of padding, and the duration tiles sit four-across instead of
   one per line — a 90px tile is still a 44px target with room to spare. */
@media (max-width:767px) {
  .consent-cols { gap:var(--space-5); }
  .consent-cols .card + .card,
  .consent-cols > div > .card,
  .consent-cols > div > section { margin-top:var(--space-5); }

  .reqstrip { padding:var(--space-4) var(--space-5); margin-bottom:var(--space-5); }
  .reqstrip__facts { gap:var(--space-2) var(--space-4); margin-top:var(--space-3); }

  .card__body { padding:var(--space-5); }
  .perm__item { padding:var(--space-3) var(--space-4); }
  .perm__group { padding:var(--space-3) var(--space-4) var(--space-2); }
  .taxlist { gap:var(--space-1); }
  .taxlist__i { padding:var(--space-2); }
  .opp { gap:var(--space-3); }
  .step { padding:var(--space-3) var(--space-4); }

  /* Four across: the tile is its own 44px target and the row costs one line
     instead of four. */
  .tiles--compact { display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:var(--space-2); }
  .tiles--compact .tile { min-width:0; padding:var(--space-3) var(--space-2); justify-content:center; }
  /* Visually hidden, NOT display:none. display:none would take the radio out
     of the accessibility tree and out of the tab order, replacing a real radio
     group with four unlabelled coloured boxes. Clipped, it keeps its role, its
     name, its group membership, arrow-key movement and focus — and the tile
     carries the visible state twice over, as a tinted surface and a ring, with
     the label itself being the value. */
  .tiles--compact .tile__in {
    position:absolute; width:1px; height:1px; padding:0; margin:-1px;
    overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
  }
  .tiles--compact .tile:has(.tile__in:checked) { box-shadow:0 0 0 2px var(--primary-600); }
  .tiles--compact .tile:has(.tile__in:focus-visible) { outline:2px solid var(--primary-600); outline-offset:2px; }

  .tile--row { flex:1 1 100%; padding:var(--space-3); }
}
