/* ============================================================
   vth-reports.css — Reports module additions on top of vth-reskin.css
   Namespaced `.vth-*`, only paints inside `.vth`. Requires vth-tokens.css,
   vth-fonts.css, vth-reset.css, vth-reskin.css to be loaded first.
   Adds only what vth-reskin.css doesn't already have (dropdown/empty-state/
   stepped builder/segmented control/toast/pagination), per
   .claude/skills/vth-reskin/references/components.md.
   ============================================================ */

/* ============================================================
   Corrections — two layers fight vth-reskin.css's component styling inside `.vth`,
   both confirmed by reading the actual cascade (not guessed):

   1) vth-reset.css resets raw elements with real specificity, not just placeholder
      values: `.vth button{background:none;border:none;}` is (0,1,1), and
      `.vth input[type=text],...,.vth select{border:none;background:transparent;...}`
      is (0,2,1) (the [type=x] attribute selector counts same as a class). Every
      single-class `.vth-btn--*`/`.vth-input`/`.vth-select` rule in vth-reskin.css is
      (0,1,0) — LOWER specificity — so it silently loses regardless of source order
      (specificity beats source order whenever they differ). Net effect: real
      <button>/<input>/<select> elements rendered with no background/border at all,
      not just the "wrong" metrics reported earlier.
   2) `.vth a{color:var(--text-link)}` is (0,1,1), beating `.vth-btn--primary{color:
      var(--text-on-blue)}` at (0,1,0) — so a <button>-styled <a> (table row actions,
      "Report on this group") got blue text/icons regardless of variant; on a blue
      background that's invisible.

   Fixed below with compound/ancestor-doubled selectors sized to comfortably clear
   each reset's specificity (button-only conflicts need (0,2,0); the input/select
   conflict needs (0,3,0) to clear its (0,2,1)) — without editing vth-reset.css or
   vth-reskin.css. Sizing values are copied from the Claude Design source's
   .btn/.field/.inp/.selreal/.seg rules, not guessed.
   ============================================================ */
.vth-btn{gap:9px;border-radius:12px;}
/* border-STYLE, not just color, needs restoring: `.vth button{border:none}` zeroes
   out style+width+color together, so a variant that only sets border-color (below)
   would still show no border at all without this — color alone can't paint a border
   whose style is "none". */
