/* =============================================================================
   Arabtec geometry + typography system layer — NO COLOUR.

   Two sources, merged:
   1. The shape / spacing / sizing / alignment system measured from claude.ai,
      claude.com, platform.claude.com and anthropic.com on 4 Sep 2026.
   2. shadcn/ui preset `b7CSfhjSC` (style "mira", radius "medium"), adopted for
      its DESIGN LANGUAGE ONLY on 11 Sep 2026.

   ---- What was taken from preset b7CSfhjSC -------------------------------
   ADOPTED   style mira · radius medium (10px base + x0.6/0.8/1/1.4/1.8/2.2/2.6
             ramp) · menuAccent subtle · fontHeading inherit · lucide icon
             GEOMETRY (24 viewBox, fill none, round caps/joins — already ours)
   REJECTED  theme "teal"      would have replaced Arabtec green #008064
             baseColor "mist"  would have replaced surfaces / ink / hairlines
             chartColor "mist" would have replaced the chart + stage ramp
             menuColor         colour-bearing
             font roboto-slab  a web font; the approved Arabtec face is
                               Arial/Helvetica, present on every target device,
                               so the app makes no third-party request and has
                               no flash of unstyled text. The preset's TYPE
                               SCALE is adopted; its TYPEFACE is not.
             .dark block       this product is light-only by design.

   HARD RULE FOR THIS FILE: it declares NO colour literal, NO background
   literal, NO border-colour literal, NO font-family. Only geometry — radius,
   spacing, size, alignment, ratio, position, weight and neutral elevation.
   Where a rule needs a surface it must reference an Arabtec token
   (var(--surface), var(--muted), ...) and never a hex or a preset value.
   The Arabtec palette, typeface, logo, watermark and page background are
   untouched by construction, not by care.

   Loads 4th of 5. Delete the <link> to revert completely.

   ---- What was measured -------------------------------------------------
   radius     claude.ai 6px x86, 10px x30, 8px, 24px · claude.com 4/8/12/24
              preset mira/medium: 10px base, ramp above
   spacing    strict 4px base; canonical 4/8/12/16/24/32
   controls   button+input h40 r10 pad 0 16 fs15 fw500 · compact h36 r8 fs14
   nav item   h32 r8 pad 0 8-12 fs14 fw400
   table      td pad 8, fs14, row 36-40, header NOT uppercase
   borders    0.5px hairline dominant, 1px next; no 1.5px anywhere
   elevation  flat. inset hairline ring + 1px lift; overlays get a 4-layer whisper
   ======================================================================== */

/* ---------------------------------------------------------------- 1. tokens */
:root {
  /* --- RADIUS ------------------------------------------------------------
     Preset b7CSfhjSC, radius "medium": --radius 0.625rem = 10px, with the
     mira ramp x0.6 / x0.8 / x1 / x1.4 / x1.8 / x2.2 / x2.6. */
  --cl-r-base: 10px;                 /* the preset's --radius */
  --cl-r-xs:  4px;                   /* micro chips, checkbox, swatch (below ramp) */
  --cl-r-sm:  6px;                   /* menu rows, small icon buttons, badges */
  --cl-r-md:  10px;                  /* nav items, secondary buttons, tab track */
  --cl-r-lg:  10px;                  /* primary buttons, inputs, small cards */
  --cl-r-xl:  10px;                  /* cards, panels, popovers (premium layer: 10px) */
  --cl-r-2xl: 18px;                  /* modals, sheets */
  --cl-r-3xl: 22px;                  /* full-bleed feature surfaces */
  --cl-r-4xl: 26px;                  /* hero surfaces */
  --r-pill: 999px;                   /* counts and tags keep the pill */

  /* --- SPACING -----------------------------------------------------------
     Canonical scale. New work uses ONLY these six steps. */
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  24px;
  --sp-6:  32px;

  /* Legacy 4px-base scale, still referenced by ~200 existing rules in this
     file. --cl-2 (6px), --cl-6 (20px) and --cl-8 (28px) sit OFF the canonical
     scale and are scheduled for migration to --sp-2 / --sp-5 / --sp-6 during
     component standardisation. Do not add new references to them. */
  --cl-1: 4px;  --cl-2: 6px;  --cl-3: 8px;  --cl-4: 12px; --cl-5: 16px;
  --cl-6: 20px; --cl-7: 24px; --cl-8: 28px; --cl-9: 32px; --cl-10: 40px;
  --cl-11: 48px; --cl-12: 64px;

  /* Page gutters by viewport — mobile 16 / tablet 20-24 / desktop 24-32.
     Applied to .content in section 12. */
  --sp-page-x: var(--sp-6);          /* desktop  32 */
  --sp-page-y: var(--sp-5);          /* desktop  24 */

  /* --- TYPOGRAPHY --------------------------------------------------------
     Scale only. The FACE is Arabtec's approved Arial/Helvetica and is set in
     arabtec-design-system.css (--font); this file never declares font-family.
     Preset fontHeading "inherit" is honoured: headings share the body face and
     are distinguished by size, weight and tracking, never by a second family. */
  --ts-display: 28px; --lh-display: 1.2;  --tr-display: -.015em;
  --ts-title:   28px; --lh-title:   1.3;  --tr-title:   -.012em;  /* page title */
  --ts-h2:      21px; --lh-h2:      1.3;  --tr-h2:      -.008em;
  --ts-h3:      17px; --lh-h3:      1.35; --tr-h3:      -.006em;  /* card title */
  --ts-h4:      15px; --lh-h4:      1.4;  --tr-h4:      normal;
  --ts-body:    14px; --lh-body:    1.5;  --tr-body:    normal;
  --ts-sm:      13px; --lh-sm:      1.45;
  --ts-xs:      12px; --lh-xs:      1.45;
  --ts-2xs:     11px; --lh-2xs:     1.4;                          /* eyebrows, counts */
  --fw-normal: 400; --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700;

  /* --- ELEVATION ---------------------------------------------------------
     Neutral by construction — black alpha only, never a hue. */
  --cl-lift:    0 1px 2px rgba(0, 0, 0, .05);
  --cl-raise:   inset 0 0 0 1px rgba(0, 0, 0, .07), 0 1px 2px rgba(0, 0, 0, .05);
  --cl-overlay: 0 4px 24px rgba(0, 0, 0, .04), 0 4px 32px rgba(0, 0, 0, .04),
                0 2px 64px rgba(0, 0, 0, .03), 0 16px 32px rgba(0, 0, 0, .03);
  --cl-pop:     0 1px 2px rgba(0, 0, 0, .04), 0 10px 28px rgba(0, 0, 0, .07),
                0 28px 64px rgba(0, 0, 0, .07);

  /* --- COMPONENT DIMENSIONS ---------------------------------------------- */
  --cl-ctl-lg: 40px;                 /* primary button, input */
  --cl-ctl-md: 36px;                 /* default button */
  --cl-ctl-sm: 32px;                 /* compact button */
  --cl-touch:  44px;                 /* mobile minimum target — asserted by tests */
  --cl-nav-h:  34px;
  --cl-row-h:  40px;
  --cl-icon-size: 18px;              /* default <Icon size> */
  --cl-icon-stroke: 1.5;             /* lucide geometry at Arabtec's lighter weight.
                                        The preset's lucide default is 2; flipping
                                        this one value adopts it app-wide. */
  --cl-hairline: 1px;

  /* --- BREAKPOINTS -------------------------------------------------------
     Canonical set. CSS cannot read a custom property inside a media query, so
     these are the single documented source of truth that every @media in the
     product must be written against:

       --bp-sm   640px   phone
       --bp-md   900px   tablet portrait / stacked tables
       --bp-lg   1180px  laptop / sidebar pressure
       --bp-xl   1440px  desktop

     The product currently also contains 480/560/700/720/820/860/1000/1024/1100
     /1200 from earlier work. Those are legacy and are to be folded into the
     four above during component standardisation; none are introduced here. */
  --bp-sm: 640px; --bp-md: 900px; --bp-lg: 1180px; --bp-xl: 1440px;

  /* Shell proportions — a wider, quieter rail and a shorter bar. */
  --sidebar-w: 264px;
  --topbar-h: 56px;

  /* Remap the app's own geometry tokens so token-driven rules follow along.
     applyBranding() clears any inline --radius / --card-radius, so these win.
     COLOUR TOKENS ARE NOT REMAPPED HERE AND MUST NOT BE. */
  --card-radius: var(--cl-r-xl);
  --radius: var(--cl-r-md);
  --r-md: var(--cl-r-md);
}

/* ------------------------------------------------------- 2. shell + chrome */
.sidebar { padding: var(--cl-4) var(--cl-4) var(--cl-6); }
.sidebar.collapsed { --sidebar-w: 68px; padding: var(--cl-4) var(--cl-2) var(--cl-6); }
.sidebar-head {
  padding: var(--cl-2) var(--cl-3) var(--cl-4);
  gap: var(--cl-3); min-height: var(--cl-ctl-lg); align-items: center;
}
.nav { padding-top: var(--cl-1); }
.nav-section {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: none;
  padding: var(--cl-5) var(--cl-3) var(--cl-2);
}

.topbar {
  height: var(--topbar-h);
  padding: 0 var(--cl-6); gap: var(--cl-4);
  border-bottom-width: 1px;
}

/* Reserve the launcher corner only on shells that actually render the dock. */
.content { padding: var(--sp-page-y) var(--sp-page-x); }
.content.has-dock { padding-bottom: 104px; }

/* -------------------------------------------------------- 3. navigation */
.nav-item {
  min-height: var(--cl-nav-h);
  padding: 0 var(--cl-4) 0 var(--cl-3);
  border-radius: var(--cl-r-md);
  font-size: 14px; font-weight: 400;
  gap: var(--cl-3); margin-bottom: 2px;
}
.nav-item.active { font-weight: 600; }
.nav-item:focus-visible { outline-width: 2px; outline-offset: 2px; }
.nav-item .nav-icon { width: 18px; height: 18px; flex: 0 0 18px; }
.nav-count {
  min-width: 20px; height: 20px; padding: 0 var(--cl-2);
  border-radius: var(--r-pill); font-size: 11px; font-weight: 600;
}
.sidebar.collapsed .nav-item { padding: 0; justify-content: center; }

/* ------------------------------------------ 4. page heads + type rhythm */
.page-head, .dash-head {
  margin-bottom: var(--cl-7); gap: var(--cl-5); align-items: flex-start;
  /* The header is a flex ROW whose actions block refuses to shrink
     (.page-head-actions is flex-shrink:0) while .page-head-main is allowed to
     collapse (min-width:0, set so long titles truncate rather than overflow).
     On Talent Pool the actions come to ~790px — wider than the 697px header at
     a 1024px window — so with nowrap the only thing that could give was the
     title block, which went to width:0 and rendered the subtitle one word per
     line, 18 lines tall. Letting the row wrap puts the actions on their own
     line instead, which is what the layout wanted all along. */
  flex-wrap: wrap;
}
/* A real basis, so the title block claims a full row once the actions wrap
   rather than sharing one and collapsing again. min-width:0 stays: it is what
   lets a long title ellipsize instead of forcing the row wider. */
.page-head-main { flex: 1 1 320px; min-width: 0; }
.page-title, .page-head .page-title, .dash-head .page-title {
  font-size: 28px; line-height: 1.15; font-weight: 800; letter-spacing: -.02em;
}
.page-sub { font-size: 14px; line-height: 1.5; margin-top: var(--cl-2); max-width: 68ch; }

/* A status pill must never let its label out of its own border. .status-chip
   already sets white-space:nowrap; .badge never did, so in a narrow column
   "Pending Approval" wrapped to two lines inside a pill still drawn at its
   24px single-line height and the second line rendered outside the border.
   Measured on Hiring Requests at 1024px: text box 47.4x30.9 in a 65.4x24 pill.
   Same rule, same result — the pill grows to fit its text, and the table's
   existing overflow-x owner handles the rest. */
.badge { white-space: nowrap; }

/* The eyebrow: a tracked uppercase label with the red dash before it
   (the dash and its colour live in arabtec-design-system.css, section 12). */
.breadcrumb, .page-head .breadcrumb, .dash-eyebrow {
  font-size: 11px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  margin-bottom: var(--cl-3);
}

.card-head, .modal-head, .dash-head {
  padding-bottom: var(--cl-4); gap: var(--cl-4);
}
.card-head h2, .card-head h3, .modal-head h2, .modal-head h3 {
  font-size: 17px; line-height: 1.35; font-weight: 600; letter-spacing: -.006em;
}

/* Heading scale + the big top / no bottom margin pattern from the docs site. */
.content h2 { font-size: 21px; line-height: 1.3;  font-weight: 600; margin: var(--cl-9) 0 var(--cl-4); }
.content h3 { font-size: 17px; line-height: 1.35; font-weight: 600; margin: var(--cl-7) 0 var(--cl-3); }
.content h4 { font-size: 15px; line-height: 1.4;  font-weight: 600; margin: var(--cl-6) 0 var(--cl-2); }
.content h2:first-child, .content h3:first-child, .content h4:first-child { margin-top: 0; }

/* ---------------------------------------------------- 5. cards + surfaces */
.card, .toolbar, .ticket-header-card, .cand-card, .rq-card, .ticket-card,
.login-card, .forced-pw-card, .notice, .pcard, .scope-card, .kpi, .dash-kpi,
.report-grid > *, .ats-card-grid > * {
  border-radius: var(--cl-r-xl);
}
.card { border-width: 1px; box-shadow: var(--cl-lift); }
.card-pad, .card > .card-pad { padding: var(--cl-6); }
.toolbar {
  border-radius: var(--cl-r-xl); padding: var(--cl-4) var(--cl-5);
  gap: var(--cl-3); align-items: center;
}
.ats-card-grid, .dash-grid-2, .report-grid { gap: var(--cl-5); }
/* Match the 16px/20px rhythm the rest of the card system uses (dash-grid-2's
   gap, card-pad's inset) instead of the old 12px/16px pair — this layer was
   the thing pulling the KPI row off that rhythm after arabtec-design-system.css
   (which already sets gap/padding to 16/20px) had put it back. */
.dash-kpi-row { gap: var(--cl-5); }
.dash-kpi, .kpi { border-radius: var(--cl-r-xl); padding: var(--cl-6); box-shadow: var(--cl-lift); }
/* Stat tile: the number leads, its label reads as an eyebrow beneath it. */
.dash-kpi { flex-direction: column; justify-content: flex-start; gap: var(--cl-2); min-height: 0; }
.dash-kpi-val, .kpi-value { order: 0; font-size: 32px; line-height: 1.1; font-weight: 800; letter-spacing: -.02em; }
.dash-kpi-label, .kpi-label { order: 1; font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; }
.dash-kpi-hint { order: 2; }
.dash-kpi-hint { font-size: 12px; line-height: 1.45; margin-top: var(--cl-1); }
.dash-kv { gap: var(--cl-2); font-size: 14px; }

