/* ==========================================================================
   course-detail.css — page-specific CSS for the two course-detail views

   ADAPTER LAYER. Hand-authored. Loads AFTER course-shared.css so it can extend
   the shared components; anything reusable belongs in that file, not this one.

   SKELETON — section headings only, no rules yet. Phases 3–7 fill these in.
   Sections follow the DESIGN-SCOPE.md §3 zone map.

   Surfaces:
     public    new_sector_pages_design/courses/training_detail-reskin.blade.php
     platform  the_training_hub/course/user-panel/course_details-reskin.blade.php

   Both extend layouts/app.blade.php and both sit inside the body-level `.vth`
   scope. Distinguish the two with the theming opt-in, which only the public page
   carries:
       .vth--sector-themed …     public only
       .vth:not(.vth--sector-themed) …   platform only
   Do not invent a second surface class.

   Same rules as course-shared.css: scoped selectors, vth- namespace, tokens only,
   no !important without a documented reason.

   BREAKPOINTS (DESIGN-SCOPE §11) — verify continuously 320px→1920px+, not only at
   these values:
     >=1340   two-column body (content + 360px rail)
     1180-1340 single column, rail beneath
     <1180    platform sidebar -> burger drawer
     <1100    public nav -> burger drawer
     <900     hero stacks; format comparison stacks
     <760     single-column; tables scroll inside their cards
   No horizontal page scroll, no clipped text, no fixed-height text container,
   44px minimum touch targets, scrollbars hidden until hover.
   ========================================================================== */


/* ==========================================================================
   PAGE FRAME — the only rules in this file that sit OUTSIDE `.vth`
   --------------------------------------------------------------------------
   Everything else here is scoped to `.vth`. These two cannot be: one styles
   <body>, the other the <section> that WRAPS the scope. They are safe because
   this stylesheet is linked by exactly two views (vth/course-detail/styles.blade.php),
   so neither selector can reach another page.

   PUBLIC ONLY. `.vth--sector-themed` is the theming opt-in that only the public
   surface carries (SectorThemeResolver::viewData(), $themed; decision C-8) and is
   already this file's documented way to tell the two surfaces apart. The platform
   page keeps the app-shell background it shares with every other portal page.

   1. BODY BACKGROUND. DESIGN-SCOPE §2.2 specifies the public page background as
      #F3F4F8 — "grey, so white cards read as cards". The theme sets
      `body{background-color:transparent}` (style.css:3), so the page rendered white
      and every white card lost its edge against it. Token, not literal:
      --website-section-bg is the BG/Website/* entry for exactly this.
      (The reference PNG cannot be read for this value — its gutters are alpha 0,
      i.e. a transparent export, not a black canvas.)

   2. SECTION PADDING. The public theme styles the bare element:
      `section{padding:100px 0}` (style.css:150), 80px under 1600px and 60px under
      575px. The wrapper carrying ng-controller="CourseDetailsController" has no
      class, so it took all 100px — dead space above the course title and below the
      modals, and the spacing conflict this fixes. The reskin sets its own rhythm
      (`.vth-coursehead{padding:var(--space-6) 0 0}`), so the right value is 0.

      Only that wrapper is touched. `.vth .vth-formats` (specificity 0,2,0) already
      out-specifies the bare element selector, and `.testimonials_row` sits outside
      the scope and KEEPS the legacy padding on purpose — it shares that styling
      with the rest of the public site.
   ========================================================================== */
body.vth--sector-themed{background-color:var(--website-section-bg);}
body.vth--sector-themed section.vth-page{padding:0;}


/* ==========================================================================
   Page container — the public surface needs its own gutter
   --------------------------------------------------------------------------
   `.vth-container` in vth-reskin.css is deliberately width:100% with NO max-width
   and NO padding. Its comment explains why: on the PLATFORM the gutter comes from
   the app shell, `body #main{padding:16px}` (new_themes/style.css:149), so capping
   there would make reskinned pages narrower than every other page in the portal.

   The public site has no `#main`. Its chrome is laid out by `.container`, which
   reskin-common-head.blade.php sets to `calc(100% - 48px)` capped at 1680px. Left
   alone, the reskinned zones would run edge to edge while the header, footer and
   the Z6 sections sat inset — which is exactly how the page was rendering.

   So the public surface gets the same measurements as its own chrome, matched to
   `.container` rather than invented, and only inside this module's scope.
   ========================================================================== */
.vth .vth-coursehead,
.vth .vth-coursebody{width:calc(100% - 48px);max-width:1680px;margin-left:auto;margin-right:auto;}

/* PLATFORM OPT-OUT. The rule above is the PUBLIC gutter. On the platform the same
   block sits inside the app shell's own padded column (`#course_content` — it was
   `.lt-full-body` until the hero was lifted out of the two-column split so it could
   run full width, as the design has it). Either way the shell has already padded it,
   and adding a second 48px would inset the hero from everything else on the dashboard
   and cap it at 1680px inside a column that is already narrower. The modifier
   hands the width back to the shell, which is what every other platform page does.
   Declared after, so it wins on source order at equal specificity. */
.vth .vth-coursehead--platform{width:100%;max-width:none;margin-left:0;margin-right:0;}

/* Below 760px the public chrome drops to a 16px gutter (reskin-common-head's
   mobile block), so the reskinned zones follow it rather than keeping 24px. */
@media (max-width:760px){
  .vth .vth-coursehead,
  .vth .vth-coursebody{width:calc(100% - 32px);}
  .vth .vth-coursehead--platform{width:100%;}
}


/* ==========================================================================
   Z1 — Course name + favourite + share row
   --------------------------------------------------------------------------
   First element after the header and the state notices. Single row.
   Breadcrumbs do NOT ship on any variant (PO ruling, PHASE-0-ANALYSIS §8.2 G-4).
   ========================================================================== */
.vth .vth-coursehead{padding:var(--space-6) 0 0;}
/* PLATFORM: no top padding here. The 24px above is the PUBLIC page's gap between the
   site header and the course name; on the platform the back link sits directly above
   and supplies its own margin, so this padding was stacking on top of it — measured
   46px above the state notice against 0px below it.

   It is restated HERE rather than in the --platform block further up, where it already
   said `padding-top:0` and was DEAD: that block is declared before this rule and both
   are (0,2,0), so this one won on source order. */
.vth .vth-coursehead--platform{padding-top:0;}

/* The state notice is a block in the hero's vertical rhythm, not something glued to the
   title. The design stacks wayfinding, banner and title in a flex column with a uniform
   18px gap (source line 114); our nearest scale step is 20px, and it was 0 — the notice
   and the course name were touching.

   Scoped to the coursehead so the shared .vth-notice keeps its own spacing rules
   everywhere else. The public page renders no notice at all, so this is platform-only
   in practice. */
.vth .vth-coursehead .vth-notice{margin-bottom:var(--space-5);}
.vth .vth-coursehead__row{display:flex;align-items:flex-start;justify-content:space-between;
  gap:var(--space-4);margin-bottom:var(--space-5);}
/* min-width:0 lets the title shrink and wrap instead of pushing the action
   buttons off the row — without it a long course name overflows the container. */