.vth-btn.vth-btn{border-style:solid;border-width:1px;}
.vth-btn--sm{height:40px;padding:0 16px;font-size:14px;border-radius:10px;}
.vth-btn--md{height:48px;padding:0 22px;font-size:15px;}
.vth-btn--xs{height:34px;padding:0 12px;font-size:13px;border-radius:9px;gap:6px;}
.vth-btn.vth-btn--primary{background:var(--vth-blue);color:var(--text-on-blue);border-color:var(--vth-blue);}
.vth-btn.vth-btn--primary:hover{background:var(--vth-blue-hover);color:var(--text-on-blue);border-color:var(--vth-blue-hover);}
.vth-btn.vth-btn--secondary{background:transparent;color:var(--vth-blue);border-color:var(--vth-blue);}
.vth-btn.vth-btn--ghost{background:#fff;color:var(--text-secondary);border-color:var(--border-default);}
.vth-btn.vth-btn--ghost:hover{background:var(--surface-muted);}
.vth-btn.vth-btn--danger{background:var(--color-error);color:#fff;border-color:var(--color-error);}
/* Disabled primary. vth-reskin.css only has `.vth-btn:disabled{opacity:.5}`, which
   on a saturated blue renders a washed-out lilac that reads as a rendering fault
   rather than a deliberate state — visible on Verify training, whose submit is
   disabled until a code is typed and is therefore the first thing on the page.
   The design carries a real token for this (`--tc-buttons-disabled-primary`, a
   light blue with muted text); its values are used here. Specificity is (0,4,0) to
   clear `.vth-btn:disabled` at (0,2,0). `pointer-events:none` from that rule still
   applies — only the colour changes. */
.vth .vth-btn.vth-btn--primary:disabled,
.vth .vth-btn.vth-btn--primary.is-disabled{
  opacity:1;background:#DAE9FF;color:var(--text-muted);border-color:#DAE9FF;
}
/* Icon-only button. vth-reskin.css's `.vth-btn--icon{padding:0;width:44px}` gives a
   width but NO height, and zeroes the padding that would otherwise have produced one
   — so with no size modifier alongside it (which is how every modal close button in
   this module is written) the button collapsed to the icon's line box and rendered as
   a flat 44x18 pill. Squared off here, and matched to whichever size modifier is
   present when there is one. */
.vth-btn.vth-btn--icon{width:36px;height:36px;padding:0;border-radius:10px;flex:0 0 auto;}
.vth-btn.vth-btn--icon.vth-btn--xs{width:34px;height:34px;border-radius:9px;}
.vth-btn.vth-btn--icon.vth-btn--sm{width:40px;height:40px;}
.vth-btn.vth-btn--icon.vth-btn--md{width:48px;height:48px;}

/* A third leak, found by rendering the page rather than reading the cascade: the
   platform theme sets `h1,h2,h3,h4{text-transform:capitalize}` on the BARE elements
   (themes/the_training_hub/new_themes/css/style.css). vth-reset.css resets margin,
   weight, line-height and colour on headings inside `.vth` but not text-transform,
   so every heading in this module has been silently title-cased — "Verify a
   certificate" rendered as "Verify A Certificate", and the same for every card
   title and section heading on every reports page. `.vth h2` is (0,1,1) and clears
   the theme's (0,0,1). */
.vth h1,.vth h2,.vth h3,.vth h4,.vth h5,.vth h6{text-transform:none;}

.vth-label{font-size:13px;font-weight:var(--weight-medium);color:var(--text-primary);}
.vth .vth-input.vth-input,.vth .vth-select.vth-select,.vth .vth-textarea.vth-textarea{
  height:44px;padding:0 12px;border:1.5px solid var(--border-default);border-radius:10px;
  font-size:14px;background:#fff;box-shadow:none;
}
.vth .vth-textarea.vth-textarea{height:auto;min-height:96px;padding:10px 12px;}
.vth .vth-input.vth-input:focus,.vth .vth-select.vth-select:focus,.vth .vth-textarea.vth-textarea:focus{
  border-color:var(--vth-blue);box-shadow:0 0 0 3px rgba(25,42,193,.12);
}

/* ---- Icon glyphs ----
   vth/components/icon.blade.php now emits a Hugeicons FONT glyph inside its
   `.vth-icon` wrapper instead of an inline <svg>. The wrapper carries the caller's
   pixel size as both its box and its font-size, so the glyph just inherits it —
   `.hgi-NN` utilities are deliberately not used, because callers pass sizes
   (19, 21, 26…) that no utility enumerates.

   `.vth{line-height:1.5}` from vth-reset.css would otherwise give the glyph a line
   box taller than its own box and push it off-centre inside chips and circular
   buttons; `line-height:1` + `display:block` keeps it centred. Colour still comes
   from `color`, exactly as currentColor did for the SVGs, so every existing tint
   rule keeps working untouched. */
.vth .vth-icon > .hgi-stroke{font-size:inherit;line-height:1;display:block;font-style:normal;}

.vth-seg__btn{border-radius:9px;line-height:1;}

/* vth-reset.css sets `.vth{line-height:1.5}` for body copy — correct for
   paragraphs, but it also cascades into every small pill/badge that doesn't set
   its own line-height, inflating their vertical padding well past what the design
   intends (a 12.5px badge at line-height:1.5 renders ~19px tall before padding is
   even added). Design's own CSS never sets a page-wide line-height, so these need
   an explicit tight value to match. */
.vth-infobar__facts > span{line-height:1;}
.vth-kicker,.vth-steplbl{line-height:1;}
.vth-fsum__chip{line-height:1;}

/* The dropdown trigger and its internal search box are real <button>/<input type=
   "text"> elements too — same fix, same reasoning as above. The search box gets its
   own class (vth-dropdown__search-input, added in vthSearchSelect.js's template)
   since ".vth-dropdown__search input" alone is only (0,1,1) — a class+element combo
   still can't clear the input reset's (0,2,1). */
.vth-dropdown__btn.vth-dropdown__btn{background:#fff;border:1.5px solid var(--border-default);border-radius:10px;}
.vth-dropdown__btn.vth-dropdown__btn:hover{border-color:var(--vth-blue);}
.vth-dropdown__btn.vth-dropdown__btn.is-open{border-color:var(--vth-blue);box-shadow:0 0 0 3px rgba(25,42,193,.12);}
.vth .vth-dropdown__search-input.vth-dropdown__search-input{
  height:38px;border:1.5px solid var(--border-default);border-radius:9px;background:#fff;
  padding:0 10px 0 36px;font-size:13.5px;
}
.vth .vth-dropdown__search-input.vth-dropdown__search-input:focus{border-color:var(--vth-blue);}

/* `.vth-qcard` (the "ask a question" tiles) is a real <button> on the Organisation
   reports page (it's an <a> on Overview, which is why this bug didn't show up
   there — .vth a doesn't touch background/border, only .vth button does). Same
   (0,1,1)-vs-(0,1,0) issue as everywhere else in this file: the button reset zeroed
   both the card's background and its entire border (style, not just color — a
   hover/active rule that only sets border-color can't paint anything if the base
   border-style already lost to "none"). */
.vth-qcard.vth-qcard{background:#fff;border:1.5px solid var(--border-default);}
.vth-qcard.vth-qcard:hover{border-color:var(--vth-blue);}
.vth-qcard.vth-qcard.is-active{border-color:var(--vth-blue);}

/* ---- Hero banner ---- */
.vth-hero{display:flex;align-items:center;gap:var(--space-5);background:linear-gradient(120deg,var(--vth-blue-dark),var(--vth-blue));color:#fff;border-radius:var(--radius-xl);padding:var(--space-6) var(--space-8);}
.vth-hero__icon{width:54px;height:54px;border-radius:var(--radius-lg);background:rgba(255,255,255,.15);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;}
/* color set explicitly (not inherited) — Materialize styles h1-h6 directly, and a
   directly-matched declaration always beats an inherited one regardless of specificity */
.vth-hero h2{margin:0 0 4px;font-size:24px;line-height:30px;font-weight:var(--weight-semibold);color:#fff;}
.vth-hero p{margin:0;font-size:var(--type-size-xs);line-height:1.5;color:rgba(255,255,255,.82);max-width:640px;}

/* ---- Card title (design's ".h-sub": 16px/24px/600 — smaller than the vth-h1..h4
   typography scale, which starts at 24px, so it needs its own class here) ---- */
.vth-card-title{margin:0;font-size:16px;line-height:24px;font-weight:var(--weight-semibold);color:var(--text-primary);}

/* ============================================================
   Page tabs — the design's underline tab row.

   Distinct from `.vth-seg` below ON PURPOSE. `.vth-seg` is a segmented control:
   a pill track holding mutually-exclusive FILTER options that sit inside a card
   next to other form fields (Organisation's subject/basis/archived, Overview's
   learner/group). This is a TAB STRIP: it switches
   the whole page between sections. The Claude Design source draws the two
   differently and so do we — a filter toggle rendered as underlined page tabs
   reads as navigation the user hasn't taken yet.

   Metrics copied from `Certificates Module.dc.html` (the `<!-- tabs -->` row),
   not eyeballed. `margin-bottom:-1px` sits the active underline ON the row's own
   1px border rather than below it.
   ============================================================ */
.vth-tabs{display:flex;align-items:center;gap:28px;padding:0 6px;border-bottom:1px solid var(--border-light);overflow-x:auto;}
/* (0,2,0), NOT a bare class. These are real <button> elements, so
   `.vth button{border:none}` in vth-reset.css at (0,1,1) outranks a single class
   and zeroes border WIDTH and STYLE together — measured on the page, where the
   active tab computed `border-bottom-width: 0px` despite a correct
   `border-bottom-color`. The result was a tab that changed colour but never drew
   its underline, so the divider ran flat and grey underneath it. Same trap, same
   fix, as `.vth-btn` and every other button in this file. */
.vth .vth-tabs__btn{
  display:inline-flex;align-items:center;gap:9px;flex-shrink:0;
  background:none;padding:10px 2px;margin-bottom:-1px;
  font-family:var(--font-sans);font-size:16px;line-height:1.4;font-weight:var(--weight-medium);
  color:var(--text-subtle);cursor:pointer;white-space:nowrap;
  /* Longhands, so nothing here can be flattened back to `none` by the reset. The
     transparent 3px reserves the underline's space on every tab — without it the
     active tab would be 3px taller than its siblings and the row would jump on
     each switch. */
  border:0 solid transparent;border-bottom-width:3px;border-bottom-style:solid;
  transition:color .15s ease,border-bottom-color .15s ease;
}
.vth .vth-tabs__btn:hover{color:var(--text-primary);}
.vth .vth-tabs__btn:focus-visible{outline:none;border-radius:6px;box-shadow:var(--shadow-focus-blue);}
/* The active tab's 3px underline sits ON the row's own 1px divider (the -1px
   bottom margin above pulls it down over it), so the divider reads as blue for
   exactly the width of the active tab and grey either side of it. */
.vth .vth-tabs__btn.is-active{color:var(--vth-blue);font-weight:var(--weight-semibold);border-bottom-color:var(--vth-blue);}
/* Count pill. Its own line-height, for the same reason the other small pills in
   this file need one — `.vth{line-height:1.5}` inflates a 12px chip to ~19px
   before padding is even added. */
/* `--border-light` rather than the design's `--surface-muted` for the inactive
   pill: the tab strip sits directly on the page ground, and #F6F6F6 on #F4F5F7 is
   a 1-step difference that renders as no pill at all. Checked against the page,
   not the design file, which draws this row on white. */
.vth-tabs__count{
  display:inline-flex;align-items:center;height:22px;padding:0 9px;flex-shrink:0;
  border-radius:var(--radius-pill);background:var(--border-light);
  font-size:var(--type-size-2xs);line-height:1;font-weight:var(--weight-bold);color:inherit;
}
.vth-tabs__btn.is-active .vth-tabs__count{background:var(--surface-blue-soft);}
/* Hint text that trails the strip sits on the baseline of the tabs, pushed right. */
.vth-tabs__hint{margin-left:auto;flex-shrink:0;padding-left:var(--space-4);align-self:center;}

/* ---- Segmented control ----
   A pill track of mutually-exclusive FILTER options. Not `.vth-tabs` — see the
   note on that component above for why the two are kept apart.

   Sized to 44px, which is the height of `.vth-input`, `.vth-select` and the
   `vth-search-select` dropdown button. That matters because these controls sit
   in `.vth-fieldgrid` cells directly BESIDE those fields (Organisation's "Age
   group" next to "Date range" and "Learners"), and at the previous 40px the row
   of controls was visibly 4px short of its neighbours with the labels no longer
   on a common baseline. Measured, not guessed. */
.vth-seg{display:inline-flex;align-items:center;gap:4px;height:44px;padding:4px;background:var(--surface-muted);border-radius:var(--radius-lg);}
.vth-seg__btn{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  height:36px;padding:0 16px;border:none;background:none;border-radius:var(--radius-md);
  font-family:var(--font-sans);font-size:var(--type-size-xs);line-height:1;
  font-weight:var(--weight-medium);color:var(--text-subtle);
  cursor:pointer;white-space:nowrap;transition:background .15s ease,color .15s ease;
}
/* The inactive segments had no hover at all, so the track read as static text
   until something was clicked. */
.vth-seg__btn:hover{color:var(--text-primary);}
.vth-seg__btn.is-active{background:#fff;color:var(--vth-blue);font-weight:var(--weight-semibold);box-shadow:var(--shadow-xs);}
.vth-seg__btn.is-active:hover{color:var(--vth-blue);}
/* Full-width variant for narrow columns (the Learner Lookup sidebar is 340px).
   The default 18px side padding is too generous there: three options overflowed
   the track and "Young People" wrapped out of its pill. Segments share the width
   equally and stay on one line instead. */
/* max-width has no effect in the 340px sidebar; it stops the segments stretching
   to absurd widths once that sidebar goes full-width in the stacked layout. */
/* 40px variant, matching `.vth-btn--sm`. For a segmented control sitting in a row
   NEXT TO a small button rather than in a field grid: the 44px base would leave that
   pair 4px out of alignment. Currently unused — its only consumer was Verify
   training's Minimal/Full record switch, removed when the full record became the
   only view. Kept as a size variant of `.vth-seg`, like `.vth-btn--sm`. */
.vth-seg--sm{height:40px;}
.vth-seg--sm .vth-seg__btn{height:32px;padding:0 14px;}

.vth-seg--fill{display:flex;width:100%;max-width:520px;}
/* Only flex/padding change — height, radius and weights stay on the base rule so a
   filled control still lines up with the 44px fields beside it. */
.vth-seg--fill .vth-seg__btn{flex:1 1 0;min-width:0;padding:0 10px;font-size:13px;}
/* No small-screen shrink rule: measured, the three age-group segments need 264px
   of content in the 280px box a 320px phone gives — they fit at this size. */

/* ---- Question cards ("ask a question" tiles) ---- */
.vth-qcards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4);}
@media (max-width:1080px){.vth-qcards{grid-template-columns:minmax(0,1fr);}}
.vth-qcard{text-align:left;background:#fff;border:1.5px solid var(--border-default);border-radius:var(--radius-lg);padding:var(--space-5);display:flex;flex-direction:column;gap:10px;cursor:pointer;font-family:var(--font-sans);transition:border-color .16s ease,box-shadow .16s ease,transform .16s ease;}
.vth-qcard:hover{border-color:var(--vth-blue);transform:translateY(-2px);box-shadow:var(--shadow-md);}
.vth-qcard.is-active{border-color:var(--vth-blue);box-shadow:var(--shadow-focus-blue);}
/* `.vth-qcard .vth-qcard__icon`, NOT the bare class. `.vth-qcard span` below is
   (0,1,1) and the tile is a <span>, so at (0,1,0) its `color` lost and every
   question-card glyph rendered in --text-subtle grey inside a blue tile —
   confirmed by reading the computed style off the page (rgb(93,93,93), not
   rgb(25,42,193)). The design has these glyphs in --vth-blue on --surface-blue-soft.
   Sized to the design's 44px tile with a 22px glyph; the icon partial's own inline
   font-size drives the glyph, so `font-size` here is inherited by nothing and the
   size comes from the caller. */
.vth-qcard .vth-qcard__icon{
  width:44px;height:44px;flex-shrink:0;border-radius:var(--radius-lg);
  background:var(--surface-blue-soft);color:var(--vth-blue);
  display:inline-flex;align-items:center;justify-content:center;
}
.vth-qcard b{font-size:var(--type-size-sm);line-height:var(--type-leading-sm);font-weight:var(--weight-semibold);color:var(--text-primary);}
/* Direct children only — as a descendant selector this also swallowed the icon
   tile and the `.vth-icon` span inside it. */
.vth-qcard > span{font-size:var(--type-size-2xs);line-height:var(--type-leading-2xs);color:var(--text-subtle);}
.vth-qcard > span.vth-qcard__icon{font-size:inherit;line-height:1;}

/* ---- Field grid / stepped builder ---- */
.vth-fieldgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4);}
@media (max-width:1040px){.vth-fieldgrid{grid-template-columns:repeat(2,minmax(0,1fr));}}
@media (max-width:560px){.vth-fieldgrid{grid-template-columns:minmax(0,1fr);}}
/* Column-count modifiers. These exist because a media query CANNOT override an
   inline style — `style="grid-template-columns:repeat(4,1fr)"` on a .vth-fieldgrid
   pinned it to four columns at every width, including phones, and silently defeated
   the two breakpoints above. Never set grid-template-columns inline on this class;
   add a modifier here instead. Declared after the base rule and its media queries
   so the cascade resolves in the intended order. */
.vth-fieldgrid--4{grid-template-columns:repeat(4,minmax(0,1fr));}
@media (max-width:1040px){.vth-fieldgrid--4{grid-template-columns:repeat(2,minmax(0,1fr));}}
@media (max-width:560px){.vth-fieldgrid--4{grid-template-columns:minmax(0,1fr);}}
.vth-fieldgrid--2-1{grid-template-columns:minmax(0,2fr) minmax(0,1fr);}
@media (max-width:760px){.vth-fieldgrid--2-1{grid-template-columns:minmax(0,1fr);}}

.vth-stepbox{border:1px solid var(--border-light);border-radius:var(--radius-lg);padding:var(--space-5);display:flex;flex-direction:column;gap:var(--space-4);}
.vth-stepbox--dashed{border-style:dashed;}
.vth-steplbl{display:flex;align-items:center;gap:10px;font-size:var(--type-size-2xs);font-weight:var(--weight-medium);letter-spacing:.05em;text-transform:uppercase;color:var(--text-muted);}
.vth-steplbl i{width:22px;height:22px;border-radius:50%;background:var(--vth-blue);color:#fff;font-size:11px;font-weight:var(--weight-medium);display:inline-flex;align-items:center;justify-content:center;font-style:normal;flex-shrink:0;}
.vth-disclose{border:none;background:none;font-family:var(--font-sans);font-size:var(--type-size-xs);font-weight:var(--weight-semibold);color:var(--vth-blue);display:inline-flex;align-items:center;gap:8px;cursor:pointer;padding:0;}

/* ---- Info / intro banner ---- */
/* Metrics match the design's info section (`Certificates Module.dc.html`, the
   "light blue info section"): 18px/22px padding, 16px radius, NO border — the blue
   ground is the whole signal, and the extra 1px rgba outline read as a disabled
   card — and a 36px white tile (not 44px) holding a 20px glyph. */
.vth-infobar{display:flex;align-items:flex-start;gap:14px;padding:18px 22px;border-radius:var(--radius-xl);background:var(--surface-blue-soft);border:none;}
.vth-infobar__icon{width:36px;height:36px;border-radius:var(--radius-md);background:#fff;color:var(--vth-blue);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;}
.vth-infobar b{font-size:var(--type-size-sm);line-height:var(--type-leading-sm);font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth-infobar p{margin:4px 0 0;font-size:13.5px;line-height:21px;color:var(--text-secondary);max-width:900px;}
.vth-infobar__facts{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px;}
/* DIRECT CHILDREN ONLY. As a descendant selector this also matched the <span>
   that vth.components.icon wraps its SVG in, so each icon became its own white
   pill with 5px/12px padding inside a 14px box — collapsing it to nothing. The
   design source doesn't hit this because its <Icon> emits a bare <svg>; ours
   wraps it. Any rule targeting a chip in here must use `>`. */
.vth-infobar__facts > span{display:inline-flex;align-items:center;gap:6px;background:#fff;border-radius:var(--radius-pill);padding:5px 12px;font-size:var(--type-size-2xs);font-weight:var(--weight-medium);color:var(--vth-blue);}

.vth-infobar__text{display:flex;flex-direction:column;gap:4px;min-width:0;}

/* ---- Checkbox rows ----
   Legacy global CSS (public/css/early_years.css) contains a bare, page-wide
   `[type="checkbox"] { opacity:0; position:absolute !important; height:100%; width:100% }`
   which leaks straight into `.vth` and renders any native checkbox INVISIBLE.
   These rules restore a real, visible control: the ancestor-qualified selector
   clears its (0,1,0) specificity, and `!important` on `position` is required
   because the legacy rule uses `!important` too. Do not "simplify" this. */
.vth-checkrow{display:flex;align-items:center;gap:10px;cursor:pointer;}
.vth-checkrow span{font-size:var(--type-size-xs);color:var(--text-secondary);}
.vth .vth-checkrow input[type="checkbox"]{
  position:static !important;
  opacity:1;
  width:18px;height:18px;
  min-width:18px;
  margin:0;
  cursor:pointer;
  accent-color:var(--vth-blue);
}

/* ---- Learner Lookup: chat-style two-pane layout ---- */
.vth-lookup{display:grid;grid-template-columns:340px minmax(0,1fr);gap:var(--space-5);align-items:start;}
@media (max-width:1080px){.vth-lookup{grid-template-columns:minmax(0,1fr);}}
.vth-lookup__list{position:sticky;top:16px;}
@media (max-width:1080px){.vth-lookup__list{position:static;}}
/* Bounded so the learner list scrolls rather than pushing the record off-screen.
   This is also the scroll-paging container — vth-infinite-scroll listens here, so
   it must keep its own overflow rather than letting the page scroll instead. */
.vth-lookup__scroll{max-height:520px;overflow-y:auto;}
/* Stacked, the sidebar sits ABOVE the record, so a 520px list means scrolling past
   the whole picker before any of the answer is visible. */
@media (max-width:1080px){.vth-lookup__scroll{max-height:300px;}}
.vth-lookup__note{margin:8px 0 0;padding:0 12px;}

/* "Recent" marker on a recently-viewed learner in the sidebar. */
.vth-pick__tag{flex-shrink:0;font-size:10px;font-weight:var(--weight-semibold);letter-spacing:.04em;text-transform:uppercase;color:var(--vth-blue);background:var(--surface-blue-soft);border-radius:var(--radius-pill);padding:3px 8px;}

/* The course filter sits in a flex row beside the learner's name. Flex items are
   content-sized, so without a fixed basis a long course title stretches the whole
   control (and pushes the name cluster around) the moment it is selected. */
.vth-lookup__coursefilter{flex:0 0 280px;width:280px;max-width:100%;margin:0;}
/* Once the header row wraps, a 280px basis leaves an odd short control against a
   full-width name cluster — let it span instead. */
@media (max-width:760px){.vth-lookup__coursefilter{flex:1 1 100%;width:100%;}}

/* ---- Card overflow utility — .vth-card sets overflow:hidden (vth-reskin.css),
   which clips any dropdown popover that opens past the card's edge. Apply this
   modifier to any .vth-card that contains a .vth-dropdown/.vth-search-select. ---- */
.vth-card--overflow-visible{overflow:visible;}

/* ============================================================
   `.vth-fill` — carry the page's full height down to the main panel

   `.newdashcontentbar .vth` (near the end of this file) makes the CONTENT COLUMN
   as tall as the sidebar rail, so the page always bottoms out level. That alone
   leaves the panel itself short: with few rows, an empty result set or a loading
   spinner, the white card stopped a few hundred pixels above the fold and the page
   read as half-rendered, with a band of grey underneath it.

   `.vth-fill` marks the element on each page that should absorb that leftover
   height. Put it on the MAIN results panel — never on every card, or several cards
   would share the slack and each end up part-grown.

   `flex:1 1 auto`, not `min-height:calc(100vh - …)`: these panels sit below a tab
   strip, an infobar or a hero whose heights are not constants (they wrap at narrow
   widths), so any hardcoded subtraction is wrong at some width and overflows the
   viewport. Growing into whatever the flex column has left over is exact at every
   size, and the panel still grows past it when its content is taller.

   `min-height` is deliberately left at `auto`: a flex item's automatic minimum size
   is what stops a long list being squashed instead of making the page scroll.

   `align-self:stretch` is a no-op in the flex columns (already the default) and is
   there for `.vth-lookup`, which is a GRID with `align-items:start`.

   Where a stage/tab wrapper sits between the container and the card — Organisation's
   two stages, Verify training's tabs, Connected accounts' tabs — BOTH the wrapper
   and the card inside it need the class, or the wrapper grows and the card inside
   it does not. Those wrappers are `.vth-stack`, i.e. already flex columns.
   ============================================================ */
.vth-fill{flex:1 1 auto;align-self:stretch;}
/* Inside a filled panel, the empty state and the spinner take the slack so they sit
   in the middle of the card rather than pinned under the filters with a large blank
   area beneath them. */
.vth-fill > .vth-empty{flex:1 1 auto;justify-content:center;}
/* Kept as an alias: `.vth-card--fill` was the first name this shipped under. */
.vth-card--fill{flex:1 1 auto;align-self:stretch;}
.vth-card--fill > .vth-empty{flex:1 1 auto;justify-content:center;}

/* A filled card that is NOT also a `.vth-stack` is a plain block, so nothing inside
   it can claim the extra height and its empty state stays pinned to the top of a
   now very tall card. Making the card a column, and letting its LAST body grow,
   passes the height through to whatever is showing — the table, the spinner or the
   empty state. Scoped to `.vth-fill` cards so no ordinary card's layout changes.
   `:last-child` is the results body on every page that needs this (Requests,
   Connected accounts, Overview); a header body above it keeps its natural height. */
.vth-card.vth-fill:not(.vth-stack),
.vth-card.vth-card--fill:not(.vth-stack){display:flex;flex-direction:column;}
.vth-card.vth-fill:not(.vth-stack) > .vth-card__body:last-child,
.vth-card.vth-card--fill:not(.vth-stack) > .vth-card__body:last-child{
  flex:1 1 auto;display:flex;flex-direction:column;
}
/* The results region inside that body takes the slack, so an empty state or a
   spinner sits in the middle of the card rather than under the header with a large
   blank area beneath it. A table is left alone — it should stay at the top. */
.vth-card.vth-fill > .vth-card__body:last-child > .vth-empty,
.vth-card.vth-card--fill > .vth-card__body:last-child > .vth-empty{flex:1 1 auto;justify-content:center;}

@media (max-width:992px){
  .vth-card.vth-fill:not(.vth-stack) > .vth-card__body:last-child,
  .vth-card.vth-card--fill:not(.vth-stack) > .vth-card__body:last-child{flex:0 1 auto;}
  .vth-card.vth-fill > .vth-card__body:last-child > .vth-empty,
  .vth-card.vth-card--fill > .vth-card__body:last-child > .vth-empty{flex:0 1 auto;justify-content:flex-start;}
}
/* Only asked to fill on a real screen. Below 992px the rail is an off-canvas drawer
   and `.vth` drops its own min-height, so there is no leftover space to take and
   these rules would do nothing anyway — switched off explicitly so they cannot
   fight a future mobile layout. */
@media (max-width:992px){
  .vth-fill,.vth-card--fill{flex:0 1 auto;}
  .vth-fill > .vth-empty,.vth-card--fill > .vth-empty{flex:0 1 auto;justify-content:flex-start;}
}

/* ---- Searchable dropdown ("sdrop") ---- */
.vth-dropdown{position:relative;}
.vth-dropdown__btn{width:100%;height:44px;border:1.5px solid var(--border-default);border-radius:10px;padding:0 var(--space-3);background:#fff;font-family:var(--font-sans);font-size:var(--type-size-xs);color:var(--text-primary);display:flex;align-items:center;gap:8px;cursor:pointer;text-align:left;}
.vth-dropdown__btn:hover{border-color:var(--vth-blue);}
.vth-dropdown__btn.is-open{border-color:var(--vth-blue);box-shadow:0 0 0 3px rgba(25,42,193,.12);}
.vth-dropdown__btn .v{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.vth-dropdown__menu{position:absolute;left:0;right:0;top:calc(100% + 6px);background:#fff;border:1px solid var(--border-light);border-radius:14px;box-shadow:var(--shadow-lg);z-index:65;padding:var(--space-2);display:flex;flex-direction:column;gap:6px;}
.vth-dropdown__search{position:relative;}
.vth-dropdown__search .vth-icon{position:absolute;left:11px;top:50%;transform:translateY(-50%);color:var(--text-muted);}
.vth-dropdown__search input{width:100%;height:38px;border:1.5px solid var(--border-default);border-radius:var(--radius-sm);padding:0 10px 0 36px;font-family:var(--font-sans);font-size:13.5px;}
.vth-dropdown__search input:focus{outline:none;border-color:var(--vth-blue);}
.vth-dropdown__list{max-height:216px;overflow-y:auto;display:flex;flex-direction:column;gap:2px;}
.vth-dropdown__item{border:none;background:none;font-family:var(--font-sans);font-size:13.5px;color:var(--text-secondary);padding:9px 11px;border-radius:var(--radius-sm);cursor:pointer;text-align:left;display:flex;align-items:center;gap:9px;width:100%;}
.vth-dropdown__item:hover{background:var(--surface-muted);color:var(--text-primary);}
.vth-dropdown__item.is-active{background:var(--surface-blue-soft);color:var(--vth-blue);font-weight:var(--weight-semibold);}
.vth-dropdown__none{padding:14px 11px;font-size:var(--type-size-xs);color:var(--text-subtle);}

/* ---- Empty state ---- */
.vth-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--space-3);padding:44px var(--space-6);}
.vth-empty__icon{width:60px;height:60px;border-radius:var(--radius-lg);background:var(--surface-muted);color:var(--text-muted);display:inline-flex;align-items:center;justify-content:center;}
.vth-empty h4{margin:0;font-size:var(--type-size-lg);font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth-empty p{margin:0;max-width:520px;color:var(--text-subtle);font-size:var(--type-size-xs);}

/* ---- Processing banner (instant vs background export) ---- */
.vth-procbar{display:flex;align-items:center;gap:var(--space-4);padding:14px 18px;border-radius:var(--radius-lg);font-size:var(--type-size-xs);}
.vth-procbar--now{background:#DDFBE7;color:#186336;}
.vth-procbar--bg{background:#FFF2D1;color:#7C3F0B;}
.vth-procbar__dial{width:64px;height:64px;border-radius:50%;background:rgba(255,255,255,.72);display:grid;place-items:center;flex-shrink:0;line-height:1.1;text-align:center;}
.vth-procbar__dial b{display:block;font-size:17px;font-weight:var(--weight-bold);}
.vth-procbar__dial i{display:block;font-size:10px;font-style:normal;opacity:.75;font-weight:var(--weight-semibold);}

/* ---- Filter-summary chips (applied-filters row above results) ---- */
.vth-fsum{display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:var(--surface-blue-soft);border-radius:var(--radius-lg);padding:12px 14px;}
.vth-fsum__chip{display:inline-flex;align-items:center;gap:7px;background:#fff;border-radius:var(--radius-pill);padding:5px 6px 5px 12px;font-size:var(--type-size-2xs);font-weight:var(--weight-medium);color:var(--vth-blue);}
.vth-fsum__chip button{border:none;background:none;cursor:pointer;color:var(--text-muted);display:inline-flex;padding:0;}

/* ---- Misc text helpers ---- */
.vth-kicker{font-size:11.5px;font-weight:var(--weight-medium);letter-spacing:.05em;text-transform:uppercase;color:var(--text-muted);}
.vth-pick{display:flex;align-items:center;gap:11px;padding:11px 12px;border-radius:var(--radius-md);cursor:pointer;border:1px solid transparent;}
/* "No Course allocated" is far longer than the other chips; .vth-badge sets no
   nowrap or shrink of its own, so in this flex row it would wrap to two lines and
   steal width from the learner's name. */
.vth-pick .vth-badge{flex-shrink:0;white-space:nowrap;}
.vth-pick:hover{background:var(--surface-muted);}
.vth-pick.is-active{background:var(--surface-blue-soft);border-color:rgba(25,42,193,.18);}
.vth-cklist{display:flex;flex-direction:column;gap:2px;overflow-y:auto;}
.vth-lnk{color:var(--vth-blue);font-weight:var(--weight-semibold);cursor:pointer;background:none;border:none;font-family:var(--font-sans);font-size:var(--type-size-xs);display:inline-flex;align-items:center;gap:6px;padding:0;}
.vth-lnk:hover{text-decoration:underline;}

/* ---- Toast ---- */
.vth-toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);background:var(--text-primary);color:#fff;padding:13px 22px;border-radius:var(--radius-md);font-size:var(--type-size-xs);font-weight:var(--weight-semibold);display:flex;align-items:center;gap:9px;z-index:1100;box-shadow:var(--shadow-lg);animation:vth-toast-in .28s ease;}
@keyframes vth-toast-in{from{opacity:0;transform:translateX(-50%) translateY(8px);}to{opacity:1;transform:translateX(-50%) translateY(0);}}
/* Leaving state, driven by VthToastService — the component only had an entrance.
   `forwards` holds the end frame so it cannot flash back to full opacity in the gap
   before the node is removed. Keep .2s in step with OUT_MS in the service. */
.vth-toast--out{animation:vth-toast-out .2s ease forwards;}
@keyframes vth-toast-out{from{opacity:1;transform:translateX(-50%) translateY(0);}to{opacity:0;transform:translateX(-50%) translateY(8px);}}
/* The component is a flex row with a gap, i.e. it always expected an icon; nothing
   sized one. Also make it clickable-to-dismiss and keep a long message on one line
   until it genuinely cannot fit. */
.vth-toast{cursor:pointer;max-width:min(560px,calc(100vw - 32px));}
.vth-toast svg{display:block;flex-shrink:0;}

/* ---- Report table (extends .vth-table from vth-reskin.css — that one is left
   untouched; this is a modifier + a "who" avatar/name/email cluster for learner
   rows, matching the design's .tbl/.who/.av/.whoname/.whomail) ---- */
.vth-table--report th{background:none;text-transform:uppercase;letter-spacing:.04em;font-size:12px;font-weight:var(--weight-bold);color:var(--text-muted);border-bottom:1.5px solid var(--border-light);white-space:nowrap;}
.vth-table--report td{font-size:14px;color:var(--text-secondary);vertical-align:middle;}
.vth-table--report tr:last-child td{border-bottom:none;}
/* Documents column: fixed so the widest cell contents can't drive the layout.
   The "Available once completed" placeholder is longer than any real value here,
   and with an auto-width column it claimed the space the course title needs. The
   trigger button stays on one line; only the placeholder sentence wraps. */
.vth-table--report .vth-table__docs{width:200px;}
/* Short, atomic values — a wrapped "2022-\n12-16" reads as two dates. The course
   title is the only column that should absorb the slack. */
.vth-table--report .vth-table__score,.vth-table--report .vth-table__when{white-space:nowrap;}
.vth-table--report td.vth-table__docs{white-space:normal;}
.vth-table--report td.vth-table__docs .vth-lnk{white-space:nowrap;}
/* Course-title column (Learner Lookup). `max-width:0` is the auto-layout recipe for
   an ellipsised table cell: it stops the cell being sized by its own text, so the
   other columns take what they need (Documents is fixed, Score/When are nowrap) and
   this one absorbs whatever is left. `width:100%` claims that leftover rather than
   collapsing. The truncation itself is on the inner .vth-trunc, not the <td> —
   overflow on a table-cell is undefined in CSS 2.1 and only works by accident. */
/* Progress bar fill. vth-reskin.css declares `.vth-progress__bar{height:100%}` but the
   markup is a <span>, and height/width do not apply to an inline box — the fill
   measured 0x0 and never appeared, at any percentage. The design's own rule is
   `.bar > i { display:block; height:100% }`; this restores the missing display.
   Two classes (0,2,0) so it also beats vth-reset's element rules. */
.vth-progress .vth-progress__bar{display:block;}
.vth-table--report .vth-table__course{max-width:0;width:29%;}
.vth-trunc{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.vth-who{display:flex;align-items:center;gap:11px;}
.vth-who__avatar{width:36px;height:36px;border-radius:50%;background:var(--surface-blue-soft);color:var(--vth-blue);display:inline-flex;align-items:center;justify-content:center;font-size:12.5px;font-weight:var(--weight-bold);flex-shrink:0;}
.vth-who__name{font-size:14px;font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth-who__email{font-size:12px;color:var(--text-subtle);}
.vth-qstate{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:var(--weight-medium);color:var(--text-subtle);}

/* ---- Pagination lives in vth-reskin.css now: it is an app-wide component
   (see public/js/directives/vthPagination.js), not a reports-only one. ---- */

/* ============================================================
   Course lists — VTH card list + Internal table

   Values copied from the design source (ui_kits/platform/reports.html, the "cpv"
   and "cc" rules), not eyeballed. The two tabs render deliberately differently:
   VTH is a browsable catalogue with imagery, Internal is a compact table of the
   company's own courses.

   NB the source reference above is written out in words on purpose. It previously
   read ".cpv*" followed by "/.cc*", which puts a `*` immediately before a `/` —
   i.e. a comment terminator — INSIDE this comment. The comment therefore ended
   four lines early, the remaining prose was parsed as a selector, and that
   selector swallowed the whole `.vth-cpv` rule below. The card list silently fell
   back to `display:block` with no gap, so every card sat flush against the next
   one. Nothing else in the file was affected, because the parser recovered at the
   next `}`. Never write `*` next to `/` in a comment here.
   ============================================================ */
/* Design's gap between cards is 16px exactly, applied on the axis the list runs —
   the cards are full-width rows, so this is the horizontal band separating them. */
.vth-cpv{display:flex;flex-direction:column;gap:16px;}
.vth-cpvcard{display:flex;gap:var(--space-6);background:#fff;border-radius:20px;padding:var(--space-4);box-shadow:0 6px 22px rgba(16,24,40,.07);}
.vth-cpvcard__img{position:relative;width:356px;height:238px;border-radius:14px;background:var(--surface-muted) center/cover no-repeat;flex-shrink:0;overflow:hidden;}
.vth-cpvcard__sec{position:absolute;left:14px;top:14px;background:var(--vth-blue);color:#fff;border-radius:var(--radius-pill);padding:8px 16px;font-size:13px;font-weight:var(--weight-medium);line-height:1.2;}
.vth-cpvcard__bd{flex:1;min-width:0;display:flex;flex-direction:column;gap:12px;padding:8px 8px 8px 0;}
.vth-cpvcard__bd h4{margin:0;font-size:22px;line-height:30px;font-weight:var(--weight-medium);color:var(--text-primary);}
.vth-cpvcard__bd p{margin:0;font-size:14px;line-height:22px;color:var(--text-secondary);}

/* The whole card opens the course, but only the TITLE is the anchor — the card also
   holds a favourite button and three tooltipped chips, and nesting those inside an
   <a> is invalid HTML and unreachable by keyboard. The title stretches its hit area
   over the card with an absolutely-positioned ::after instead; the fav button and
   the format chips sit above it on z-index so they stay clickable. */
.vth-cpvcard{position:relative;}
.vth .vth-cpvcard__title{color:inherit;text-decoration:none;}
.vth .vth-cpvcard__title:hover{color:var(--vth-blue);text-decoration:underline;}
.vth-cpvcard__title::after{content:"";position:absolute;inset:0;border-radius:20px;z-index:0;}
.vth .vth-cpvcard__title:focus-visible{outline:none;}
.vth-cpvcard__title:focus-visible::after{box-shadow:var(--shadow-focus-blue);}
.vth-cpvcard__fav,.vth-cpvcard__modes,.vth-cpvcard__sec{position:relative;z-index:1;}

/* Tag row — level / CPD minutes / country / draft. Same set and order as the public
   catalogue's `.bundle-tag` row. */
.vth-cpvcard__tags{display:flex;flex-wrap:wrap;gap:8px;align-items:center;}
/* Fixed box so flags of differing aspect ratios (england.png, uk.png, wales.png …)
   all occupy the same space and none of them stretches the pill.
   `.vth img{display:block}` from vth-reset.css already applies. */
.vth-cc__flag{width:14px;height:14px;object-fit:cover;border-radius:50%;flex-shrink:0;}

/* Available options — one icon chip per delivery format. */
.vth-cpvcard__modes{display:flex;align-items:center;gap:9px;margin-top:auto;flex-wrap:wrap;}
.vth-cpvcard__modes-label{font-size:12px;font-weight:var(--weight-medium);color:var(--text-muted);}
/* Circular icon chip — the glyph alone, on a round tinted ground.
   Square box with `border-radius:50%`, not a pill with padding: the chips carry no
   label, so a padded pill left each icon adrift in a wide lozenge and the three of
   them came out different widths (Online/At your venue/Virtual glyphs differ in
   advance width). Equal width and height makes them a consistent set.
   The name is not shown, so it is carried by `aria-label` for assistive tech and by
   the `.vth-tip` tooltip for everyone else. */
.vth-cpvmode{width:36px;height:36px;flex-shrink:0;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;line-height:0;background:var(--surface-muted);color:var(--text-secondary);}
/* One tone per format, matching the public catalogue's colour language:
   1 Online (e-learning), 2 At your venue, 3 Virtual. */
.vth-cpvmode--1{background:rgb(211,237,255);color:#1D2C92;}
.vth-cpvmode--2{background:rgb(137,236,174);color:#16512D;}
.vth-cpvmode--3{background:rgb(255,218,121);color:#4A2300;}
/* Stack the image above the text before it gets too narrow to read beside it. */
@media (max-width:980px){
  .vth-cpvcard{flex-direction:column;}
  .vth-cpvcard__img{width:100%;height:210px;}
  .vth-cpvcard__bd{padding:0;}
  .vth-cpvcard__bd h4{font-size:19px;line-height:26px;}
}

/* Course chips (level / CPD minutes / draft) */
.vth-ccard__chips{display:flex;flex-wrap:wrap;gap:7px;}
/* Fixed HEIGHT rather than vertical padding: a pill containing a flag image and one
   containing only text must come out the same height, and padding alone cannot
   guarantee that once an <img> raises the line box. */
.vth-cc{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 14px;font-size:13px;line-height:1;font-weight:var(--weight-medium);border-radius:var(--radius-pill);white-space:nowrap;}
.vth-cc--b{background:rgb(211,237,255);color:#1D2C92;}
.vth-cc--y{background:rgb(255,218,121);color:#4A2300;}
.vth-cc--g{background:rgb(137,236,174);color:#16512D;}
.vth-cc--n{background:var(--surface-muted);color:var(--text-subtle);}

/* Action row above the results — wraps rather than overflowing on narrow screens. */
.vth-listbar{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;}
.vth-listbar__actions{display:flex;gap:10px;flex-wrap:wrap;position:relative;align-items:center;}

/* Internal tab: a compact table. Scrolls inside itself so the page never does. */
.vth-tblwrap{overflow-x:auto;}

/* Favourite toggle on a card's image. Values from the design's .cpvcard-fav.
   This is a favourite, NOT a download selection — downloads follow the filters. */
/* Favourite toggle. The button sits ON the course artwork, so BOTH states carry the
   same solid white disc — only the heart changes (grey outline = not saved, red fill
   = saved). It used to be a 45%-black scrim with a white outline heart, which
   disappeared against light or busy imagery; the saved state was the only legible
   one. The disc is white in both states so the control is always findable, and the
   shadow keeps it off a white-ish photo. */
/* NOTE THE DOUBLED CLASS — it is load-bearing, not a typo.
   `vth-reset.css` resets `.vth button{background:none}` at (0,1,1), which beats a
   single-class (0,1,0) rule regardless of load order. So this button's background
   NEVER rendered in the unsaved state: the intended dark scrim was silently dropped
   and the white outline heart floated on the bare artwork, invisible over light or
   busy images. Only `.is-on` looked right, because two classes (0,2,0) win.
   Self-doubling the class is the same fix `.vth-btn.vth-btn` uses above.

   Both states now carry the same solid white disc — only the heart changes (grey
   outline = not saved, red fill = saved) — so the control is always findable, and
   the shadow lifts it off a near-white photo. */
/* Course title as a link. `vth-reset.css` sets `.vth a{color:var(--text-link)}` at
   (0,1,1), which would beat a single-class rule and paint every course title blue —
   so this is the two-class form. The title keeps the card's own colour and only
   signals itself on hover, which is what stops a catalogue of links looking like a
   wall of hyperlinks. */
.vth a.vth-cpvcard__link{color:inherit;text-decoration:none;}
.vth a.vth-cpvcard__link:hover{color:var(--vth-blue);text-decoration:underline;}
.vth-cpvcard__fav.vth-cpvcard__fav{position:absolute;right:14px;top:14px;width:32px;height:32px;border-radius:50%;background:#fff;color:var(--text-subtle);display:grid;place-items:center;border:none;cursor:pointer;padding:0;box-shadow:0 2px 8px rgba(16,24,40,.22);transition:background .15s ease,color .15s ease;}
/* Hovering an unsaved card previews the action in the saved colour. */
.vth-cpvcard__fav.vth-cpvcard__fav:hover{background:#fff;color:var(--color-error);}
.vth-cpvcard__fav.vth-cpvcard__fav.is-on{background:#fff;color:var(--color-error);}
.vth-cpvcard__fav.vth-cpvcard__fav.is-on:hover{background:var(--surface-muted);}
/* While the save is in flight the button is disabled — say so rather than looking
   inert for no reason. */
.vth-cpvcard__fav.vth-cpvcard__fav:disabled{opacity:.55;cursor:default;}
/* The heart was riding 1.5px high. `place-items:center` centres the ng-if <span>
   correctly, but vth.components.icon nests its own `display:inline-flex` wrapper
   inside it — an INLINE-level box, so it sits on a text baseline and the line box
   reserves descender space beneath it (20px line box around a 17px icon: 6px above,
   9px below). Making the wrapper a flex box with no line-height takes the baseline
   out of the equation entirely. */
.vth-cpvcard__fav.vth-cpvcard__fav > span{display:flex;align-items:center;justify-content:center;line-height:0;}
.vth-cpvcard__fav svg{display:block;}

/* ==========================================================================
   Verify training (/reports/verify-training + the public /verify-certificate)
   Values copied from the design source's own CSS (ui_kits/platform/reports.html:
   .linkbox, .reducer, and the ResultCard banner), not eyeballed.

   Everything else this page needs is REUSED: .vth-fieldgrid--4 + .vth-stat-card
   for the results tiles, .vth-badge--* for outcome chips, .vth-alert--info/
   --warning for the design's .note, .vth-empty for its .zero, and
   .vth-procbar/.vth-procbar__dial for the "prepared in the background" panel.
   ========================================================================== */

/* Upload dropzone — the design's .linkbox.
   The real <input type="file"> is taken out of the layout rather than styled: it
   is not in vth-reset.css's input list, so it would otherwise pick up whatever
   Materialize/legacy CSS says about file inputs. The <label> is the control. */
.vth-dropzone{display:flex;align-items:center;gap:var(--space-3);background:var(--surface-muted);border:1.5px dashed var(--border-default);border-radius:var(--radius-lg);padding:10px 10px 10px 14px;cursor:pointer;margin:0;}
.vth-dropzone:hover{border-color:var(--vth-blue);}
.vth-dropzone.is-dragging{border-color:var(--vth-blue);background:var(--surface-blue-soft);}
.vth-dropzone__label{flex:1;min-width:0;font-family:ui-monospace,Menlo,monospace;font-size:13px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.vth-dropzone__input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0;}
/* Not a real <button>, so it needs the .vth-btn look without the reset fight. */
.vth-dropzone__cta{display:inline-flex;align-items:center;gap:6px;background:#fff;border:1px solid var(--border-default);border-radius:var(--radius-md);padding:6px 12px;font-size:12.5px;font-weight:var(--weight-medium);color:var(--text-primary);line-height:1;flex-shrink:0;}

/* Small action button that sits ON a coloured .vth-procbar — borrows the bar's
   own text colour, so it can't be a .vth-btn variant. Two classes to clear
   vth-reset.css's `.vth button{background:none;border:none}` (0,1,1). */
.vth-reducer.vth-reducer{border:1px solid currentColor;background:rgba(255,255,255,.6);border-radius:var(--radius-md);padding:7px 12px;font-family:var(--font-sans);font-size:12.5px;font-weight:var(--weight-medium);color:inherit;cursor:pointer;display:inline-flex;align-items:center;gap:7px;line-height:1;}
.vth-reducer.vth-reducer:hover{background:#fff;}

/* ---- The result card ----------------------------------------------------
   Validity and record state are shown as two SEPARATE strips, never merged.
   An archived record is usually also lapsed (in this database, always), and one
   combined banner can only ever show one of those two facts — which is the bug
   on the legacy page, where an archived, three-years-lapsed certificate
   rendered as a plain green "Certificate Verified". ---------------------- */
.vth-verify__banner{display:flex;align-items:center;gap:var(--space-3);border-radius:13px;padding:14px 16px;}
.vth-verify__banner b{display:block;font-size:17px;line-height:23px;}
.vth-verify__banner span{font-size:13px;opacity:.9;}
.vth-verify__banner svg{flex-shrink:0;}
.vth-verify--valid{background:#DDFBE7;color:#186336;}
.vth-verify--lapsed{background:#FFF2D1;color:#7C3F0B;}
.vth-verify--missing{background:#FEE2E2;color:#EB3939;}
.vth-verify--unknown{background:var(--surface-muted);color:var(--text-secondary);}

/* The archived strip. Deliberately neutral, not red: an archived record means
   the learner's record was archived by their organisation, NOT that the
   certificate was withdrawn — the training still took place. */
.vth-verify__state{display:flex;align-items:flex-start;gap:11px;border-radius:12px;padding:13px 16px;background:var(--surface-muted);color:var(--text-secondary);font-size:13.5px;line-height:1.55;border:1px solid var(--border-default);}
.vth-verify__state svg{flex-shrink:0;margin-top:1px;}
.vth-verify__state b{display:block;color:var(--text-primary);font-weight:var(--weight-semibold);margin-bottom:2px;}

/* Codes are case-sensitive base62 — never text-transform them. */
.vth-mono{font-family:ui-monospace,Menlo,monospace;letter-spacing:.06em;}
.vth-code-input.vth-code-input{font-family:ui-monospace,Menlo,monospace;letter-spacing:.06em;font-size:16px;height:52px;}

/* Bulk preview/results tables keep the code column narrow and unwrapped. */
.vth-verify__codecell{font-family:ui-monospace,Menlo,monospace;font-size:13px;white-space:nowrap;}

/* ---- Verify training: the entry card (pre-submission) ----
   Metrics from the Claude Design's `verifyShowForm` section. Applies ONLY to the
   form shown before a code is submitted; the result card below it is untouched. */
.vth-verify__entry{padding:44px 4px 20px;text-align:center;}
.vth-verify__entry-icon{
  width:64px;height:64px;margin:0 auto 16px;border-radius:18px;
  background:var(--surface-blue-soft);color:var(--vth-blue);
  display:inline-flex;align-items:center;justify-content:center;
}
/* Materialize styles h1-h6 directly, so these need explicit values rather than
   relying on inheritance — same reason `.vth-hero h2` does. */
.vth-verify__entry-title{margin:0 0 6px;font-size:24px;line-height:32px;font-weight:var(--weight-semibold);color:var(--text-primary);}
/* `.vth p{margin:0}` in vth-reset.css is (0,1,1) and beats a single class at
   (0,1,0) — measured on the rendered page, where this lead sat hard against the
   card's left edge instead of centring under the heading. Qualified to (0,2,1) so
   the `auto` side margins survive. Same trap the corrections block at the top of
   this file documents for buttons and inputs. */
.vth p.vth-verify__entry-lead{margin:0 auto 24px;max-width:420px;font-size:14.5px;line-height:22px;color:var(--text-subtle);}
/* The field group is left-aligned inside the centred card — a centred label above a
   left-aligned input reads as a mistake. */
.vth-verify__entry-form{display:flex;flex-direction:column;gap:14px;max-width:400px;margin:0 auto;text-align:left;}
.vth-verify__entry-form .vth-field{display:flex;flex-direction:column;gap:6px;}
/* Full-width submit. `.vth-btn` is inline-flex, so width has to be set explicitly. */
.vth-btn.vth-verify__entry-submit{width:100%;justify-content:center;}
.vth-verify__entry-note{margin:0;}

@media (max-width:560px){
  .vth-verify__entry{padding:28px 0 12px;}
  .vth-verify__entry-icon{width:56px;height:56px;border-radius:16px;}
  .vth-verify__entry-title{font-size:20px;line-height:28px;}
}

/* Page-tab header block: the underline strip, then the explanatory line beneath it.
   Shared by Verify training and Connected accounts.

   The hint is deliberately a SIBLING of `.vth-tabs`, not a child. `.vth-tabs` is
   `overflow-x:auto` so the tabs themselves stay reachable on a phone; a trailing
   sentence inside that row would be the thing consuming the width and pushing the
   last tab out of view. */
.vth-verify__tabs{display:flex;flex-direction:column;gap:8px;min-width:0;}
.vth-verify__hint{display:block;padding:0 6px;}
/* Below ~560px the three tabs no longer fit at full size. They tighten rather than
   scroll, so all three stay visible without a swipe — the strip keeps its
   `overflow-x:auto` as the last resort for very long labels. */
@media (max-width:560px){
  .vth-verify__tabs .vth-tabs{gap:16px;}
  .vth-verify__tabs .vth-tabs__btn{font-size:14px;gap:7px;padding:11px 2px;}
}

/* Search field with a leading icon — the design's `.srch`.
   `.vth-search` was used by the markup but had NO rule anywhere, so the icon simply
   sat in the normal flow above the input instead of inside it.
   The icon partial wraps its SVG in `<span class="vth-icon">`, so that span is what
   has to be positioned — not the svg. The input's left padding needs (0,3,0) to
   clear vth-reset.css's `.vth input[type=text]{padding:0}`, which is (0,2,1). */
.vth-search{position:relative;}
.vth-search > .vth-icon{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--text-muted);pointer-events:none;z-index:1;}
.vth .vth-search .vth-input.vth-input{padding-left:40px;}

/* Action popover anchored to a button, rather than filling a form field's width.
   `.vth-dropdown__menu` is `left:0;right:0` for the search-select case. */
.vth-dropdown__menu--auto{left:auto;right:0;min-width:190px;}
.vth-dropdown__menu--auto .vth-dropdown__item{white-space:nowrap;}

/* ============================================================
   Learner — Certificates & documents (/learner-course/access-certificate)
   Values copied from the design source (ui_kits/platform/learner-training.html,
   section L3: .hero/.stats/.crow/.dbtn), not eyeballed.
   ============================================================ */

/* Stat tiles: 4-up, reflowing. A modifier class, never an inline
   grid-template-columns — an inline style beats every media query. */
.vth-statgrid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;}
@media (max-width:900px){.vth-statgrid{grid-template-columns:repeat(2,minmax(0,1fr));}}
@media (max-width:520px){.vth-statgrid{grid-template-columns:minmax(0,1fr);}}
.vth-stat{border:1px solid var(--border-light);border-radius:14px;padding:16px;}
.vth-stat__n{font-size:26px;font-weight:var(--weight-bold);color:var(--text-primary);line-height:1;}
.vth-stat__l{font-size:12.5px;line-height:1.3;color:var(--text-subtle);margin-top:6px;}

/* Completed-course rows */
.vth-crows{display:flex;flex-direction:column;}
.vth-crow{display:grid;grid-template-columns:44px minmax(0,1fr) auto;gap:16px;align-items:center;padding:16px 4px;border-bottom:1px solid var(--border-light);}
.vth-crow:last-child{border-bottom:none;}
.vth-crow__i{width:44px;height:44px;border-radius:14px;display:grid;place-items:center;background:#DDFBE7;color:#186336;}
.vth-crow__main{min-width:0;}
.vth-crow__main b{display:block;font-size:15.5px;line-height:22px;font-weight:var(--weight-medium);color:var(--text-primary);}
.vth-crow__meta{font-size:13px;line-height:19px;color:var(--text-subtle);margin-top:3px;}
.vth-crow__chips{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;}
.vth-crow__acts{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end;}
/* Below this the three-column row cannot hold a name and three buttons side by
   side; stack so the actions sit under the course rather than squeezing it. */
@media (max-width:760px){
  .vth-crow{grid-template-columns:44px minmax(0,1fr);row-gap:12px;}
  .vth-crow__acts{grid-column:1 / -1;justify-content:flex-start;}
}

/* Document buttons — real <button>/<a> elements, so they need the compound
   selector that clears vth-reset.css (see css-gotchas.md). */
.vth-dbtn.vth-dbtn{display:inline-flex;align-items:center;gap:7px;height:36px;padding:0 13px;border-radius:10px;border:1px solid var(--border-default);background:#fff;font-family:var(--font-sans);font-size:13px;font-weight:var(--weight-medium);color:var(--text-secondary);cursor:pointer;white-space:nowrap;text-decoration:none;}
.vth-dbtn.vth-dbtn:hover{border-color:var(--vth-blue);color:var(--vth-blue);}

/* "Loading 6 more…" sentinel */
.vth-loadmore{display:flex;align-items:center;justify-content:center;gap:10px;padding:18px 0 6px;font-size:13px;color:var(--text-subtle);}
.vth-loadmore__spin{width:16px;height:16px;border-radius:50%;border:2px solid var(--border-default);border-top-color:var(--vth-blue);animation:vth-spin .7s linear infinite;}
@keyframes vth-spin{to{transform:rotate(360deg);}}

/* Header row of the record card: identity left, actions right, wrapping cleanly. */
.vth-recordbar{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;}
.vth-recordbar__acts{display:flex;gap:10px;align-items:center;flex-wrap:wrap;position:relative;}

/* Filter row above the list. */
.vth-filterrow{display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end;border-top:1px solid var(--border-light);padding-top:16px;}
.vth-filterrow__search{flex:1 1 240px;min-width:210px;margin:0;}
.vth-filterrow__f{flex:0 1 180px;min-width:150px;margin:0;}

/* ---- Learner — Share my training ---- */
/* Two columns on desktop, stacked once the form would get too narrow to fill in. */
.vth-sharegrid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--space-6);}
@media (max-width:860px){.vth-sharegrid{grid-template-columns:minmax(0,1fr);}}
.vth-sharegrid__aside{display:flex;flex-direction:column;gap:14px;}

/* The generated link, ready to copy. Values from the design's .linkbox.
   `min-width:0` lets the <code> shrink and ellipsis rather than forcing the row
   wider than its column. */
.vth-linkbox{display:flex;align-items:center;gap:10px;background:var(--surface-muted);border:1.5px dashed var(--border-default);border-radius:12px;padding:10px 10px 10px 14px;}
.vth-linkbox code{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;color:var(--text-primary);}

/* Preview of the email the recipient actually receives (design's .emailtpl). */
.vth-emailtpl{border:1px solid var(--border-light);border-radius:14px;overflow:hidden;}
.vth-emailtpl__head{background:var(--surface-subtle);padding:12px 16px;border-bottom:1px solid var(--border-light);font-size:13px;line-height:1.4;color:var(--text-subtle);}
.vth-emailtpl__body{padding:18px 18px 20px;font-size:14px;line-height:1.65;color:var(--text-secondary);}
/* The stored template is real email HTML — keep it from breaking the panel. */
.vth-emailtpl__body img{max-width:100%;height:auto;}
.vth-emailtpl__body table{width:100%!important;max-width:100%;border-collapse:collapse;}
.vth-emailtpl__body a{color:var(--vth-blue);}
.vth-emailtpl__scroll{max-height:360px;overflow-y:auto;}

/* One step below .vth-small (15px / --type-size-sm) for supporting lines that sit
   under a control — the design sets these at 13px, and 15px reads as body copy next
   to the field it is describing.

   Named to continue the existing scale (.vth-small -> .vth-xsmall) rather than
   inventing a parallel one. 13px is not a token: --type-size-xs is 14px and the next
   step down does not exist, while 13px is already used 15 times in this file, copied
   from the design source. Adding a token would mean editing vth-tokens.css, which
   this module does not own. */
.vth-xsmall{font-size:13px;line-height:1.5;}

/* Inline validation message under a field. */
.vth-field__error{display:block;margin-top:6px;font-size:12.5px;line-height:1.3;color:var(--color-error);}
.vth .vth-input.is-error{border-color:var(--color-error);}

/* ---- Activity table (learner) ----
   Column widths from the design's L4 table: a fixed, non-wrapping timestamp and a
   40% activity-type column, leaving the description the remaining space. */
.vth-table--activity .vth-table__when{width:150px;white-space:nowrap;}
.vth-table--activity .vth-table__acttype{width:40%;color:var(--text-primary);}
/* `action`/`message` are server-rendered HTML; keep long unbroken strings from
   forcing the table wider than its scroll container. */
.vth-table--activity td{overflow-wrap:anywhere;}
.vth-table--activity td b{font-weight:var(--weight-medium);color:var(--text-primary);}

/* ============================================================================
   CONNECTED ACCOUNTS — the employer row
   ----------------------------------------------------------------------------
   Used by learner-v2/my-employer.blade.php for both the current organisation and
   each pending invitation. One component, two tones: the invite variant differs
   only in the icon tile so the two read as the same kind of thing — an
   organisation, and your relationship to it.

   Scoped under .vth like everything else here, and built from tokens rather than
   hex so it follows the theme. Materialize styles `div` and `span` loosely enough
   that the explicit display/flex here is load-bearing, not decorative.
   ========================================================================== */
.vth .vth-employer {
    display: flex;
    align-items: center;
    gap: 18px;
    border: 1.5px solid var(--vth-border-subtle, #EAECF0);
    border-radius: 18px;
    padding: 20px;
    background: var(--vth-surface, #FFF);
}

.vth .vth-employer__icon {
    width: 56px;
    height: 56px;
    border-radius: 16px;
    background: var(--vth-surface-blue-soft, #EAF0FF);
    color: var(--vth-blue, #2C5FE8);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* A pending invitation, not a settled relationship — warmer so the two are
   distinguishable at a glance when both are on screen. */
.vth .vth-employer__icon--invite {
    background: var(--vth-yellow-100, #FFF2D1);
    color: var(--vth-yellow-800, #984D08);
}

.vth .vth-employer__name {
    font-size: 17px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--vth-text-primary, #1D1D1D);
}

@media (max-width: 640px) {
    .vth .vth-employer {
        align-items: flex-start;
        flex-direction: column;
        gap: 14px;
    }
}

/* ============================================================
   Shell-aware layout — the content column, not the window

   Every breakpoint above this point is written against the VIEWPORT, but nothing
   in this file is ever laid out at viewport width. The reports/learner views sit
   in the right-hand column of the platform shell, and that column's width depends
   on whether the sidebar rail is present:

     > 992px   rail is a 276px sticky card + 16px gutter, inside #main's 16px
               padding (plus .vth-container's own)  ->  content ~= viewport - 330
     <= 992px  rail becomes an off-canvas drawer     ->  content ~= viewport - 40

   The two offsets are measured off the rendered page, not read off the shell CSS,
   and the breakpoints below are `content + offset` rounded to the nearest sensible
   number — a handful of px either way is well inside the range where any of these
   layouts is genuinely ambiguous.

   So at a 1200px window the grids below have ~868px, not 1200px — and a rule that
   fires at `max-width:1080px` never runs, leaving three columns crammed into a
   space that fits two. Worse, crossing 992px makes the content column ~292px
   WIDER, so some viewport-tuned rules also collapse layouts on tablet that had
   plenty of room.

   The Claude Design source hits the same problem and solves it the same way —
   "Measure the component's OWN width, not the window" (Certificates Module.dc.html).
   Container queries would be the modern expression of this, but `container-type`
   implies `contain: layout`, which makes the container a containing block for
   `position:fixed` descendants — and `.vth-modal__backdrop` (organisation,
   connected-accounts, requests, my-employer) and `.vth-toast` are both fixed and
   both live inside `.vth`. A backdrop that covers only the content column instead
   of the page is a worse bug than the one being fixed, so the two bands are
   expressed arithmetically instead.

   Everything below is ADDITIVE — it corrects only the bands the earlier rules get
   wrong. No rule above was edited.
   ============================================================ */

/* ---- Band A: rail present. viewport = target content width + 332 ----
   Only rules whose existing viewport number does NOT already land on the right
   content width are restated. `.vth-qcards` (1080), `.vth-lookup` (1080) and
   `.vth-fieldgrid{,--4}`'s 3->2 step (1040) happen to be correct for this band
   already, and are deliberately absent. */
@media (min-width:993px) and (max-width:852px){
  /* 520px of content: two columns of form fields stop fitting */
  .vth-fieldgrid,.vth-fieldgrid--4{grid-template-columns:minmax(0,1fr);}
}
@media (min-width:993px) and (max-width:1052px){
  /* 720px of content */
  .vth-fieldgrid--2-1{grid-template-columns:minmax(0,1fr);}
  .vth-lookup__coursefilter{flex:1 1 100%;width:100%;}
  /* The course row cannot hold a name and three buttons side by side */
  .vth-crow{grid-template-columns:44px minmax(0,1fr);row-gap:12px;}
  .vth-crow__acts{grid-column:1 / -1;justify-content:flex-start;}
}
@media (min-width:993px) and (max-width:1272px){
  /* 940px: stack the course preview image above its text */
  .vth-cpvcard{flex-direction:column;}
  .vth-cpvcard__img{width:100%;height:210px;}
  .vth-cpvcard__bd{padding:0;}
  .vth-cpvcard__bd h4{font-size:19px;line-height:26px;}
}
@media (min-width:993px) and (max-width:1192px){
  .vth-statgrid{grid-template-columns:repeat(2,minmax(0,1fr));}   /* 860px */
}
@media (min-width:993px) and (max-width:812px){
  .vth-statgrid{grid-template-columns:minmax(0,1fr);}             /* 480px */
}
@media (min-width:993px) and (max-width:1152px){
  .vth-sharegrid{grid-template-columns:minmax(0,1fr);}            /* 820px */
}
@media (min-width:993px) and (max-width:932px){
  .vth .vth-employer{align-items:flex-start;flex-direction:column;gap:14px;}  /* 600px */
}

/* ---- Band B: rail off-canvas, content = viewport - 40 ----
   Three rules above fire from 1040-1080px, but here the column is nearly the whole
   window — so between their true content breakpoint and 992px they collapse grids
   that had room. Restored to their wide form across that gap; the collapse still
   happens, just at the width where it is actually needed. Every other
   viewport-tuned rule in this file already lands within a few px of its
   content-correct value in this band and is left alone. */
@media (max-width:992px) and (min-width:789px){
  .vth-qcards{grid-template-columns:repeat(3,minmax(0,1fr));}     /* 748px */
  .vth-lookup{grid-template-columns:340px minmax(0,1fr);}
  .vth-lookup__list{position:sticky;}
  .vth-lookup__scroll{max-height:520px;}
}
@media (max-width:992px) and (min-width:749px){
  .vth-fieldgrid{grid-template-columns:repeat(3,minmax(0,1fr));}  /* 708px */
  .vth-fieldgrid--4{grid-template-columns:repeat(4,minmax(0,1fr));}
}

/* ---- Wide content must scroll inside its own card, never widen the page ----
   The design wraps every over-wide table in its own `overflow-x:auto` box
   (`<div class="cm-scroll">`). Without `min-width:0` the grid/flex item holding
   such a table refuses to shrink below its content, so the overflow lands on the
   page instead of the card and the whole layout gains a horizontal scrollbar. */
.vth-card,.vth-stepbox,.vth-lookup > *{min-width:0;}

/* ============================================================
   Right-section height

   The sidebar rail is pinned at `height:calc(100vh - 32px)` (themes/…/new_themes/
   css/style.css, `.left-rail`). On a short reports page — the Verify entry form,
   Coming soon, an empty result set — the content column stops hundreds of pixels
   above it and the page bottoms out looking clipped, as though the rest failed to
   load. Flooring the column at the rail's height makes the two end together while
   still letting the DOM drive the height beyond that: this is a min, never a fixed
   height, so nothing is ever cut off.

   Qualified by `.newdashcontentbar` — the shell's content <section> — rather than
   applied to `.vth` alone, for two reasons: the global shell CSS is left untouched,
   and the PUBLIC /verify-certificate page renders this same view OUTSIDE the shell,
   where there is no rail to match and a forced 100vh would just push the site
   footer down an empty screen. Dropped at <=992px, where the rail becomes an
   off-canvas drawer and has no height for the content to match.
   ============================================================ */
/* `.vth` is NOT the first thing in the content column. `#content.newdashcontentbar`
   opens with the platform `<header>` (page title, notifications, profile menu) plus
   its 30px bottom margin, and only then yields the view. So `min-height:calc(100vh -
   32px)` — which assumes `.vth` starts at the top of the viewport — overshot by the
   full height of that header: measured at 1400x900, `.vth` ran to y=877 against a
   rail ending at 797 and gave the document 80px of scroll with almost no content in
   it. That is the stray space below the fold, and the reason the column no longer
   lined up with the rail.

   Fixed by measuring nothing. The column is a flex item of `.wrapper`, which
   stretches it to the tallest item — and `.left-rail` is exactly `calc(100vh -
   32px)` — so `#content` already has the right height. Making it a flex column and
   letting `.vth` take what is left hands the correct remainder to `.vth` whatever
   the header happens to measure, at any width, with no arithmetic to get wrong.

   `:has(> .vth)` keeps this off every legacy page in the app. `#content` is the
   shared shell for every logged-in screen, and turning it into a flex container
   globally would stop margins collapsing inside it and could nudge spacing on views
   that have nothing to do with this module. Where `:has()` is unsupported nothing
   matches and the panel simply sizes to its content — shorter than ideal, but never
   the overflow above. */
.newdashcontentbar:has(> .vth){display:flex;flex-direction:column;}
.newdashcontentbar:has(> .vth) > .vth{flex:1 1 auto;min-height:0;}
.newdashcontentbar .vth{display:flex;flex-direction:column;}
.newdashcontentbar .vth > .vth-container{flex:1 1 auto;min-height:0;}
/* Below 992px the rail is an off-canvas drawer, so there is no height to match and
   nothing should be stretched to a viewport that the mobile layout does not fill. */
@media (max-width:992px){
  .newdashcontentbar:has(> .vth){display:block;}
  .newdashcontentbar:has(> .vth) > .vth{flex:0 1 auto;}
}

/* ============================================================
   CSS-only tooltip — `.vth-tip[data-tip]`

   Used by the course card's delivery-format chips, and available to anything else
   inside `.vth` that needs a hint on hover or keyboard focus.

   Deliberately not Materialize's `.tooltipped` or the tippy.js that layouts/app
   loads: both attach on init, so every element added later by an ng-repeat — a
   filter change, a page turn, an infinite-scroll append — comes up with no tooltip
   until something re-runs the initialiser. (That is exactly why the public
   catalogue's format tooltips stop working after paging.) A pure-CSS tooltip has
   no init step and cannot fall out of sync with the DOM.

   `aria-label` on the element carries the same information for screen readers;
   this is the visual layer only, hence aria-hidden is not needed on the
   pseudo-elements (generated content is not exposed as content by AT).
   ============================================================ */
.vth-tip{position:relative;}
.vth-tip::after,
.vth-tip::before{
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .13s ease,visibility .13s ease,transform .13s ease;
}
/* The bubble. `white-space:normal` + a max-width so a two-line tooltip wraps
   instead of running off the card. */
.vth-tip::after{
  content:attr(data-tip);
  position:absolute;bottom:calc(100% + 9px);left:50%;
  transform:translate(-50%,3px);
  z-index:30;width:max-content;max-width:240px;
  padding:8px 11px;border-radius:var(--radius-md);
  background:var(--text-primary);color:#fff;
  font-family:var(--font-sans);font-size:12px;font-weight:var(--weight-regular);
  line-height:1.45;white-space:normal;text-align:center;
  box-shadow:var(--shadow-md);
}
/* The arrow. */
.vth-tip::before{
  content:"";position:absolute;bottom:calc(100% + 3px);left:50%;
  transform:translate(-50%,3px);z-index:30;
  border:6px solid transparent;border-top-color:var(--text-primary);
}
.vth-tip:hover::after,.vth-tip:hover::before,
.vth-tip:focus-visible::after,.vth-tip:focus-visible::before{
  opacity:1;visibility:visible;transform:translate(-50%,0);
}
/* Near the left/right edge of a card the centred bubble would overflow; these
   modifiers pin it to the element's edge instead. Opt-in, so the default stays
   centred. */
.vth-tip--start::after{left:0;transform:translate(0,3px);}
.vth-tip--start:hover::after,.vth-tip--start:focus-visible::after{transform:translate(0,0);}
.vth-tip--end::after{left:auto;right:0;transform:translate(0,3px);}
.vth-tip--end:hover::after,.vth-tip--end:focus-visible::after{transform:translate(0,0);}