/* --------------------------------------------------------- 6. buttons */
/* Default 40px and compact 32px controls, all on an 8px radius. */
.btn, button.btn, a.btn {
  min-height: var(--cl-ctl-lg);   /* 40px — matches claude.ai's measured button and PR #6's --at-control-h */
  padding: 0 var(--cl-5);
  border-radius: var(--cl-r-md);
  border-width: 1px;
  font-size: 14px; font-weight: 500; line-height: 1.2;
  letter-spacing: normal;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--cl-2);
  box-shadow: none;
}
.btn.btn-lg { min-height: var(--cl-ctl-lg); padding: 0 var(--cl-6); font-size: 15px; }
.btn.btn-sm, .btn-sm { min-height: var(--cl-ctl-sm); padding: 0 var(--cl-4); font-size: 13px; border-radius: var(--cl-r-md); }
.btn.btn-xs, .btn-xs { min-height: var(--cl-ctl-sm); padding: 0 var(--cl-4); font-size: 13px; border-radius: var(--cl-r-md); }
/* `.btn.small` is the third spelling of the compact button and the compact rule
   above never listed it, so it kept a 34px box on `7px 13px` from an earlier
   sheet. Height and padding only: its font size is left where it is. Section 20
   (1) has the measurements. */
.btn.small { min-height: var(--cl-ctl-sm); padding: 0 var(--cl-4); }
.btn-ghost, .btn-link { border-width: 1px; }
.btn:focus-visible { outline-width: 2px; outline-offset: 2px; }

.icon-btn {
  width: var(--cl-ctl-lg); height: var(--cl-ctl-lg);
  border-radius: var(--cl-r-md); border-width: 1px;
  display: inline-grid; place-items: center; padding: 0;
}
/* View switches contain labels (Cards / Table / Board), not just an icon.
   A 36px square makes their text escape the hit area at ordinary font sizes. */
.view-toggle-btn {
  width: auto; height: auto; min-height: var(--cl-ctl-sm);
  padding: var(--cl-2) var(--cl-4); flex: 0 0 auto;
  border-radius: var(--cl-r-md); border-width: 1px;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn-row, .btn-group, .actions, .card-actions, .modal-foot { gap: var(--cl-3); }

/* ---------------------------------------------------- 7. form controls */
.field input, .field select, .field textarea,
.toolbar input, .toolbar select, .cmdk-input,
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=search], input[type=date], input[type=datetime-local], input[type=time], select,
/* a bare <input> carries no type attribute, so the list above never matches it */
input:not([type]), .gsearch input, .cmdk input {
  border-radius: var(--cl-r-md);
  min-height: var(--cl-ctl-lg);
  padding: 0 var(--cl-4);
  font-size: 14px; line-height: 1.4;
  border-width: 1px;
}
.field textarea, textarea {
  border-radius: var(--cl-r-md);
  padding: var(--cl-4); min-height: 96px; line-height: 1.55; font-size: 14px;
}
/* Single-value selects draw their own box (2026-10-15). WebKit — Safari and
   every iOS browser — ignores min-height and padding on a select painted with
   the native menulist appearance and sizes it from its font: 21px next to the
   40/44px inputs it sits beside. With the native appearance off, the select
   obeys the same control height as every other field in every engine; the
   chevron replaces the native arrow and the right padding keeps text off it.
   Multi-row selects (`multiple` / `size`) keep their native list box. */
select:not([multiple]):not([size]) {
  -webkit-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2352606D' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right var(--sp-2) center; background-size: 16px 16px;
  padding-right: var(--sp-6);
}
.bulk-bar select:not([multiple]):not([size]) { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23FFFFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); }
/* High-contrast / forced-colours mode may drop the chevron image; give the
   select its native arrow back there so it still reads as a dropdown. */
@media (forced-colors: active) {
  select:not([multiple]):not([size]) { -webkit-appearance: menulist; appearance: auto; background-image: none; padding-right: var(--cl-4); }
}
.field { gap: var(--cl-2); }
.field label, .field > span {
  font-size: 13px; font-weight: 500; letter-spacing: normal; text-transform: none;
  margin-bottom: var(--cl-1);
}
.field-hint, .field-error { font-size: 12px; line-height: 1.45; margin-top: var(--cl-1); }
.form-grid, .field-row, .radio-row { gap: var(--cl-4); }
.radio-row label, .checkbox-row label { gap: var(--cl-3); font-size: 14px; }
input[type=checkbox], input[type=radio] { width: 16px; height: 16px; }
input[type=checkbox] { border-radius: var(--cl-r-xs); }

/* ------------------------------------------------ 8. tabs + segmented */
.tabbar, .profile-tabs, .seg-tabs, .tabs, .control-tabs {
  padding: var(--cl-1); gap: 2px; border-radius: var(--cl-r-md);
}
.tabbar-btn, .profile-tab, .seg-tab, .tabs button {
  min-height: var(--cl-ctl-sm); padding: 0 var(--cl-4);
  border-radius: var(--cl-r-sm);
  font-size: 14px; font-weight: 500; letter-spacing: normal; text-transform: none;
}
.tabbar-btn.active, .profile-tab.active, .seg-tab.active { font-weight: 600; box-shadow: var(--cl-lift); }

/* --------------------------------------------- 9. chips, badges, status */
/* Badges become small rounded rectangles; true pills stay only on counts. */
.status-chip, .chip, .badge, .tag, .pill-tag, .src-tag {
  height: 24px; padding: 0 var(--cl-3);
  border-radius: var(--cl-r-sm); border-width: 1px;
  font-size: 12px; font-weight: 500; letter-spacing: normal; text-transform: none;
  display: inline-flex; align-items: center; gap: var(--cl-1);
}
.status-chip.lg, .chip.lg { height: 28px; padding: 0 var(--cl-4); font-size: 13px; }
.src-dot { width: 6px; height: 6px; border-radius: var(--r-pill); }

/* ---------------------------------------------------------- 10. tables */
.table-wrap, .responsive-table, .review-table { border-radius: var(--cl-r-xl); }
.table, table { font-size: 14px; }
table.table thead th, .table thead th, table thead th {
  font-size: 11px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
  padding: var(--cl-3) var(--cl-4);
  border-bottom-width: 1px;
}
table.table tbody td, .table tbody td, table tbody td {
  padding: var(--cl-4); font-size: 14px; line-height: 1.45;
  border-bottom-width: 1px; height: var(--cl-row-h);
}
.sort-th { gap: var(--cl-1); }
.bulk-bar { border-radius: var(--cl-r-xl); padding: var(--cl-3) var(--cl-5); gap: var(--cl-3); }

/* ------------------------------------------------ 11. modals + overlays */
.modal {
  border-radius: var(--cl-r-xl);
  box-shadow: var(--cl-pop);
  border-width: 1px;
}
.modal-head { padding: var(--cl-6) var(--cl-6) var(--cl-4); }
.modal-body { padding: 0 var(--cl-6) var(--cl-6); }
.modal-foot { padding: var(--cl-4) var(--cl-6) var(--cl-6); gap: var(--cl-3); }
.rq-pop, .popover, .menu, .dropdown, .cmdk {
  border-radius: var(--cl-r-xl); box-shadow: var(--cl-overlay);
  border-width: 1px; padding: var(--cl-1);
}
.rq-pop-item, .menu-item, .dropdown-item {
  min-height: var(--cl-ctl-lg); padding: 0 var(--cl-4);
  border-radius: var(--cl-r-sm); font-size: 14px; gap: var(--cl-3);
  display: flex; align-items: center;
}

/* --------------------------------------------------- 12. misc surfaces */
.empty { padding: var(--cl-11) var(--cl-6); gap: var(--cl-4); }
.empty .ico { margin-bottom: var(--cl-2); }
.empty-title { font-size: 16px; font-weight: 600; line-height: 1.35; }
.empty p { font-size: 14px; line-height: 1.55; max-width: 46ch; margin-inline: auto; }
.empty-action { margin-top: var(--cl-5); }

.gsearch, .gsearch input { border-radius: var(--cl-r-md); }
.gsearch { min-height: var(--cl-ctl-lg); padding: 0 var(--cl-4); gap: var(--cl-3); }

/* The launcher stays a pill: it is a circle at rest that expands into a pill on
   hover, and Claude's own floating controls are pills too. Only the elevation
   and the label are brought onto the system. */
.anyhelp-fab { box-shadow: var(--cl-pop); border-width: 1px; }
.anyhelp-fab-copy { font-size: 14px; font-weight: 500; letter-spacing: normal; }

.profile-header { border-radius: var(--cl-r-xl); padding: var(--cl-6); gap: var(--cl-5); }
.funnel-mini { gap: var(--cl-1); border-radius: var(--cl-r-sm); }
.event, .action-item, .role-row, .recip {
  border-radius: var(--cl-r-md); padding: var(--cl-4); gap: var(--cl-3);
}
.roles-grid { gap: var(--cl-5); }
.login-card { border-radius: var(--cl-r-2xl); padding: var(--cl-9); box-shadow: var(--cl-overlay); }
.login-brand { margin-bottom: var(--cl-7); gap: var(--cl-3); }