.vth .vth-coursehead__title{min-width:0;font-size:var(--type-size-3xl);line-height:1.25;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-coursehead__actions{display:flex;align-items:center;gap:var(--space-2);flex-shrink:0;}
/* 44px is the minimum touch target DESIGN-SCOPE §11 requires, so the circular
   icon buttons are sized to it rather than to the glyph. */
.vth .vth-iconbtn{display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:50%;cursor:pointer;
  background:var(--surface-card);color:var(--text-primary);
  border:1px solid var(--border-light);}
.vth .vth-iconbtn i{font-size: var(--type-size-xl);}
.vth .vth-iconbtn:hover{background:var(--surface-muted);color:var(--text-primary);}
.vth .vth-iconbtn:focus-visible{outline:none;box-shadow:var(--shadow-focus-blue);}
.vth .vth-iconbtn.is-on{color:var(--fav-heart-selected);}

@media (max-width:760px){
  .vth .vth-coursehead__title{font-size:var(--type-size-title-6);}
}
@media (max-width:420px){
  .vth .vth-coursehead__title{font-size:var(--type-size-xl);}
}


/* ==========================================================================
   Z2 — Identity card (split hero)
   --------------------------------------------------------------------------
   Two columns: media left (max 420px), content right. Stacks below 900px.
   Split layout ONLY — the wide-image hero variant is out of scope.
   ========================================================================== */
.vth .vth-idcard{display:grid;grid-template-columns:minmax(0,420px) minmax(0,1fr);
  gap:var(--space-6);align-items:start;
  background:var(--surface-card);border:1px solid var(--border-light);
  border-radius:var(--radius-xl);padding:var(--space-6);}

/* The ONLY element that clips. An overflow:hidden on a wrapper would trap the
   format tooltips, which are ::after layers on the chips (course-shared.css §2)
   — acceptance criterion 10. */
/* `width:100%` IS LOAD-BEARING — without it the image overflowed its column and
   overlapped the body text. MEASURED in Chrome at 1440px before the fix: the grid
   track was 420px but the media computed to 533.33px (= 300 x 16/9), running 89px
   into the content column (media right edge 578 vs body x 489).

   WHY: the card sets `align-items:start`, so the item is not stretched in the block
   axis; with `aspect-ratio` set and BOTH axes auto, Chrome resolves the height first
   (`min-height:300px` beats the 236.25px the ratio wants at 420px) and then derives
   the WIDTH back from that height through the ratio — 533.33px. Giving the inline
   axis a definite value stops the ratio being run backwards.

   `min-width:0` does NOT fix this (verified — the width stayed 533.33px): this is the
   used width being computed from the ratio, not a minimum being applied. Neither does
   `overflow:hidden`. Only a definite inline size does.

   With it: width = the 420px track, height = max(420x9/16, 300px) = 300px — the
   intended box. Below 900px `min-height` is 0, so the ratio governs normally. */
.vth .vth-idcard__media{position:relative;overflow:hidden;width:100%;
  border-radius:18px;aspect-ratio:16/9;min-height:300px;
  background:var(--surface-muted);}
.vth .vth-idcard__img{width:100%;height:100%;object-fit:cover;}
.vth .vth-idcard__tag{position:absolute;left:14px;top:14px;z-index:1;}
/* Mandatory, bottom-left. Semantic — identical under every sector. */
.vth .vth-idcard__flag{position:absolute;left:14px;bottom:14px;z-index:1;
  background:var(--badge-yellow-bg);color:var(--badge-yellow-ink);
  border-radius:var(--radius-pill);padding:6px 14px;
  font-size:var(--type-size-2xs);font-weight:var(--weight-semibold);line-height:1;}

/* The §5 order is the source order — badges, formats, layers, excerpt, price,
   actions, terms — so it stays correct for screen readers and with CSS off. No
   grid `order` anywhere in this card. */
.vth .vth-idcard__body{display:flex;flex-direction:column;gap:var(--space-4);min-width:0;}
.vth .vth-idcard__layers{display:flex;align-items:center;gap:var(--space-2);
  font-size:var(--type-size-xs);color:var(--text-muted);}
.vth .vth-prices-wrap{margin-top:var(--space-1);}
/* The reference puts the primary and its neighbour on one line and Request Call
   Back on the next. That is not a fixed break — the row wraps naturally and the
   soft-blue variant is what marks the third action as tertiary, so it reads
   correctly whether it wraps or not. */
.vth .vth-idcard__actions{display:flex;flex-wrap:wrap;gap:var(--space-3);}
.vth .vth-idcard__terms{font-size:var(--type-size-2xs);line-height:1.5;color:var(--text-subtle);}
/* The reference sets "Terms and conditions" as BOLD BLUE against the grey sentence,
   with no rule under it — the weight and colour carry the affordance at this size,
   where an underline on 12px text reads as noise. The underline comes back on hover
   and on keyboard focus so the link is never signalled by colour alone (it also
   still passes without colour: it stays bolder than the surrounding text). */
.vth .vth-idcard__terms a{color:var(--text-link);font-weight:var(--weight-semibold);
  text-decoration:none;}
.vth .vth-idcard__terms a:hover,
.vth .vth-idcard__terms a:focus-visible{text-decoration:underline;}

/* Below 900px the hero stacks (DESIGN-SCOPE §11). The media loses its min-height
   so it does not eat the fold on a phone. */
@media (max-width:900px){
  .vth .vth-idcard{grid-template-columns:minmax(0,1fr);gap:var(--space-4);}
  .vth .vth-idcard__media{min-height:0;}
}
@media (max-width:420px){
  .vth .vth-idcard{padding:var(--space-4);}
  /* Full-width actions below the point where two pills stop fitting side by side.
     They keep a 44px MINIMUM height (DESIGN-SCOPE §11 touch target) but must be
     allowed to grow, because the label now wraps.

     MEASURED at 320px before this: "Book At Your Venue or Virtual" and "Request
     Online Training Price" spilled their container by 52px and 26px. The shared
     base sets `.vth-btn{white-space:nowrap}` (vth-reskin.css), so a flex item with
     `min-width:auto` takes its min-content width from the un-wrappable label and
     overruns the card. The page sets `overflow-x:clip`, so it did not scroll — the
     labels were simply cut off, which is worse.

     Fixed HERE and not in the base: vth-reskin.css is linked by 8 views, and the
     nowrap default is right for every other button on them. min-width:0 lets the
     item shrink below min-content; white-space:normal lets it use the room. */
  .vth .vth-idcard__actions .vth-btn{flex:1 1 100%;min-width:0;white-space:normal;
    height:auto;min-height:44px;padding-top:10px;padding-bottom:10px;line-height:1.3;}
}

/* ==========================================================================
   Z2 (reference) — composition and order
   --------------------------------------------------------------------------

   Media column
     16:9 image, 18px radius, 300px min height
     sector tag top-left · favourite top-right · Mandatory flag bottom-left
     (Top-rated does not ship — PO ruling)

   Content column, in this order
     1  badges          CPD / Level / Country+flag / Competency
     2  format icons    32px circles + body-level tooltips
     3  Educational Layers   N Educational Layers + icon  (courses.slides)
     4  excerpt         two lines + measured read-more, expanding in place
     5  price lines     one block per format statement, per §1.1
     6  action buttons  per Z-actions below
     7  terms line
   ========================================================================== */


/* ==========================================================================
   Z2a — Action sets
   --------------------------------------------------------------------------
   Right-aligned. Overflow folds to a three-dots menu on a MEASURED fit test.
   Quick Buy sits on the format-icon row, right-aligned, never in the overflow
   pipeline.

   Public: exactly TWO actions, always, whatever the format or price state —
   Book At Your Venue or Virtual (primary, sector-themed) and Request Online
   Training Price (the design system's `white` variant, NOT sector-themed).
   No other course CTA anywhere on the public page: no rail button, no in-tab
   call-back.

   Platform: brand-coloured in every sector. Resolved by ONE PHP resolver, not by
   conditionals scattered through Blade. The no-Online-format guard runs FIRST and
   returns immediately — a single Request Callback for every role and both
   ownership states, rendered as a finished composition, not a row with buttons
   removed.

   Utility actions on completed courses are 38px icon-only circular buttons with
   hover tooltips, matching the Certificate module treatment.
   ========================================================================== */


/* ==========================================================================
   Z-layout — the two-column body
   --------------------------------------------------------------------------
   >=1340px: content + a 360px rail. Below that, single column with the rail
   beneath (DESIGN-SCOPE §11).

   The sticky offsets are CUSTOM PROPERTIES set by course-detail.js from measured
   DOM values, never hardcoded (§8.1): --vth-rail-top is the tab strip's height
   plus the column gap, and --vth-rail-stick / --vth-left-stick are 16px when the
   column fits the viewport, otherwise viewportHeight - columnHeight - 16 so the
   column rides along and pins only when its last card comes into view. The
   fallbacks below are what renders before JS runs, or if it never does.
   ========================================================================== */
.vth .vth-coursebody{display:grid;grid-template-columns:minmax(0,1fr);
  gap:var(--space-6);padding:var(--space-8) 0;align-items:start;}
@media (min-width:1340px){
  .vth .vth-coursebody{grid-template-columns:minmax(0,1fr) 360px;}
  .vth .vth-coursebody__main{position:sticky;top:var(--vth-left-stick,16px);}
  /* The rail's first card lines up with the top of the TAB CONTENT, past the
     strip — hence the measured offset rather than a guess that breaks the moment
     the strip wraps to two lines. */
  .vth .vth-rail{position:sticky;top:var(--vth-rail-stick,16px);
    margin-top:var(--vth-rail-top,0);}
}
.vth .vth-coursebody__main{min-width:0;}
/* NO nested scroller anywhere in the rail (§8.1): a second scrollbar inside a page
   that already scrolls is the fastest way to hide content nobody knows is there. */
.vth .vth-rail{display:flex;flex-direction:column;gap:var(--space-4);min-width:0;}
/* 19px in the design source (line 1133); --type-size-xl is the nearest token at 20px.
   A first pass off the screenshot had this at 24px. */
.vth .vth-rail__title{font-size:var(--type-size-xl);line-height:1.25;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-rail__foot{font-size:var(--type-size-2xs);color:var(--text-subtle);}

/* ---- The rail's single call to action --------------------------------------
   The source gives it the full width of the card at 48px with a 12px radius and a
   15px/600 label (line 1037). 48px sits between --vth-btn-h-md (44) and -lg (52),
   so it gets its own token rather than being typed as a number in one rule — the
   platform's legacy `.full-width-btn` in the same rail is set from the same value,
   so the two kinds of rail button cannot drift apart.

   Only `width` and `height` are stated here: everything else — colour, hover,
   focus ring, disabled — comes from `.vth-btn--primary`, so this stays one button
   component rather than becoming a second one. */
/* Declared on both scopes: the reskinned rail card lives inside .vth, the legacy
   rail buttons below do not, and they must resolve to the same height. */
.vth,#CourseDetails{--vth-rail-cta-h:48px;}
.vth .vth-rail__cta{
  display:flex;width:100%;height:var(--vth-rail-cta-h);
  border-radius:var(--radius-lg);
  font-size:var(--type-size-sm);font-weight:var(--weight-semibold);
}

/* ---- Subscription bundles that already include this course ------------------
   Not in the reference — kept because it is real commercial information for the
   account holder with no other home on the page. Quiet, and separated from the
   footnote above by a hairline so it cannot be mistaken for part of the price. */
.vth .vth-rail__bundles{
  padding-top:var(--space-4);border-top:1px solid var(--border-light);
}
.vth .vth-rail__bundles .vth-rail__sub{margin-bottom:var(--space-3);}
.vth .vth-chips{display:flex;flex-wrap:wrap;gap:var(--space-2);
  margin:0;padding:0;list-style:none;}
/* 12px in the source (line 1135), which is exactly --type-size-2xs. */
.vth .vth-rail__sub{margin-top:calc(var(--space-2) * -1);
  font-size:var(--type-size-2xs);color:var(--text-muted);}
/* The course-expert card leads with an eyebrow, not a title — it labels the person
   below rather than announcing a section. */
.vth .vth-rail__eyebrow{font-size:var(--type-size-2xs);font-weight:var(--weight-semibold);
  letter-spacing:.06em;text-transform:uppercase;color:var(--text-muted);}

/* PLATFORM RAIL. It is not a sticky second column here — it sits inside the
   dashboard's own right-hand column, which the app shell already positions. All
   this adds is the card rhythm, so the new cards sit flush with the legacy ones
   above them rather than introducing a second layout system. */
.vth .vth-rail--platform{gap:var(--space-6);}

/* Rail benefits — each with a RELEVANT icon, not a generic tick (§8). */
/* MEASURED OFF THE DESIGN SOURCE (`Course Detail.dc.html` lines 1025-1033), not a
   screenshot — which is why these are smaller than a first pass off the image made
   them: the tile is 30x30 with an 8px radius and a 17px glyph, and the row is 14px
   with an 11px gap. A screenshot cannot be trusted for absolute sizes; the source
   states them. */
.vth .vth-benefits{display:flex;flex-direction:column;gap:var(--space-3);}
.vth .vth-benefits li{display:flex;align-items:center;gap:var(--space-3);
  font-size:var(--type-size-xs);color:var(--text-secondary);}
/* The tile, not the glyph. vth/components/icon writes width/height/font-size INLINE,
   so its own box cannot be resized from here — the source's soft rounded square is
   this wrapper, with the glyph centred inside it. */
.vth .vth-benefits__ico{display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;flex-shrink:0;border-radius:var(--radius-md);
  background:var(--surface-blue-soft);color:var(--vth-blue);}
.vth .vth-benefits li .vth-icon{color:var(--vth-blue);flex-shrink:0;}

/* Course expert */
.vth .vth-expert{display:flex;flex-direction:column;gap:var(--space-4);}
/* flex-start, not center: with a role line present the avatar must align to the top of
   the two-line block, not float against its middle. */
.vth .vth-expert__who{display:flex;align-items:flex-start;gap:var(--space-4);}
.vth .vth-expert__avatar{width:64px;height:64px;border-radius:50%;object-fit:cover;flex-shrink:0;}
.vth .vth-expert__id{display:flex;flex-direction:column;gap:var(--space-1);min-width:0;}
.vth .vth-expert__name{font-size:var(--type-size-title-6);line-height:1.25;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-expert__meta{font-size:var(--type-size-xs);line-height:1.4;color:var(--text-muted);}


/* ==========================================================================
   Z3 — Tab strip
   --------------------------------------------------------------------------
   RADIO INPUTS, NOT JAVASCRIPT. The tabs are `input[type=radio]` + `label`, so
   they switch with CSS alone: they work with JS disabled, and a radio group is
   already arrow-key navigable without any keyboard handling of our own. That also
   keeps us clear of Materialize's ul.tabs entirely — no .indicator, no per-element
   init, nothing for an AJAX re-render to leave behind.

   Underline treatment: icon + 16px/600 label, blue underline on the active tab.
   NO count pill (PO ruling, PHASE-0-ANALYSIS §8.2 G-3).

   The strip is Materialize `ul.tabs` today (sliding .indicator, uppercase
   anchors, overflow-x:auto) and the bodies load by Angular AJAX. Restyling it
   means replacing the markup AND the init together — never half-restyle a
   Materialize-JS widget (blade-angular-usage.md §4).

   PRESERVE the href anchors and the .course-detail-tabs container class: the E2E
   test-id helper at course_details-reskin.blade.php:3615-3619 selects on
   `.course-detail-tabs a[href="#Admin"]` and friends. Change them and the suite
   goes blind — update the helper in the same commit if you must.

   Order is fixed: Learning Outcome first, Curriculum second.
   ========================================================================== */
/* THE CONTAINER RULE — WITHOUT IT THIS COMPONENT COLLAPSES.

   It was called `.vth-tabs`, which is ALREADY a shared design-system component in
   vth-reskin.css:111 — `display:flex;gap;border-bottom`, a horizontal page-tab bar
   whose children are `.vth-tabs__btn`. Four other views use it (certificates access,
   verify-training, connected-accounts, course-lists). Here `.vth-tabs` was the
   WRAPPER around radios + strip + panels, so that shared rule flexed them into a ROW:
   measured, the strip rendered 68px wide and 699px tall beside the panel, and the
   panel sat 76px off the strip's left edge. Renamed to `.vth-coursetabs` — this is a
   different component and must not squat on that name.

   Column with a gap, matching the design source's left stack (line 333:
   display:flex;flex-direction:column;gap:14px). The hidden panels are display:none,
   so they generate no box and the gap never doubles. */
.vth .vth-coursetabs{display:flex;flex-direction:column;gap:var(--space-4);min-width:0;}

/* The radios drive everything and are never seen. Positioned off-screen rather
   than display:none so they stay focusable and the label association survives. */
.vth .vth-coursetabs__radio{position:absolute;opacity:0;width:1px;height:1px;
  pointer-events:none;margin:0;}
/* SCROLLS, NEVER WRAPS — the design source puts the strip in an overflow-x:auto box
   with min-width:max-content on the tabs (lines 334-335), so a strip too wide for the
   column slides instead of reflowing into two rows and shoving the content down. That
   matters most on the platform, which has six tabs, and at 320px where even two do not
   fit. -webkit-overflow-scrolling keeps the slide smooth on iOS. */
.vth .vth-coursetabs__strip{display:flex;align-items:center;gap:var(--space-6);
  border-bottom:1px solid var(--border-light);min-width:0;
  overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
  scrollbar-width:none;}
.vth .vth-coursetabs__strip::-webkit-scrollbar{display:none;}
/* flex-shrink:0 is what makes the strip SCROLL: without it the tabs squeeze to fit
   and the labels wrap instead. min-height 52px is the source's tab size. */
.vth .vth-coursetabs__tab{display:inline-flex;align-items:center;flex-shrink:0;
  min-height:52px;padding:var(--space-3) 0;cursor:pointer;white-space:nowrap;
  font-size:var(--type-size-xs);line-height:var(--type-leading-xs);font-weight:var(--weight-semibold);
  color:var(--text-subtle);border-bottom:2px solid transparent;margin-bottom:-1px;}
.vth .vth-coursetabs__tab:hover{color:var(--text-primary);}

/* Active tab and visible panel, both from :checked. The sibling combinators rely
   on the radios preceding the strip and the panels in source order — do not move
   them. */
.vth #vth-tab-outcome:checked ~ .vth-coursetabs__strip label[for="vth-tab-outcome"],
.vth #vth-tab-curriculum:checked ~ .vth-coursetabs__strip label[for="vth-tab-curriculum"]{
  color:var(--vth-blue);border-bottom-color:var(--vth-blue);}
/* Keyboard focus has to be visible on the LABEL, because the input itself is
   off-screen — without this the tabs are invisible to keyboard users. */
.vth .vth-coursetabs__radio:focus-visible ~ .vth-coursetabs__strip label[for]{outline:none;}
.vth #vth-tab-outcome:focus-visible ~ .vth-coursetabs__strip label[for="vth-tab-outcome"],
.vth #vth-tab-curriculum:focus-visible ~ .vth-coursetabs__strip label[for="vth-tab-curriculum"]{
  box-shadow:var(--shadow-focus-blue);border-radius:var(--radius-xs);}

.vth .vth-coursetabs__panel{display:none;min-width:0;}
.vth #vth-tab-outcome:checked ~ .vth-coursetabs__panel--outcome,
.vth #vth-tab-curriculum:checked ~ .vth-coursetabs__panel--curriculum{display:block;}

/* Long-form content from the CMS: the only place in this module where raw
   authored HTML lands, so it needs element selectors — scoped, and confined to
   .vth-prose so they cannot leak. */
.vth .vth-prose{font-size:var(--type-size-sm);line-height:1.65;color:var(--text-secondary);}
.vth .vth-prose h1,.vth .vth-prose h2,.vth .vth-prose h3,.vth .vth-prose h4{
  margin:var(--space-6) 0 var(--space-3);font-size:var(--type-size-xl);line-height:1.3;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-prose h1:first-child,.vth .vth-prose h2:first-child,
.vth .vth-prose h3:first-child,.vth .vth-prose h4:first-child{margin-top:0;}
.vth .vth-prose p{margin:0 0 var(--space-4);}
/* ---- A COMPONENT INSIDE PROSE MUST READ AS IT DOES ANYWHERE ELSE -------------
   `.vth .vth-prose` sets font-size, line-height and color ON THE CONTAINER, and all
   three are INHERITED properties — so every design-system component rendered into
   the tab picked up prose's body copy instead of the scope's. Measured against the
   identical markup placed elsewhere in `.vth`:

       .vth-notice         14px / 23.1px / #2B2B2B   was   15px / 22.5px / #131313
       .vth-notice__title  line-height 23.1px        was   21px
       .vth-notice__copy   14px / 23.1px / #2B2B2B   was   15px / 22.5px / #131313
       .vth-notice__icon   14px                      was   15px
       .vth-btn            line-height 23.1px        was   21px

   ZERO-SPECIFICITY ON PURPOSE. Both halves are wrapped in :where(), so the whole
   selector is (0,0,0). That is the only weight that does both jobs at once:

     - it still beats INHERITANCE, because any declaration on an element beats a
       value inherited from an ancestor, whatever its specificity; and
     - it loses to every component's own rule, including single-class ones like
       `.vth-btn--md` (0,1,0). A stronger selector — even `.vth .vth-prose
       [class*="vth-"]` at (0,2,0) — would have out-ranked those and resized the
       button and the notice title instead of restoring them.

   The values are the SCOPE's, not new ones: `.vth` itself sets line-height:1.5 and
   --text-primary (vth-reset.css:8) and inherits 15px from the page, which is exactly
   what the "elsewhere" column above measures.

   `:where()` is unsupported only on browsers this app already cannot run; if it were,
   the selector is simply dropped and behaviour returns to what it is today. */
:where(.vth .vth-prose) :where([class*="vth-"]){
  font-size:var(--type-size-xl);
  line-height:var(--space-6);
  color:var(--text-primary);
}

/* A DESIGN-SYSTEM COMPONENT DROPPED INSIDE PROSE GOVERNS ITS OWN CHILDREN.
   `.vth .vth-prose p` is (0,2,1); `.vth .vth-notice__body` is (0,2,0) — so the prose
   rule won, and the notice's two lines picked up a 16px bottom margin each. That
   component spaces itself with a flex `gap` on .vth-notice__copy, so the extra margin
   showed up as a gap that grew on its own and a trailing space under the last line.

   Written as "anything inside a vth- component", not as "inside .vth-notice", because
   prose is CMS HTML and any component may be rendered next to it — the same class of
   bug would come back with the next one. `margin:0` restates what the reset
   (`.vth p{margin:0}`) already gives; it is here to out-specify the prose rule, and
   the component's own rules still win over it where they set a margin. */
.vth .vth-prose [class*="vth-"] p{margin:0;}
/* LISTS IN AUTHORED PROSE
   Two different rules strip markers inside the scope and they need different answers:

     vth-reset.css:13   `.vth ul,.vth ol`                 (0,1,1)  list-style:none
     materialize.css:613 `ul:not(.browser-default)`       (0,1,1)  list-style-type:none
     materialize.css:614 `ul:not(.browser-default) li`    (0,1,2)  list-style-type:none

   The first two sit on the LIST, so `.vth .vth-prose ul` (0,2,1) already beat them.
   The third sits on the LIST ITEM, and nothing here answered it — an explicit
   `list-style-type` on the li beats the value it would otherwise inherit from the ul,
   so every marker vanished however the ul was styled. The fix is to put the marker on
   the li too, at (0,2,2), which outranks it.

   Nesting is spelled out because Materialize's rule reaches every depth, and browsers
   only vary the marker by depth through UA defaults that are being overridden here. */
.vth .vth-prose ul,.vth .vth-prose ol{margin:0 0 var(--space-4);padding-left:var(--space-6);}
.vth .vth-prose ul{list-style:disc;}
.vth .vth-prose ol{list-style:decimal;}
.vth .vth-prose li{margin-bottom:var(--space-2);}
/* (0,2,2) — the rules that matter. */
.vth .vth-prose ul > li{list-style-type:disc;}
.vth .vth-prose ol > li{list-style-type:decimal;}
/* Depth markers live with the other nested-list rules further down. */
/* AUTHORED LINKS ONLY — never a component that happens to be an <a>.
   `.vth .vth-prose a` is (0,2,1) and was beating `.vth-btn`'s text-decoration:none
   (0,1,0) AND `.vth .vth-btn--white` / `--ghost`'s colour (0,2,0). Measured against
   the same buttons placed elsewhere in `.vth`: every variant came out UNDERLINED in
   prose, and --white and --ghost were painted the link colour (#0D4B50) instead of
   --text-primary (#131313).

   The `.vth-prose__cta` wrapper in the Learning Outcome tab masked half of this — it
   cancels the underline, so the tab's own Request Call Back looked right while a
   button anywhere else in the same prose did not, and its colour was wrong either way.

   `:not([class*="vth-"])` is the same namespace boundary vth-reset.css uses for its
   own `a` rule: CLAUDE.md requires every component class to carry the `vth-` prefix,
   so the attribute IS the line between "authored link" and "ours". */
.vth .vth-prose a:not([class*="vth-"]){color:var(--text-link);text-decoration:underline;}
.vth .vth-prose img{max-width:100%;height:auto;border-radius:var(--radius-md);}

/* ==========================================================================
   AUTHORED HTML — the rest of what the CMS can send

   WHAT IS ACTUALLY IN THE DATA, not a guess. Counted across the 3,129 live
   courses, over the four columns getCourseDesById() concatenates
   (course_description_new, course_description, course_numbered_bullet_points,
   course_regulatory_wording):

       p 28397 · li 14073 · strong 3129 · meta 2594 · ul 2352 · b 1254
       ol 1088 · br 192 · div 83 · em 56

   and on the attribute side: dir 4142, charset 2594, role 1856 — the signature of
   copy pasted out of Word — against just 4 inline `style=` attributes in the whole
   table. So this content is plain authored markup, not styled markup: the work is
   covering ELEMENTS, and there is almost no inline CSS to fight.

   The rules above already handle p, ul, ol, li, a, img and h1-h4. Everything below
   is either a live tag with no rule yet (strong, b, em, div, br, meta) or a tag the
   editor can produce at any time (h5/h6, tables, quotes, code, definition lists,
   embeds). All of it stays inside .vth-prose, where element selectors cannot leak.
   ========================================================================== */

/* ---- Inline emphasis ------------------------------------------------------
   Live tags. `b` and `strong` are not reset, so they inherit the browser's bold —
   pinned to the type scale's own weight so authored bold matches component bold
   instead of being a shade heavier. */
.vth .vth-prose strong{font-weight:var(--weight-medium);color:var(--text-primary);}
.vth .vth-prose b{font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-prose em{font-style:italic;}
.vth .vth-prose u,.vth .vth-prose ins{text-decoration:underline;text-underline-offset:2px;}
.vth .vth-prose s,.vth .vth-prose del,.vth .vth-prose strike{text-decoration:line-through;color:var(--text-subtle);}
.vth .vth-prose small{font-size:var(--type-size-2xs);color:var(--text-subtle);}
.vth .vth-prose mark{background:var(--badge-yellow-bg);color:var(--badge-yellow-ink);
  padding:0 var(--space-1);border-radius:var(--radius-xs);}
/* sub/sup must not open up the line they sit on. */
.vth .vth-prose sub,.vth .vth-prose sup{font-size:.75em;line-height:0;position:relative;vertical-align:baseline;}
.vth .vth-prose sup{top:-.5em;}
.vth .vth-prose sub{bottom:-.25em;}
.vth .vth-prose abbr[title]{text-decoration:underline dotted;cursor:help;}

/* ---- Headings the editor can still emit ----------------------------------
   h1-h4 are handled above and all share one size, which is right for a course
   description. h5/h6 were left out: the reset zeroes their margin and pins
   line-height, so without this they arrived as unspaced body-sized text. */
.vth .vth-prose h5,.vth .vth-prose h6{
  margin:var(--space-5) 0 var(--space-2);font-size:var(--type-size-xs);line-height:1.4;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-prose h5:first-child,.vth .vth-prose h6:first-child{margin-top:0;}

/* ---- Blocks --------------------------------------------------------------
   `div` is live (83): Word wraps stray runs in one, and with no margin two of them
   ran together as a single paragraph. Only DIVs THAT ARE NOT OURS get the spacing —
   a component rendered into prose keeps its own box. */
.vth .vth-prose div:not([class]){margin-bottom:var(--space-4);}
.vth .vth-prose div:not([class]):last-child{margin-bottom:0;}
/* `meta` is live (2594) — Word paste leaves <meta charset> in the body. The HTML
   parser keeps it in the DOM; browsers do not render it, but say so explicitly so it
   can never take part in layout. Same for anything else non-rendering that survives a
   paste. */
.vth .vth-prose meta,.vth .vth-prose link,.vth .vth-prose style,.vth .vth-prose title{display:none;}

/* ---- Empty paragraphs -----------------------------------------------------
   A `<p>` with nothing in it should not push the next block down, so it drops the
   24px the base rule gives every paragraph. (0,3,1) against the base rule's (0,2,1),
   so no !important is needed.

   READ THE SECOND RULE BEFORE TRUSTING THE FIRST. `:empty` means NO child nodes at
   all, and a text node counts — so it matches `<p></p>` and nothing else. Counted
   over the same four columns as the table at the top of this section:

       <p></p>            8
       <p>&nbsp;</p>   2412        <-- NOT matched by :empty
       <p><br></p>        0

   The shape that actually fills this data is the one `:empty` cannot see: `&nbsp;`
   is a text node (U+00A0), so the paragraph is not empty by the spec's definition,
   and no selector can test text content. Such a paragraph also holds a real line
   box, so even at margin 0 it still occupies a blank line — CSS cannot remove it at
   all. Stripping it belongs where the four columns are concatenated
   (NavigationHelper::getCourseDesById()), not here.

   WHITESPACE-ONLY PARAGRAPHS — `<p> </p>`, `<p>\n   </p>` — ARE NOT `:empty` EITHER,
   and they need no rule. Selectors 4 redefined `:empty` to match whitespace-only
   elements; Chrome has not shipped it (checked on 154: `.matches(':empty')` is false
   for a space, two spaces, a newline, a tab and a CRLF+indent). No selector can see
   text content, so CSS cannot reach them at all.

   It does not matter, because `.vth-prose` is a BLOCK container and margins collapse.
   Such a paragraph has zero height and no border or padding, so its own top and bottom
   margins collapse through it and merge with its neighbours'. Measured — the gap
   between two real paragraphs:

       nothing between them        16px
       <p> </p> between them       16px      <- costs nothing
       <p>\n   </p> between them   16px      <- costs nothing
       <p></p> between them        16px      <- costs nothing, even without the rule
       <p>&nbsp;</p> between them  57px      <- +41px, a whole blank line

   THE `:empty` RULE IS THEREFORE INSURANCE, NOT A FIX, and the thing it insures
   against is this file: the moment `.vth-prose` is given `display:flex` or `grid`,
   margin collapsing stops and every one of those shapes starts costing a full gap —
   the truly-empty one this rule would still catch, the whitespace ones nothing could.
   Keep `.vth-prose` a block, or strip the empties server-side.

   The `:has()` rule is separate ON PURPOSE: an invalid selector invalidates its
   whole rule, so keeping it apart means a browser without :has() still gets the
   `:empty` one. Neither shape it covers occurs in the data today, but both are what
   editors leave behind — a `<br>` standing in for a blank line, and a formatting
   wrapper whose text was deleted out of it.

   THE WRAPPER LIST IS NAMED, NOT `:only-child:empty`. A void element is `:empty` by
   definition, so the generic form would also match `<p><img></p>`, `<p><input></p>`
   and `<p><iframe></iframe></p>` — paragraphs that very much do have content. Only
   inline formatting elements, which render nothing at all when emptied, are listed. */
.vth .vth-prose p:empty{margin-bottom:0;}
.vth .vth-prose p:has(> br:only-child),
.vth .vth-prose p:has(> span:only-child:empty),
.vth .vth-prose p:has(> strong:only-child:empty),
.vth .vth-prose p:has(> b:only-child:empty),
.vth .vth-prose p:has(> em:only-child:empty),
.vth .vth-prose p:has(> i:only-child:empty),
.vth .vth-prose p:has(> a:only-child:empty){margin-bottom:0;}

.vth .vth-prose blockquote{
  margin:0 0 var(--space-4);padding:var(--space-3) var(--space-5);
  border-left:3px solid var(--border-light);color:var(--text-subtle);font-style:italic;}
.vth .vth-prose blockquote > :last-child{margin-bottom:0;}
.vth .vth-prose hr{
  margin:var(--space-6) 0;border:0;border-top:1px solid var(--border-light);}
.vth .vth-prose pre{
  margin:0 0 var(--space-4);padding:var(--space-4);overflow-x:auto;
  background:var(--surface-muted);border-radius:var(--radius-md);
  font-size:var(--type-size-2xs);line-height:1.5;}
.vth .vth-prose code,.vth .vth-prose kbd,.vth .vth-prose samp{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.9em;background:var(--surface-muted);
  padding:1px var(--space-1);border-radius:var(--radius-xs);}
/* A code block already has its own surface — the inner <code> must not double it. */
.vth .vth-prose pre code{background:none;padding:0;font-size:inherit;}

/* ---- Nested and definition lists ----------------------------------------- */
.vth .vth-prose li > ul,.vth .vth-prose li > ol{margin:var(--space-2) 0 0;}
.vth .vth-prose ul ul{list-style:circle;}
.vth .vth-prose ul ul ul{list-style:square;}
/* The same (0,2,2) answer to materialize.css:614 as the top level, per depth. The
   list-level rules above cannot carry it: an explicit list-style-type on the li beats
   anything it would otherwise inherit from its list. */
.vth .vth-prose ul ul > li{list-style-type:circle;}
.vth .vth-prose ul ul ul > li{list-style-type:square;}
/* Ordered depths had no rule at all before; browsers repeat `decimal` forever. */
.vth .vth-prose ol ol{list-style:lower-alpha;}
.vth .vth-prose ol ol ol{list-style:lower-roman;}
.vth .vth-prose ol ol > li{list-style-type:lower-alpha;}
.vth .vth-prose ol ol ol > li{list-style-type:lower-roman;}
.vth .vth-prose li:last-child{margin-bottom:0;}
.vth .vth-prose li > p{margin-bottom:var(--space-2);}
.vth .vth-prose li > p:last-child{margin-bottom:0;}
.vth .vth-prose dl{margin:0 0 var(--space-4);}
.vth .vth-prose dt{font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-prose dd{margin:0 0 var(--space-3) var(--space-5);}

/* ---- Tables --------------------------------------------------------------
   vth-reset.css zeroes th/td padding and text-align for component tables, so an
   authored table arrived as unreadable run-together text. This restores it to the
   same look as .vth-table without asking the editor to add a class.

   The wrapper rule is the important one: a pasted table is usually wider than a
   card, and `display:block;overflow-x:auto` makes the TABLE scroll inside the card
   rather than the page scrolling sideways. */
.vth .vth-prose table{
  display:block;width:100%;max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;
  margin:0 0 var(--space-4);border-collapse:collapse;
  font-size:var(--type-size-2xs);}
.vth .vth-prose th,.vth .vth-prose td{
  padding:var(--space-3);text-align:left;vertical-align:top;
  border:1px solid var(--border-light);}
.vth .vth-prose th{background:var(--surface-subtle);font-weight:var(--weight-semibold);
  color:var(--text-primary);white-space:nowrap;}
.vth .vth-prose caption{caption-side:top;text-align:left;padding-bottom:var(--space-2);
  font-size:var(--type-size-2xs);color:var(--text-subtle);}

/* ---- Figures and embeds --------------------------------------------------- */
.vth .vth-prose figure{margin:0 0 var(--space-4);}
.vth .vth-prose figcaption{margin-top:var(--space-2);
  font-size:var(--type-size-2xs);color:var(--text-subtle);}
/* An embed with no aspect-ratio collapses to its default 150px box. */
.vth .vth-prose iframe,.vth .vth-prose video,.vth .vth-prose embed,.vth .vth-prose object{
  display:block;max-width:100%;width:100%;aspect-ratio:16/9;height:auto;
  margin:0 0 var(--space-4);border:0;border-radius:var(--radius-md);}

/* ---- Whole-block hygiene --------------------------------------------------
   A pasted URL or a long unbroken word would otherwise widen the card and push the
   page sideways; this keeps the break inside the text. */
.vth .vth-prose{overflow-wrap:anywhere;word-break:break-word;}
/* No leading or trailing gap inside the card, whatever tag the content happens to
   start or end with — the card's own padding is the spacing. */
.vth .vth-prose > :first-child{margin-top:0;}
.vth .vth-prose > :last-child{margin-bottom:0;}
/* ...and the same when the real first element is preceded by something that does not
   render. Word paste leaves <meta charset> at the top of 2,594 of these descriptions,
   so `:first-child` matched the META and the heading after it kept its 24px top
   margin — measured exactly that before this rule. Each non-rendering tag is named
   rather than using a `:not()` chain, because only these four can survive a paste. */
.vth .vth-prose > meta:first-child + *,
.vth .vth-prose > link:first-child + *,
.vth .vth-prose > style:first-child + *,
.vth .vth-prose > title:first-child + *{margin-top:0;}
/* `dir` is on 4,142 elements in this data. Honour an explicit RTL run rather than
   letting it inherit the page's direction and mis-order its punctuation. */
.vth .vth-prose [dir="rtl"]{direction:rtl;text-align:right;}
/* The tab's Request Call Back sits on its own line below the notes, left-aligned —
   it is the end of the read, not a floating action. */
.vth .vth-prose__cta{margin-top:var(--space-6) !important;}
.vth .vth-prose .vth-prose__cta a{text-decoration: none;}
.vth .vth-prose__note{display:flex;align-items:center;gap:var(--space-2);
  margin-top:var(--space-4);padding:var(--space-3);border-radius:var(--radius-md);
  background:var(--surface-blue-soft);color:var(--vth-blue);
  font-size:var(--type-size-2xs);}

/* Tables scroll inside their card below 760px; the header stays put (§11). */
/* ==========================================================================
   Curriculum table — design source lines 665-698
   --------------------------------------------------------------------------
   The base `.vth-table` in vth-reskin.css is shared with 14 other views, so the
   curriculum-specific treatment is a MODIFIER here and that file is untouched.
   ========================================================================== */
.vth .vth-panel__title{font-size:var(--type-size-xl);line-height:1.25;
  font-weight:var(--weight-semibold);color:var(--text-primary);
  margin-bottom:var(--space-4);}

.vth .vth-tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
/* CURRICULUM TABLE — reference Course Detail.dc.html:669-694.

   EVERY SELECTOR HERE IS `.vth .vth-table.vth-table--curriculum`, NOT
   `.vth .vth-table--curriculum`, AND THAT IS THE BUG THIS FIXES. The short form scores
   (0,2,1) — exactly the same as `.vth .vth-table td` further down this file — so source
   order decided it and the generic rule, being later, won. The cell padding was
   resolving to 12px instead of the reference's 15px, measured on the live public page.
   Naming the base class too makes it (0,3,1) and the variant wins on its own merit
   rather than on where it happens to sit in the file.

   The literals are the reference's own numbers where the token scale has no match:
   14.5px type, 10px header radius, 15px/14px cells. --divider-grey IS an exact match
   for its #EAECF0 row rule, so that one is a token. */
.vth .vth-table.vth-table--curriculum{min-width:600px;font-size:14.5px;}
/* The header is a floating grey BAND with rounded ends, not a ruled row: the base
   table's bottom border is removed and the radius put on the outer cells. */
.vth-table--curriculum thead th{background:var(--surface-muted);
  border-bottom:0;color:var(--text-primary);padding:14px;
  font-size:14.5px;text-transform:none;letter-spacing:0;
  font-weight:var(--weight-semibold);}
.vth .vth-table.vth-table--curriculum thead th:first-child{border-radius:10px 0 0 0; width: auto;}
.vth .vth-table.vth-table--curriculum thead th:last-child{border-radius:0 10px 0 0;}
.vth .vth-table.vth-table--curriculum td{padding:15px 14px;
  border-bottom:1px solid var(--divider-grey);}
/* The unit name is the row's subject, so it carries the weight; type and duration
   are supporting metadata and stay regular. */
.vth .vth-table.vth-table--curriculum .vth-unitname{font-weight:var(--weight-medium);}
.learning-outcome .vth ul li{list-style: disc !important;}
/* Type — a pill, so the category reads as a label rather than more of the title. */
.vth .vth-unittype{display:inline-flex;align-items:center;gap:7px;white-space:nowrap;
  background:var(--surface-muted);color:var(--text-secondary);
  border-radius:var(--radius-pill);padding:5px 11px;
  /* 12.5px is the reference's own value; the scale's nearest is 12. */
  font-size:12.5px;font-weight:var(--weight-medium);}
/* Duration — clock at reduced opacity, per the source, so the glyph supports the
   value instead of competing with it. */
.vth .vth-unitdur{display:inline-flex;align-items:center;gap:7px;white-space:nowrap;
  color:var(--text-subtle);}
.vth .vth-unitdur .vth-icon{opacity:.6;}
.vth .vth-nowrap{white-space:nowrap;}
.vth .vth-table th{font-size:var(--type-size-2xs);text-transform:uppercase;
  letter-spacing:.04em;color:var(--text-muted);font-weight:var(--weight-semibold);
  padding:var(--space-3) var(--space-3);border-bottom:1px solid var(--border-light);}
.vth .vth-table td{font-size:var(--type-size-xs);color:var(--text-secondary);
  padding:var(--space-3) var(--space-3);border-bottom:1px solid var(--border-light);}
.vth .vth-table tr:last-child td{border-bottom:none;}
/* …EXCEPT the two tables matched to the reference, which rule EVERY row including the
   last (dc.html:678 and :448 both put the border on the <tr> inside the loop, and both
   design screenshots show the line under the final row). Exempted here rather than
   changing the rule above, so the LOR, magic-link and reports tables keep the look they
   have. (0,4,2) against that rule's (0,3,2). */
.vth .vth-table.vth-table--curriculum tr:last-child td,
.vth .vth-table.vth-table--learners tr:last-child td{
  border-bottom:1px solid var(--divider-grey);}


/* ==========================================================================
   Z4 — Tab content
   --------------------------------------------------------------------------
   Keep the existing loading mechanism — server-render stays server-render, AJAX
   stays AJAX. Do not convert one to the other.

   Every tab needs all four content states (see state-block in course-shared.css)
   with copy specific to the tab and the cause.

   Tabs, in order:
     Learning Outcome  long-form, named sections. "Discussed at booking" note for
                       non-Online formats. No CTA on the public surface.
     Curriculum        whenever the course has an Online format — all users, every
                       ownership and progress state. Unit / Type / Duration, plus a
                       per-unit status column for learner roles ONLY.
     Learners Details  Sub Company. Three collapsible sections: Learners Allocated
                       (selection toolbar, search, filter, table, pagination),
                       Activity, Stats (KPI strip + three donuts + Download Stats).
     LOR               select-all + row checkboxes, two searchable multi-selects,
                       selection-aware download.
     Course Activity   Quiz Activity / Reflective Elements segmented control, one
                       accordion row per learner.
     Magic Link        status filter + table + Create/Revoke, credit impact stated.

   Tables scroll inside their card below 760px; headers stay put.
   Outcome badges and status colours are semantic — never sector themed.
   ========================================================================== */


/* ==========================================================================
   Z5 — Right rail
   --------------------------------------------------------------------------
   Present at >=1340px; stacks beneath the content below that.
   Not rendered at all for Master Company.

   Cards, in order:
     commercial panel   price lines + the five benefits, each with a RELEVANT icon
                        (not generic ticks). No CTA on the public surface.
     Your progress      learner roles: percentage bar, units complete, next up
     Share This Course  Sub Company + learner roles, added state only. Credit
                        balance, Create Magic Link, Buy More Credits, last-created
                        link with copy.
     Tailor This Course Sub Company only. Option chips with icons. Submit Request
                        shows no further action after submitting.
     Course expert      author/reviewer card
     Popular courses    label is "Popular courses", not "Related courses". Uses the
                        shared course card.

   The five benefits are a FIXED list (DESIGN-SCOPE §8): Unlimited access · Course
   badge · Course certificate · Include extensive handbook · Include learning
   outcome report.

   SCROLL BEHAVIOUR (§8.1) — all of this is measured, none of it hardcoded:
     - first card aligns to the top of the tab content, offset measured from the
       DOM (tab strip height + column gap)
     - all four corners rounded
     - NO nested scroller; the rail scrolls with the page
     - sticky offset: 16px when the rail fits the viewport, otherwise
       viewportHeight - railHeight - 16, so it pins only once its last card is in
       view and both columns can then scroll simultaneously
     - the same rule applies to the tab-content column
   ========================================================================== */


/* ==========================================================================
   Z4 — Learners Details tab (Materialize accordion, restyled NOT replaced)
   --------------------------------------------------------------------------
   The accordion is Materialize's `.collapsible`, initialised by
   $('.collapsible').collapsible() and re-initialised after every AJAX tab load.
   blade-angular-usage.md §4: a JS-enhanced widget is either left alone or replaced
   markup AND init together. Only its typography and chrome are restyled here — the
   mechanics, the .active class it toggles and the ng-class that drives d-block are
   all untouched.

   .vth-accordion is added BESIDE .collapsible, never instead of it: the JS selects
   on .collapsible.
   ========================================================================== */
.vth .vth-accordion{border:none;box-shadow:none;margin:0;}
.vth .vth-accordion > li{background:var(--surface-card);border:1px solid var(--border-light);
  border-radius:var(--radius-lg);margin-bottom:var(--space-4);overflow:hidden;}
/* font-size is the SECTION heading's, not body copy's: the source sets these three
   ("Learners Allocated This Course", "Activity", "Stats") at 19px/600 (lines 378,
   511, 559). 18px is the nearest step on our type scale. */
.vth .vth-accordion .collapsible-header{display:flex;align-items:center;justify-content:space-between;
  gap:var(--space-3);padding:var(--space-4) var(--space-5);cursor:pointer;
  border:none;background:var(--surface-card); border-radius: var(--radius-lg);
  font-size:var(--type-size-lg);font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-accordion .collapsible-header:hover{background:var(--surface-muted);}

/* The collapse chevron (source line 2563): ArrowDown01Round shut, ArrowUp01Round
   open, 18px at .6 opacity. ONE glyph, flipped on .active — Materialize already
   toggles that class, so a second node would only be another thing to keep in sync.
   It replaces `lms-arrow-right`, which pointed RIGHT and never moved: style.css
   rotates that glyph only under `.nav-extended ul li a.collapsible-header.active`,
   and these headers are plain <div>s, so the rule never matched and the arrow
   communicated no state at all. */
.vth .vth-accordion .collapsible-header .vth-icon{flex-shrink:0;opacity:.6;
  transition:transform .2s ease;}
.vth .vth-accordion .collapsible-header.active .vth-icon{transform:rotate(180deg);}
@media (prefers-reduced-motion:reduce){
  .vth .vth-accordion .collapsible-header .vth-icon{transition:none;}
}
.vth .vth-accordion .collapsible-body{padding:var(--space-5);border:none;
  border-top:1px solid var(--border-light);}

/* The learner search sits inside a Materialize .input-field. The scoped reset
   strips its border, height and padding (it is input[type=text]); .vth-input is
   added in the markup to give it the design system's own field instead. */
.vth .vth-input{width:100%;height:40px;padding:0 var(--space-3);
  border:1px solid var(--border-light);border-radius:var(--radius-md);
  background:var(--surface-page);box-shadow:var(--shadow-xs);
  font-family:var(--font-sans);font-size:var(--type-size-xs);color:var(--text-primary);}
.vth .vth-input:focus{outline:none;border-color:var(--border-blue);box-shadow:var(--shadow-focus-blue);}

/* The bulk-action row wraps rather than overflowing; the buttons keep their
   ng-disabled behaviour, which Phase 7 converts to the explain-on-click treatment. */
.vth .allocate-dropdown-select-part{display:flex;flex-wrap:wrap;gap:var(--space-2);
  margin-bottom:var(--space-4);}




/* ==========================================================================
   Disabled legacy controls must still explain themselves (§10.3)
   --------------------------------------------------------------------------
   A disabled element dispatches NO mouse events, so a legacy ng-disabled button
   cannot answer "why not?" on its own. pointer-events:none lets the click fall
   through to .vth-inertwrap, which carries the explanation and is read by
   course-detail.js. ng-disabled is untouched, so the bulk action still cannot
   fire — which is the point: these are credit operations over many learners.
   ========================================================================== */
.vth .vth-inertwrap{display:inline-flex;cursor:not-allowed;}
.vth .vth-inertwrap > button[disabled]{pointer-events:none;opacity:.5;}


/* ==========================================================================
   Rail — Share This Course (magic link)
   --------------------------------------------------------------------------
   Two FULL-WIDTH stacked buttons, per the reference. Stacking is not decoration:
   Create Magic Link spends a credit and Buy More Credits leaves the page, and side
   by side at 360px they became equal-weight twins.
   ========================================================================== */
.vth .vth-magic__head{display:flex;align-items:center;gap:var(--space-3);}
.vth .vth-magic__icon{display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border-radius:50%;flex-shrink:0;
  background:var(--surface-blue-soft);color:var(--vth-blue);}
.vth .vth-magic__body{font-size:var(--type-size-xs);line-height:1.55;color:var(--text-subtle);}
/* The credit warning is bolded in the copy itself — it is the one sentence a person
   must not skim, because the charge happens on CREATE, not on use. */
.vth .vth-magic__body b{color:var(--text-primary);font-weight:var(--weight-semibold);}
.vth .vth-magic__balance{display:flex;align-items:center;justify-content:space-between;
  gap:var(--space-3);padding:var(--space-3);border-radius:var(--radius-md);
  background:var(--surface-muted);
  font-size:var(--type-size-2xs);color:var(--text-subtle);}
.vth .vth-magic__balance strong{font-size:var(--type-size-xs);color:var(--text-primary);
  font-weight:var(--weight-semibold);white-space:nowrap;}
.vth .vth-magic__nocredit{display:flex;align-items:flex-start;gap:var(--space-2);
  padding:var(--space-3);border-radius:var(--radius-md);
  background:var(--surface-warning-soft);color:var(--status-warning);
  font-size:var(--type-size-2xs);line-height:1.5;}
.vth .vth-magic__actions{display:flex;flex-direction:column;gap:var(--space-2);}
.vth .vth-magic__actions .vth-btn{width:100%;}


/* ==========================================================================
   Rail — Tailor This Course!  (styled from OUTSIDE the component)
   --------------------------------------------------------------------------
   <course-tailoring> renders from
   public/js/components/course-tailoring/course-tailoring.html, which is shared with
   the PRE-RESKIN course_details.blade.php. INSTRUCTIONS §1 keeps that view
   untouched, so the template is not edited — these rules reach into it, and only
   the two course-detail pages load this file.

   THE THEME HAS ITS OWN .tailor-course-request BLOCK (style.css:2863-2873) AND IT
   IS NOT REMOVED. Deleting it would strip this card on the pre-reskin fallback
   view, which is what the controller serves when RESKIN_ENABLED is off — so instead
   every property that block sets is answered here. The ones that were leaking
   through, each because the old rule simply had no opinion on that property:

       box-shadow:2px 4px 10px rgba(0,0,0,.1)   on the card
       transform:rotate(90deg)                  on the title glyph
       max-width:40px                           on the icon well — this capped the
           well at 40px no matter what `width` said
       :hover{transform:translateY(-2px);box-shadow:0 6px 14px …}  on the tiles
       .option-box span{font-size:14px;color:#374151}              on the labels —
           (0,3,0), while the old rule set type on .option-box, also (0,3,0), so the
           theme's span rule won every time

   Every `!important` below answers an `!important` in the theme or in Materialize,
   never a specificity gap — those are settled by the `.vth` prefix.

   Numbers are the reference's (Course Detail.dc.html:1091-1111), not the token
   scale's where the two disagree: the card is 18px-round on 20px of padding, the
   tiles 14px-round, the label 14.5px. Tokens are used wherever one matches.
   ========================================================================== */
.vth .tailor-course-request{
  /* The two tile tints are blue-tinted lavenders that exist nowhere in the token
     scale (--surface-blue-soft is #DAE9FF, a sky blue, and reads wrong here).
     Defined on the card rather than in vth-tokens.css because nothing else uses
     them; if a second component ever does, they lift out unchanged. */
  --tailor-tile-bg:#E6E9FA;
  --tailor-tile-bg-active:#D7DDF8;
  background:var(--surface-card);
  border:0;
  border-radius:18px;
  box-shadow:0 1px 2px rgba(16,24,40,.05);
  padding:var(--space-5);}
.vth .tailor-course-request .card-content{padding:0;}

/* ---- Heading: a 22px blue glyph beside 20px/600 blue text (reference line 1096) */
.vth .tailor-course-request .title{display:flex;align-items:center;gap:10px;
  margin:0 0 10px;
  font-size:var(--type-size-xl);line-height:1.3;
  font-weight:var(--weight-semibold);
  /* answers the theme's own !important on this colour — without it --reskin-blue
     wins and the heading stops following the sector accent */
  color:var(--vth-blue) !important;}
.vth .tailor-course-request .title i{font-size:22px;line-height:1;flex-shrink:0;
  transform:none;                       /* the theme rotates it a quarter turn */
  /* THE GLYPH IS SWAPPED IN CSS, NOT IN THE TEMPLATE. The reference uses Setting07;
     the shared template hard-codes <i class="lms-magic-wand">, and editing it would
     change the pre-reskin view too. icon-style.css pins the icomoon family with
     !important on [class^="lms-"], which is what this !important answers; the
     :before then inherits the family and only `content` has to change. */
  font-family:"hgi-stroke-rounded" !important;
  font-style:normal;-webkit-font-smoothing:antialiased;}
.vth .tailor-course-request .title i:before{content:"\46BA";}   /* Setting07 */

.vth .tailor-course-request .description,
.vth .tailor-course-request p{margin:0 0 18px;
  font-size:var(--type-size-xs);line-height:1.6;color:var(--text-subtle);}

/* ---- Option tiles: a 2x2 grid, icon above label (reference line 1100) --------- */
.vth .tailor-course-request .options{display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:14px;margin:0 0 18px;}
.vth .tailor-course-request .option-box{display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:var(--space-3);text-align:center;
  padding:var(--space-5) var(--space-3);cursor:pointer;
  background:var(--tailor-tile-bg);border:1px solid transparent;border-radius:14px;
  box-shadow:none;transform:none;
  transition:filter .15s ease,border-color .15s ease,background .15s ease;}
/* The reference darkens the tile on hover and does not move it; the theme lifts it
   2px and drops a shadow, so both are cancelled above and answered here. */
.vth .tailor-course-request .option-box:hover{
  filter:brightness(.97);background:var(--tailor-tile-bg-active);
  box-shadow:none;transform:none;}
/* (0,4,0). The theme sets label type on `.option-box span` at (0,3,0), so the label
   has to be addressed at the span too — setting it on the tile loses. */
.vth .tailor-course-request .option-box span{display:block;
  font-size:14.5px;line-height:1.35;
  font-weight:var(--weight-medium);color:var(--text-primary);}
/* The icon well is the `.icon` DIV, not the <i>: the glyph keeps its own size and
   the circle is the wrapper, so the two cannot fight over one box. 52px with a soft
   shadow (reference line 1103). max-width is set because the theme caps it at 40px,
   which beat `width` silently. */
.vth .tailor-course-request .option-box .icon{display:inline-flex;align-items:center;
  justify-content:center;width:52px;max-width:52px;height:52px;flex-shrink:0;
  border-radius:50%;background:var(--surface-card);color:var(--vth-blue);
  box-shadow:0 1px 2px rgba(16,24,40,.06);}
.vth .tailor-course-request .option-box .icon i{font-size:24px;line-height:1;
  color:inherit;
  /* Same swap as the heading, keyed to the LEGACY CLASS rather than :nth-child: the
     four icons come from a PHP constant
     (CourseTailoringRequest::CUSTOMISED_COURSE_CATEGORES) whose `icon` values are
     icomoon names, and ng-repeat also renders a hidden fifth entry, so position is
     not a safe handle. Class names are. */
  font-family:"hgi-stroke-rounded" !important;
  font-style:normal;-webkit-font-smoothing:antialiased;}
.vth .tailor-course-request .option-box .icon i.lms-g_translate:before{content:"\437D";} /* MessageMultiple01 */
.vth .tailor-course-request .option-box .icon i.lms-study:before{content:"\440A";}       /* Mortarboard01 */
.vth .tailor-course-request .option-box .icon i.lms-earth:before{content:"\40CB";}       /* Globe */
.vth .tailor-course-request .option-box .icon i.lms-plus:before{content:"\3AA9";}        /* Add01 */

/* ---- Request: full width, 52px, 12px radius, 16px/600 (reference line 1110) ----
   style.css sets `.btn{border-radius:30px !important}` plus its own size and weight,
   and Materialize sets the height and text-transform — which is what the flags here
   answer, not a specificity gap. */
.vth .tailor-course-request .btn{display:flex;align-items:center;justify-content:center;
  width:100%;height:52px;line-height:52px;min-height:52px;margin:0;padding:0;
  border:0;border-radius:var(--radius-lg) !important;
  background:var(--vth-blue);color:var(--text-on-blue);
  font-family:var(--font-sans);font-size:var(--type-size-base) !important;
  font-weight:var(--weight-semibold) !important;
  text-transform:none;letter-spacing:0;
  /* the template also carries .btn-shadow, which style.css:2459 sets with
     !important — the reference gives this button no shadow at all */
  box-shadow:none !important;}
.vth .tailor-course-request .btn:hover,
.vth .tailor-course-request .btn:focus{background:var(--vth-blue-hover);
  color:var(--text-on-blue);box-shadow:none !important;}


/* ==========================================================================
   Z6 — Public-only marketing blocks
   --------------------------------------------------------------------------
   Train your way: one card per delivery format the course actually offers,
   carrying the same icon and colour as the Z2 chips, its inclusions, and the §1.1
   price statement for that format alone.

   The testimonial block below stays legacy and is NOT styled here — it shares
   .testimonials_row with the rest of the public site and sits outside the scope.
   ========================================================================== */
/* SPEC: design source lines 1181-1210 + the `compare` data at 2355. Section is a
   white card; the three format cards are a 3-up grid that becomes one column below
   900px (source: compareCols = vw >= 900 ? repeat(3,...) : 1fr). */
/* PADDING IS LOAD-BEARING — do not drop it. The public theme styles the bare element,
   `section{padding:100px 0}` (style.css:150, 80px under 1600px, 60px under 575px), and
   this component is a <section>. With no padding declared here the theme's rule is the
   only one in play and the card grew an 80px top and bottom band. Ours out-specifies it
   ((0,2,0) vs (0,0,1)) the moment it declares the property, so no !important is needed
   and style.css is not touched.

   The section is a WHITE CARD on the page background, per the source (line 1181:
   background #fff, radius 20, shadow, padding L.cardPad) — that card is where the
   padding belongs. */
/* margin, not `0 auto`: after this section became a CARD its outer spacing went with
   the old `padding:var(--space-16) 0`, and it ended up flush against the content above
   AND below — measured 0px to both. The source stacks these sections with an 18px gap
   (line 1180), so each one carries its own vertical rhythm here. */
.vth .vth-formats{width:calc(100% - 48px);max-width:1680px;margin:var(--space-5) auto;
  background:var(--surface-card);border-radius:var(--radius-2xl);
  box-shadow:var(--shadow-xs);padding:var(--space-6);}
.vth .vth-formats__head{margin-bottom:var(--space-6);}
/* Blue, uppercase, tracked — the section's kicker, not a heading. */
.vth .vth-formats__eyebrow{font-size:var(--type-size-xxs);font-weight:var(--weight-bold);
  letter-spacing:.04em;text-transform:uppercase;color:var(--vth-blue);
  margin-bottom:var(--space-2);}
/* 38px >=760px, 26px below (source h2). max-width keeps the line from running the
   full card width on a wide screen, which is what the source's 760px cap is for. */
/* 38px in the source; --type-size-title-4 (40px) is the nearest token. Drops to
   --type-size-title-6 (24px, source says 26px) below 760px in the block further down. */
.vth .vth-formats__title{font-size:var(--type-size-title-4);line-height:1.15;
  font-weight:var(--weight-semibold);color:var(--text-primary);
  max-width:760px;margin-bottom:var(--space-3);}
.vth .vth-formats__title-accent{color:var(--vth-blue);}
.vth .vth-formats__sub{font-size:var(--type-size-base);line-height:1.6;
  color:var(--text-subtle);max-width:680px;}

.vth .vth-formats__grid{display:grid;gap:var(--space-5);
  grid-template-columns:repeat(3,minmax(0,1fr));}

/* The card. position:relative carries the MOST POPULAR tag, which sits ON the top
   border — hence the tag's negative offset and the card's own overflow staying
   visible. */
.vth .vth-fmtcard{position:relative;display:flex;flex-direction:column;min-width:0;
  background:var(--surface-card);border:1px solid var(--border-light);
  /* Source says 20px; --radius-xl (16) and --radius-2xl (24) sit either side of it
     by the same 4px, so this takes 16 to match every other card on the page rather
     than being the one component that is rounder than its neighbours. */
  border-radius:var(--radius-xl);padding:var(--space-6) var(--space-6);
  box-shadow:var(--shadow-xs);}
/* The featured card is outlined in brand blue and lifted — the source gives it a
   blue-tinted shadow rather than the neutral one, so the ring reads as deliberate. */
.vth .vth-fmtcard--featured{border-color:var(--vth-blue);
  box-shadow:var(--shadow-blue-sm);}
.vth .vth-fmtcard__tag{position:absolute;top:-11px;left:var(--space-6);
  background:var(--vth-blue);color:var(--text-on-blue);
  font-size:var(--type-size-2xs);font-weight:var(--weight-bold);letter-spacing:.04em;
  text-transform:uppercase;padding:5px 12px;border-radius:var(--radius-pill);}

/* Round tile, one tone per format. The glyph inherits the tone's ink through
   `color`, so the icon needs no colour of its own. */
.vth .vth-fmtcard__ico{display:inline-flex;align-items:center;justify-content:center;
  width:52px;height:52px;border-radius:50%;flex-shrink:0;margin-bottom:var(--space-4);}
.vth .vth-fmtcard--green .vth-fmtcard__ico{background:var(--badge-green-bg);color:var(--badge-green-ink);}
.vth .vth-fmtcard--yellow .vth-fmtcard__ico{background:var(--badge-yellow-bg);color:var(--badge-yellow-ink);}
.vth .vth-fmtcard--blue .vth-fmtcard__ico{background:var(--badge-countries-bg);color:var(--badge-countries-ink);}

.vth .vth-fmtcard__title{font-size:var(--type-size-xl);line-height:1.25;
  font-weight:var(--weight-semibold);color:var(--text-primary);margin-bottom:var(--space-1);}
.vth .vth-fmtcard__blurb{font-size:var(--type-size-xs);line-height:1.6;
  color:var(--text-subtle);margin-bottom:var(--space-5);}

.vth .vth-fmtcard__list{display:flex;flex-direction:column;gap:var(--space-3);flex:1;}
.vth .vth-fmtcard__list li{display:flex;align-items:flex-start;gap:var(--space-3);
  font-size:var(--type-size-xs);line-height:1.5;color:var(--text-secondary);}
/* Each bullet gets the card's own ink on a neutral disc, so the three cards read as
   a set while still being told apart at a glance. */
.vth .vth-fmtcard__bullet{display:inline-flex;align-items:center;justify-content:center;
  width:24px;height:24px;border-radius:50%;flex-shrink:0;background:var(--surface-muted);}
.vth .vth-fmtcard--green .vth-fmtcard__bullet{color:var(--badge-green-ink);}
.vth .vth-fmtcard--yellow .vth-fmtcard__bullet{color:var(--badge-yellow-ink);}
.vth .vth-fmtcard--blue .vth-fmtcard__bullet{color:var(--badge-countries-ink);}

/* margin-top:auto pins the price to the card's foot, so the three prices line up
   across cards whose bullet lists differ in length. */
.vth .vth-fmtcard__price{margin-top:auto;padding-top:var(--space-5);
  border-top:1px solid var(--border-light);}
.vth .vth-fmtcard__price .vth-price__value{font-size:var(--type-size-title-6);}

.vth .vth-footnotes{margin-top:var(--space-5);display:flex;flex-direction:column;
  gap:var(--space-1);font-size:var(--type-size-2xs);line-height:1.6;color:var(--text-subtle);}
.vth .vth-footnotes li{display:flex;gap:var(--space-2);}
/* 899.98, not 900: the source switches at `vw >= 900 ? 3 columns : 1`, so 900 itself
   must still be three. A plain max-width:900px made the boundary value single-column. */
@media (max-width:899.98px){
  .vth .vth-formats__grid{grid-template-columns:minmax(0,1fr);}
}
@media (max-width:760px){
  /* Still declares padding: dropping it here would hand the 60px band back to
     style.css's `section` rule below 575px. Source: cardPad 24px -> 16px under 760. */
  .vth .vth-formats{width:calc(100% - 32px);padding:var(--space-4);}
  .vth .vth-formats__title{font-size:var(--type-size-title-6);}
}


/* ==========================================================================
   Z-states — notice banners (DESIGN-SCOPE §10.2)
   --------------------------------------------------------------------------
   At-Venue-only / Virtual-only override · Master Company read-only ·
   "Not in your account yet" · request sent / accepted / declined ·
   learner progress.
   Semantic colours only. Never sector themed.
   ========================================================================== */


/* ==========================================================================
   Z7 — QUOTE + CONTACT (design source lines 1215-1231)
   --------------------------------------------------------------------------
   A tinted panel holding the quote, with a white CTA card sitting inside it on the
   right. Two columns above 900px and one below, matching the source's
   `quoteCols: vw >= 900 ? "minmax(0,1fr) 340px" : "minmax(0,1fr)"`.

   align-items:center is the source's: the CTA card is shorter than the quote and is
   meant to sit against its middle, not stretch to its height.
   ========================================================================== */
.vth .vth-quote{width:calc(100% - 48px);max-width:1680px;margin:var(--space-5) auto var(--space-5);
  background:var(--website-stat-card-bg);border-radius:var(--radius-2xl);
  padding:var(--space-6);
  display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-6);align-items:center;}
.vth .vth-quote__body{min-width:0;}
.vth .vth-quote__eyebrow{font-size:var(--type-size-xxs);font-weight:var(--weight-bold);
  letter-spacing:.04em;text-transform:uppercase;color:var(--vth-blue);
  margin-bottom:var(--space-2);}
/* 26px >=760, 20px below (source `quote`). --type-size-title-5 is 28px, the nearest
   token above; it drops to --type-size-xl (20px) exactly at the source's breakpoint. */
.vth .vth-quote__text{font-size:var(--type-size-title-5);line-height:1.4;
  font-weight:var(--weight-semibold);color:var(--text-primary);
  margin-bottom:var(--space-4);}
.vth .vth-quote__by{font-size:var(--type-size-xs);color:var(--text-subtle);}

.vth .vth-quote__cta{background:var(--surface-card);border-radius:var(--radius-xl);
  padding:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);min-width:0;}
.vth .vth-quote__ctatitle{font-size:var(--type-size-lg);line-height:1.3;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-quote__ctabody{font-size:var(--type-size-xs);line-height:1.6;color:var(--text-subtle);}
/* Full width and stacked: they are the section's only two actions and the card is
   340px, where side by side would give each about 150px. */
.vth .vth-quote__cta .vth-btn{width:100%;}

@media (min-width:900px){
  .vth .vth-quote{grid-template-columns:minmax(0,1fr) 340px;}
}
@media (max-width:760px){
  .vth .vth-quote{width:calc(100% - 32px);padding:var(--space-4);}
  .vth .vth-quote__text{font-size:var(--type-size-xl);}
}
#CourseDetails #course_content {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
/* ---- THE RAIL OWNS ITS OWN SPACING -------------------------------------------
   Both rails are a flex column with ONE gap, so every card is separated by the same
   16px and no child has to carry a margin utility to space itself.

   16px is the SAME number as the gutter between the two columns further down: one
   spacing step for this page, horizontally and vertically, at every resolution.

   BOTH RULES MUST STAY INSIDE A MEDIA QUERY, and it is not a preference — it is the
   only thing keeping each rail on the surface it belongs to. style.css toggles the
   two copies with `display` and nothing else:

       .show-mobile{display:none}                       (style.css:1112, unscoped)
       @media (max-width:1199px){ .show-mobile{display:block}
                                  .hide-mobile{display:none} }   (:3366-3367)

   `display:flex` here at any width would therefore REVEAL the copy that is meant to
   be hidden — the mobile stack on desktop, or the desktop rail on a phone — and the
   page would render both. So the desktop rule lives at min-width:1200 (the block
   below) and the mobile rule at max-width:1199, each matching style.css's own
   breakpoint exactly.

   The mobile rail is addressed as a CHILD OF .lt-full-body, not as `.show-mobile`
   alone: `.show-mobile` is a general theme utility and the ajax-loaded tab content
   can carry it too. */
@media only screen and (max-width: 1199px) {
  #CourseDetails .lt-full-body > .show-mobile {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }
}
@media only screen and (min-width: 1200px) {
  #CourseDetails .dash-flex.v2-flex > .rt-side-body {
    margin-top: calc(var(--vth-tabstrip-h) + var(--vth-tabstrip-gap));
  }

  /* ---- THE TWO COLUMNS: a 360px rail and a 16px gutter, at every desktop width ----
     The theme sizes this row differently in each band, which is where the uneven
     gutter came from:

         .v2-flex .rt-side-body{width:400px}   + .rt-side-body{padding-left:24px}
         @media (max-width:1299px){ .rt-side-body{width:28%;padding-left:16px} }

     so the gutter was 24px on a wide screen and 16px between 1200 and 1299, and the
     rail was 400px — wider than the reference, which caps it at 360
     (`railCols: "minmax(0,1fr) 360px"`, Course Detail.dc.html:1752).

     WHY margin-left AND NOT gap. `.dash-flex` is the flex container, but the rail is
     not its last child: two modal wrappers follow it in the DOM (`#email_allocated`
     at view line 1122 and the AllocateMultipleCourseController div at 1147). Their
     contents are fixed-position modals, so they measure zero and are invisible
     today — but they ARE flex items, and a `gap` on the row would pay out between
     them too, adding 32px of dead width to the right of the rail. A margin on the
     rail alone cannot do that.

     margin rather than the theme's padding for a second reason: Materialize sets
     box-sizing:border-box globally, so `width:360px;padding-left:16px` would leave a
     344px card. The margin sits outside the border box, so the card is 360px exactly.

     clamp() keeps it responsive between the 1200px breakpoint and a wide monitor:
     30% of the row, never below 300px, never above the reference's 360px. The left
     column takes whatever is left — `flex:1 1 0%` with `min-width:0`, which is also
     what stops a long unbroken string in the tab content from widening the row. */
  #CourseDetails .dash-flex.v2-flex > .lt-full-body {
    width: auto;
    flex: 1 1 0%;
    min-width: 0;
  }
  #CourseDetails .dash-flex.v2-flex > .rt-side-body {
    flex: 0 0 auto;
    width: clamp(300px, 30%, 360px);
    max-width: 360px;
    padding-left: 0;
    margin-left: 16px;
  }

  /* The desktop rail, same stack. `display` is set here rather than beside the
     margin-top above so the min-width:1200 guard described in the block above is
     the one and only place it can come from. */
  #CourseDetails .rt-side-body.hide-mobile {
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  /* ---- STICKY COLUMNS, as the public page scrolls -----------------------------
     Both columns ride with the scroll and pin when their last card comes into view,
     which is what the source does (`railPos: sticky`, line 1755) and what the
     public page already implements.

     align-items MATTERS AND IS THE WHOLE TRICK. `.dash-flex` is a flex row whose
     align-items computes to `normal`, i.e. stretch — so each column is stretched to
     the height of the TALLER one. A stretched item has no spare room inside its
     container, and `position:sticky` on it does nothing at all: it is already as
     tall as the thing it would slide within. flex-start lets each column be its own
     height, which is what gives sticky something to move in.

     The offsets are CUSTOM PROPERTIES measured by course-detail.js::initRail(),
     never hardcoded: 16px while the column fits the viewport, otherwise
     viewportHeight - columnHeight - 16, so a column taller than the screen scrolls
     with the page and only pins once its bottom is reached. The fallbacks here are
     what renders before that JS runs, or if it never does.

     Only above 1200px. style.css stacks this row into a single column at 1199px,
     and a sticky element in a one-column stack just covers what follows it. */
  #CourseDetails .dash-flex.v2-flex { align-items: flex-start; }
  #CourseDetails .dash-flex.v2-flex > .lt-full-body {
    position: sticky;
    top: var(--vth-left-stick, 16px);
  }
  #CourseDetails .dash-flex.v2-flex > .rt-side-body {
    position: sticky;
    top: var(--vth-rail-stick, 16px);
  }
}
/* ==========================================================================
   PLATFORM PAGE STRUCTURE (the logged-in course-detail view)

   These were briefly in a page-specific stylesheet; that file is gone and this is
   where they belong — they are this module's own rules, not a veneer over the
   theme.

   SAFE IN THIS SHARED FILE. Every selector is scoped to `#CourseDetails`, which
   exists on the platform view alone, so none of it can match on the public page
   that also loads this stylesheet.
   ========================================================================== */