/* Approved shared layout completion. Color remains in the design system. */
.page-head-actions { display: flex; flex-wrap: wrap; gap: 8px; max-width: 100%; }
.page-head-main .detail-back { margin-bottom: 8px; }
.detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.detail-grid > *, .roles-layout > * { min-width: 0; }
.toolbar input, .toolbar select { height: auto; }
.filter-toolbar { flex-wrap: nowrap; }
.toolbar-search { flex: 1 1 240px; min-width: 140px; }
.toolbar-search > input { width: 100%; min-width: 0; }
.toolbar-secondary { display: flex; align-items: center; gap: 8px; flex: 2 1 540px; min-width: 0; flex-wrap: wrap; }
.toolbar-secondary > input, .toolbar-secondary > select { flex: 1 1 88px; min-width: 0; width: auto; }
.toolbar-secondary > .sselect { flex: 1 1 150px; min-width: 0; }
.toolbar-secondary > .switch { flex: 0 0 auto; }
.toolbar-count { flex: 0 0 auto; }
.filter-toggle { display: none; }
.density-compact table.table tbody td, .density-compact table.table thead th { padding: 7px 12px; }
.density-spacious table.table tbody td, .density-spacious table.table thead th { padding: 18px 20px; }
.dash-kpi-row.kpi-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.role-choice { width: 100%; display: flex; align-items: center; text-align: left; min-height: 40px; padding: 8px 12px; border-width: 1px; border-style: solid; border-radius: 8px; margin-bottom: 4px; font: inherit; }
.permissions-save { position: sticky; top: var(--topbar-h); z-index: 3; flex-wrap: wrap; }
.permission-code { display: block; font-size: 12px; margin-top: 4px; overflow-wrap: anywhere; }
.permission-toggle { align-items: flex-start; }
.mobile-nav > button { min-width: 0; }
.mobile-nav > button > span { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* UI Step 12: at 1440 every stage column was already at this 200px floor —
   6+ columns need more than the board's client width, so `1fr` never gets to
   grow and the kanban already scrolls inside itself (scrollWidth 1264 vs
   client 1097, max scrollLeft 167 — accepted: the board owns that overflow,
   the page never does). Measured the nesting inside one card at that 200px
   floor: kan-body padding (10px/side) then .pcard padding (14px/side) then
   an 8px gap to the ⋯ menu (32px) left only 108px for the name+role column —
   46% of the card was chrome before either string started. Every one of
   those paddings/gaps is a fixed px, not a percentage, so raising the floor
   passes straight through: +32px here is +32px in that lane, unlike shaving
   the paddings themselves, which would also thin the touch/visual rhythm of
   every other card element for a few px each. The board already scrolls, so
   spending width on the floor costs nothing this table did not already
   accept — it just moves more of the board into view per scroll instead of
   less. */
/* Five columns fit inside the content column from 1440 up (5 × 208 + 4 gaps
   = 1088px, inside the 1098px the card leaves at 1440, measured); below that
   the board scrolls sideways, and the scrollbar is always drawn so a column
   cut at the right edge reads as "more this way", not as a cropped page. */
.kanban { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(208px, 1fr); gap: 12px; align-items: start; overflow-x: auto; scrollbar-width: thin; scrollbar-gutter: stable; padding-bottom: 16px; }
.kanban::-webkit-scrollbar { height: 8px; }
.kanban::-webkit-scrollbar-track { background: transparent; }
.kanban::-webkit-scrollbar-thumb { background: #C9CDD6; border-radius: 8px; }
.kan-col { width: auto; min-width: 0; max-height: none; display: grid; grid-template-columns: minmax(0, 1fr); }
.kan-body { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; padding: 10px; overflow: visible; min-height: 92px; }
.kan-head { min-width: 0; }
.kan-title { min-width: 0; white-space: normal; }
.pcard { min-width: 0; width: 100%; box-sizing: border-box; padding: 14px; gap: 7px; min-height: 103px; border-radius: 10px; cursor: pointer; }
.pcard:has(.pipeline-menu) { z-index: 5; }
.pcard-id, .pcard-top, .pcard-actions, .pcard-footer, .pcard-facts { min-width: 0; }
.pcard-top { align-items: flex-start; gap: 8px; }
button.pcard-name { font: inherit; font-size: 14px; font-weight: 600; padding: 0; border: 0; max-width: 100%; text-align: left; cursor: pointer; }
/* Measured: a 20.3px-tall single line of 14px/1.45 text is the whole click
   target for "open this candidate" — the card's primary action. Padding
   raises the hit area to 28.3px; the equal negative margin cancels the box
   model change it would otherwise add, so `.pcard-id`'s layout height (and
   therefore the card's) is unchanged — verified by comparing `.pcard-top`'s
   rect before and after. The desktop card never grows to buy this; only the
   button's own hit-testable padding box does. */
button.pcard-name { padding-block: 4px; margin-block: -4px; }
.pcard-role { font-size: 11.5px; }
.pcard-actions { position: relative; display: block; padding: 0; border: 0; flex: 0 0 auto; }
.pcard-menu-trigger { width: 32px; height: 32px; min-height: 32px; border: 1px solid transparent; }
.pcard-footer { display: flex; align-items: baseline; gap: 8px; border-top-width: 1px; border-top-style: solid; padding-top: 8px; margin-top: auto; }
.pcard-facts { display: block; flex: 1 1 auto; font-size: 11px; line-height: 1.5; overflow-wrap: anywhere; }
.pcard-match { flex: 0 0 auto; font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; }
.pcard-request, .pcard-residual { font-size: 11px; overflow-wrap: anywhere; }
.pipeline-menu { position: absolute; right: 0; top: 100%; margin-top: 6px; width: 174px; min-width: 0; max-width: calc(100vw - 48px); z-index: 10; }
.pipeline-menu .menu-item { width: 100%; min-height: 40px; height: auto; padding: 8px; white-space: normal; text-align: left; border: 0; cursor: pointer; font: inherit; font-size: 12px; }
.empty .ico { width: 72px; height: 72px; }
.empty .ico svg { width: 72px; height: 72px; }
.email-settings-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 320px); gap: 20px; align-items: start; }
.email-settings-form, .email-settings-aside, .email-settings-grid .field { min-width: 0; }
.email-fieldset { border: 0; padding: 0; margin: 0; min-width: 0; display: grid; gap: 16px; }
.email-settings-aside { display: grid; gap: 16px; }
.email-settings-actions { position: sticky; bottom: 16px; z-index: 2; padding: 16px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); box-shadow: var(--cl-lift); display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
.email-settings-grid input, .email-settings-grid select { width: 100%; min-width: 0; }
.email-password-field { grid-column: 1 / -1; }
.email-password-row, .email-provider-note { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.email-password-row > span { flex: 1 1 150px; }
.email-provider-note > span { flex: 1 1 200px; }
.email-test-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.email-test-actions > .btn { flex: 1 1 auto; }
.email-status-list { display: grid; gap: 6px; margin: 16px 0; overflow-wrap: anywhere; }
.email-status-list dt { font-size: 12px; color: var(--muted); }
.email-status-list dd { margin: 0 0 10px; }
.email-settings-link { justify-self: start; }
/* Sign-in is frozen: preserve its pre-handoff 10px control geometry. */
.login-wrap .btn, .login-wrap input { border-radius: var(--cl-r-lg); }
@media (max-width: 1200px) and (min-width: 641px) {
  .responsive-table [data-priority="secondary"] { display: none; }
  .table-wrap .responsive-table { min-width: 0; }
}
@media (max-width: 1100px) {
  .filter-toolbar { flex-wrap: wrap; }
  .filter-toggle { display: inline-flex; }
  .toolbar-secondary { display: none; flex: 1 1 100%; order: 3; }
  .toolbar-secondary.expanded { display: flex; }
  .toolbar-secondary > input, .toolbar-secondary > select { flex-basis: 140px; }
  .toolbar-secondary > .sselect { flex-basis: 100%; }
  .dash-kpi-row.kpi-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .detail-grid { grid-template-columns: minmax(0, 1fr); }
  .email-settings-grid { grid-template-columns: minmax(0, 1fr); }
  .email-settings-actions { bottom: 76px; }
  .role-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .page-head-actions { width: 100%; }
  .page-head-actions > .btn { flex: 1 1 auto; }
  .toolbar-search { flex-basis: 180px; }
  .toolbar-count { width: 100%; }
  .filter-toolbar .toolbar-search > input { min-width: 0; }
  .role-choice, button.pcard-name { min-height: 44px; }
  .pipeline-menu .menu-item { min-height: 44px; }
  .role-row { grid-template-columns: minmax(0, 1fr); }
}

/* ------------------------------------------------------ 13. responsive */
@media (max-width: 1180px) {
  :root { --sidebar-w: 240px; }
  .content { --sp-page-x: var(--sp-5); }   /* laptop 24 */
  .nav-item { padding-left: var(--cl-3); padding-right: var(--cl-4); }
  .sidebar-head { padding-left: var(--cl-3); padding-right: var(--cl-3); }
}
@media (max-width: 900px) {
  .topbar { height: 52px; padding: 0 var(--cl-4); gap: var(--cl-3); }
  .sidebar.collapsed { --sidebar-w: 264px; padding: var(--cl-4) var(--cl-4) var(--cl-6); }
  .content { --sp-page-x: var(--sp-4); padding: var(--cl-6) var(--sp-page-x) 80px; }   /* tablet 20/16 */
  .page-title, .page-head .page-title, .dash-head .page-title { font-size: 24px; }
}
@media (max-width: 640px) {
  :root { --cl-ctl-lg: 44px; }
  .content { --sp-page-x: var(--sp-4); padding: var(--sp-4) var(--sp-page-x) 80px; }   /* phone 16/16 */
  .content.has-dock { padding-bottom: 96px; }
  .page-title, .page-head .page-title, .dash-head .page-title { font-size: 22px; }
  .page-head, .dash-head { margin-bottom: var(--cl-5); gap: var(--cl-4); }
  .card-pad, .card > .card-pad { padding: var(--cl-5); }
  .modal { border-radius: var(--cl-r-xl); }
  .modal-head { padding: var(--cl-5) var(--cl-5) var(--cl-3); }
  .modal-body { padding: 0 var(--cl-5) var(--cl-5); }
  .modal-foot { padding: var(--cl-3) var(--cl-5) var(--cl-5); }
  /* 44px, not 40: the UI-readiness release set this touch target deliberately
     and this layer loads after it, so it must not quietly shrink it. */
  .btn, button.btn, a.btn, .btn.btn-sm, .btn.btn-xs, .btn-sm, .btn-xs, .btn.small, .tabbar-btn, .profile-tab, .seg-tab, .tabs button, .menu-item { min-height: 44px; }
  .icon-btn { width: 44px; height: 44px; min-height: 44px; }
  .email-settings-actions > .btn { flex: 1 1 auto; }
  .view-toggle-btn { min-height: 44px; }
}

/* ============================================================================
   14. LAYOUT INTEGRITY CONTRACT
   ----------------------------------------------------------------------------
   Mandatory, product-wide. Enforced here so a page cannot opt out by accident.

     1. Perfectly usable at 100% browser zoom. Nothing may require zoom-out.
     2. No text escaping its container.
     3. No accidental wrapping of a label that was meant to stay on one line.
     4. No unnecessary whole-page horizontal scrolling.
     5. Long text must INTENTIONALLY fit, wrap, or truncate with a tooltip.
     6. Never shrink a font to hide a broken container size.

   Reference case: the role label `Recruitment Manager` in Settings -> Users.
   It must sit inside its chip at 100% zoom without breaking mid-word and
   without pushing the row wider than the viewport.
   ========================================================================= */

/* (1) The min-width:0 floor. A grid/flex child defaults to min-width:auto,
   which is its MIN-CONTENT width — that single default is what lets one long
   unbroken string push an entire page wider than the screen. Declared here on
   the shell axes so it cannot be lost to stylesheet load order. */
.shell { grid-template-columns: var(--sidebar-w) minmax(0, 1fr); }
.shell > .main, .shell > .content, .main, .content { min-width: 0; }
.card, .card-pad, .toolbar, .filter-toolbar, .page-head, .form-grid, .field { min-width: 0; }

/* (2)(3)(5) Label containment. A chip/badge states its intent explicitly:
   it stays on one line and is cut off at its own edge, rather than breaking
   mid-word or spilling past its rounded outline. `.status-chip` previously
   combined `white-space: nowrap` with a FIXED height, so a long label
   overflowed the pill outline; height becomes a floor, not a ceiling.

   HOW THE CUT LOOKS, AND WHY IT IS NOT ALWAYS AN ELLIPSIS. `text-overflow`
   applies to the inline content of a BLOCK container. Section 9 above makes
   `.chip`, `.badge` and `.status-chip` `display: inline-flex`, so a bare text
   child becomes an anonymous flex item: it does not inherit `text-overflow`,
   and an over-long label is GUILLOTINED mid-glyph rather than ellipsised.
   `text-overflow` is still declared here because it is what renders for the
   members of this list that are block boxes (`.code-pill`, `.count-pill`) and
   because it becomes correct the instant a member stops being a flex
   container — but it must NOT be read as a promise of a visible "…" on the
   flex ones. Either way the label is CONTAINED, which is all this contract
   asks for. What was cut is recovered through section 15's tooltip policy,
   which is why every `.chip` in app.jsx holding unbounded text carries a
   `title`. Changing `.chip` to a block box is not a cosmetic swap — 13 sites
   compose it and several pass icon children that rely on the flex box — so it
   is deliberately left alone; see section 19 (7). */
.chip, .badge, .status-chip, .src-chip, .meta-chip, .code-pill, .count-pill {
  min-width: 0;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  height: auto;
  min-height: 24px;
}
/* A cell holding several chips wraps BETWEEN chips, never inside one. */
.chip-cell, td > .chip + .chip, .filter-chips { flex-wrap: wrap; }
td:has(> .chip) { white-space: normal; }

/* Content-driven text that is allowed to wrap does so on word boundaries, and
   falls back to breaking only for strings with no boundary (URLs, IDs). */
.cell-strong, .cell-sub, .cell-sub-only, .page-sub, .muted, .kan-title, .pcard-name {
  overflow-wrap: break-word;
  word-break: normal;
}

/* (4) Page-level horizontal overflow is owned by the element that overflows —
   never by clipping the document. `html, body { overflow-x: hidden }` in the
   design-system layer masks real bugs; this makes the scroll container explicit
   so the mask has nothing left to hide. */
.table-wrap, .card > .table-wrap, .card.flush > .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* (6) Font size is a typographic decision, never a fitting mechanism.
   The scale in section 1 is the only permitted set of sizes. */


/* ============================================================================
   15. TABLE STANDARD  (defined now; tables are rebuilt in the next phase)
   ----------------------------------------------------------------------------
   Sorting must never produce: a visible full-table refresh, flashing, a column
   width change, header movement, accidental text wrapping, or a layout shift.

   Two halves — this file owns the first, app.jsx owns the second.

   CSS CONTRACT (enforced below)
     - Column widths come from an explicit layout, not from content
       measurement, so they cannot change when the rows change.
     - Header cells never wrap and never move.
     - The sort affordance occupies its space whether or not it is active,
       so activating a sort cannot reflow the header.

   JS CONTRACT (documented here, applied during component standardisation)
     - A sort must NOT set the row state to null. `setCandidates(null)` unmounts
       the table and renders <Skeleton/>, which is the visible flash; the
       remount is what lets column widths re-measure. Sorting must keep the
       previous rows mounted and mark the table busy instead.
     - Sort state cycles Neutral -> Ascending -> Descending.
     - TOOLTIP POLICY — one rule, product-wide, and the only one. A label
       carries a `title` with its full text when its content is UNBOUNDED:
       free text a user typed, or a name that arrives from company data. Such
       a label can outgrow its box at some supported width, and what is cut is
       then unrecoverable from the screen. A label drawn from a FIXED enum that
       has been measured to fit at every supported width carries no `title` — a
       tooltip that repeats what is already legible is noise, not help.
       The test of "can it be cut", note, is the content, not the rendering:
       `.chip` is `display: inline-flex` and therefore guillotines mid-glyph
       instead of showing "…" (section 14), which makes the `title` on an
       unbounded chip more necessary, not less.
   ========================================================================= */

/* Stable geometry, OPT-IN. `table-layout: fixed` is the mechanism that stops
   column widths re-measuring when rows change — but it distributes width
   EQUALLY unless the table also declares its columns. Switching it on globally
   today, before any table has a <colgroup>, would give a 7-column table seven
   identical columns and make the problem worse. So it is exposed as a class
   that component standardisation applies per table, together with that table's
   <colgroup>:

       <div class="table-wrap">
         <table class="table table-fixed">
           <colgroup><col style="width:22%"><col style="width:26%">...</colgroup>

   Until a table opts in it keeps today's auto layout and today's behaviour. */
table.table, .table-wrap > table { width: 100%; }
table.table-fixed, .table-wrap > table.table-fixed { table-layout: fixed; }

/* Header stability. */
table.table thead th, .table thead th {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
/* Sortable column headers.
   Three states — neutral, ascending, descending — that differ ONLY in which
   glyph occupies the caret box and how strongly it is drawn. Nothing that can
   move anything is allowed to vary with state: no font-size, no font-weight,
   no padding, no margin, no border width, no transform. The box is 16px in all
   three states and now always holds an icon, so the label cannot shift.
   Reserved before any sorting happens: an unsorted header already renders its
   neutral glyph at full size. */
.sort-th { cursor: pointer; user-select: none; }
/* The flex row the caret and label were always written for (see SortTh). It
   lives on a wrapper, not on the `<th>`, because `display: flex` on a table
   cell drops the cell out of the table's column sizing. The label is the only
   thing allowed to shrink; the caret keeps its reserved 16px, so a header can
   never lose its sort affordance to a narrow column. */
.sort-th-inner { display: flex; align-items: center; gap: var(--cl-1); min-width: 0; }
.sort-th > span:first-child, .sort-th .sort-label {
  min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sort-th .sort-caret {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: 0 0 16px; margin-left: var(--sp-1);
  vertical-align: middle;
}
/* Opacity is the only thing that separates the states, so switching between
   them can never reflow. Colour comes from `.sort-th.active`, which the Arabtec
   layer already owns; the icon inherits it through stroke: currentColor. */
.sort-th .sort-caret[data-sort="none"] { opacity: .34; }
.sort-th:hover .sort-caret[data-sort="none"],
.sort-th:focus-visible .sort-caret[data-sort="none"] { opacity: .72; }
.sort-th .sort-caret[data-sort="ascending"],
.sort-th .sort-caret[data-sort="descending"] { opacity: 1; }
.sort-th .sort-caret > svg { transition: opacity .12s ease; }
/* Body cells wrap on purpose or truncate on purpose — never by accident. */
table.table tbody td, .table tbody td { overflow-wrap: break-word; }
/* While a query is in flight the table stays mounted and the current rows stay
   readable — only the BODY settles slightly, so the header keeps full contrast
   and the sort controls stay clickable through the fetch (nothing here dims or
   disables the header, which is what keeps sorting usable while busy). Opacity only: no size change,
   nothing can shift, and no input is blocked (a request-sequence guard in the
   loader, not a pointer-events trap, is what keeps a stale response from
   overwriting a newer one). */
.table-busy tbody { opacity: .72; transition: opacity .12s ease; }


/* ============================================================================
   16. SHARED COMPONENT LAYER — the approved set
   ----------------------------------------------------------------------------
   These fifteen categories have ONE approved implementation each. New pages
   compose them; they do not re-implement them. The canonical component is named
   where it already exists in app.jsx, and the duplicates it replaces are listed
   so component standardisation has an explicit worklist.

     Button        .btn (+ -secondary -ghost -danger -sm)   canonical
     Input         .field input                             canonical
     Select        .field select                            canonical
     Checkbox      .field input[type=checkbox]              canonical
     Card          .card / .card-pad / .card-head           canonical
                     replaces: .pcard, .rq-card, .ticket-card
     Badge         <Badge> / .badge                         canonical
                     replaces: .chip, .status-chip, .cvi-badge, .src-chip,
                               .meta-chip, .score-badge
     Tabs          <ViewToggle> / .view-toggle              canonical
                     replaces: .tabs, .tabbar, .seg-tabs, .profile-tabs,
                               .control-tabs, .anyhelp-tabs
     Dialog        <Modal> / <Confirm>                      canonical
     Dropdown      .menu / .menu-item                       canonical
                     replaces: .rq-pop-*, .pipeline-menu
     Table         table.table inside .table-wrap           canonical
                     replaces: .responsive-table (merge), .review-table,
                               .parse-review-table, .preview-table, .cvi-table
     Pagination    <Pager> / .pager                         canonical
     Filters       <FilterToolbar> / .filter-toolbar        canonical
     Empty state   <Empty> + <EmptyArt>                     canonical
                     replaces: .state-empty, .dash-state, .cvi-state,
                               .kan-empty, .notif-empty, .rq-pop-empty
     Loading       <Skeleton>                               canonical
     Page header   <PageHead> / .page-head                  canonical

   Every one of the above draws its colour from the Arabtec tokens in
   arabtec-design-system.css. This file sets their geometry only.
   ========================================================================= */

/* ============================================================================
   17. CANDIDATES TABLE — scoped, intentional column widths
   ----------------------------------------------------------------------------
   THE PROBLEM. This table ran on the browser's AUTO layout, which solves column
   widths from the content currently rendered. Reorder the rows and the widest
   value in a column changes, so the solve returns different widths and every
   column visibly resizes. Task 3.1 did not cause this — it exposed it: the
   table used to unmount behind a skeleton, which hid the re-measure inside a
   remount. Now that the rows stay mounted, the reflow happens in place.

   WHY NOT <colgroup>. A <col> is POSITIONAL, and between 641px and 1200px this
   table hides its two `data-priority="secondary"` columns with `display: none`,
   which removes those cells from the row entirely. The remaining cells then
   shift into earlier column slots, so a 9-<col> group would hand Location the
   width meant for Graduation. Widths are therefore declared ON THE HEADER CELLS
   and keyed by `data-col`, so each width travels with its own cell and a hidden
   column takes its width away with it. Naming also means reordering a column
   cannot silently reassign widths, which nth-child would.

   Scoped to `.candidates-table`. No other table is affected, and the global
   `table-layout` stays auto.
   ========================================================================= */
table.candidates-table {
  table-layout: fixed;
  width: 100%;
  /* Nine columns need about this much before text stops being readable. Below
     it the WRAPPER scrolls (.table-wrap already owns overflow-x) rather than
     the columns compressing. */
  min-width: 1020px;
}

/* Percentages, so the columns keep their proportions at any table width. They
   sum to 100 across the nine columns; when the two secondary columns are hidden
   their share is redistributed proportionally among the rest, which keeps the
   same relative emphasis with more room each.

   TWO COLUMNS HOLD A FIXED-SIZE CONTROL and were sized on a wrong assumption.
   Measured in a browser: the CV cell holds a "Download" text button that needs
   84px, and the widest real Stage label ("Screening", from New / Screening /
   Fit / Unfit) needs 82px; with 24px of cell padding that is 108px and 106px.
   At 4%, CV was clipped at EVERY table width — 40px at 1440, 37px at 1280,
   48px at 1024 — and Stage lost 15px at 1024. CV goes to 11% and Stage to 11%,
   which clears both at the narrowest width either regime allows. The space is
   donated by University (-4), Request (-2), Candidate (-1) and Position (-1);
   Graduation is left alone because 6% is already only just enough for a
   four-digit year, and no operational column drops below the width it was
   measured readable at. */
/* University and Graduation are the two narrowest columns in the nine-column
   regime, and 24px of horizontal padding is 39% of Graduation's 61px at 1366 —
   the width band where these two columns exist at all. The percentages above
   are each at a measured floor (see the guards in ui_sort_stability_test), so
   the only honest place to find room for the label is the padding that is
   disproportionate on these two columns specifically. 4px here, and only here;
   every other column keeps the table's standard 12px. Measured at 1366, the
   narrowest width that renders these columns: it is what lets "University"
   and "Year" print in full beside a caret that keeps its reserved 16px. */
.candidates-table > thead > tr > th[data-priority="secondary"],
.candidates-table > tbody > tr > td[data-priority="secondary"] { padding-left: 4px; padding-right: 4px; }
/* Same reasoning for the caret gap: 4px is proportionate beside a 160px label
   and not beside a 29px one. The caret itself keeps its full reserved 16px. */
.candidates-table > thead > tr > th[data-priority="secondary"] .sort-th-inner { gap: 2px; }
.candidates-table > thead > tr > th[data-col="select"]     { width: 4%; }
.candidates-table > thead > tr > th[data-col="name"]       { width: 20%; }
.candidates-table > thead > tr > th[data-col="position"]   { width: 16%; }
.candidates-table > thead > tr > th[data-col="university"] { width: 9%; }
.candidates-table > thead > tr > th[data-col="graduation"] { width: 6%; }
.candidates-table > thead > tr > th[data-col="location"]   { width: 11%; }
.candidates-table > thead > tr > th[data-col="request"]    { width: 12%; }
.candidates-table > thead > tr > th[data-col="stage"]      { width: 11%; }
.candidates-table > thead > tr > th[data-col="cv"]         { width: 11%; }

/* WHEN THE SECONDARY COLUMNS STEP ASIDE — derived, not chosen.
   Nine columns need the 1020px above. Measured in a browser, this table's
   wrapper gets `viewport - 330px` (264 sidebar + 2x32 gutters + card padding),
   so nine columns stop fitting below 1350px. Hiding them any later leaves a
   laptop scrolling; hiding them any earlier throws away columns that fit.
   Hence 1349px: 1366 and 1440 keep all nine, 1280 and 1300 drop to seven.

   This reuses the existing `data-priority="secondary"` mechanism — the same
   attribute and the same `display: none` — but SCOPED to this table, so the
   product-wide 1200px breakpoint that other responsive tables rely on is left
   exactly where it is.

   The min-width drops to the seven-column floor with them. Below 1200px the
   product-wide rule `.table-wrap .responsive-table { min-width: 0 }` outranks
   this one (two classes beats one class plus an element) and the columns
   compress to fit instead — that is the long-standing compact behaviour, and
   it is deliberately left alone: forcing 860px there would make 1024px scroll
   where today it does not. So this floor governs the 1201-1349 band, which is
   the band that needed it. */
@media (max-width: 1349px) and (min-width: 641px) {
  .candidates-table [data-priority="secondary"] { display: none; }
  /* This floor has to OUTRANK the product-wide
     `.table-wrap .responsive-table { min-width: 0 }` (two classes), which was
     silently winning against a one-class selector and letting the table
     compress to 734px at 1024 — which is what clipped the Download button.
     Below 860px the wrapper scrolls instead, which is what a wrapper is for:
     a clipped control is not a readable column. It stays inside this
     min-width:641px block so the phone card mode still releases it. */
  .table-wrap > table.candidates-table { min-width: 860px; }
}

/* LONG CONTENT, per column and on purpose.
   Fixed layout means a cell can no longer widen its column, so every run of
   content states what it does instead. These truncate on one line and carry a
   `title` with the full value (set in app.jsx), which is the "truncate with
   tooltip" option rather than letting text escape or shrinking the type. */
.candidates-table .idcell-name,
.candidates-table td[data-label="Position"] .cell-strong,
.candidates-table td[data-label="Position"] .cell-sub,
.candidates-table td[data-priority="secondary"] .cell-strong,
.candidates-table td[data-priority="secondary"] .cell-sub,
.candidates-table td[data-label="Location"] {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The name truncates; the history badge beside it must not be the first thing
   clipped, so the row holds the badge at its natural size. */
.candidates-table .cell-strong:has(> .idcell-name) {
  display: flex; align-items: center; gap: var(--sp-1); min-width: 0;
}
.candidates-table .idcell-name { flex: 0 1 auto; }
.candidates-table .cell-strong > .hist-badge-btn { flex: 0 0 auto; }
/* Every cell keeps its content inside its own box. The Request cell is exempt:
   it owns a picker whose panel (.rq-pop) is position:fixed and so escapes any
   clipping ancestor today — the exemption means it stays correct even if that
   panel is ever changed to absolute. */
.candidates-table > tbody > tr > td:not([data-label="Request"]) { overflow: hidden; }
/* The tag chips under a name wrap between chips; none of them widens a column. */
.candidates-table .idcell-tags { display: flex; flex-wrap: wrap; min-width: 0; }

/* The phone layout stacks each row into a card and hides the header entirely,
   so none of the column sizing above applies. Release the fixed layout and the
   min-width so a card can be as narrow as the screen, and let the values wrap
   instead of truncating — there is a full row width to use. */
@media (max-width: 640px) {
  table.candidates-table { table-layout: auto; min-width: 0; }
  .candidates-table .idcell-name,
  .candidates-table td[data-label="Position"] .cell-strong,
  .candidates-table td[data-label="Position"] .cell-sub,
  .candidates-table td[data-label="Location"] {
    white-space: normal; overflow: visible; text-overflow: clip;
  }
  .candidates-table > tbody > tr > td:not([data-label="Request"]) { overflow: visible; }
}

/* Users row actions. A flex row that is allowed to wrap, instead of four
   inline-block buttons held on one line by an inline `white-space: nowrap`:
   the nowrap made the cell's intrinsic minimum 359px and pushed the whole
   table 12px past its card at 1440. The gap replaces the `{' '}` separators
   the markup used to rely on, so the spacing no longer depends on whitespace
   text nodes. */
/* A table cell stays a table cell: `display: flex` on the <td> took it out of
   the row's box, so its wrapped buttons drew across the next row's border.
   Two inline controls (Edit + More) now fit on one line. */
.user-actions { white-space: nowrap; text-align: end; }
.user-actions > * { display: inline-flex; vertical-align: middle; }
.user-actions > * + * { margin-inline-start: var(--sp-2); }

/* CV Inbox status header: four facts on one line, wrapping to a column on a
   phone. A grid, not a table — these are labelled values, not rows. */
.cvi-status { display: flex; flex-wrap: wrap; gap: var(--cl-6); align-items: flex-start; }
.cvi-status-item { display: flex; flex-direction: column; gap: var(--cl-1); min-width: 0; }
.cvi-status-k {
  font-size: 11px; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted);
}
.cvi-status-actions { margin-inline-start: auto; align-self: center; }
/* Waiting to parse: the Period / At most selects and their buttons sit in one
   row on the spacing scale (R16). These classes had no rule at all, so the
   select and "Refresh from mailbox" touched, and the count list ran inline. */
.cvi-range, .cvi-parsebar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.cvi-range-label { margin-inline-end: var(--sp-1); }
.cvi-range select, .cvi-parsebar select { width: auto; min-width: 0; }
.cvi-groups {
  list-style: none; margin: 0 0 var(--sp-4); padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--sp-2) var(--sp-4);
}
.cvi-group { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--cl-ctl-sm); }
.cvi-group-name { flex: 1 1 auto; min-width: 0; }
.cvi-group-count { font-variant-numeric: tabular-nums; }
.cvi-selected { margin-inline-end: auto; }
/* A wrapping row of toggles inside a form field (user editor: roles,
   projects, sites). The gap is the row's, so no toggle carries a margin. */
.tag-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
/* Phone-only control (arabtec-mobile.css shows it inside the phone shell). */
.btn.dash-figures-toggle { display: none; }
/* Notification grid (Settings > Notifications): one tick per cell. Not a
   stacked responsive table: on a phone a grid of ticks is read by swiping
   sideways, with the event name pinned on the left. */
.notif-grid { table-layout: fixed; width: 100%; min-width: 880px; }
.notif-grid .notif-event { width: 240px; }
.notif-grid .notif-tick { width: 72px; padding-left: var(--sp-1); padding-right: var(--sp-1); text-align: center; }
.notif-grid th { text-transform: none; letter-spacing: 0; }
.notif-grid th.notif-who { white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.25; }
.notif-grid td:first-child, .notif-grid th:first-child { position: sticky; left: 0; z-index: 1; }
/* Talent Pool card facts line: stage, request, experience and place. */
.cc-facts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-2); margin-top: var(--sp-1); font-size: 12px; min-width: 0; }
.cc-req { font-variant-numeric: tabular-nums; font-weight: 600; }
.tag-row > .tag-toggle { margin: 0; }
.user-form-rest { margin-top: var(--sp-4); }
/* Priority level (PriorityBadge): three small bars and a word, no pill. */
.prio { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: 13px; white-space: nowrap; }
.prio-bars { display: inline-flex; align-items: flex-end; gap: var(--sp-1); }
.prio-bars i { display: block; width: 4px; border-radius: 1px; }
.prio-bars i:nth-child(1) { height: 6px; } .prio-bars i:nth-child(2) { height: 9px; } .prio-bars i:nth-child(3) { height: 12px; }
.user-form-switch { display: flex; align-items: center; min-height: var(--cl-ctl-lg); }
/* Standalone interview / offer: the picked candidate and the Talent Pool
   search results (Interviews → Schedule interview, Offers → Create offer). */
.standalone-link { margin-bottom: var(--sp-4); }
.standalone-picked { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); min-height: var(--cl-ctl-lg); }
.standalone-picked > .btn { margin-inline-start: auto; }
.standalone-results {
  list-style: none; margin: var(--sp-1) 0 0; padding: var(--sp-1) 0;
  max-height: 240px; overflow-y: auto; border-width: 1px; border-style: solid; border-radius: var(--cl-r-md);
}
.standalone-results > li { padding: var(--sp-2) var(--sp-3); cursor: pointer; }
.kl-editor textarea { width: 100%; min-height: 320px; font-variant-numeric: tabular-nums; }
.kl-editor-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }
.kl-editor-count { margin-inline-end: auto; }
.kl-editor-actions { display: inline-flex; flex-wrap: wrap; gap: var(--sp-2); }
.cvi-tabs { margin-bottom: var(--cl-5); }
/* The count rides inside its tab so selecting one cannot change the tab's
   width and shove the row along — the same rule the screen tabs follow. */
.seg-count {
  margin-inline-start: var(--cl-2); font-variant-numeric: tabular-nums;
  font-weight: 600; color: var(--muted);
}
.seg-tab.active .seg-count { color: inherit; }
@media (max-width: 640px) {
  .cvi-status { flex-direction: column; gap: var(--cl-4); }
  .cvi-status-actions { margin-inline-start: 0; align-self: stretch; }
  .cvi-status-actions .btn { width: 100%; }
}

/* One opt-in clamp for every table cell whose value is free text of unbounded
   length. Rows in Hiring Requests, Interviews and Offers were each as tall as
   their longest string happened to wrap: measured at 1440, request rows ran
   59-112px, interview rows 71-102px and offer rows 66-106px, so none of the
   three tables had a scanning rhythm. Two lines, and the full string stays
   reachable on the `title` — the pattern `.rq-link-chip` and the candidates
   table already use.

   The `.cell-strong` / `.cell-sub` variants are two-class selectors on
   purpose: the reflow layer loads last and declares a bare
   `.cell-strong { display: block }`, which outranks a single-class rule here
   whatever it says. Winning on specificity rather than on order is what keeps
   the clamp alive through that layer. */
.clamp-2,
.cell-strong.clamp-2,
.cell-sub.clamp-2,
.cell-sub-only.clamp-2,
/* `.table tbody td .cell-sub { display: block }` scores (0,2,2) and beat the
   two-class form above, so the clamp went inert inside exactly the tables it
   was written for — `-webkit-line-clamp` was set but `display` was not
   `-webkit-box`, which silently does nothing. These reach (0,3,2). */
.table tbody td .cell-sub.clamp-2,
table tbody td .cell-sub.clamp-2,
.table tbody td .cell-strong.clamp-2,
table tbody td .cell-strong.clamp-2 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
/* The position title is the bigger half of the same defect, and was missed the
   first time because Project / Site is the more obviously long-looking column.
   Measured at 1440 with the clamp on Project / Site already in place, the row
   heights still ran 59 / 66 / 77 / 94 / 112px, and the tallest row's cells
   showed why: Project / Site was down to its bounded 40.6px while Position
   was 85.7px — "Principal Mechanical, Electrical and Plumbing (MEP)
   Coordination Engineer — Infrastructure Delivery" wrapping to four lines.
   Same treatment, same reason: two lines, full string on the `title`.
   Its own class, not `.rq-title` — that one is the request CARD heading
   (h3, 15px, 12px bottom margin, styled across five sheets) and reusing
   it here dragged all of that onto a table cell. */