/* ---- PAGE RHYTHM: #course_content IS THE DESIGN'S <main> ----------------------
   The source lays the page out as one flex column with a single gap:

       <main style="display:flex;flex-direction:column;gap:18px">   (line 114)

   and #course_content holds exactly the four children that column has — the back
   link, the state banner, the hero, and the tabs/rail row. So the spacing between
   them is ONE declaration rather than a margin on each child.

   18px is the source's own value, deliberately off our 4px step: it is the page's
   outer rhythm, not a component's internal spacing.

   THE PER-CHILD MARGINS ARE ZEROED. A flex gap does not collapse margins, it adds
   to them, so the theme's 22px under .back-page would have made that pair 40px
   while every other pair was 18px. */

#CourseDetails #course_content > .back-page { margin-bottom: 0; }
#CourseDetails #course_content > .vth > .vth-notice { margin-bottom: 0; }
/* min-width:0 lets the row shrink instead of being forced wide by a long table
   inside a tab panel — without it a flex item refuses to go below its content. */
#CourseDetails #course_content > .dash-flex { min-width: 0; }

/* ---- TAB STRIP — the design's underline tabs (source line 335, 52px) ----------
   EVERY selector here carries `:not(.vth-seg--cao)`.

   `.course-detail-tabs` wraps the page strip AND all six tab panels (it opens at
   course_details-reskin.blade.php:451 and closes at :1037), so a bare
   `.course-detail-tabs .tabs` also matched the segmented controls nested inside the
   Learners Details and Course Activity Outcomes panels — and being ID-led with
   `!important`, it won every time. Those panels rendered their pill control as a
   52px underline strip.

   The exclusion names `.vth-seg--cao`, a marker class carried ONLY by the outcomes
   control - deliberately NOT `.tabs-secondary`, which would also free the Learners
   Details segmented control and change how that tab renders today. The brief was the
   outcomes tab; nothing else should move.

   REPORT-ONLY: the Learners Details control (`.tabs.tabs-secondary` inside #Admin) is
   still caught by these rules and renders as a 52px underline strip rather than the
   `.vth-seg` pill it was built for - its `.vth-seg` rules are dead there. Adding
   `vth-seg--cao` to that ul, or widening this to `:not(.tabs-secondary)`, fixes it.
   That is a visible design change to an approved screen, so it needs sign-off first.

   The `.tab a` rules had no `.tabs` in the chain at all, so they pick up the marker
   here too. */