/* ============================================================================
   17c. CANDIDATES TABLE — the "move" affordance was the tallest thing in the row
   ----------------------------------------------------------------------------
   `.rq-link-btn` is a text button ("Move to another request") and
   `arabtec-responsive.css` forces `white-space: normal; overflow-wrap: anywhere`
   on it unconditionally (a deliberate choice there — see its own "Link-to-
   request is a control, not a desktop-only chip" comment — so the control
   never overflows a phone card). Inside the Request column's 131px lane that
   same wrap put the label on three lines stacked under the `.rq-link-chip`,
   measured at 128px tall at 1440 and 143px at 1024 for one candidate — the
   tallest thing on the row, for a SECONDARY action.

   The primary chip (the active request) is untouched and stays fully visible.
   The secondary "move" control becomes icon-only with the label preserved as
   its accessible name (`aria-label` + `title`, the same pair every icon-only
   control in this product already carries — see the CV column's review/
   download buttons) instead of visible text, so keyboard and screen-reader
   users keep an identical name and the box shrinks to one line. Two classes
   so it outranks the unscoped `.rq-link-btn` wrap rule regardless of sheet
   order, without touching that rule's phone behaviour for anything else that
   still uses a plain `.rq-link-btn` (the "Link to Request" control, which
   keeps its visible label — it is the primary action when nothing is linked
   yet, not the secondary one this defect is about). */
.rq-link-btn.rq-link-btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; flex: 0 0 auto;
  white-space: nowrap; overflow-wrap: normal;
}

/* ============================================================================
   18. FILTER TOOLBAR — predictable rows, search-first proportions
   ----------------------------------------------------------------------------
   Shared by <FilterToolbar>, which has exactly two consumers: the Talent Pool
   (Candidates) and Hiring Requests. Geometry only — no colour, no font-size.

   Three measured defects are corrected here.

   1. THE FILTERS BUTTON WAS VISIBLE ON DESKTOP. `.filter-toggle { display:
      none }` is one class (0,1,0) and `button.btn { display: inline-flex }`
      further up this file is a class plus an element (0,1,1), so the base
      rule never won and the disclosure button rendered at 1440 and 1366 beside
      the five filters it was meant to reveal. Both states are re-declared with
      enough specificity to land.

   2. THE SEARCH FIELD WAS THE NARROWEST FLEXIBLE CONTROL. It grew at 1 while
      the filter group grew at 2, so at 1440 the primary search got 258px and
      the five secondary filters shared 616px. The search now grows fastest.

   3. THE RESULT COUNT ORPHANED ITSELF ONTO ITS OWN ROW at 1280, 768 and 390,
      and at 1200 the toolbar reached three rows. It is now pinned to the right
      of the first row and kept out of the wrap flow, so the structure is always
      either one row, or search+count over a full filter row.

   The filter group's basis is its five inputs at their own 88px basis plus the
   gaps between them, so it either fits on one line or wraps as a whole unit —
   never leaving one stray filter behind.
   ========================================================================= */

/* (1) The disclosure button belongs to narrow widths only. */
.filter-toolbar > button.filter-toggle,
.filter-toolbar > .filter-toggle { display: none; }

/* (2)(3) Search grows fastest; the count never joins the wrap flow. */
.filter-toolbar > .toolbar-search { flex: 3 1 280px; min-width: 140px; }
.filter-toolbar > .toolbar-secondary { flex: 2 1 472px; }
.filter-toolbar > .toolbar-count { flex: 0 0 auto; margin-left: auto; }

/* (5) A FILTER MAY NOT ABSORB A WHOLE WRAPPED LINE. Every control in the group
   grows at 1 from an 88px basis, so whenever the group wraps and a line ends up
   sparse, the one growable control on it takes all the leftover width. On
   Hiring Requests — three selects, two switches, a sort select and a direction
   button — that put a 436px sort select next to a 56px button at 1200, and
   490px at 1280. Measured with `width: max-content`, the widest filter these
   two pages own needs 270px (the owner select, showing a full name), so no
   filter has any use for more than that. Capping growth leaves the surplus
   unused on the line instead, which is what a part-filled row should look like.

   This clamps growth only; the 88px basis is untouched, so it changes no wrap
   decision and no row structure. On desktop the Talent Pool's five text filters
   reach 91-123px against a 172px need, so they never meet the cap.

   Above 640px only. On a phone the toolbar is a deliberate stack and a filter
   alone on a line is SUPPOSED to fill it — measured at 390px with the panel
   open, the cap left the fifth filter at 280px in a 324px row, which is exactly
   the ragged edge it exists to prevent elsewhere. A sparse wrapped line is a
   wide-toolbar problem, so the cap stays on wide toolbars. */
@media (min-width: 641px) {
  .toolbar-secondary > input,
  .toolbar-secondary > select { max-width: 280px; }
  /* A select never clips its own labels ("All statu", "All priori" at 1440):
     it may not shrink below its widest option; the group wraps instead. */
  .toolbar-secondary > select { min-width: max-content; }
}

/* (6) THE SORT SELECT TRUNCATED ITS OWN SELECTED LABEL. Every control in the
   group shares the same 88px flex-basis regardless of the text it has to
   carry — fine for "All statuses" (95.6px natural), "All priorities" (96.4px)
   and "All owners" (88.6px), all of which fit inside the ~103px each control
   actually renders at on Hiring Requests. The sort select is different: its
   widest option, "Sort: Created", needs 129.5px including the native arrow
   (measured with the real select, not a clone — a <select>'s intrinsic width
   is set by its widest OPTION, not the one currently selected, so this is the
   floor for every choice in the list). At an identical 103.2px it rendered
   "Sort: Cre…".

   Raising this one control's basis is not the same change as (5)'s cap: (5)
   bounds how far a control may GROW on a sparse wrapped line; this only
   raises where it STARTS from, so no wrap decision on Hiring Requests or the
   Talent Pool (whose own toolbar does not use this class) changes. Scoped to
   a class on the JSX select rather than a structural selector so it travels
   with the control it fixes, not with whatever happens to sit in that slot. */
.toolbar-secondary > select.sort-select { flex-basis: 132px; }

/* (4) THE SCREEN TABS JUMPED WHEN YOU PICKED ONE. `.seg-tab` renders at
   font-weight 500 and `.seg-tab.active` at 600, so selecting a tab made its own
   label wider — "Screening" went 122px to 126px — and shoved every tab after it
   sideways by 2-4px. Emphasis stays, the reflow goes: the weight is now the
   same in both states, and the active tab is still distinguished by its
   background, its colour and its lift, exactly as the sort headers were handled
   in the header-stability work. Scoped to .seg-tab, which only the Talent Pool
   screen tabs use; .tabbar-btn, .profile-tab and .tabs button keep their
   existing behaviour. Font SIZE is untouched. */
.seg-tab, .seg-tab.active { font-weight: 600; }

@media (max-width: 1100px) {
  /* Filters collapse behind the disclosure, so it has to be reachable. */
  .filter-toolbar > button.filter-toggle,
  .filter-toolbar > .filter-toggle { display: inline-flex; }
}

/* Below 640px the toolbar is already a deliberate stack: `.toolbar-count`
   carries `width: 100%` (declared in both arabtec-responsive.css and this
   file's mobile section), so the result count takes its own full-width row by
   design. That is left exactly as it is — an attempt here to make the
   disclosure and the count share a row changed nothing measurable, because
   that width rule decides the line break, and overriding a deliberate mobile
   rule shared with every other toolbar is outside this task. */


/* ==========================================================================
   18b. COMPACT COMPOSITION — one page, not a stack of rectangles
   ----------------------------------------------------------------------------
   Measured at 1440×900 before this section: the first working row (table,
   board, KPI row) began at y=260–571 because every page opened with a tall
   head (82–178px), then a bordered toolbar card (66–126px), then a pill tab
   track, then a card head — each a full-width white rectangle. The remedy is
   hierarchy instead of boxes: the head is shorter, the toolbar and the tabs sit
   on the canvas with a hairline, KPI tiles are 88px, card heads and paddings
   step down. Geometry only; colour lives in arabtec-design-system.css §12.
   ========================================================================= */
.content { padding: var(--sp-4) var(--sp-5) var(--sp-5); }
.content.has-dock { padding-bottom: 96px; }
.page-head, .dash-head { margin-bottom: var(--sp-4); gap: var(--sp-3) var(--sp-4); align-items: flex-end; }
.page-title, .page-head .page-title, .dash-head .page-title { font-size: 24px; line-height: 1.15; }
.page-sub { font-size: 13px; line-height: 1.45; margin-top: var(--sp-1); max-width: 72ch; }
.breadcrumb, .page-head .breadcrumb, .dash-eyebrow { margin-bottom: var(--sp-1); }
.page-head-main .detail-back { margin-bottom: var(--sp-1); }
.page-head-actions { gap: var(--sp-2); align-items: center; }
/* The upload call-to-action keeps its size note on the same line. */
.upload-cta { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.upload-cta-hint { display: none; } /* the limit rides the button's tooltip */

/* Toolbar and Ask bar: controls on the canvas, no surface of their own. */
.toolbar { padding: 0; margin-bottom: var(--sp-3); border-radius: 0; border-width: 0; box-shadow: none; }
.toolbar.ask-bar { padding: 0; margin-bottom: var(--sp-3); }

/* Tabs: an underline row, never a second track of pills. */
.seg-tabs { padding: 0; margin-bottom: var(--sp-4); gap: var(--sp-5); border-radius: 0; border-width: 0 0 1px; }
.seg-tab { min-height: 36px; padding: 0 2px; border-radius: 0; border-width: 0 0 2px; border-style: solid; }
.profile-tabs { padding: 0; gap: var(--sp-5); }
.profile-tab { padding: 8px 2px 10px; }
.tabbar, .tabs { padding: 0; margin-bottom: var(--sp-4); gap: var(--sp-5); border-radius: 0; border-width: 0 0 1px; }
.tabbar-btn { min-height: 36px; padding: 0 2px; border-radius: 0; border-width: 0 0 2px; border-style: solid; margin-bottom: -1px; }
.notice { padding: 8px 12px; }

/* Stat tiles: 88px, the number leads. */
.dash-kpi-row { gap: var(--sp-3); margin-bottom: var(--sp-4); }
.dash-kpi, .kpi { padding: var(--sp-3) var(--sp-4); min-height: 0; gap: 2px; }
.dash-kpi-val, .kpi-value { font-size: 26px; line-height: 1.1; }
.dash-kpi-hint { margin-top: 2px; }

/* Cards: tighter heads and insets; grids on a 12px rhythm. */
.card-head { min-height: 44px; padding: 10px 16px; gap: var(--sp-3); }
.card-head h2, .card-head h3 { font-size: 15px; line-height: 1.3; }
.card-pad, .card > .card-pad { padding: var(--sp-4); }
.ats-card-grid, .dash-grid-2, .report-grid { gap: var(--sp-3); }
.dash-bars { gap: 8px; }
.dash-bar-l { width: 120px; flex-basis: 120px; }
.dash-bar-track { height: 8px; }

/* Detail heads live on the canvas: the record's title is the page title. */
.ticket-header-card { padding: 0 0 var(--sp-3); margin-bottom: var(--sp-3); border-width: 0 0 1px; border-radius: 0; box-shadow: none; }
.ticket-header-card::before { display: none; }
.card.profile-shell { border-width: 0 0 1px; border-radius: 0; box-shadow: none; margin-bottom: var(--sp-4); }
.profile-header { padding: 0 0 var(--sp-3); gap: var(--sp-4); }
.ph-avatar { width: 48px; height: 48px; font-size: 16px; }
.ph-name { font-size: 24px; line-height: 1.15; }
.app-context { padding: 8px 0; }
.profile-overview .form-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3) var(--sp-5); }
.profile-overview .form-grid > .full { grid-column: 1 / -1; }

/* Empty states: a 120px illustration, then the words, then one action. */
.empty { padding: var(--sp-6) var(--sp-5); gap: var(--sp-2); }
.empty .ico, .empty .ico svg { width: 120px; height: 120px; }
.empty .ico { margin: 0 auto var(--sp-2); }
.empty-kicker { font-size: 12px; line-height: 1.4; letter-spacing: .04em; }
.empty-title { font-size: 16px; }
.empty-action { margin-top: var(--sp-3); }

/* CV Inbox: the mailbox status is a meta row under the title, not a card. */
section.card:has(> .cvi-status) { border-width: 0 0 1px; border-radius: 0; box-shadow: none; margin-bottom: var(--sp-4); }
section.card:has(> .cvi-status) > .cvi-status { padding: 0 0 var(--sp-3); gap: var(--sp-3) var(--sp-5); }

@media (max-width: 1180px) {
  .profile-overview .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ==========================================================================
   18c. ROW RULES — one control height, one spacing scale, aligned rows
   ----------------------------------------------------------------------------
   Owner review, 4 Oct 2026, on the live Talent Pool at 1920 wide: "buttons are
   crowded and irregular … where's the spacing and margins rule". Measured on
   production before this section:
     • head row: view switch segments 34px beside 40px buttons, gaps 15/8/9/8;
     • filter row: search 717px, five 112px inputs, a 162px select, and
       "All classifications" stranded alone on a second line at 280px;
     • cards: five columns of ~300px with name and university side by side,
       so both truncated ("Zagazig Univer…", "Senior Store Ke…").

   The rules, which apply to every page that uses these shared parts:
     R-a  Every control in a head row or a filter row is 40px (--cl-ctl-lg).
     R-b  8px between controls of one group, 16px between groups, 12px
          between rows. Nothing else.
     R-c  A filter row is a grid of equal columns. A control that wraps lands
          in a column; it is never stretched across a line on its own.
     R-d  On wide screens the search and the result count share the first row,
          and the filters own the second — always the same two rows.
     R-e  A card never sets two text columns side by side; its content stacks
          under the name, so nothing is cut off at any column count.
   Geometry only; colour stays in arabtec-design-system.css.
   ========================================================================= */

/* R-a / R-b — the head row. The view switch is a 40px box (3 + 34 + 3) with
   the same 8px corner as the buttons beside it, not a pill, and it is a group
   of its own: 8px gap + 8px margin = 16px to the actions. */
.page-head-actions { gap: var(--sp-2); }
.page-head-actions .view-toggle {
  box-sizing: border-box; height: var(--cl-ctl-lg); /* track inset stays the base 3px */
  align-items: stretch; border-radius: var(--cl-r-md);
}
.page-head-actions .view-toggle-btn { height: 100%; padding: 0 var(--sp-3); border-radius: var(--cl-r-sm); }
.page-head-actions > .view-toggle { margin-inline-end: var(--sp-2); }

/* R-c / R-d — the filter row on wide screens. Below 1101px the filters live
   behind the disclosure button (section 18) and keep their stacked layout. */
@media (min-width: 1101px) {
  .toolbar.filter-toolbar { flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4); }
  .filter-toolbar > .toolbar-secondary {
    order: 1; flex: 1 0 100%;
    display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
    gap: var(--sp-2); align-items: center;
  }
  .filter-toolbar > .toolbar-secondary > input,
  .filter-toolbar > .toolbar-secondary > select { width: 100%; min-width: 0; max-width: none; }
  /* The sort-direction button is a standard .btn (40px, R17); a switch is a
     label, so it is given the control height here. */
  .filter-toolbar > .toolbar-secondary > .btn { justify-self: start; }
  .filter-toolbar > .toolbar-secondary > .switch {
    justify-self: start; box-sizing: border-box;
    height: var(--cl-ctl-lg); padding-top: 0; padding-bottom: 0;
  }
}

/* R-e — candidate cards: wider columns, one stacked text column. */
/* min(320px, 100%): a card column never asks for more than the space it has.
   A bare 320px minimum loads after the phone rules and overrode them, pushing
   every card 16px past a 320px screen (2026-10-15). */
.cand-grid { grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); gap: var(--sp-3); }
.cand-card { padding: var(--sp-4); gap: var(--sp-3); }
.cand-card .cc-face { grid-template-columns: 40px minmax(0, 1fr); gap: var(--sp-1) var(--sp-3); }
.cand-card .cc-avatar { grid-row: auto; width: 40px; height: 40px; }
.cand-card .cc-uni { grid-column: 2; text-align: start; }
/* An empty education block would only print two dashes. Cards in a row still
   end level: grid rows stretch and the footer is pushed to the bottom. */
.cand-card .cc-uni.is-empty { display: none; }
.cand-card .cc-facts { margin-top: var(--sp-2); }
.cand-card .cc-foot { padding-top: var(--sp-3); gap: var(--sp-3); }

/* ============================================================================
   19. COMPACT STATE COMPONENTS — badges, chips and tabs are ONE family
   ----------------------------------------------------------------------------
   Section 14 wrote the containment contract for a compact label: it stays on
   one line, it never breaks mid-word, it never spills past its own rounded
   edge, and it never shrinks its font to fit. That contract named seven
   classes. The product ships more than seven, and the ones it missed are the
   ones that carry the longest strings — project names, request titles, tag
   text, free-text filter values. This section finishes the set and gives the
   tab families the same freedom from state-driven movement that `.seg-tab`
   received in section 18.

   Every number below was measured in a browser against these five stylesheets
   at 1440, 1366, 1280, 1200, 1024, 768, 430, 390 and 360, using the real
   markup from app.jsx and real Arabtec data (the nine role names from
   backend/src/lib/permissions.js, the three project names from
   backend/prisma/seed.js).

   ---- THE REFERENCE CASE: the role chip in Settings -> Users ---------------
   `Recruitment Manager` is the longest of the nine role names. In the Users
   table it renders at 133.38px. The Role(s) column is 193.26px at 1440 and
   160.38px at 360, and it cannot go below that, for two INDEPENDENT reasons —
   worth separating, because only one of them is durable:

     1. UsersPage renders a bare `<table>` inside a plain `.card` (app.jsx),
        which matches the first half of the selector list at
        arabtec-design-system.css:988-989 (`.card table:not(.responsive-table)
        :not(.review-table):not(.parse-review-table), .modal-body table… {
        min-width: 680px }`), so the table does carry a 680px floor, and
        `.card:has(table…) { overflow-x: auto }` in the same file gives it its
        own horizontal scroll. Note this floor belongs to the selector LIST,
        not to `.modal-body` alone — a bare table in a card is covered.
     2. Independently of any floor: this table uses AUTOMATIC table layout, so
        every column's used width is at least its min-content width, and
        section 14 gives `.chip` `white-space: nowrap`. The min-content width
        of a nowrap chip is the whole chip, so the Role(s) column can never be
        narrower than the widest chip standing in it, at any viewport.

   The chip therefore FITS at every width, with 27px of slack at the narrowest,
   and `scrollWidth === clientWidth` throughout.

   WHAT WOULD KILL THE GUARANTEE. Reason 2 is the one that holds the shape, and
   it dies silently if this table is ever given `table-layout: fixed` or a
   `<colgroup>`: fixed layout stops consulting content, the min-content floor
   disappears, and the chip starts being cut — with no ellipsis to show for it,
   because `.chip` is a flex box and guillotines (section 14). If that day
   comes, the role chip needs a `title` under section 15's tooltip policy.

   The decision TODAY is FIT, not truncate-with-tooltip, and the nine role
   names are a fixed, measured enum — so section 15's policy gives them no
   `title`, and they have none. Section 14's containment stays as the safety
   floor underneath: if a future role name is longer, the chip cuts inside its
   own edge instead of breaking the row. The same nine names are rendered a
   second time by `.tag-toggle` in the user editor — alongside project and site
   names, which ARE unbounded, so that component does carry titles — and that
   one DID break; see (2).
   ========================================================================= */

/* (1) CONTAINMENT, EXTENDED. These four families had no width ceiling at all:
   `max-width: none`, so a long value sets their intrinsic width and pushes
   whatever row they sit in. A ceiling of 100% makes the parent — a table cell,
   a wrapped chip row, a flex line — the authority on how wide a label may be,
   which is the same rule section 14 gave `.chip`. `min-width: 0` is the flex
   floor from section 14 (1): without it a flex child refuses to shrink below
   its min-content width, which is exactly how one long string widens a page. */
.chip-filter, .cvi-badge, .preview-status-badge, .tag-toggle {
  max-width: 100%;
  min-width: 0;
}

/* (2) `.tag-toggle` WAS `display: inline`, AND IT BROKE THE PILL IN HALF.
   The user editor renders every role, project and site name through this
   class. As a bare inline box it wrapped BETWEEN WORDS like running text, so
   at 360px the real project name `I-City New Cairo — Lagoon Beach Park`
   rendered as two fragments — 104.99px on one line, 139.78px on the next —
   each drawing its own 1px border and its own background. The rounded edge was
   cut open in the middle of the name. Worse, an inline box contributes only
   its font's content area to the line, not its padding, so the two 28px
   fragments sat 19px apart and overlapped each other.

   `inline-block` is the fix, not `inline-flex`: with a single text child it is
   the only one of the two where `text-overflow: ellipsis` actually renders an
   ellipsis (on a flex container the text becomes an anonymous flex item, which
   does not inherit `text-overflow`, so the label is guillotined mid-glyph
   instead). Measured at 360px after the change: one line box, 248.23px, whole.

   `line-height: normal` is what the `<button>` form of this same class already
   resolved to from the UA sheet, while the `<span>` form inherited the body's
   1.45. Under `display: inline` that difference was invisible, because an
   inline box takes its height from the font. Under `inline-block` it is not:
   the two forms would have rendered 28.5px and 32.13px side by side. Pinning
   it to the button's own value puts both at 28.5px with the glyphs 7px/7.5px
   from the edges — one component, one height, and the button form does not
   move by a single pixel. */
.tag-toggle {
  display: inline-block;
  vertical-align: middle;
  line-height: normal;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* (3) `.chip-filter` CARRIES FREE TEXT. Its label is built from user data —
   `Request: R-0041 · <request title>`, `Owner: <name>`, a raw search term — so
   it is the one chip whose content has no upper bound at all. It wrapped
   inside its own pill (`white-space: normal`) and had no ceiling.

   The label is given its own element in app.jsx precisely so the ellipsis has
   a block box to render in; the remove button is pinned so the truncation eats
   the label and never the control. app.jsx also puts the full label in a
   `title`, because here truncation really does hide something. */
.chip-filter { white-space: nowrap; overflow: hidden; }
.chip-filter > .chip-filter-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.chip-filter > button { flex: 0 0 auto; }

/* (4) The two intake badges truncate rather than clip, for the same reason:
   a single text child in a block box ellipsises, in a flex box it does not.
   `.preview-status-badge` is already `inline-block`; `.cvi-badge` moves from
   `inline-flex`, measured identical at 133.88 x 21.95 before and after,
   because its content was a single line box the full height of the content
   area — the flex centring had nothing to centre. Their labels come from fixed
   enum tables and fit today at every measured width; this is the floor under
   them, not a change to what they show. */
.cvi-badge { display: inline-block; }
.cvi-badge, .preview-status-badge { overflow: hidden; text-overflow: ellipsis; }

/* (5) SELECTING A TAB MOVED THE TABS AFTER IT — on two more families.
   Section 18 (4) fixed `.seg-tab`. The same defect was still live one rule
   higher up this file, at section 8: `.tabbar-btn, .profile-tab` render at
   font-weight 500 and `.tabbar-btn.active, .profile-tab.active` at 600, so
   selecting a tab widened its own label and shoved everything after it
   sideways. Measured at 1440: `Conversation` went 106.49px to 113.47px and
   pushed the four tabs behind it 6.98px to the right; `Overview` went 82.35px
   to 86.27px and pushed four tabs 3.92px. Both are pure metric movement — the
   label says the same word in the same size, just wider.

   Emphasis stays and the reflow goes, exactly as it did for `.seg-tab`: the
   weight is now equal in both states and the selected tab is still the white
   pill on the grey track, which is how `.control-tab`, `.view-toggle-btn` and
   `.seg-tab` already say "selected".

   BORDER WIDTH WAS CHECKED AND NEEDS NO RESERVATION. `.tabbar-btn.active` and
   `.profile-tab.active` do set `border-bottom-color` (styles.css,
   arabtec-approved-ui.css), but arabtec-design-system.css replaced the whole
   underline treatment with the pill and set `border: 0` on BOTH the base and
   the active rule. Computed border width is 0px on all four sides in both
   states — verified in the browser — so those colour declarations paint
   nothing and there is no width for the active state to add. Reserving 2px
   here would not prevent a shift; it would introduce a visible line the
   approved design deliberately removed. */
.tabbar-btn, .tabbar-btn.active,
.profile-tab, .profile-tab.active { font-weight: 600; }

/* (6) THE ANYHELP TABS. Fixed at the source instead of here. The bug was an
   asymmetry — `.anyhelp-tabs button` declared no border while
   `.anyhelp-tabs button.on` declared 1.5px — and the base rule that owns the
   component's colour is the only place that asymmetry can be closed without
   also removing the selected tab's only outline. The base now reserves
   `1.5px solid transparent`; `.on` repaints the same 1.5px in green. Nothing
   for this layer to add. */

/* (7) NOT CHANGED, AND WHY.
   `.chip`'s DISPLAY stays `inline-flex`, which means `.chip` GUILLOTINES —
                   it cuts mid-glyph and shows no "…", because a bare text
                   child of a flex container is an anonymous flex item and does
                   not inherit `text-overflow` (the same mechanism explained at
                   (2) for `.tag-toggle`, where the fix WAS applied). It is not
                   applied here: 13 sites compose `.chip`, several of them with
                   an icon or a dot child that the flex box is aligning, so
                   making it a block box is a component change and not a
                   containment fix. The consequence is that nothing on a `.chip`
                   signals that text was cut — which is exactly why every
                   unbounded `.chip` in app.jsx carries a `title` (section 15's
                   tooltip policy). The `title` is not decoration on these; it
                   is the only place the cut characters still exist.
   `.score-badge`  renders 0-100 in tabular numerals behind a 34px min-width
                   and already carries `title="Match score N of 100"`. It has
                   no long-text case to have.
   `.rq-link-chip` already truncates: its subtitle line is capped at 150px with
                   `text-overflow: ellipsis` (measured clipping 250px of title
                   into 150px at every width) and the chip carries the full
                   ticket number, title and status in a `title`. Correct as
                   found.
   `.chip-warn`    is `class="chip chip-warn"` — it inherits section 14's
                   containment in full. Correct as found.
   `.hist-badge-btn` is a bare button wrapping a `<Badge>`; the badge is
                   contained by section 14 and the button already carries the
                   full application history in a `title`. Correct as found.
   `.control-tab`, `.view-toggle-btn`, `.seg-tab` were toggled in the browser
                   at all nine widths: every button's rect, and every
                   neighbour's rect, byte-identical. Correct as found.
   State rules    no `:hover`, `:focus`, `:focus-visible`, `:active`, `.active`
                   or `.on` rule anywhere in the five stylesheets declares a
                   layout property whose value DIFFERS from the base rule's —
                   descendant-combinator forms such as
                   `.filter-chips .chip-filter.on` included. The focus ring is
                   an `outline`, which is painted outside the box and reserves
                   nothing. Verified by walking the whole CSSOM, and by
                   focusing and selecting each control and re-measuring it and
                   its siblings. */

/* ============================================================================
   20. FORM CONTROLS — ONE BOX PER PURPOSE, AND NO STATE MAY CHANGE IT

   Measured in a browser against all five sheets in load order, with the real
   markup from app.jsx, at 1440, 1366, 1280, 1200, 1024, 768, 430, 390 and 360.
   Every button variant and every input context was toggled through hover,
   focus, focus-visible, active and disabled and re-measured, along with its
   next sibling.

   WHAT WAS ALREADY RIGHT, and is only guarded from here on:

   - NO STATE CHANGES A BOX. At all nine widths, the rect of every `.btn`
     variant, every `.icon-btn`, every input, select and textarea is identical
     in hover, focus, focus-visible, active and disabled to its resting rect —
     width, height, padding, border width, type size, type weight and gap — and
     no neighbour moves. Emphasis is carried by colour, background and outline
     only. `:active`'s `transform: translateY(.5px)` is the one exception worth
     naming and it is CORRECT: a transform paints outside layout, so the button
     dips half a pixel and nothing around it moves. Verified, kept.
   - ONE DEFAULT AND ONE COMPACT BUTTON BOX. `.btn`, `-secondary`, `-ghost`,
     `-danger`, `-danger-solid`, `-success`, `-navy`, `-outline-blue`,
     `-primary-solid` and `-block` all resolve to the same default box; the
     `-sm` and `-xs` forms all resolve to the same compact box. Correct as
     found — nothing in this section touches them.

   WHAT THIS SECTION CLOSES — four controls that sat off the system:
   ======================================================================== */

/* (1) `.btn.small` WAS A THIRD COMPACT BUTTON. Every other compact button is
   32px tall on `0 12px`; `.btn.small` measured 34px tall on `7px 13px` at
   1600 — the only button in the product carrying VERTICAL padding, a third
   horizontal value, and a third height. It reached that box because the
   compact rule in section 6 lists `.btn.btn-sm` and `.btn.btn-xs` but not
   `.btn.small`, so `.btn.small` fell through to `.btn-sm, .btn.small
   { min-height: 34px; padding: 7px 13px }` in arabtec-design-system.css —
   which is (0,2,0) for the `.btn.small` half of its selector list and so
   outranks everything section 6 has to say.
   The 32px height was only ever visible between 1024 and 1440, where the
   responsive laptop band happens to name `.btn.small` alongside the others;
   above 1440 and below 1024 it was 34px. No JSX composes the class today,
   which is precisely why it is worth pinning now: it is already declared in
   three sheets, so the next use of it would have inherited the odd box
   silently. Its FONT SIZE is left where it is — that is a typographic
   decision, not a fitting mechanism. */
/* The rule itself lives in section 6, beside the other compact buttons and,
   crucially, BEFORE the 640px touch-floor block — declared here it would have
   outranked that block and left `.btn.small` 32px under a thumb. */

/* (2) THE ANYHELP REQUEST PICKER WAS 38px ON A PHONE. `.anyhelp-empty select`
   is the live hiring-request picker in the team-chat panel (app.jsx). It set
   its own `min-height: 38px`, `padding: 0 14px` and a 999px pill radius at a
   specificity the shared control rules cannot reach, so it was the one control
   in the panel that stayed 38px at 360 and 390 — under the 44px touch floor
   the UI-readiness release asserts — and the one select in the product still
   drawn as a pill while every other is on the 8px radius.
   Height comes from --cl-ctl-lg, which section 13 already steps to 44px below
   640px, so the touch floor is inherited rather than restated. Its FONT SIZE
   is deliberately left alone: a font size is a typographic decision, never a
   fitting mechanism (section 14 rule (6)). */
.anyhelp-empty select {
  min-height: var(--cl-ctl-lg);
  padding: 0 var(--cl-4);
  border-radius: var(--cl-r-md);
}

/* (3) THE COMPOSER TEXTAREA WAS THE ONLY 12px RADIUS IN THE PRODUCT. The mira
   ramp is 4 / 6 / 8 / 10 / 14 / 18 / 22 / 26; `.anyhelp-composer textarea` sat
   at 12, between two steps, beside a `.field textarea` on 8. Its HEIGHT is not
   touched — a chat composer is deliberately a short box that grows to a
   120px cap, and that is a component decision, not a drift. */
.anyhelp-composer textarea { border-radius: var(--cl-r-md); }

/* (4) THE FOCUS RING WAS BEING DELETED ON EVERY SCOPED FIELD.
   `:focus-visible` was declared four times across the sheets. What actually
   won, measured: a `.btn` got a 2px solid outline at 2px offset AND a 3px
   box-shadow halo — two overlapping rings — while a `.field input`, a
   `.field select`, a `.field textarea`, a `.toolbar input` or a `.toolbar
   select` got NO outline at all, because `.field input:focus { outline: none }`
   is (0,2,1) and the only rule that restored an outline,
   `input:focus-visible`, is (0,1,1). Those fields fell back to a
   near-white mint halo — about 1.1:1 on white, well under the 3:1 a focus
   indicator is asked for, and the same objection the design system already recorded
   when it replaced the mockup's mint ring on buttons.
   So: one ring, on every control. The WIDTH, the OFFSET and the STYLE keyword
   are geometry and live here; the outline COLOUR stays in the design system,
   exactly as a border's width lives here and its colour does not. An outline
   is painted outside the border box and reserves no space, which is why it is
   the only emphasis a focus state may add. */
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible,
.toolbar input:focus-visible, .toolbar select:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline-style: solid;
  outline-width: 2px;
  outline-offset: 2px;
}