#CourseDetails .course-detail-tabs .tabs:not(.vth-seg--cao) {
  display: flex !important; align-items: center; gap: var(--space-6);
  height: auto !important; min-height: 52px;
  background: transparent !important;
  border-bottom: 1px solid var(--border-light);
  overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
#CourseDetails .course-detail-tabs .tabs:not(.vth-seg--cao)::-webkit-scrollbar { display: none; }

/* Keeps the strip scrollable rather than page-widening. `overflow-x:auto` above only
   works while the chain around it can be narrower than its content; a flex item's
   automatic minimum is its content width, which with six tabs is wider than a phone.
   min-width:0 releases that at each level between the column and the strip.

   HONEST NOTE: this is defensive, not a fix for an observed break. A horizontal
   scroll seen at 320/360px turned out to be a fixed width on the measuring harness's
   own rail stand-in, not the page. With that corrected there is no page scroll at
   320, 360, 390 or 430 with or without these rules — they are kept because the
   ancestors are flex items above 1200px, where the minimum is real. */
#CourseDetails .lt-full-body,
#CourseDetails .course-detail-tabs,
#CourseDetails .course-detail-tabs .tab-rest { min-width: 0; }
#CourseDetails .course-detail-tabs .tabs:not(.vth-seg--cao) .tab {
  flex: 0 0 auto !important; height: auto !important; line-height: normal !important;
  text-transform: none !important; margin: 0 !important; padding: 0 !important;
}
#CourseDetails .course-detail-tabs .tabs:not(.vth-seg--cao) .tab a {
  display: inline-flex !important; align-items: center; min-height: 52px;
  padding: var(--space-3) 0 !important;
  font-size: var(--type-size-xs) !important; line-height: var(--type-leading-xs) !important;
  font-weight: var(--weight-semibold) !important;
  color: var(--text-subtle) !important; text-transform: none !important;
  white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