/* (5) NOT CHANGED, AND WHY.
   36px vs 40px    CLOSED by the cards-and-containers step. The laptop band
                   stepped control height at BOTH its edges, so controls were
                   smallest mid-range; the overrides are gone and height now
                   comes from --cl-ctl-lg alone — 40px above the phone floor,
                   44px below.
   `.ask-input`    `arabtec-approved-ui.css` sets `min-width: 260px` and
                   `arabtec-responsive.css` releases it through
                   `.page-head:has(+ .ask-bar) + .ask-bar .ask-input
                   { min-width: 0 }`. That selector IS live — PageHead renders
                   `.page-head` immediately followed by `.toolbar.ask-bar` —
                   and the computed `min-width` measures 0px at 390. Not a
                   defect. Not touched.
   `.pcard-actions .btn`
                   styles.css gives it a 28px box and a `.55` disabled opacity.
                   `.pcard-actions` contains only an `.icon-btn` and a `.menu`
                   in every JSX file, so both rules are dead. Removing them is
                   the dead-rule backlog, not this section; the divergent
                   opacity is aligned at its source instead.
   `.decision-select`, `.review-edit`, `.bulk-bar select`
                   also sit under the 44px floor on a phone (38px and 30px) and
                   carry off-ramp 7px radii. They belong to the review table
                   and the bulk bar — table geometry, which this step does not
                   touch. Measured and recorded, deliberately not changed.
   `.view-toggle-btn`, `.pcard-name`
                   resolve `outline-style: none` under `:focus-visible`, so
                   they show no keyboard focus ring. `.view-toggle-btn` is a
                   member of the tab family that section 19 owns and
                   `.pcard-name` is a candidate-card title; both are outside
                   the form-control family. Recorded, not changed.
   BUSY LABEL WIDTH
                   there is no loading COMPONENT, but there is a loading state:
                   ~20 buttons swap their label while a request is in flight
                   ("Save" -> "Saving…", "Ask" -> "Searching…"). That is a
                   width change on state — measured at 1440 between -38.93px
                   and +53.70px. No width is reserved, on purpose: every
                   CSS-only reservation is a per-site magic number that is
                   wrong the moment a label is reworded, and the markup
                   alternative is a new busy-button component at ~20 call
                   sites. The measured consequence is bounded: in
                   `.page-head-actions`, `.toolbar.ask-bar` and
                   `.anyhelp-composer` the flexible field absorbs it and no
                   neighbour moves at all; only the right-anchored
                   `.modal-foot` shifts its Cancel button, by 16.09px, after
                   the user has already clicked. Recorded as backlog. */

/* ============================================================================
   21. OVERLAYS — DIALOGS AND THE CV REVIEW SIDE PANEL

   Measured in a browser against all five sheets in load order, with markup
   copied verbatim out of app.jsx, at 1440, 1280, 1024, 768, 430, 390 and 360.
   Geometry only: every colour, background, border colour and type size for
   `.cvrev-*` lives in the design system, beside the rest of the palette.

   WHAT THE AUDIT FOUND ALREADY CORRECT, and is not touched here:

   - `.modal` fits at every measured width. It is `width: 100%` on a
     `max-width: 560px` (760px when `wide`) inside a `.modal-overlay` whose
     padding is 20px above 640px and 8px below it. Measured widths: 560 at
     1440/1280/1024/768 for the default dialog, 760 at 1440/1280 and 728 at 768
     for the wide one, and 414 / 374 / 344 at 430 / 390 / 360 — every one
     inside the usable viewport, with the document's horizontal overflow
     measured at exactly 0 at all seven widths.
   - `.modal > .modal-body` already carries `flex: 1 1 auto; min-height: 0;
     overflow-y: auto`, and `.modal` itself is `max-height: min(92vh, 100%)`
     (94vh under 640px), so a long dialog scrolls its BODY while the head and
     foot stay put. Measured on the wide dialog at 1440: panel 828px tall with
     its body the only scroller. Correct as found.
   - Dialog semantics — role, aria-modal, the labelled close control, the Tab
     trap, Escape and focus restore — are behaviour, asserted by
     ui_readiness_test, and nothing in this section changes them. The new panel
     joins that contract rather than opting out of it.

   WHAT THIS SECTION CLOSES:

   (1) THE STACKED PARSE REVIEW SPLIT ONE SHORT SCREEN INTO TWO SHORTER ONES.
       `.parse-review-body` is a grid with `overflow: hidden`; at 900px and
       below it collapses to one column, so the two panes became two ROWS
       sharing one fixed height that cannot scroll. Measured at 390x844 before
       this change: the body box was 627px tall, and inside it the parsed
       results were a 334px window onto 771px of content (438px reachable only
       by scrolling that pane) while the CV sat in a 293px box whose own
       viewport showed 252px of a 524px document. Nothing was unreachable —
       but a recruiter checking a value against the source was scrolling two
       separate 300px-tall panes against each other on a phone.
       Now, BELOW 900px ONLY, the body is the single scroller and each pane
       sizes to its own content: results 772px (no inner scrollbar at all) and
       the CV on a 60vh floor — 506px, twice what it had. Above 900px nothing
       changes: the two columns still each own their scroller, which is correct
       when they sit side by side.
       `min-height: auto` on the results pane is the load-bearing half. A grid
       item whose `overflow` is not `visible` has an automatic minimum size of
       ZERO, so simply letting the body scroll left the results row collapsed
       to 81px — measured, and worse than what it replaced. The pane has to
       give up its own scroller for the row to size to its content.

   (2) THE CV REVIEW SIDE PANEL'S BOX. New component; the rules below are its
       geometry. Two points are load-bearing:
       - it is a SIDE panel, so the overlay's centring padding is released and
         the panel hugs the inline-end edge at `min(1040px, 100%)`. At 1024 and
         below that resolves to the full viewport width, which is why nothing
         overflows on a phone: there is no fixed pixel width to overflow with.
       - the note composer is the LAST flex child at `flex: 0 0 auto`, so it is
         pinned to the bottom of the panel and stays reachable while the
         details and the CV scroll. That is the requirement — one note at the
         bottom — expressed as layout rather than as a scroll position.
   ======================================================================== */

/* (1) The stacked parse-review layout scrolls as ONE surface; the two-column
   one still does not (there each column owns its own scroller). */
@media (max-width: 900px) {
  .parse-review-body { overflow-y: auto; }
  .parse-review-results { overflow: visible; min-height: auto; }
  .parse-review-cv { min-height: 60vh; }
}

/* (2) THE CV REVIEW SIDE PANEL. */
/* The scrim keeps everything `.modal-overlay` already provides — the fixed
   inset, the z-index, and the rules that hide the mobile nav and the Anyhelp
   FAB behind an open dialog. Only its box changes: a panel that hugs an edge
   cannot also be centred inside 20px of padding. */
.modal-overlay.cvrev-overlay { padding: 0; place-items: stretch; }

.cvrev-panel {
  margin-inline-start: auto;
  /* 25 Oct 2026: the CV is the point of this panel — the panel takes the whole
     viewport and the details column is capped, so the document gets roughly
     twice the width it had at min(1040px, 50%). */
  width: min(1800px, 100%);
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.cvrev-head { flex: 0 0 auto; }
.cvrev-head-id { min-width: 0; display: grid; gap: var(--cl-1); }
.cvrev-head-id h3 { margin: 0; }
.cvrev-head-sub { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Details and CV side by side. `minmax(0, 1fr)` twice, not `1fr 1fr`: a grid
   track's default `min-width: auto` is the content's minimum, so a long
   unbroken value (an email, a LinkedIn URL) would push the track — and the
   panel — wider than the viewport. */
.cvrev-body {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  overflow: hidden;
}
.cvrev-details { min-width: 0; min-height: 0; overflow-y: auto; padding: var(--cl-5) var(--cl-6); }
.cvrev-col-title { margin: 0 0 var(--cl-3); }
.cvrev-cv { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
/* CvFilePreview is reused verbatim; it only needs to be told to fill the cell. */
.cvrev-cv > .parse-review-cv { flex: 1 1 auto; min-height: 0; }
.cvrev-cv-state { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--cl-5); display: grid; align-content: center; }

.cvrev-table { width: 100%; border-collapse: collapse; }
.cvrev-table td { padding: var(--cl-2) var(--cl-3) var(--cl-2) 0; vertical-align: top; }
.cvrev-table td:first-child { width: 1%; white-space: nowrap; }
.cvrev-table td:last-child { word-break: break-word; }
.cvrev-section-row td { padding: var(--cl-4) 0 var(--cl-1); }

.cvrev-note { flex: 0 0 auto; display: grid; gap: var(--cl-2); padding: var(--cl-4) var(--cl-6); }
.cvrev-note-row { display: flex; gap: var(--cl-3); align-items: flex-start; }
.cvrev-note-row textarea { flex: 1 1 auto; min-width: 0; resize: vertical; }
.cvrev-note-row .btn { flex: 0 0 auto; }
.cvrev-foot { flex: 0 0 auto; }

/* Below 900px the two columns stack, so the BODY becomes the scroller and each
   pane releases its own. The CV keeps a floor height — a preview with no
   height at all is a blank surface, which is exactly what this panel exists to
   avoid. The composer stays pinned outside the scroller. */
@media (max-width: 900px) {
  .cvrev-body { grid-template-columns: minmax(0, 1fr); overflow-y: auto; }
  /* `min-height: auto` is not decoration: a grid item whose overflow is not
     `visible` has an automatic minimum size of zero, so leaving the pane's own
     scroller on collapsed this row to 25px with its table painting straight
     over the CV below it. Measured at 390 before this line. */
  .cvrev-details { overflow: visible; min-height: auto; }
  .cvrev-cv { overflow: visible; min-height: 60vh; }
}
@media (max-width: 640px) {
  .cvrev-details { padding: var(--cl-4) var(--cl-5); }
  .cvrev-note { padding: var(--cl-3) var(--cl-5); }
  /* A 44px-floor button beside a textarea leaves the field too narrow to read
     on a phone, so the composer stacks and the button spans the row. */
  .cvrev-note-row { flex-wrap: wrap; }
  .cvrev-note-row .btn { flex: 1 1 100%; }
}

/* Interview calendar (Interviews → Day / Week / Month): geometry only; the
   paint is in arabtec-design-system.css. --cal-cols and --cal-hour are set
   inline by the component, so a Friday column or a phone's taller hour needs
   no extra rule. */
.cal-card { padding: 0; overflow: hidden; }
.cal-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-4); padding: var(--sp-3) var(--sp-4); border-bottom-width: 1px; border-bottom-style: solid; }
.cal-nav { display: inline-flex; align-items: center; gap: var(--sp-1); }
.cal-title { margin: 0; font-variant-numeric: tabular-nums; }
.cal-head, .cal-body { display: grid; grid-template-columns: 56px repeat(var(--cal-cols), minmax(0, 1fr)); }
.cal-head { border-bottom-width: 1px; border-bottom-style: solid; }
.cal-dayhead { display: flex; flex-direction: column; align-items: center; gap: 0; padding: var(--sp-2) var(--sp-1); border-inline-start-width: 1px; border-inline-start-style: solid; }
.cal-dow { display: block; letter-spacing: .08em; text-transform: uppercase; }
.cal-dom { display: block; font-variant-numeric: tabular-nums; }
.cal-dayhead.today { border-bottom-width: 2px; border-bottom-style: solid; }
.cal-time { height: var(--cal-hour); padding-inline-end: var(--sp-2); text-align: end; transform: translateY(-7px); font-variant-numeric: tabular-nums; }
.cal-time:first-child { visibility: hidden; }
.cal-col { position: relative; border-inline-start-width: 1px; border-inline-start-style: solid; }
/* Longhands, never the `border` shorthand: this sheet loads after the design
   system, and a shorthand here would reset the border colour it paints. */