#CourseDetails .course-detail-tabs .tabs:not(.vth-seg--cao) .tab a:hover { color: var(--text-primary) !important; }
#CourseDetails .course-detail-tabs .tabs:not(.vth-seg--cao) .tab a.active {
  color: var(--vth-blue) !important; border-bottom-color: var(--vth-blue);
}
/* Materialize's .indicator is a JS-positioned sliding bar; with the active tab
   drawing its own 2px underline the two render as a doubled line. */
#CourseDetails .course-detail-tabs .tabs:not(.vth-seg--cao) .indicator { display: none !important; }

/* ---- THE TWO COLUMNS START LEVEL -----------------------------------------------
   The left column's first white box is a tab PANEL, so it starts below the tab
   strip; the right column's first card has nothing above it. Left to itself the
   rail sits a whole strip higher and the two columns look staggered.

   The source solves it the same way — `railTop: tabH + 14` (line 1754) — and the
   offset is DERIVED here rather than typed as a number, because it has to equal
   whatever the strip actually occupies. Both halves come from the same two
   variables, and the strip's own margin is pinned to one of them so the two cannot
   drift apart: change the gap and the rail follows.

   Only above 1200px. style.css stacks .dash-flex into a single column at
   max-width:1199px, and below that the offset would just be a gap above the rail. */
#CourseDetails { --vth-tabstrip-h: 52px; --vth-tabstrip-gap: 12px; }
#CourseDetails .course-detail-tabs .tabs:not(.vth-seg--cao) { margin-bottom: var(--vth-tabstrip-gap) !important; }


/* ---- Tab panels hold their height --------------------------------------------
   Switching tabs swapped panels of very different heights, so the page jumped and
   the rail moved with it. A floor keeps the column stable; taller content grows. */
#CourseDetails { --vth-tabpanel-min: 420px; }
@media only screen and (max-width: 767px) { #CourseDetails { --vth-tabpanel-min: 240px; } }

/* ---- RAIL CTAs ---------------------------------------------------------------
   The source gives the rail's buttons full width, 48px, a 12px radius and a 15px/600
   label (line 1037). They arrived 36px/6px/12px-500 from `.btn` + `.full-width-btn`,
   reading as a secondary control under a 28px price. */
#CourseDetails .rt-side-body .btn.full-width-btn {
  display: flex !important; align-items: center; justify-content: center;
  width: 100% !important; height: var(--vth-rail-cta-h) !important; min-height: var(--vth-rail-cta-h);
  padding: 0 var(--space-4) !important;
  border-radius: var(--radius-lg) !important;
  font-size: var(--type-size-sm) !important;
  font-weight: var(--weight-semibold) !important;
  line-height: 1.2 !important;
  text-transform: none !important;
  white-space: normal;
}

/* ---- PANEL CARDS -------------------------------------------------------------
   Matched to .vth-card — measured and copied — so both kinds of panel on this page
   are indistinguishable. Divergences from the source (radius 18 vs our 16; its 5%
   shadow vs .vth-card's border) are design-system-wide, raised in HANDOVER 9. */
#CourseDetails .white-box-24:not(.tailor-course-request) {
  border-radius: var(--radius-xl) !important;
  box-shadow: none !important;
  border: 1px solid var(--border-light) !important;
}
/* THE ONE EXCEPTION, and it is carved out here rather than fought with a fourth
   !important up in the Tailor block. That card is matched to the reference exactly
   — 18px round, no border, a 5% shadow — because it is the one panel the design
   shows with a shadow instead of a hairline. Everything else stays normalised. */


/* ==========================================================================
   CREATE MAGIC LINK — #generateMagicLinkModal  (styled from OUTSIDE the component)
   --------------------------------------------------------------------------
   Rendered by public/js/components/course-magic-link-history/course-magic-link-modal.html,
   which is ALSO used by magic_link_history.blade.php and the pre-reskin
   course_details.blade.php. The template's STRUCTURE is therefore not touched — only
   its copy — and everything visual is done here.

   SCOPED TO #CourseDetails, NOT `.vth`, AND DELIBERATELY SO. `.vth` would drag
   vth-reset.css across the WHOLE component, and this template renders four modals:
   #magicLinkDetailsModal, #shareMagicLinkModal and #emailInviteModal come with it.
   The reset strips border, padding and background from every text input and textarea
   inside the scope — which those three are full of, and which nothing here would put
   back. #CourseDetails is on this page and no other, the tokens are declared on
   :root so they resolve outside `.vth` anyway, and Materialize's own
   `*{box-sizing:inherit}` already gives the border-box these rules assume.

   It also outranks the theme more cleanly: (2,1,0) against `.magic-link-modal
   .credit-card`'s (0,2,0).

   Numbers are the reference's (Course Detail.dc.html:1338-1425).

   WHAT THE THEME ALREADY SETS, and is answered below:
       .tailor-course-modal{width:720px !important; …}      the shell
       .magic-link-modal .select-title{font-size:12px; …}   the section heads
       .magic-link-modal .credit-card{padding:18px;border:1px solid #D7E4F3;radius:10px}
       .magic-link-modal .credit-card:has(:checked){border-color:#24618C;background:…}
       .magic-link-modal .credit-warning{…}                 the amber note
       .tailor-course-modal.tellmoremodal .modal-header .wa-icon-whatapp{… !important}
       .modal .modal-footer{margin-top:var(--space40); justify-content:center}
   Every !important below answers an !important there or in Materialize.

   THE STACKING FIX IS NOT HERE — it is the view. See the comment on the moved
   <course-magic-link-modal>: it used to render inside .lt-full-body, which is
   position:sticky above 1200px and therefore a stacking context, so the modal's
   z-index resolved inside the column and Materialize's body-level overlay covered it.
   ========================================================================== */

/* ---- Shell: 680px, 20px round, its own scroll (reference line 1341) -----------
   THE LOCAL VALUES LIVE ON THE ROOT. There is no shared option-card in the reskin to
   reuse — checked, and vth-reskin.css has .vth-card, .vth-field, .vth-input and a
   .vth-checkbox component, but nothing selectable-card shaped, and no radio at all.
   A `.vth-optioncard` could be added there, but it would need its class ON the
   markup, and this markup belongs to a template three pages share. So the values are
   declared once here instead of being repeated across the six rules that use them;
   if a second option card ever appears, these lift straight into vth-reskin.css. */
#CourseDetails #generateMagicLinkModal.modal{
  --ml-option-border:#E4E4E7;
  --ml-option-border-on:var(--vth-blue);
  --ml-option-bg:var(--surface-card);
  --ml-option-bg-hover:#F9FAFB;
  --ml-option-bg-on:#F8FAFF;
  --ml-option-dot:#C8C8CE;
  --ml-option-pad:var(--space-4);
  --ml-option-control:20px;   /* the ring Materialize draws */
  --ml-option-gap:var(--space-3);
  width:100% !important;max-width:680px !important;
  max-height:90vh !important;overflow-y:auto !important;
  border-radius:20px !important;
  box-shadow:0 10px 15px -3px rgba(0,0,0,.18) !important;
  background:var(--surface-card);padding:0 !important;}

/* ---- Header (reference line 1342) -------------------------------------------- */
#CourseDetails #generateMagicLinkModal .modal-header{
  display:flex;align-items:flex-start;justify-content:flex-start;gap:var(--space-4);
  padding:var(--space-6) var(--space-6) var(--space-5);
  border-bottom:1px solid var(--border-light);background:var(--surface-card) !important;}
/* The 52px tile. The theme paints .wa-icon-whatapp with !important, which is what the
   flags here answer — not a specificity gap. */
#CourseDetails #generateMagicLinkModal .modal-header .wa-icon{
  width:52px;height:52px;flex-shrink:0;border-radius:14px;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--surface-blue-soft) !important;color:var(--vth-blue) !important;}
/* Link01, swapped in CSS: the template hard-codes lms-magic-wand and is shared.
   icon-style.css pins the icomoon family with !important on [class^="lms-"], so the
   family needs a flag; the :before then inherits it and only `content` changes.
   The theme ALSO rotates these glyphs 90deg, hence transform:none. */
#CourseDetails #generateMagicLinkModal .modal-header .wa-icon i{
  font-family:"hgi-stroke-rounded" !important;font-style:normal;
  font-size:26px;line-height:1;color:var(--vth-blue) !important;
  transform:none !important;-webkit-font-smoothing:antialiased;}
#CourseDetails #generateMagicLinkModal .modal-header .wa-icon i:before{content:"\4289";}
#CourseDetails #generateMagicLinkModal .modal-header h3{
  margin:0 0 var(--space-1);font-size:22px;line-height:1.25;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
#CourseDetails #generateMagicLinkModal .modal-header p{
  margin:0;font-size:14.5px;line-height:1.4;color:var(--text-subtle);}
/* Close: a 36px circle. The template's close is an <i>, so the glyph and the circle
   are the same element — hence the flex centring on the <i> itself. */
#CourseDetails #generateMagicLinkModal .modal-header .close-btn{
  width:36px;height:36px;flex-shrink:0;border-radius:50%;border:0;
  display:inline-flex;align-items:center;justify-content:center;cursor:pointer;
  background:var(--surface-muted);color:var(--text-primary);
  font-family:"hgi-stroke-rounded" !important;font-style:normal;font-size:20px;line-height:1;
  transition:background .15s ease;-webkit-font-smoothing:antialiased;}
#CourseDetails #generateMagicLinkModal .modal-header .close-btn:before{content:"\3D42";}
#CourseDetails #generateMagicLinkModal .modal-header .close-btn:hover{background:#EAEAEC;}

/* ---- Body: one column, 22px rhythm (reference line 1355) ---------------------- */
#CourseDetails #generateMagicLinkModal .modal-content{
  padding:22px var(--space-6) !important;
  display:flex;flex-direction:column;gap:22px;
  max-height:none !important;overflow:visible !important;}
/* The template wraps credit type in .credit-type-section and expiry in a bare div;
   both are flow children of the flex column, so their own margins would double the
   gap. The gap owns the rhythm. */
#CourseDetails #generateMagicLinkModal .modal-content > *{margin:0 !important;}
#CourseDetails #generateMagicLinkModal .credit-type-section,
#CourseDetails #generateMagicLinkModal .modal-content > div{
  display:flex;flex-direction:column;gap:var(--space-3);}

/* Section heads: 18px glyph + 15px/600 (reference line 1357) */
#CourseDetails #generateMagicLinkModal .select-title{
  display:flex;align-items:center;gap:9px;margin:0;
  font-size:15px;line-height:1.3;font-weight:var(--weight-semibold);
  color:var(--text-primary);text-transform:none;}
#CourseDetails #generateMagicLinkModal .select-title i{
  font-family:"hgi-stroke-rounded" !important;font-style:normal;
  font-size:18px;line-height:1;color:var(--text-secondary,#3F3F46);
  transform:none;-webkit-font-smoothing:antialiased;}
#CourseDetails #generateMagicLinkModal .select-title i.lms-wallet:before{content:"\4A21";}    /* Wallet01 */
#CourseDetails #generateMagicLinkModal .select-title i.lms-calendar1:before{content:"\3CE4";} /* Calendar01 */

/* ---- The option cards -------------------------------------------------------
   MATERIALIZE NEVER DREW THIS RADIO. Its control is generated by

       [type="radio"]+label:before,[type="radio"]+label:after{content:'';…}

   — on `+ label`. The template's input is followed by a `<span class="credit-card-box">`,
   not a label, so that rule has never matched and no pseudo-element was ever generated.
   Meanwhile `[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;
   opacity:0}` hides the input unconditionally. Hidden input, no drawn control: the
   radio was invisible here, and on the two other pages that render this template.

   (An earlier pass assumed Materialize drew it and merely repositioned the pseudo-
   elements. Reading their computed style told us nothing — getComputedStyle happily
   reports values for a pseudo-element that was never generated, which is why that
   pass measured green.)

   So the control is drawn here, in full: `content:""` and `position:absolute` are
   ours, not overrides. */
/* `:not(.f2f)` because the template uses .row three times: twice for a pair of option
   cards, and once — as `.row f2f` — for the expiry date field on its own. Without the
   exclusion the date field became a cell in a two-column grid. */
#CourseDetails #generateMagicLinkModal .row{margin:0 !important;}
#CourseDetails #generateMagicLinkModal .row:not(.f2f){display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-3);}
/* EQUAL HEIGHTS, ALL THE WAY DOWN. A grid item stretches by default, so `.col` is
   already as tall as the tallest in its row — but the <label> and the <span> inside
   it are not, and the <span> is what carries the border and the background. Without
   these two the taller card (a two-line subtitle) left the other visibly short.
   align-items is deliberately NOT touched: stretch is what does the work. */
#CourseDetails #generateMagicLinkModal .row:not(.f2f) > .col{width:100% !important;
  margin:0 !important;padding:0 !important;left:auto;right:auto;
  display:flex;}
#CourseDetails #generateMagicLinkModal .credit-card{height:100%;}
#CourseDetails #generateMagicLinkModal .credit-card-box{height:100%;}
/* THE BOX IS THE SPAN, NOT THE LABEL, AND THAT IS THE POINT. The obvious rule is
   `.credit-card:has(input:checked)` — which the theme itself uses — but Chrome does
   not restyle a card that already existed when its radio's checkedness flips: a
   freshly-created one renders selected correctly while the one on the page does not.
   Measured both, side by side, after a real click. Since the radio starts unchecked
   and the user clicks it, that is exactly the path that matters.

   Painting the box on `input:checked + .credit-card-box` instead is an ordinary
   adjacent-sibling selector with no `:has()` anywhere, so it invalidates the way
   every other form state on this page does. The label keeps only the click target. */
#CourseDetails #generateMagicLinkModal .credit-card{
  display:block;width:100%;margin:0;padding:0;border:0;background:none;cursor:pointer;}
/* padding-left = 16 padding + 20 control + 12 gap, so the text starts where the
   reference puts it and the control sits inside the border, not on it. */
#CourseDetails #generateMagicLinkModal .credit-card-box{
  display:block;position:relative;min-width:0;
  padding:var(--ml-option-pad) var(--ml-option-pad) var(--ml-option-pad)
          calc(var(--ml-option-pad) + var(--ml-option-control) + var(--ml-option-gap));
  border:1.5px solid var(--ml-option-border);border-radius:var(--radius-lg);
  background:var(--ml-option-bg);
  transition:border-color .15s ease,background .15s ease;}
#CourseDetails #generateMagicLinkModal .credit-card:hover .credit-card-box{
  background:var(--ml-option-bg-hover);}
#CourseDetails #generateMagicLinkModal [type="radio"]:checked + .credit-card-box{
  border-color:var(--ml-option-border-on);background:var(--ml-option-bg-on);}
#CourseDetails #generateMagicLinkModal .credit-card-box-title{
  margin:0;font-size:15.5px;line-height:1.35;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
#CourseDetails #generateMagicLinkModal .credit-card-box-subtitle{
  display:block;margin:2px 0 0;font-size:13.5px;line-height:1.4;color:#8A8A8A;}
/* THE NATIVE INPUT HAS TO BE HIDDEN HERE, because the theme deliberately un-hides it.
   Materialize hides radios with

       [type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0}

   and style.css:409 reverses exactly that — `{opacity:1;position:static;left:0}` — then
   :410 restyles the native input itself as the control (appearance:none, a 20px box,
   float:left, background-clip:content-box). That is the theme's whole radio design.

   Inside this card it cannot work: the input floats at the label's left edge while
   `.credit-card-box` is position:relative with its own background, and a positioned
   box paints above a float — so the theme's radio was rendered and then covered by the
   card. That is the "radio isn't coming" report; it was drawn, just buried.

   It is hidden rather than repositioned because the reference's control is a ring with
   a gapped dot, which the theme's filled box is not. Kept focusable (opacity, not
   display:none) so the keyboard still reaches it and the focus ring below shows. */
#CourseDetails #generateMagicLinkModal .credit-card input[type="radio"]{
  position:absolute;width:1px;height:1px;margin:0;padding:0;
  /* min-width too: style.css:410 sets `width:20px;min-width:20px`, and min-width beats
     width, so without this the hidden input still occupied a 20px box. */
  min-width:0;min-height:0;
  opacity:0;pointer-events:none;float:none;border:0;background:none;
  appearance:none;-webkit-appearance:none;}

/* The ring (::before) and the dot (::after), both generated here — see above: nothing
   in the cascade generates them, so `content` and `position` are ours, not overrides.
   The whole card is the click target because the <label> wraps the input. */
#CourseDetails #generateMagicLinkModal .credit-card [type="radio"] + .credit-card-box:before,
#CourseDetails #generateMagicLinkModal .credit-card [type="radio"] + .credit-card-box:after{
  content:"";position:absolute;display:block;margin:0;border-radius:50%;
  transition:border-color .15s ease,background .15s ease,transform .15s ease;}
/* Vertically centred on the title's first line: 16px padding + half of the 21px line
   box, less half the control. */
#CourseDetails #generateMagicLinkModal .credit-card [type="radio"] + .credit-card-box:before{
  top:calc(var(--ml-option-pad) + 1px);left:var(--ml-option-pad);
  width:var(--ml-option-control);height:var(--ml-option-control);
  border:2px solid var(--ml-option-dot);background:transparent;}
#CourseDetails #generateMagicLinkModal .credit-card [type="radio"] + .credit-card-box:after{
  top:calc(var(--ml-option-pad) + 6px);left:calc(var(--ml-option-pad) + 5px);
  width:10px;height:10px;background:var(--ml-option-border-on);transform:scale(0);}
#CourseDetails #generateMagicLinkModal .credit-card [type="radio"]:checked + .credit-card-box:before{
  border-color:var(--ml-option-border-on);}
#CourseDetails #generateMagicLinkModal .credit-card [type="radio"]:checked + .credit-card-box:after{
  transform:scale(1);}
/* Keyboard focus has to be visible somewhere: the input is opacity:0, so the ring
   shows it. */
#CourseDetails #generateMagicLinkModal .credit-card [type="radio"]:focus + .credit-card-box:before{
  box-shadow:0 0 0 4px rgba(25,42,193,.15);}

/* ---- The amber note (reference line 1380) ------------------------------------ */
/* #CourseDetails #generateMagicLinkModal .credit-warning{
  display:flex;align-items:flex-start;gap:11px;
  padding:14px var(--space-4);border-radius:var(--radius-lg);
  border:1px solid #FDE68A;background:#FFFBEB;
  font-size:var(--type-size-xs);line-height:1.55;color:#78350F;} */
#CourseDetails #generateMagicLinkModal .credit-warning i{
  flex-shrink:0;margin-top:1px;color:#B45309;
  font-family:"hgi-stroke-rounded" !important;font-style:normal;
  font-size:19px;line-height:1;transform:none;-webkit-font-smoothing:antialiased;}
#CourseDetails #generateMagicLinkModal .credit-warning i:before{content:"\3B03";}  /* AlertCircle */
#CourseDetails #generateMagicLinkModal .credit-warning b{font-weight:700;color:inherit;}

/* ---- Expiry date field (reference line 1412) --------------------------------- */
#CourseDetails #generateMagicLinkModal .row.f2f > .col{width:100% !important;
  margin:0 !important;padding:0 !important;left:auto;right:auto;}
#CourseDetails #generateMagicLinkModal .choosdate{display:flex;flex-direction:column;gap:7px;
  padding-top:2px;}
/* PICKADATE INSERTS ITS ROOT BEFORE THE INPUT, not after — measured: the DOM order in
   this container is label, .picker, input, icon, error. Laid out in flow (see the
   picker block below) that put the calendar ABOVE the field. `order` moves it without
   touching the JS that inserts it. */
#CourseDetails #generateMagicLinkModal .choosdate > .label-form{order:0;}
#CourseDetails #generateMagicLinkModal .choosdate > input.datepicker{order:1;}
#CourseDetails #generateMagicLinkModal .choosdate > .error{order:2;}
#CourseDetails #generateMagicLinkModal .choosdate > .picker{order:3;}
#CourseDetails #generateMagicLinkModal .label-form{
  font-size:13px;font-weight:var(--weight-semibold);color:var(--text-secondary,#3F3F46);}
/* Materialize styles text inputs globally (border-bottom, 3rem height, a focus
   box-shadow). Without the `.vth` reset in play every one of those is answered here. */
#CourseDetails #generateMagicLinkModal input.datepicker{
  box-sizing:border-box;height:48px;max-width:260px;margin:0;padding:0 14px;
  box-shadow:none !important;
  border:1px solid #D1D1D1 !important;border-radius:var(--radius-lg);
  background:var(--surface-card);
  font-family:var(--font-sans);font-size:15px;color:var(--text-primary);}
#CourseDetails #generateMagicLinkModal input.datepicker:focus{
  border-color:var(--vth-blue) !important;box-shadow:none !important;}
#CourseDetails #generateMagicLinkModal .calendar-icon-right{display:none;}
#CourseDetails #generateMagicLinkModal .error{display:block;font-size:12.5px;color:var(--status-error,#D92929);}

/* ---- The date picker ---------------------------------------------------------
   THE PICKER IS STILL PICKADATE, RESTYLED — not swapped, and not `<input type=date>`.
   The component's JS already configures it (min = tomorrow, format yyyy-mm-dd,
   month/year selects) in course-magic-link-modal.js::showDatePicker, and that JS is
   shared with two other pages. Replacing the library would mean changing it; the
   browser's native control would mean giving up the min-date rule and taking whatever
   each OS draws. So the markup and behaviour stay and only the paint changes.

   WHAT MATERIALIZE DRAWS BY DEFAULT, and why none of it survives below:
     .picker__holder{position:fixed; background:rgba(0,0,0,.32)}   a full-screen scrim
     .picker__frame{position:absolute; opacity:0 → 1; top:10%}     a centred dialog
     .picker__date-display{background:#26a69a; font-size:2.8rem}   a big teal banner
     .picker__day--selected{background:#26a69a}                    teal, everywhere

   The scrim is the part that had to go regardless of taste: `position:fixed` resolves
   against the nearest transformed ancestor, and Materialize leaves `transform:scaleX(1)`
   on an open `.modal` — so the "full-screen" overlay was being trapped inside this
   modal's box anyway.

   IT FLOATS: absolutely positioned against the field, so opening it does not push the
   rest of the modal down. It opens UPWARDS — `bottom:100%` — and that direction is not
   arbitrary. The expiry field is the last control in the body, with only the sticky
   footer beneath it, while the whole modal body sits above; the modal is the scroll
   container (`overflow-y:auto`), so anything hanging below the field would be clipped
   by it and could not be scrolled into view, because an absolutely positioned box adds
   nothing to its scroll height. Opening up puts it over content that is already there.
   Measured at 1440/900/700/600 — see the handover. */
#CourseDetails #generateMagicLinkModal .picker{
  position:absolute;z-index:30;font-size:inherit;color:var(--text-primary);
  /* the input is 260px wide; the calendar keeps its own width and hangs off the left */
  left:0;bottom:calc(100% - 20px);width:auto;}
#CourseDetails #generateMagicLinkModal .choosdate{position:relative;}
#CourseDetails #generateMagicLinkModal .picker__holder{
  position:static;display:none;width:auto;overflow:visible;
  background:none !important;transition:none;}
#CourseDetails #generateMagicLinkModal .picker--opened .picker__holder{
  display:block;top:auto;background:none !important;}
#CourseDetails #generateMagicLinkModal .picker__frame{
  position:static;opacity:1;filter:none;
  margin:0;bottom:auto;top:auto;
  width:320px;min-width:0;max-width:none;max-height:none;
  transform:none;transition:none;}
#CourseDetails #generateMagicLinkModal .picker__wrap{display:block;height:auto;width:auto;}
#CourseDetails #generateMagicLinkModal .picker__box{
  display:block;padding:var(--space-4);
  border:1px solid var(--border-light);border-radius:var(--radius-lg);
  background:var(--surface-card);box-shadow:var(--shadow-md);overflow:hidden;}
/* The teal banner has no counterpart in the reference — the field already shows the
   date, so the calendar does not repeat it. */
#CourseDetails #generateMagicLinkModal .picker__date-display{display:none;}
#CourseDetails #generateMagicLinkModal .picker__calendar-container{padding:0;}

/* Header: month and year selects between two arrows */
#CourseDetails #generateMagicLinkModal .picker__header{
  display:flex;align-items:center;gap:var(--space-2);
  margin:0 0 var(--space-3);text-align:left;}
#CourseDetails #generateMagicLinkModal .picker__month,
#CourseDetails #generateMagicLinkModal .picker__year{
  display:inline-block;margin:0;font-size:var(--type-size-xs);
  font-weight:var(--weight-semibold);color:var(--text-primary);}
#CourseDetails #generateMagicLinkModal .picker__select--month,
#CourseDetails #generateMagicLinkModal .picker__select--year{
  display:inline-block;height:32px;margin:0 var(--space-1) 0 0;padding:0 var(--space-2);
  width:auto !important;
  border:1px solid var(--border-light);border-radius:var(--radius-md);
  background:var(--surface-card);
  font-family:var(--font-sans);font-size:13px;font-weight:var(--weight-medium);
  color:var(--text-primary);cursor:pointer;}
#CourseDetails #generateMagicLinkModal .picker__select--month:focus,
#CourseDetails #generateMagicLinkModal .picker__select--year:focus{
  border-color:var(--vth-blue);outline:none;}
/* The arrows are CSS triangles on ::before. Squared off into 30px round buttons with
   a chevron, and pulled out of the absolute positioning the default gives them. */
#CourseDetails #generateMagicLinkModal .picker__nav--prev,
#CourseDetails #generateMagicLinkModal .picker__nav--next{
  position:static;width:30px;height:30px;padding:0;flex-shrink:0;
  display:inline-flex;align-items:center;justify-content:center;
  border-radius:50%;background:var(--surface-muted);cursor:pointer;
  transition:background .15s ease;}
#CourseDetails #generateMagicLinkModal .picker__nav--next{margin-left:auto;}
#CourseDetails #generateMagicLinkModal .picker__nav--prev:hover,
#CourseDetails #generateMagicLinkModal .picker__nav--next:hover{
  background:var(--surface-blue-soft);color:var(--vth-blue);}
#CourseDetails #generateMagicLinkModal .picker__nav--prev:before,
#CourseDetails #generateMagicLinkModal .picker__nav--next:before{
  content:"";display:block;margin:0;
  width:7px;height:7px;border:0;border-top:2px solid var(--text-primary);
  border-right:2px solid var(--text-primary);}
#CourseDetails #generateMagicLinkModal .picker__nav--prev:before{
  transform:rotate(-135deg);margin-left:3px;}
#CourseDetails #generateMagicLinkModal .picker__nav--next:before{
  transform:rotate(45deg);margin-right:3px;}
#CourseDetails #generateMagicLinkModal .picker__nav--disabled,
#CourseDetails #generateMagicLinkModal .picker__nav--disabled:hover{
  opacity:.35;cursor:default;background:var(--surface-muted);}

/* Grid.
   THE CELLS ARE SQUARE ON PURPOSE. `width:100%` with seven columns gave cells about
   41px wide and 36px tall, and `border-radius:50%` on a 41x36 box draws an OVAL, not a
   circle — which is what the highlighted dates looked wrong as. Fixing the cell to
   38x38 and centring the table makes every circle a circle, at any frame width. */
#CourseDetails #generateMagicLinkModal .picker__table{
  margin:0 auto;font-size:13px;table-layout:fixed;width:auto;}
#CourseDetails #generateMagicLinkModal .picker__table th,
#CourseDetails #generateMagicLinkModal .picker__table td{width:38px;}
#CourseDetails #generateMagicLinkModal .picker__weekday{
  width:38px;padding:0 0 var(--space-2);font-size:11px;font-weight:var(--weight-semibold);
  color:var(--text-subtle);text-transform:uppercase;letter-spacing:.02em;}
#CourseDetails #generateMagicLinkModal .picker__day{
  width:38px;height:38px;padding:0;line-height:38px;border:0;border-radius:50%;
  color:var(--text-primary);font-weight:var(--weight-regular);
  transition:background .12s ease,color .12s ease;}
#CourseDetails #generateMagicLinkModal .picker__day--infocus{
  padding:0;border:0;color:var(--text-primary);letter-spacing:0;}
#CourseDetails #generateMagicLinkModal .picker__day--outfocus{
  display:table-cell;color:transparent;pointer-events:none;}
#CourseDetails #generateMagicLinkModal .picker__day--infocus:hover{
  background:var(--surface-blue-soft);color:var(--vth-blue);font-weight:var(--weight-medium);}
/* Today is a ring, the selection is a filled disc — so the two never read as the same
   thing, which is exactly what the default's two teals did. */
/* Today is a ring, the selection a filled disc. The ring is an inset shadow rather
   than a border so it cannot change the cell's size and re-oval it. */
#CourseDetails #generateMagicLinkModal .picker__day.picker__day--today{
  color:var(--vth-blue);font-weight:var(--weight-semibold);
  box-shadow:inset 0 0 0 1.5px var(--vth-blue);}
#CourseDetails #generateMagicLinkModal .picker__day--selected,
#CourseDetails #generateMagicLinkModal .picker__day--selected:hover,
#CourseDetails #generateMagicLinkModal .picker--focused .picker__day--selected,
#CourseDetails #generateMagicLinkModal .picker__day.picker__day--today.picker__day--selected{
  transform:none;border-radius:50%;box-shadow:none;
  background:var(--vth-blue);color:var(--text-on-blue);
  font-weight:var(--weight-semibold);}
#CourseDetails #generateMagicLinkModal .picker__day--disabled,
#CourseDetails #generateMagicLinkModal .picker__day--disabled:hover,
#CourseDetails #generateMagicLinkModal .picker--focused .picker__day--disabled{
  background:none;border:0;color:var(--text-muted,#B4B4B4);cursor:default;}
#CourseDetails #generateMagicLinkModal .picker__day--highlighted{border:0;}

/* Footer: Clear on the left, Today and Ok on the right, all as text buttons */
#CourseDetails #generateMagicLinkModal .picker__footer{
  display:flex;align-items:center;gap:var(--space-2);
  margin-top:var(--space-3);padding:var(--space-3) 0 0;
  border-top:1px solid var(--border-light);text-align:left;}
/* MATERIALIZE RENAMES THESE. Base pickadate emits .picker__button--today/--clear/
   --close; the Materialize build emits .picker__today/.picker__clear/.picker__close,
   and a DOM dump of a live picker confirms only the short ones are present. Both sets
   are listed so this survives either build. */
#CourseDetails #generateMagicLinkModal .picker__footer .picker__today,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__clear,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__close,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--today,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--clear,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--close{
  width:auto;float:none;display:inline-flex;align-items:center;
  height:34px;padding:0 var(--space-3);margin:0;
  border:0;border-radius:var(--radius-md);background:none;
  font-family:var(--font-sans);font-size:13px;font-weight:var(--weight-semibold);
  color:var(--vth-blue);text-transform:none;cursor:pointer;}
#CourseDetails #generateMagicLinkModal .picker__footer .picker__clear,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--clear{
  color:var(--status-error,#D92929);margin-right:auto;float:none;}
#CourseDetails #generateMagicLinkModal .picker__footer .picker__today:hover,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__close:hover,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--today:hover,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--close:hover{
  background:var(--surface-blue-soft);}
#CourseDetails #generateMagicLinkModal .picker__footer .picker__clear:hover,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--clear:hover{
  background:var(--surface-error-soft);}
/* Materialize also focus-paints these teal. */
#CourseDetails #generateMagicLinkModal .picker__footer button:focus,
#CourseDetails #generateMagicLinkModal button.picker__today:focus,
#CourseDetails #generateMagicLinkModal button.picker__clear:focus,
#CourseDetails #generateMagicLinkModal button.picker__close:focus{
  background-color:var(--surface-muted);outline:none;}
#CourseDetails #generateMagicLinkModal .picker__footer button:focus{outline:none;background:var(--surface-muted);}
/* The default puts a triangle before Today and a bar before Clear. */
#CourseDetails #generateMagicLinkModal .picker__footer .picker__today:before,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__clear:before,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__close:before,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--today:before,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--clear:before,
#CourseDetails #generateMagicLinkModal .picker__footer .picker__button--close:before{
  content:none;}

/* ---- Footer (reference line 1420) -------------------------------------------- */
#CourseDetails #generateMagicLinkModal .modal-footer{
  position:sticky;bottom:0;z-index:1;
  margin:0 !important;padding:18px var(--space-6) !important;height:auto !important;
  display:flex;justify-content:flex-end;
  background:var(--surface-card) !important;
  border-top:1px solid var(--border-light);border-radius:0 0 20px 20px;}
#CourseDetails #generateMagicLinkModal .modal-footer-right{
  display:flex;gap:var(--space-3);justify-content:flex-end;flex-wrap:wrap;
  flex:0 0 auto !important;max-width:none !important;}
/* Both buttons: 48px, 12px radius, 15px/600. style.css sets `.btn{border-radius:30px
   !important}` and its own size/weight, and Materialize sets the height and
   text-transform — which is what these flags answer. */
#CourseDetails #generateMagicLinkModal .modal-footer .btn{
  display:inline-flex;align-items:center;justify-content:center;
  height:48px;line-height:48px;margin:0 !important;
  border-radius:var(--radius-lg) !important;
  font-family:var(--font-sans);font-size:15px !important;
  font-weight:var(--weight-semibold) !important;
  text-transform:none;letter-spacing:0;box-shadow:none !important;}
#CourseDetails #generateMagicLinkModal .modal-footer .btn-secondry-outline{
  padding:0 var(--space-6);
  border:1.5px solid var(--vth-blue);background:var(--surface-card);color:var(--vth-blue);}
#CourseDetails #generateMagicLinkModal .modal-footer .btn-secondry-outline:hover{
  background:#F5F7FF;color:var(--vth-blue);}
#CourseDetails #generateMagicLinkModal .modal-footer .secondry-btn{
  padding:0 30px;border:0;
  background:var(--vth-blue) !important;color:var(--text-on-blue) !important;}
#CourseDetails #generateMagicLinkModal .modal-footer .secondry-btn:hover{filter:brightness(1.06);}
#CourseDetails #generateMagicLinkModal .modal-footer .secondry-btn[disabled]{
  background:#8E99E4 !important;cursor:default;}

/* ---- One column below 620px, as the reference does (`ml.cols`, line 2656) ----- */
@media (max-width:619px){
  #CourseDetails #generateMagicLinkModal .row:not(.f2f){grid-template-columns:minmax(0,1fr);}
  #CourseDetails #generateMagicLinkModal .modal-header{padding:var(--space-5) var(--space-4) var(--space-4);}
  #CourseDetails #generateMagicLinkModal .modal-content{padding:var(--space-5) var(--space-4) !important;}
  #CourseDetails #generateMagicLinkModal .modal-footer{padding:var(--space-4) !important;}
  #CourseDetails #generateMagicLinkModal .modal-footer .btn{flex:1 1 auto;}
}


/* ==========================================================================
   LEARNERS DETAILS TAB — reference Course Detail.dc.html:370-500
   --------------------------------------------------------------------------
   The markup is course_allocate_learner-reskin.blade.php, injected by
   getSectionHTML('Admin'). It carries its own `.vth` wrapper because it lands
   outside the page's, so everything here is `.vth`-scoped like the rest of the file.

   THE TABLE REUSES .vth-table--learners THE SAME WAY CURRICULUM REUSES ITS VARIANT,
   and for the same reason it is written `.vth .vth-table.vth-table--learners`: at
   (0,2,1) it would tie with `.vth .vth-table td` further down this file and lose on
   source order. That tie already cost the curriculum table its cell padding once.
   ========================================================================== */

/* ---- Accordion header: title, count, chevron (reference line 377) ------------- */
.vth .vth-acc__title{flex:1;}
.vth .vth-acc__meta{font-size:13px;font-weight:var(--weight-regular);color:var(--text-subtle);}

/* ---- Bulk bar — only rendered while a selection exists (reference line 384) ---- */
.vth .vth-bulkbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;
  margin-bottom:14px;padding:10px 12px 10px var(--space-4);
  background:var(--surface-blue-soft);border:1px solid #CFD8FA;
  border-radius:var(--radius-lg); width: 100%;}
.vth .vth-bulkbar__count{font-size:var(--type-size-xs);font-weight:var(--weight-semibold);
  color:var(--vth-blue);}