.cal-slot { display: block; width: 100%; height: calc(var(--cal-hour) / 2); padding: 0; margin: 0; border-width: 0 0 1px; border-style: none none dashed; border-radius: 0; }
.cal-slot:nth-child(even) { border-bottom-style: solid; }
button.cal-slot { cursor: pointer; }
button.cal-slot:focus-visible { outline: 0; }
.cal-now { position: absolute; inset-inline: 0; height: 2px; z-index: 3; pointer-events: none; }
.cal-now::before { content: ''; position: absolute; inset-inline-start: -4px; top: -3px; width: 8px; height: 8px; border-radius: 50%; }
.cal-ev { position: absolute; z-index: 2; display: flex; flex-direction: column; gap: 0; padding: var(--sp-1) var(--sp-2); overflow: hidden; text-align: start; border-width: 1px; border-style: solid; border-inline-start-width: 3px; border-radius: var(--cl-r-sm); cursor: pointer; }
.cal-ev:focus-visible { outline: 0; }
.cal-ev-time { font-variant-numeric: tabular-nums; }
.cal-ev-who { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.cal-ev-status { letter-spacing: .06em; text-transform: uppercase; }
.cal-s-cancelled { border-style: dashed; text-decoration: line-through; }
.cal-month-head, .cal-month-row { display: grid; grid-template-columns: repeat(var(--cal-cols), minmax(0, 1fr)); }
.cal-month-head .cal-dow { padding: var(--sp-2) var(--sp-3); border-bottom-width: 1px; border-bottom-style: solid; }
.cal-mcell { position: relative; display: flex; flex-direction: column; gap: var(--sp-1); min-height: 104px; padding: var(--sp-2); border-bottom-width: 1px; border-bottom-style: solid; border-inline-start-width: 1px; border-inline-start-style: solid; }
.cal-mfill { position: absolute; inset: 0; z-index: 0; padding: 0; border: 0; border-radius: 0; cursor: pointer; }
.cal-mfill:focus-visible { outline: 0; }
.cal-mdate, .cal-mev, .cal-more { position: relative; z-index: 1; }
.cal-mdate { align-self: flex-start; min-width: 28px; padding: 0 var(--sp-2); border: 0; border-radius: var(--cl-r-sm); cursor: pointer; font-variant-numeric: tabular-nums; }
.cal-mev { display: block; width: 100%; padding: 0 var(--sp-2); overflow: hidden; text-align: start; white-space: nowrap; text-overflow: ellipsis; border-width: 0; border-style: none; border-inline-start-width: 3px; border-inline-start-style: solid; border-radius: var(--cl-r-xs); cursor: pointer; }
.cal-more { align-self: flex-start; padding: 0 var(--sp-2); border: 0; cursor: pointer; }
.cal-strip { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--sp-1); padding: var(--sp-2) var(--sp-3); border-bottom-width: 1px; border-bottom-style: solid; }
.cal-strip-day { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0; min-height: 48px; padding: 0; border-width: 1px; border-style: solid; border-radius: var(--cl-r-md); cursor: pointer; }
.cal-strip-day > span { letter-spacing: .06em; text-transform: uppercase; }
.cal-strip-day > strong { font-variant-numeric: tabular-nums; }
.cal-clash { margin-top: var(--sp-3); }

/* Candidate Review: the full name typed from the CV when the reader found none. */
.intake-name-entry { max-width: 480px; margin-top: var(--sp-4); }

/* Candidate Review: the CV in a side panel beside the review, not a new page.
   Wide screens split the page; narrower ones slide the panel in from the
   inline end over the review. Paint is in arabtec-design-system.css. */
.intake-review-split.with-cv { display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 44%); gap: var(--sp-4); align-items: start; }
.intake-review-main { min-width: 0; }
.intake-cv-side { position: sticky; top: var(--sp-4); display: flex; flex-direction: column; height: calc(100vh - 128px); min-height: 480px; overflow: hidden; border-width: 1px; border-style: solid; border-radius: var(--cl-r-xl); }
.intake-cv-side-head { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); border-bottom-width: 1px; border-bottom-style: solid; }
.intake-cv-side-head > strong { flex: 1 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.intake-cv-side > .parse-review-cv { flex: 1 1 auto; min-height: 0; }
.intake-cv-side-state { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--sp-4); display: grid; align-content: center; }
@media (max-width: 1100px) {
  .intake-review-split.with-cv { grid-template-columns: minmax(0, 1fr); }
  .intake-cv-side { position: fixed; top: 0; bottom: 0; inset-inline-end: 0; z-index: 60; width: min(92vw, 560px); height: auto; min-height: 0; border-radius: 0; }
}
.parse-review-name { margin-top: var(--sp-3); }

/* Roles & Permissions: one section per area, a count and Select all in the
   head, switches in two columns (one on narrow screens). */
.perm-search { margin-bottom: var(--sp-4); max-width: 360px; }
.perm-search .input { width: 100%; }
.perm-area { padding: var(--sp-4) 0; }
.perm-area:first-of-type { border-top: 0; padding-top: 0; }
.perm-area-head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.perm-area-head h4 { margin: 0; }
.perm-area-count { margin-right: auto; }
.perm-area-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3) var(--sp-5); }
@media (max-width: 900px) { .perm-area-grid { grid-template-columns: 1fr; } }

/* ==========================================================================
   22. OFFER COMPOSE — the form beside the letter it produces
   ----------------------------------------------------------------------------
   Owner request, 5 Oct 2026: on Create offer, preview the offer letter on the
   right half and show each field still to be filled as a blank in the letter,
   updating while typing. The letter is the server's own (lib/offer-letter.js),
   in a sandboxed frame scaled to the pane. Geometry only.
   ========================================================================= */
.modal.offer-compose { width: min(1320px, 96vw); max-width: none; height: min(920px, 94vh); display: flex; flex-direction: column; }
.offer-compose-body { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.offer-compose-form { min-height: 0; overflow-y: auto; padding: var(--sp-4) var(--sp-5); }
.offer-compose-form .section-title { margin: var(--sp-5) 0 var(--sp-2); }
.offer-compose-form .section-title:first-of-type { margin-top: var(--sp-3); }
.offer-compose-form .notice { margin-bottom: var(--sp-3); }
.offer-letter-pane { min-height: 0; overflow-y: auto; padding: var(--sp-4); border-left-width: 1px; border-left-style: solid; }
.offer-letter-scale { overflow: hidden; }
.offer-letter-scale iframe { display: block; border: 0; }
.offer-total { min-height: var(--cl-ctl-lg); display: flex; align-items: center; font-weight: 600; font-variant-numeric: tabular-nums; }
.modal.offer-letter-modal { width: min(940px, 96vw); max-width: none; height: min(960px, 94vh); display: flex; flex-direction: column; }
.modal.offer-letter-modal .modal-body { flex: 1 1 auto; min-height: 0; padding: 0; display: flex; }
.modal.offer-letter-modal .offer-letter-pane { flex: 1 1 auto; border-left-width: 0; }
@media (max-width: 900px) {
  .modal.offer-compose { height: 94vh; }
  .offer-compose-body { grid-template-columns: 1fr; overflow-y: auto; }
  .offer-compose-form, .offer-letter-pane { overflow: visible; }
  .offer-letter-pane { border-left-width: 0; border-top-width: 1px; border-top-style: solid; }
}

/* ==========================================================================
   23. ASSESSMENT PROGRESS, CHANGE REASON AND HISTORY (audit 2026-10-05)
   ----------------------------------------------------------------------------
   The form opens with where each part stands; changing a submitted evaluation
   asks why; the history lists who did what. Geometry only.
   ========================================================================= */
.assess-progress { margin: 0 0 var(--sp-5); }
.assess-progress ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.assess-progress a { display: inline-flex; align-items: center; gap: var(--sp-2); min-height: var(--cl-ctl-lg); padding: 0 var(--sp-3); border-width: 1px; border-style: solid; border-radius: var(--cl-r-md); text-decoration: none; }
.assess-progress-label { font-weight: 600; }
/* The save row is a wrapping flex row; the reason takes a full line of its own,
   between the state line and the buttons. */
.assess-reason { flex: 1 0 100%; display: grid; gap: var(--sp-1); margin: var(--sp-1) 0; }
.assess-bar-row > .btn-row { margin-left: auto; }
.assess-reason textarea { width: 100%; }
.assess-history { margin-top: var(--sp-5); }
.assess-history-list { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: grid; gap: var(--sp-2); }
.assess-history-list li { display: grid; gap: var(--sp-1); padding-bottom: var(--sp-2); border-bottom-width: 1px; border-bottom-style: solid; }
.assess-history-when { font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
  .assess-progress ol { display: grid; grid-template-columns: 1fr; }
  .assess-progress a { width: 100%; justify-content: space-between; }
}

/* ==========================================================================
   24. INTERVIEW SCHEDULING & COMMUNICATIONS (audit 2026-10-06)
   ----------------------------------------------------------------------------
   One dialog: 1 Candidate & CV, 2 Interview & panel, 3 Invitations (a real
   sequence, so it is numbered), with the rendered messages beside it. The
   send dialog and the templates reuse the same editor. Geometry only.
   ========================================================================= */
.modal.iv-compose { width: min(1240px, 96vw); max-width: none; height: min(920px, 94vh); display: flex; flex-direction: column; }
.iv-compose-body { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
.iv-compose-form { min-height: 0; overflow-y: auto; padding: var(--sp-4) var(--sp-5); display: grid; gap: var(--sp-5); align-content: start; }
.iv-preview { min-height: 0; overflow-y: auto; padding: var(--sp-4); border-left-width: 1px; border-left-style: solid; display: grid; gap: var(--sp-3); align-content: start; }
.iv-step { display: grid; gap: var(--sp-3); }
.iv-step h4 { display: flex; align-items: center; gap: var(--sp-2); margin: 0; }
.iv-step-n { display: inline-grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; }
.iv-sub { font-weight: 600; margin-top: var(--sp-1); }
.iv-cv { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); }
.iv-cv .field-hint { flex: 1 0 100%; margin: 0; }
.iv-cv-label { font-weight: 600; }
.iv-cv-file { display: inline-flex; align-items: center; gap: var(--sp-2); }
.iv-file { cursor: pointer; }
.iv-panel-pick { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.iv-panel-pick .tag-toggle { min-height: var(--cl-ctl-sm); }
.iv-avail { display: grid; gap: var(--sp-2); min-height: 24px; }
.iv-avail .notice { display: grid; gap: var(--sp-2); }
.iv-ack { display: inline-flex; align-items: center; gap: var(--sp-2); font-weight: 500; }
.iv-recipients { display: grid; gap: var(--sp-2); }
/* Template note: messages come from the organisation templates; per-interview
   wording is folded away (owner, 12 Oct 2026). Hairline, no side bar (R20). */
.iv-template-note { display: grid; gap: var(--sp-2); }
.iv-customize { display: grid; gap: var(--sp-3); }
.iv-customize > summary { cursor: pointer; min-height: 40px; display: flex; align-items: center; color: var(--action); font-weight: 600; list-style-position: inside; }
.iv-customize > summary:focus-visible { outline: 2px solid var(--action); outline-offset: 2px; border-radius: 8px; }
.iv-rcpt { display: grid; gap: var(--sp-1); }
.iv-rcpt > label, label.iv-rcpt { display: flex; align-items: flex-start; gap: var(--sp-2); }
.iv-rcpt span { display: grid; gap: 4px; }
.iv-attach { display: inline-flex; align-items: center; gap: var(--sp-2); padding-left: var(--sp-6); }
.iv-editor { display: grid; gap: var(--sp-2); }
.iv-editor-label { font-weight: 600; }
.iv-editor textarea { width: 100%; resize: vertical; }
.iv-chips { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.chip-btn { min-height: 28px; padding: 0 var(--sp-2); border-width: 1px; border-style: solid; border-radius: var(--cl-r-sm); cursor: pointer; }
.iv-wa { display: grid; gap: var(--sp-2); padding: var(--sp-3); border-width: 1px; border-style: solid; border-radius: var(--cl-r-md); }
.iv-wa-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--sp-2); }
.iv-wa textarea { width: 100%; resize: vertical; }
.iv-preview-title { font-weight: 600; }
.iv-preview-card { display: grid; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-width: 1px; border-style: solid; border-radius: var(--cl-r-md); }
.iv-preview-subject { font-weight: 600; }
.iv-preview-body { white-space: pre-wrap; overflow-wrap: anywhere; }
.modal-foot.iv-foot { flex-wrap: wrap; align-items: center; }
.iv-foot-error, .iv-foot > .muted { flex: 1 1 280px; margin-right: auto; }
.iv-result { display: grid; gap: var(--sp-3); }
.iv-dispatch-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.iv-dispatch-list li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-1) var(--sp-3); align-items: center; padding: var(--sp-2) 0; border-bottom-width: 1px; border-bottom-style: solid; }
.iv-dispatch-who { display: grid; gap: var(--sp-1); min-width: 0; }
.iv-dispatch-reason { grid-column: 1 / -1; }
.iv-dups { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: grid; gap: var(--sp-2); }
.iv-dups li { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--sp-2); }
.iv-comms { margin-top: var(--sp-4); display: grid; gap: var(--sp-3); }
.iv-batch { display: grid; gap: var(--sp-1); }
.iv-batch-head { display: flex; gap: var(--sp-2); align-items: baseline; }
.iv-tpl-body { display: grid; gap: var(--sp-5); }
.iv-tpl-link { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); }
@media (max-width: 900px) {
  .modal.iv-compose { height: 94vh; }
  /* One scrolling column: rows size to their content (no min-height:0 squeeze). */
  .iv-compose-body { grid-template-columns: 1fr; overflow-y: auto; align-content: start; }
  .iv-compose-form, .iv-preview { overflow: visible; min-height: auto; }
  .iv-preview { border-left-width: 0; border-top-width: 1px; border-top-style: solid; }
  .iv-attach { padding-left: var(--sp-5); }
}

/* Document-only side panel (thread attachments): the file takes the full width. */
.cvrev-body.cvrev-body--doc { grid-template-columns: minmax(0, 1fr); }

/* ---- SearchSelect (25 Oct 2026) — geometry only; colour and type live in
   arabtec-design-system.css, like every other component here. ---- */
.sselect { position: relative; min-width: 0; }
.sselect > input { width: 100%; padding-inline-end: var(--sp-6); text-overflow: ellipsis; }
/* One affordance at the end of the box: the caret while empty, the × once a value is chosen. */
.sselect-caret { position: absolute; inset-inline-end: 12px; top: 50%; transform: translateY(-50%); pointer-events: none; }
.sselect.has-value .sselect-caret { display: none; }
.sselect-clear { position: absolute; inset-inline-end: 8px; top: 50%; transform: translateY(-50%); border: 0; padding: 0 4px; cursor: pointer; }
.sselect-list { position: absolute; z-index: 60; top: calc(100% + 4px); inset-inline-start: 0; min-width: 100%; max-width: min(420px, 92vw); max-height: 280px; overflow-y: auto; margin: 0; padding: 4px; list-style: none; }
.sselect-opt { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; padding: 8px 12px; cursor: pointer; white-space: nowrap; }
.sselect-opt > span { overflow: hidden; text-overflow: ellipsis; }
.sselect-opt > small { flex: 0 0 auto; }
.sselect-empty { padding: 8px 12px; }
.field > .sselect > input { width: 100%; }
.dash-filter .sselect { min-width: 170px; }

/* ---- Stakeholder decisions on a Shortlisted card (25 Oct 2026) ---- */
.pcard-decide { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; padding-top: 8px; }
.pcard-decide.compact { margin-top: 4px; padding-top: 0; display: inline-flex; vertical-align: middle; }
.pcard-decide .btn { min-height: 32px; }

/* ---- CV full page (25 Oct 2026): the CV alone, in its own browser tab ---- */
.cvfull { position: fixed; inset: 0; display: flex; flex-direction: column; }
.cvfull-head { flex: 0 0 auto; display: flex; align-items: center; gap: 12px; padding: 8px 16px; }
.cvfull-head-id { flex: 1 1 auto; min-width: 0; display: grid; gap: 4px; }
.cvfull-head-id h1, .cvfull-head-id span { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cvfull-actions { display: flex; gap: 8px; flex: 0 0 auto; }
.cvfull-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.cvfull-body > .parse-review-cv { flex: 1 1 auto; min-height: 0; }
.cvfull-body .parse-review-cv-frame { min-height: 0; height: 100%; }
.cvfull-state { flex: 1 1 auto; display: grid; place-items: center; padding: 24px; }

/* ---- Recommended-candidate chip on a pipeline card (25 Oct 2026) ---- */
.pcard-recommended { display: inline-flex; margin-top: 4px; }