.vth .vth-bulkbar__sep{width:1px;height:22px;background:#CFD8FA;flex-shrink:0;}
.vth .vth-bulkbar__actions{display:flex;gap:var(--space-2);flex-wrap:wrap;flex:1;}
.vth .vth-bulkbtn{display:inline-flex;align-items:center;gap:var(--space-2);
  height:36px;padding:0 14px;margin:0;
  border:1px solid #CFD8FA;border-radius:9px;background:var(--surface-card);
  color:var(--vth-blue);
  font-family:var(--font-sans);font-size:13.5px;font-weight:var(--weight-semibold);
  cursor:pointer;transition:filter .12s ease;}
.vth .vth-bulkbtn:hover{filter:brightness(.98);}
/* Reset is destructive and the reference colours it so — red ink on a red hairline,
   not a red fill, because it still sits beside a non-destructive twin. */
.vth .vth-bulkbtn--danger{border-color:#F7C6C6;color:var(--status-error,#D92929);}
.vth .vth-bulkbar__clear{border:0;background:none;padding:0;margin:0;cursor:pointer;
  font-family:var(--font-sans);font-size:13.5px;font-weight:var(--weight-semibold);
  color:var(--text-subtle);}
.vth .vth-bulkbar__clear:hover{color:var(--text-primary);}

/* ---- Toolbar: search, Select Learner, overflow (reference line 399) ----------- */
.vth .vth-ltools{display:flex; width: 100%; gap:10px;flex-wrap:wrap;align-items:center;
  margin-bottom:14px;}
.vth .vth-ltools__search{display:flex;align-items:center;gap:var(--space-2);
  flex:1;min-width:220px;height:44px;padding:0 14px;
  border:1px solid var(--border-default);border-radius:var(--radius-lg);
  background:var(--surface-card);}
.vth .vth-ltools__search .vth-icon{opacity:.5;flex-shrink:0;}
.vth .vth-ltools__search input{flex:1;min-width:0;height:auto;margin:0;padding:0;
  border:0 !important;outline:none;box-shadow:none !important;background:transparent;
  font-family:var(--font-sans);font-size:var(--type-size-xs);color:var(--text-primary);}
/* The Select Learner control is the legacy .multiple-check-div, moved here from the
   table header. Only its box is restyled; its markup and its delegated open/close
   handler in custom_scripts.js are untouched. */
.vth .vth-ltools__select{margin:0;min-width:170px;}
.vth .vth-ltools__select .multiple-check-div{position:relative;}
.vth .vth-ltools__select .multiple-check-div > h2{display:flex;align-items:center;
  gap:var(--space-2);height:44px;margin:0;padding:0 12px;
  border:1px solid var(--border-default);border-radius:var(--radius-lg);
  background:var(--surface-card);cursor:pointer;}
.vth .vth-ltools__select .multiple-check-div > h2 > a{flex:1;
  font-family:var(--font-sans);font-size:var(--type-size-xs);
  font-weight:var(--weight-regular);color:var(--text-secondary);}
.vth .vth-ltools__select .multiple-check-div > h2 .caret{font-size:10px;color:var(--text-subtle);}
.vth .vth-ltools__more{position:relative;}
.vth .vth-ltools__morebtn{display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;margin:0;padding:0;
  border:1px solid var(--border-default);border-radius:var(--radius-lg);
  background:var(--surface-card);color:var(--text-secondary);cursor:pointer;
  transition:background .12s ease;}
.vth .vth-ltools__morebtn:hover{background:var(--surface-muted);}

/* ---- The course-wide menu (reference line 415) -------------------------------- */
.vth .vth-lmenu{position:absolute;right:0;top:50px;z-index:30;width:250px;padding:6px;
  background:var(--surface-card);border:1px solid #EDEDF0;border-radius:var(--radius-lg);
  box-shadow:0 10px 15px -3px rgba(0,0,0,.12);}
.vth .vth-lmenu__label{margin:0;padding:8px 10px 6px;
  font-size:11px;font-weight:700;letter-spacing:.04em;color:#8A8A8A;}
.vth .vth-lmenu__item{display:flex;align-items:center;gap:10px;width:100%;
  margin:0;padding:10px;border:0;border-radius:9px;background:none;
  font-family:var(--font-sans);font-size:var(--type-size-xs);
  font-weight:var(--weight-medium);color:var(--text-secondary);
  text-align:left;cursor:pointer;}
.vth .vth-lmenu__item:hover{background:var(--surface-muted);}
.vth .vth-lmenu__item .vth-icon{flex-shrink:0;}
.vth .vth-lmenu__item--danger{color:var(--status-error,#D92929);}

/* ---- The table (reference line 429) ------------------------------------------- */
.vth .vth-table.vth-table--learners{min-width:680px;font-size:var(--type-size-xs);}
.vth .vth-table.vth-table--learners thead th{background:var(--surface-muted); text-align: center;
  border-bottom:0;color:var(--text-primary);padding:14px 12px;
  font-size:var(--type-size-xs);text-transform:none;letter-spacing:0;
  font-weight:var(--weight-semibold);}
.vth .vth-table.vth-table--learners td{padding:14px 12px;
  border-bottom:1px solid var(--divider-grey);color:var(--text-subtle);}
.vth .vth-table.vth-table--learners .vth-col-check{width:48px;}
.vth .vth-table.vth-table--learners .vth-col-action{text-align:right;}
/* The checkboxes are the legacy ones the Angular bindings drive; only their size and
   tick colour are set, so ng-model/ng-checked and checkUncehckAll() are untouched. */
.vth .vth-table.vth-table--learners input[type="checkbox"]{
  position:static;opacity:1;width:18px;height:18px;min-width:18px;min-height:18px;
  margin:0;padding:0;float:none; border: 1px solid;
  accent-color:var(--vth-blue);cursor:pointer;}
/* Name cell: avatar and name on one line (reference line 452). */
.vth .vth-learner{display:inline-flex;align-items:center;gap:10px;min-width:0;}
.vth .vth-learner__pic{display:inline-flex;flex-shrink:0;width:36px;height:36px;}
.vth .vth-learner__pic img{width:36px;height:36px;border-radius:50%;object-fit:cover;}
.vth .vth-learner__name{font-weight:var(--weight-medium);color:var(--text-primary);}

/* ---- Progress cell (reference line 467) --------------------------------------- */
.vth .vth-prog{display:inline-flex;align-items:center;gap:var(--space-2); flex-direction: column;}
.vth .vth-prog__track{display:inline-block;width:74px;height:6px;flex-shrink:0;
  border-radius:var(--radius-pill);background:#EDEDF2;overflow:hidden;}
.vth .vth-prog__bar{display:block;height:100%;background:var(--vth-blue);
  transition:width .2s ease;}
/* Complete is green and not-started is grey, so the bar says the same thing as the
   label beside it rather than repeating the in-progress blue at 0% and 100%. */
.vth .vth-prog__bar.is-done{background:var(--status-success,#187D3F);}
.vth .vth-prog__bar.is-none{background:var(--border-default);}
.vth .vth-prog__label{font-size:12.5px;color:var(--text-subtle);white-space:nowrap;}

/* ---- Row actions -------------------------------------------------------------- */
.vth .vth-table--learners .allocation_reset_icons{display:flex;gap:6px;
  justify-content:flex-end;align-items:center;margin:0;padding:0;list-style:none;}
.vth .vth-table--learners .allocation_reset_icons > li{margin:0;}
.vth .vth-table--learners .allocation_reset_icons a,
.vth .vth-table--learners .allocation_reset_icons p{display:inline-flex;
  align-items:center;justify-content:center;width:32px;height:32px;margin:0;
  border-radius:var(--radius-md);color:var(--text-secondary);cursor:pointer;
  transition:background .12s ease;}
.vth .vth-table--learners .allocation_reset_icons a:hover,
.vth .vth-table--learners .allocation_reset_icons p:hover{background:#F4F4F5;}
.vth .vth-table--learners .allocation_reset_icons i{font-size:18px;line-height:1;}
/* Remove is the destructive one, and the reference is the only thing that says so. */
.vth .vth-table--learners .allocation_reset_icons li:nth-child(4) a{color:var(--status-error,#D92929);}

/* ---- Footer: summary beside the pager (reference line 495) --------------------- */
.vth .vth-lfoot{display:flex;align-items:center;justify-content:space-between;
  gap:var(--space-3);flex-wrap:wrap;padding-top:4px; width: 100%;}
.vth .vth-lfoot__summary{font-size:12.5px;color:var(--text-subtle);}
.vth .vth-lfoot .pagination-box{margin:0;}

/* ---- Empty states (reference lines 483 and 490) -------------------------------- */
.vth .vth-lempty{padding:44px var(--space-5);text-align:center;
  border:1px dashed #E0E0E6;border-radius:14px; width: 100%;}
.vth .table-sc{width: 100%;}
.vth .vth-lempty__title{margin:0 0 6px;font-size:15px;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-lempty__body{margin:0 0 var(--space-4);font-size:13.5px;color:#6D6D6D;}
.vth .vth-lempty__btn{height:40px;padding:0 18px;margin:0;
  border:1px solid var(--border-default);border-radius:var(--radius-md);
  background:var(--surface-card);
  font-family:var(--font-sans);font-size:var(--type-size-xs);
  font-weight:var(--weight-semibold);color:var(--text-primary);cursor:pointer;}
.vth .vth-lempty__btn:hover{background:var(--surface-muted);}

@media (max-width:640px){
  .vth .vth-ltools__search{min-width:0;flex:1 1 100%;}
  .vth .vth-ltools__select{flex:1 1 auto;}
}


/* ==========================================================================
   LEARNERS DETAILS — round two: the reported defects
   ========================================================================== */

/* ---- The $mdDialog confirmations must not take the left nav with them --------
   `sendReminder()` and the reset confirmations are Angular Material dialogs, and
   $mdUtil.disableScrollAround locks the page by setting, ON <body>:

       position:fixed; width:100%; overflow-y:scroll; top:-<scrollY>px

   `position:fixed` on <body> makes it the containing block for the shell's own
   fixed left rail, and the negative `top` shifts the whole thing off screen — which
   is the nav "disappearing from the visible DOM" when Send Reminder to All is
   clicked. Neutralised here, so the dialog opens and the chrome stays put.

   SCOPED BY FILE, NOT BY SELECTOR: course-detail.css is linked only by the two
   course-detail pages, so no other screen's dialogs change behaviour. The trade is
   that the page behind the dialog can still scroll; a shifted navigation is worse. */
body.md-dialog-is-showing{position:static !important;top:auto !important;
  width:auto !important;height:auto !important;overflow-y:visible !important;}

/* ---- Select Learner dropdown: it was in normal flow -------------------------
   `.multiple-check-box-select` has `display:none` and nothing else in style.css —
   no position, no z-index, no background. The other themes' stylesheets
   (sub_master, f2fpublic, childrens_home, leaving_care) all give it
   `position:absolute;z-index:99;background:#fff`, and this theme simply never did,
   so on the platform page the panel opened INSIDE the flow and the table drew over
   it. Measured: position `static`, z-index `auto`, background transparent. */
.vth .vth-ltools__select .multiple-check-box-select{
  /* Was a flat top:10px, which assumed a short trigger and overlapped it once the
     toolbar pickers grew to 42px - the panel covered its own label and the button
     beside it. Anchoring to the trigger's full height works whatever that height is,
     so every picker on the page drops below its own control. */
  position:absolute;top:6px;;left:0;z-index:40;
  width:100%;min-width:240px;
  /* NOT a scroller — the list inside it is. With max-height/overflow here too, the
     panel's own footer was pushed past its height and clipped: Clear and Done were
     cut in half. One scroller, on the list, keeps the footer pinned under it. */
  background:var(--surface-card);
  border:1px solid var(--border-light);border-radius:var(--radius-lg);
  box-shadow:0 10px 15px -3px rgba(0,0,0,.12);padding:6px;}
.vth .vth-ltools__select .multiple-check-box-select ul{margin:0;padding:0;list-style:none;}
.vth .vth-ltools__select .multiple-check-box-select li{margin:0;}
/* The search row sticks so it stays reachable while the list scrolls. */
.vth .vth-ltools__select .multiple-check-box-select li.multiple-search{
  position:sticky;top:0;z-index:1;padding:4px 4px 8px;background:var(--surface-card);}
.vth .vth-ltools__select .multiple-check-box-select li.multiple-search input{
  width:100%;height:38px;margin:0;padding:0 12px;
  border:1px solid var(--border-default) !important;border-radius:var(--radius-md);
  background:var(--surface-card);box-shadow:none !important;
  font-family:var(--font-sans);font-size:var(--type-size-xs);}
/* Each option is a row with its checkbox; the list markup comes from
   getLearnerListDetail() and is not changed here. */
/* flex-start, so the checkbox sits level with the NAME rather than with the gap
   between the name and the username beneath it. */
.vth .vth-ltools__select .multiple-check-box-select .bundle-learner-selection-list{
  display:flex;align-items:flex-start;gap:12px;padding:10px;border-radius:8px;
  font-size:var(--type-size-xs);color:var(--text-secondary);cursor:pointer;}
.vth .vth-ltools__select .multiple-check-box-select .bundle-learner-selection-list:hover{
  background:var(--surface-muted);}
.vth .vth-ltools__select .multiple-check-box-select label{margin:0;cursor:pointer;}

/* ---- Row action icons (reference line 481) ------------------------------------
   Hugeicons now, sized and coloured like the reference: 18px glyphs in 32px hit
   areas, neutral except the destructive one. */
.vth .vth-table--learners .allocation_reset_icons i{font-size:18px;line-height:1;
  color:inherit;}
.vth .vth-table--learners .allocation_reset_icons li.is-danger a{color:var(--status-error,#D92929);}
.vth .vth-table--learners .allocation_reset_icons li.is-danger a:hover{background:#FDECEC;}
/* The chevron: ONE glyph, rotated. The toggle script swaps lms-arrow-right for
   lms-arrow-down on this element, so the rotation keys off those class names rather
   than off a second icon — the script keeps working untouched. */
.vth .vth-rowchevron{display:inline-block;transition:transform .15s ease;}
.vth .vth-rowchevron.lms-arrow-down{transform:rotate(90deg);}
/* The legacy font would otherwise paint its own glyph underneath the Hugeicons one. */
.vth .vth-rowchevron.lms-arrow-right:before,
.vth .vth-rowchevron.lms-arrow-down:before{content:"\43CE";font-family:"hgi-stroke-rounded";}

/* ---- Pagination (reference line 497) ------------------------------------------
   The active page is a filled blue square with white text; the rest are plain. The
   legacy pager paints a near-white active chip with dark text at a 2px radius. */
.vth .vth-lfoot .pagination{display:flex;align-items:center;gap:6px;margin:0;}
.vth .vth-lfoot .pagination li{display:inline-flex;height:40px;min-width:40px;margin:0;}
.vth .vth-lfoot .pagination li a{display:inline-flex;align-items:center;justify-content:center;
  width:100%;height:40px;min-width:40px;padding:0 10px;margin:0;
  border:1px solid var(--border-light);border-radius:var(--radius-md);
  background:var(--surface-card);
  font-family:var(--font-sans);font-size:var(--type-size-xs);
  font-weight:var(--weight-medium);color:var(--text-secondary);line-height:1;}
.vth .vth-lfoot .pagination li a:hover{background:var(--surface-muted);}
.vth .vth-lfoot .pagination li.active a,
.vth .vth-lfoot .pagination li.active span{background:var(--vth-blue);border-color:var(--vth-blue);
  color:var(--text-on-blue);font-weight:var(--weight-semibold);}
.vth .vth-lfoot .pagination li.disabled a,
.vth .vth-lfoot .pagination li.disabled span{opacity:.4;cursor:default;}

/* ==========================================================================
   ACTIVITY (reference Course Detail.dc.html:516-548)
   ========================================================================== */
.vth .vth-table.vth-table--activity{min-width:760px;font-size:var(--type-size-xs);}
.vth .vth-table.vth-table--activity thead th{background:var(--surface-muted);
  border-bottom:0;color:var(--text-primary);padding:12px;text-align:left;
  font-size:var(--type-size-xs);text-transform:none;letter-spacing:0;
  font-weight:var(--weight-semibold);}
.vth .vth-table.vth-table--activity thead th:first-child{width:56px;}
.vth .vth-table.vth-table--activity thead th:last-child{white-space:nowrap;}
.vth .vth-table.vth-table--activity td{padding:14px 12px;text-align:left;
  vertical-align:top;border-bottom:1px solid var(--divider-grey);}
.vth .vth-table.vth-table--activity tr:last-child td{border-bottom:1px solid var(--divider-grey);}
.vth .vth-table.vth-table--activity .vth-col-n{color:var(--text-muted,#8A8A8A);width:56px;}
.vth .vth-table.vth-table--activity .vth-col-when{white-space:nowrap;}
/* The tag. Tones are the reference's badge palette, and the tokens already exist. */
.vth .vth-atag{display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  font-size: 12.5px;
  font-weight: var(--weight-medium);
  line-height: 16px;}
.vth .vth-atag--yellow{background:var(--badge-yellow-bg,#FFF2D1);color:var(--badge-yellow-ink,#7A5200);}
.vth .vth-atag--green{background:var(--surface-green-soft,#DDFBE7);color:#16512D;}
.vth .vth-atag--blue{background:var(--surface-blue-soft,#DAE9FF);color:var(--vth-blue);}
.vth .vth-atag--red{background:var(--surface-error-soft,#FEF2F2);color:var(--status-error,#D92929);}
.vth .vth-atag--grey{background:var(--badge-grey-bg,#F6F6F6);color:var(--badge-grey-ink,#2B2B2B);}
/* The event: avatar beside the sentence. */
.vth .vth-aevent{display:inline-flex;align-items:flex-start;gap:11px;min-width:0;}
.vth .vth-aevent__pic{flex-shrink:0;display:inline-flex;width:36px;height:36px;}
.vth .vth-aevent__pic img{width:36px;height:36px;border-radius:50%;object-fit:cover;}
.vth .vth-aevent__text{min-width:0;font-size:var(--type-size-xs);line-height:1.45;
  color:var(--text-secondary);}
/* The message is authored HTML and often bolds the learner's name — the reference
   gives that name the primary ink and the surrounding words the secondary. */
.vth .vth-aevent__text b,.vth .vth-aevent__text strong{
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-aevent__text p{margin:0;}
.vth .vth-awhen{display:flex;flex-direction:column;gap:2px;}
.vth .vth-awhen__d{font-size:var(--type-size-xs);color:var(--text-secondary);}
.vth .vth-awhen__t{font-size:12.5px;color:var(--text-muted,#8A8A8A);}

/* ==========================================================================
   STATS (reference Course Detail.dc.html:556-600)
   ========================================================================== */
.vth #stats{display:flex;flex-direction:column;gap:18px;}
/* KPI strip: one tinted panel, four centred cells with hairline dividers. */
.vth .vth-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  background:#F6F7FB;border-radius:var(--radius-xl);padding:22px 8px;}
.vth .vth-kpi{display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:var(--space-2);padding:0 var(--space-5);
  border-left:1px solid var(--border-light);}
.vth .vth-kpi:first-child{border-left-color:transparent;}
.vth .vth-kpi__label{font-size:13.5px;line-height:1.45;color:var(--text-subtle);}
.vth .vth-kpi__val{display:flex;align-items:baseline;gap:2px;}
.vth .vth-kpi__n{font-size:30px;line-height:1.1;font-weight:700;color:var(--text-primary);}
.vth .vth-kpi__suffix{font-size:15px;font-weight:var(--weight-medium);color:var(--text-muted,#8A8A8A);}
/* Donuts: three bordered cards. The ring itself is the theme's existing
   .progress-learner conic gradient — only its accent and the card around it are set. */
.vth .vth-donuts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4);}
.vth .vth-donut{display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:14px;padding:var(--space-5);
  border:1px solid var(--border-light);border-radius:var(--radius-xl);}
.vth .vth-donut__label{font-size:13.5px;line-height:1.45;
  font-weight:var(--weight-semibold);color:var(--text-secondary);}
/* THE DONUT IS BUILT HERE NOW, NOT INHERITED FROM `.state-graph`.
   That is what broke it. The base `.progress-learner` (style.css:668) is a 38px box
   whose ::before is a 26px SOLID disc — a dot, not a ring. Everything that made it a
   donut lived on `.state-graph .progress-learner` (:789-790): the 110px size, the
   22px band, the grey track as an inset box-shadow, `border:none`, the centred
   figure, and — the important one — the `mask:radial-gradient(...)` on ::before that
   punches the hole. Dropping the `.state-graph` wrapper dropped all of it, and the
   donuts fell back to little green dots.

   (My earlier probe measured the donut's BOX at 150px and passed. The box was never
   the problem; the ring inside it was. A size check is not an appearance check.)

   Rebuilt standalone so it cannot break again by losing a wrapper, and with the
   reference's proportions: 150px across an 18px band, the remainder in grey rather
   than transparent, the value centred at 30px/700. */
/* THE TONE IS DECLARED ON THE CARD AND NAMED `--tone`, NOT `--c`, and both halves of
   that matter.

   On the CARD, because the legend lives in a sibling subtree of the ring: a property
   set on the ring never reaches the swatch, so the green and yellow donuts drew blue
   keys. One level up, the ring and the legend read the same value.

   Named `--tone`, because `.progress-learner` (style.css:668) declares `--c:#15A51B` ON
   THE RING ITSELF, and a declaration on the element always beats an inherited value —
   so reusing `--c` on the card left the ring green while its legend went blue. The ring
   maps --tone into --c explicitly, which is the only way to overrule the element's own
   declaration without !important. */
.vth .vth-donut{--tone:var(--vth-blue);--track:#E7E7E7;}
.vth .vth-donut--green{--tone:var(--status-success,#187D3F);}
.vth .vth-donut--yellow{--tone:var(--vth-yellow,#FFB900);}
.vth .vth-donut__ring{
  --c:var(--tone);--p:0;--b:18px;--w:150px;
  position:relative;width:var(--w);height:var(--w);
  display:inline-grid;place-content:center;
  border:0;border-radius:50%;box-shadow:none;
  font-size:30px;line-height:1.1;font-weight:700;
  color:var(--text-primary);letter-spacing:0;}
/* One conic gradient carries both the value and the track; the mask turns the pie
   into a ring. `inset:0` rather than a repeated width/height so the two cannot drift. */
.vth .vth-donut__ring::before{
  content:"";position:absolute;inset:0;width:auto;height:auto;
  border-radius:50%;background-size:auto;
  background:conic-gradient(var(--c) calc(var(--p)*1%),var(--track) 0);
  -webkit-mask:radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 calc(100% - var(--b)));
          mask:radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 calc(100% - var(--b)));}
/* style.css:675 draws a 108px white halo on every .progress-learner inside
   .course-detail-tabs — sized for the old 110px ring, so at 150px it cut across this
   one. The fragment is injected inside that container, so it has to be answered. */
.vth .vth-donut__ring::after{content:none;}
/* The reference puts a two-row legend beside the ring: the value, then the remainder. */
.vth .vth-donut__figure{display:flex;align-items:center;justify-content:center;
  gap:var(--space-4);width:100%;}
.vth .vth-donut__legend{display:flex;flex-direction:column;gap:var(--space-2);}
.vth .vth-donut__key{display:flex;align-items:center;gap:var(--space-2);
  font-size:12.5px;color:var(--text-subtle);}
.vth .vth-donut__sw{width:10px;height:10px;border-radius:3px;flex-shrink:0;
  background:var(--tone);}
.vth .vth-donut__key--rest .vth-donut__sw{background:var(--track);}
.vth .vth-donut__cap{font-size:12.5px;color:var(--text-muted,#8A8A8A);}
/* Download Stats: heading and button on one row, then a three-column checklist. */
.vth .vth-dl{margin:0;padding:18px;border:1px solid var(--border-light);border-radius:14px;}
.vth .vth-dl__head{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;
  margin-bottom:14px;}
.vth .vth-dl__title{flex:1;margin:0;font-size:var(--type-size-lg);
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-dl__btn{display:inline-flex;align-items:center;justify-content:center;
  gap:var(--space-2);height:42px;padding:0 18px;margin:0;
  border:0;border-radius:var(--radius-md) !important;
  background:var(--vth-blue) !important;color:var(--text-on-blue) !important;
  font-family:var(--font-sans);font-size:var(--type-size-xs) !important;
  font-weight:var(--weight-semibold) !important;
  text-transform:none;letter-spacing:0;box-shadow:none !important;}
.vth .vth-dl__grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:10px 18px;}
.vth .vth-dl__opt{display:flex;align-items:center;gap:10px;margin:0;padding:0;
  width:auto;font-size:var(--type-size-xs);color:var(--text-secondary);cursor:pointer;}
/* 17px, per the reference. The theme already paints these with appearance:none —
   white with a grey border, brand blue with a tick when checked — so only the size and
   the min-width (which beats width) are set; the tick and the toggle are untouched. */
.vth .vth-dl__opt input[type="checkbox"]{position:static;opacity:1;
  width:17px;height:17px;min-width:17px;min-height:17px;margin:0;float:none;
  flex-shrink:0;cursor:pointer;}
/* The reference's footnote under the checklist (dc.html:601). */
.vth .vth-dl__note{margin:14px 0 0;font-size:12px;line-height:1.5;
  color:var(--text-muted,#8A8A8A);}

@media (max-width:900px){
  .vth .vth-kpis{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:22px;}
  .vth .vth-kpi:nth-child(odd){border-left-color:transparent;}
  .vth .vth-donuts{grid-template-columns:minmax(0,1fr);}
  .vth .vth-dl__grid{grid-template-columns:repeat(2,minmax(0,1fr));}
}
@media (max-width:560px){
  .vth .vth-kpis{grid-template-columns:minmax(0,1fr);}
  .vth .vth-kpi{border-left-color:transparent;}
  .vth .vth-dl__grid{grid-template-columns:minmax(0,1fr);}
}


/* ==========================================================================
   LEARNERS DETAILS — layout and picker defects
   ========================================================================== */

/* ---- 1.2  The accordion card was clipping the dropdown --------------------
   `.accordion-box{overflow:hidden}` (style.css:461) is there to keep content inside
   the card's 16px radius, and it also clips ANY absolutely-positioned child — so the
   Select Learner panel could never escape the card no matter what z-index it had.
   That is the "hiding behind the whole tab's div" report.

   Lifted only for this fragment's accordions. Safe here because nothing inside these
   cards paints to the corners: the header and body both sit on the card's own white,
   so there is no square background left to poke out of the radius. Every other
   `.accordion-box` on the site keeps the clip. */
.vth .vth-accordion > .accordion-box{overflow:visible;}

/* ---- 1.3  …which means the table now has to clip itself -------------------
   `.table-sc` had no overflow rule anywhere, and `.vth-table--learners` carries a
   680px floor, so the table simply ran out of the card's right edge. It was hidden
   before only because the accordion clipped it — remove that clip and the overflow
   becomes visible, so the scroll belongs here. `min-width:0` because a grid/flex item
   will not shrink below its content without it. */
.vth .vth-accordion .table-sc{overflow-x:auto;-webkit-overflow-scrolling:touch;
  max-width:100%;min-width:0;}
.vth .vth-accordion .collapsible-body,
.vth .vth-accordion .row-margin,
.vth .vth-accordion .allocate-dropdown{min-width:0;max-width:100%;}

/* ---- 1.1  The Select Learner picker (reference screenshot) ---------------- */
.vth .vth-ltools__select .multiple-check-box-select{padding:0;}
.vth .vth-ltools__select .multiple-check-box-select > ul{max-height:280px;overflow-y:auto;
  -webkit-overflow-scrolling:touch;padding:6px;margin:0;}
/* Search row: icon + field, stuck to the top of the scroll area. */
.vth .vth-ltools__select .multiple-check-box-select li.multiple-search{
  display:flex;align-items:center;gap:var(--space-2);
  position:sticky;top:-10;z-index:1;margin:0 0 4px;
  padding:6px 6px 10px;background:var(--surface-card);
  border-bottom:1px solid var(--border-light);}
.vth .vth-ltools__select .multiple-check-box-select li.multiple-search .vth-icon{opacity:.5;flex-shrink:0;}
.vth .vth-ltools__select .multiple-check-box-select li.multiple-search input{
  flex:1;min-width:0;height:34px;margin:0;padding:0;
  border:0 !important;box-shadow:none !important;background:transparent;
  font-family:var(--font-sans);font-size:var(--type-size-xs);}
/* Option rows: checkbox, then name over username. The <li> markup is built by
   getLearnerListDetail(), so this styles what that jQuery emits. */
.vth .vth-ltools__select .bundle-learner-selection-list{
  display:flex;align-items:flex-start;gap:12px;
  margin:0;padding:10px;border-radius:var(--radius-md);cursor:pointer;}
.vth .vth-ltools__select .bundle-learner-selection-list:hover{background:var(--surface-muted);}
.vth .vth-ltools__select .bundle-learner-selection-list input[type="checkbox"]{
  position:static;opacity:1;width:18px;height:18px;min-width:18px;min-height:18px;
  margin:2px 0 0;float:none;flex-shrink:0;cursor:pointer; border: 1px solid var(--color-99);}
/* The reference sets the name a step above the username so the pair reads as one
   record rather than two equal lines. */
.vth .vth-ltools__select .bundle-learner-selection-list label{display: block;
  margin: 0;
  min-width: 0;
  cursor: pointer;
  font-size: var(--type-size-2xs);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  line-height: 1.6;
  text-transform: capitalize;}
.vth .vth-ltools__select .bundle-learner-selection-list label small,
.vth .vth-ltools__select .bundle-learner-selection-list label .vth-lsel__sub{display: block;
  font-size: var(--type-size-2xs);
  font-weight: var(--weight-regular);
  color: var(--text-subtle);
  margin-top: 2px;
  text-transform: lowercase;}
/* Footer: Clear on the left, Done on the right. */
.vth .vth-lsel__foot{display:flex;align-items:center;justify-content:space-between;
  gap:var(--space-3);padding:10px 14px;
  border-top:1px solid var(--border-light);background:var(--surface-card);
  border-radius:0 0 var(--radius-lg) var(--radius-lg);}
.vth .vth-lsel__clear,.vth .vth-lsel__done{
  margin:0;padding:0;border:0;background:none;cursor:pointer;
  font-family:var(--font-sans);font-size:var(--type-size-xs);
  font-weight:var(--weight-semibold);}
.vth .vth-lsel__clear{color:var(--text-subtle);}
.vth .vth-lsel__clear:hover{color:var(--text-primary);}
.vth .vth-lsel__done{color:var(--vth-blue);}
/* The trigger must not grow with the selection — the label is a count now, but a long
   single name still has to be contained. */
.vth .vth-ltools__select .multiple-check-div > h2 > a{
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

/* ---- 1.6  Pagination boundary links ---------------------------------------
   `boundary-links` renders first/last as « and »; the step links keep single
   chevrons. The directive is untouched — only its label text and this paint. */
.vth .vth-lfoot .pagination li.pagination-first a,
.vth .vth-lfoot .pagination li.pagination-last a,
.vth .vth-lfoot .pagination li.pagination-prev a,
.vth .vth-lfoot .pagination li.pagination-next a{
  font-size:15px;color:var(--text-secondary);padding:0 8px;}


@media (max-width:560px){
  .vth .vth-lfoot{justify-content:center;}
}


/* ==========================================================================
   LOR TAB — reference Course Detail.dc.html:702-817
   ========================================================================== */
.vth .vth-lor{display:flex;flex-direction:column;gap:var(--space-4);}

/* ---- The completion banner (reference line 705) ------------------------------ */
.vth .vth-lorbanner{display:flex;align-items:center;gap:var(--space-3);
  padding:14px 18px;border-radius:var(--radius-lg);background:#EAF7EE;}
.vth .vth-lorbanner__title{flex:1;font-size:17px;line-height:1.3;
  font-weight:var(--weight-semibold);color:var(--text-primary);}
.vth .vth-lorbanner__count{display:inline-flex;align-items:center;justify-content:center;
  min-width:34px;height:34px;padding:0 10px;border-radius:var(--radius-pill);
  background:var(--surface-card);color:var(--status-success,#187D3F);
  font-size:var(--type-size-xs);font-weight:700;}

/* ---- Toolbar: note left, controls right (reference line 709) ------------------ */
.vth .vth-lortools{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  justify-content:flex-end;margin:0;}
.vth .vth-lortools__note{margin-right:auto;font-size:13px;color:var(--text-subtle);}
.vth .vth-lortools__dl{display:inline-flex;align-items:center;justify-content:center;
  width:42px;height:42px;margin:0;padding:0;flex-shrink:0;
  border:1px solid var(--border-default);border-radius:var(--radius-md);
  background:var(--surface-card);color:var(--text-secondary);cursor:pointer;
  transition:background .12s ease;}
.vth .vth-lortools__dl:hover{background:var(--surface-muted);}
/* ARMED — at least one learner ticked. The reference fills the button once there is
   something to download, so the control reads as live before it is pressed. Driven by
   `is-armed` from course-detail.js rather than `:has(.course_lor:checked)`: the rows are
   ng-repeat'd and replaced on every page and filter change, and a class set from the
   same event that ticks the box is deterministic where :has() invalidation is not. */
.vth .vth-lortools__dl.is-armed{background:var(--vth-blue);border-color:var(--vth-blue);
  color:var(--text-on-blue);}
.vth .vth-lortools__dl.is-armed:hover{background:var(--vth-blue-hover);
  border-color:var(--vth-blue-hover);}
.vth .vth-lortools__dl.disable{opacity:.5;cursor:default;}
/* The real <select multiple> stays in the DOM for downloadCourseLors() to read; it is
   taken out of sight and out of the tab order rather than display:none, because a
   display:none control is skipped by some serialisers. */
.vth .vth-lor__hiddenselect{position:absolute !important;width:1px;height:1px;
  margin:-1px;padding:0;border:0;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden;}
/* Both pickers sit at 42px to match the download button beside them. */
.vth .vth-lor__pick{min-width:200px;margin:0;}
.vth .vth-lor__pick .multiple-check-div > h2{height:42px;}
.vth .vth-lor__pick .multiple-check-box-select{right:0;left:auto;width:min(90vw,320px);}
/* The theme capitalises this label, so "All questions" came out "All Questions" — the
   reference uses sentence case for both pickers. */
.vth .vth-ltools__select .multiple-check-div > h2 > a{text-transform:none;
  text-decoration:none;}
/* The caret is a literal ▼ in the markup, shared with the pre-reskin views, so it is
   swapped for the reference's chevron here rather than in the template. The glyph is
   hidden by transparent colour and replaced on the pseudo-element — the same approach
   the Tailor card uses for its heading icon. */
.vth .vth-ltools__select .multiple-check-div > h2 .caret{
  display:inline-flex;align-items:center;justify-content:center;
  width:16px;height:16px;flex-shrink:0;color:transparent;font-size:0;}
.vth .vth-ltools__select .multiple-check-div > h2 .caret:before{
  content:"\3B5A";font-family:"hgi-stroke-rounded";font-style:normal;
  font-size:16px;line-height:1;color:var(--text-subtle);opacity:.6;
  -webkit-font-smoothing:antialiased;}

/* ---- Table (reference line 779) ---------------------------------------------- */
.vth .vth-table.vth-table--lor{min-width:560px;font-size:14.5px;}
.vth .vth-table thead th{background:var(--surface-muted);
  border-bottom:0;color:var(--text-primary);padding:14px;text-align:left;
  font-size:14.5px;text-transform:none;letter-spacing:0;
  font-weight:var(--weight-semibold);}
.vth .vth-table thead th:first-child{border-radius:10px 0 0 0;width:48px;}
.vth .vth-table thead th:last-child{border-radius:0 10px 0 0;}
.vth .vth-table.vth-table--lor td{padding:14px;
  border-bottom:1px solid var(--divider-grey);color:var(--text-secondary);}
.vth .vth-table tr:last-child td{border-bottom:1px solid var(--divider-grey);}
.vth .vth-table.vth-table--lor .vth-col-check{width:48px;}
.vth .vth-table input[type="checkbox"]{
  position:static;opacity:1;width:18px;height:18px;min-width:18px;min-height:18px;
  margin:0;padding:0;float:none;cursor:pointer; border: 1px solid var(--color-99) !important;}
/* The learner cell reuses the Learners Details treatment — same avatar, same weight. */
.vth .vth-table--lor .vth-learner__pic,
.vth .vth-table--lor .vth-learner__pic img{width:36px;height:36px;}
.vth .vth-table--lor .vth-learner__pic img{border-radius:50%;object-fit:cover;}
.vth .vth-table--lor .vth-learner__name{font-weight:var(--weight-semibold);
  color:var(--text-primary);}
/* REVIEW ON LOR — small, heavy, letter-spaced and upper, per the reference. */
.vth .vth-lorlink{display:inline-block;
  font-size:13px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;
  color:var(--vth-blue);white-space:nowrap;}
.vth .vth-lorlink:hover{color:var(--vth-blue-hover);text-decoration:underline;}

@media (max-width:720px){
  .vth .vth-lortools{justify-content:flex-start;}
  .vth .vth-lortools__note{margin-right:0;flex:1 1 100%;}
  .vth .vth-lor__pick{flex:1 1 auto;min-width:0;}
}


/* ==========================================================================
   COURSE ACTIVITY OUTCOMES — Course Detail.dc.html:820-921
   Almost everything here is borrowed: the toolbar button is .vth-lortools__dl,
   the pickers are .vth-ltools__select / .vth-lor__pick, the tables are
   .vth-table, the badges .vth-atag, the empty state .vth-lempty and the footer
   .vth-lfoot — all shared with Learners Details and LOR, per the brief. What is
   new below is only the learner accordion, which no other tab has.
   ========================================================================== */
.vth .vth-caohead{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin:0 0 var(--space-4);}
.vth .vth-caohead__title{flex:1 1 auto;margin:0;font-size:20px;
  font-weight:var(--weight-semibold);color:var(--text-primary);line-height:1.3;}
/* Both toolbars live in the header and carry `outcomes_tabs_content`, so
   showOutcomesTabs() shows and hides them with their panels. jQuery .hide()/.show()
   writes an inline display, and .show() restores to "" — which lands back on this
   flex, not on block. */
.vth .vth-caotools{display:flex;align-items:center;gap:10px;flex:0 0 auto;margin:0;}

/* SEGMENTED CONTROL (reference line 840).
   .vth-seg is shared with the Learners Details tabs, so every rule below is scoped to
   .vth-cao__panel and changes THIS control only.

   The theme owns the look through `.tabs.tabs-secondary` (style.css:959-967), which is
   (0,4,0) on the active anchor and so beat the plain `.vth .vth-seg__btn.active`. Four
   things had to be undone: the 12px/400 type, the split `6px 0 0 6px` radii that made
   the pill a half-rounded slab, the rgba(36,97,140,.1) active fill, and a `.tab::after`
   that drew a 1px rule between the tabs. Matching the theme's own selector and adding
   the panel scope wins on specificity without !important. */
.vth .vth-cao__panel .tabs.tabs-secondary{
  height:auto;padding:5px;border:0;border-radius:14px;
  background:var(--surface-muted);box-shadow:none;}
.vth .vth-cao__panel .tabs.tabs-secondary .tab{
  height:auto;line-height:normal;}
/* The 1px divider between the tabs. */
.vth .vth-cao__panel .tabs.tabs-secondary .tab::after{display:none;}
.vth .vth-cao__panel .tabs.tabs-secondary .tab a,
.vth .vth-cao__panel .tabs.tabs-secondary .tab:first-child a,
.vth .vth-cao__panel .tabs.tabs-secondary .tab:last-child a{
  padding:10px 22px;border-radius:10px;
  font-size:var(--type-size-xs);font-weight:var(--weight-semibold);line-height:1.2;
  color:var(--text-primary);background-color:transparent;box-shadow:none;
  text-transform:none;letter-spacing:0;transition:background-color .12s ease;}
.vth .vth-cao__panel .tabs.tabs-secondary .tab a:hover{
  color:var(--text-primary);background-color:rgba(0,0,0,.03);}
/* ACTIVE — a white pill lifted off the track, blue and bold. */
.vth .vth-cao__panel .tabs.tabs-secondary .tab a.active,
.vth .vth-cao__panel .tabs.tabs-secondary .tab:first-child a.active,
.vth .vth-cao__panel .tabs.tabs-secondary .tab:last-child a.active{
  background-color:var(--surface-card);color:var(--vth-blue);
  font-weight:var(--weight-semibold);
  box-shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.04);}

.vth .vth-caobody{margin-top:var(--space-4);}
.vth .vth-caolist{margin:0;border:0;box-shadow:none;}

/* LEARNER ACCORDION ----------------------------------------------------- */
.vth .vth-cao{margin:0 0 10px;border:1px solid var(--border-light);
  border-radius:14px;overflow:hidden;background:var(--surface-card);}
.vth .vth-cao:last-child{margin-bottom:0;}
.vth .vth-cao__head{display:flex;align-items:center;gap:12px;
  padding:14px 16px;min-height:0;line-height:1.35;border:0;background:none;
  cursor:pointer;}
.vth .vth-cao__head:hover{background:var(--surface-muted);}
.vth .vth-cao__head > input[type="checkbox"]{position:static;opacity:1;
  width:18px;height:18px;flex-shrink:0;margin:0;accent-color:var(--vth-blue);
  cursor:pointer;}
.vth .vth-cao__pic{width:36px;height:36px;flex-shrink:0;border-radius:50%;
  overflow:hidden;background:var(--surface-muted);}
.vth .vth-cao__pic img{width:100%;height:100%;object-fit:cover;display:block;}
.vth .vth-cao__id{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1 1 auto;}
.vth .vth-cao__name{font-size:14.5px;font-weight:var(--weight-semibold);
  color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.vth .vth-cao__user{font-size:12.5px;color:var(--text-subtle);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.vth .vth-cao__meta{display:flex;align-items:center;gap:10px;flex-shrink:0;
  margin-left:auto;}
.vth .vth-cao__summary{font-size:13px;color:var(--text-secondary);}
.vth .vth-cao__chev{font-size:18px;color:var(--text-secondary);opacity:.55;
  transition:transform .15s ease;}
.vth .vth-cao.active > .vth-cao__head .vth-cao__chev{transform:rotate(180deg);}
/* Materialize gives .collapsible-body a border and padding of its own. */
/* Materialize's .collapsible-body carries its own padding and border, and the theme
   adds more on top with a heavier selector - hence the element name here, which buys
   the extra specificity without !important. The reference runs the table and the
   reflective blocks flush to the card edge. */
.vth .vth-caolist > li.vth-cao > div.vth-cao__body{padding:0;border:0;
  border-top:1px solid #EFEFF2;}

/* QUIZ TABLE (reference line 866) ---------------------------------------- */
.vth .vth-cao__body .vth-table--cao{width:100%;margin:0;min-width:520px;border:0;}
/* Question takes the slack; the two short columns size to their content. */
.vth .vth-cao__body .vth-table--cao th:first-child,
.vth .vth-cao__body .vth-table--cao td:first-child{width:auto;}
.vth .vth-cao__body .vth-table--cao th:nth-child(2),
.vth .vth-cao__body .vth-table--cao td:nth-child(2){width:140px;}
.vth .vth-cao__body .vth-table--cao th:last-child,
.vth .vth-cao__body .vth-table--cao td:last-child{width:110px;}
.vth .vth-cao__body .vth-table--cao thead th{background:#F6F6F6;padding:12px 16px;
  border:0;font-size:12px;font-weight:var(--weight-semibold);
  color:var(--text-secondary);text-transform:none;white-space:nowrap;}
.vth .vth-cao__body .vth-table--cao tbody td{padding:13px 16px;border:0;
  border-top:1px solid #EFEFF2;font-size:13px;color:var(--text-primary);
  vertical-align:middle;}
.vth .vth-cao__body .vth-table--cao tbody tr:first-child td{border-top:0;}
/* No column rules - the reference separates rows only. */
.vth .vth-cao__body .vth-table--cao th,
.vth .vth-cao__body .vth-table--cao td{border-left:0;border-right:0;}

/* REFLECTIVE BLOCKS (reference line 890) --------------------------------- */
.vth .vth-refl{display:flex;flex-direction:column;gap:5px;padding:16px;
  border-top:1px solid #EFEFF2;}
.vth .vth-refl:first-child{border-top:0;}
.vth .vth-refl__step{font-size:11.5px;font-weight:var(--weight-bold);
  letter-spacing:.04em;text-transform:uppercase;color:var(--vth-blue);}
.vth .vth-refl__q{font-size:14.5px;font-weight:var(--weight-semibold);
  color:var(--text-primary);}
.vth .vth-refl__a{font-size:14px;line-height:1.6;color:var(--text-secondary);
  white-space:pre-line;overflow-wrap:anywhere;}

@media (max-width:640px){
  .vth .vth-caohead__title{flex:1 1 100%;}
  .vth .vth-caotools{flex:1 1 100%;justify-content:flex-end;}
  .vth .vth-cao__meta{gap:6px;}
  .vth .vth-cao__summary{display:none;}
}


