/* Training admin workspace (courses + learning paths) — only the rules that
   have NO Bootstrap utility/class equivalent live here. Everything else
   (spacing, colors, cards, info tips, rows) is expressed with Bootstrap
   classes in the templates.

   Kept because Bootstrap can't express them without changing the approved look
   or breaking behavior:
   - the hero version switcher (the hero itself and the tab bar are the
     app-wide `.detail-hero` / `.detail-tabs` shell in custom.css),
   - fixed-pixel sizes for the lesson/picker type-icon chips,
   - functional selectors: the Q&A checkbox disclosure and the Enrollments
     hover/focus row menu,
   - a couple of design-specific picker-modal rules with no utility equivalent.
   Everything with a Bootstrap/AdminKit/common-file equivalent uses that
   instead (e.g. .border-dashed lives in the app-wide custom.css).
   Colors still reference var(--bs-*) tokens (dark-mode & brand aware).

   Class names come in three families: `tw-` for this sheet's own utilities and
   page chrome, plus the BEM block `course-player__*` and the `.training-pdf-viewer`
   height-cascade hook.
   Those two block names are also STIMULUS IDENTIFIERS, so renaming either means
   moving the class, the identifier, every `data-*-target`/`-value`, the controller
   file and its index.js registration together — miss one and nothing errors at
   boot. A few selectors below carry no prefix at all, because the name is a
   contract with Bootstrap, SortableJS, Tom Select or Quill. */

/* ── Hero banner, back pill and underline tab bar ──────────────────────────
   MOVED to custom.css ("DETAIL SHELL", 2026-09-04) as `.detail-hero*` /
   `.detail-tabs` / `.detail-meta` the day Sites became their second consumer.
   Only the version switcher below — a Training-only control that sits ON the
   hero — stays here. */

/* ── Hero version switcher ───────────────────────────────────────────────────
   The version pills are a CONTROL, not metadata, so they sit in one segmented
   well set apart from the static type/category chips beside them — otherwise an
   unselected version is pixel-identical to the category chip and the row reads
   as five unrelated pills. The well reuses .detail-hero__back's fill language
   (translucent black + hairline white border + blur), which is already proven
   legible over ANY banner variant: bright image, dark image, or gradient.
   Inside it there are exactly TWO states and nothing else varies — selected and
   unselected. Draft is carried by the pill's own LABEL ("v3 (draft)"), never by
   a fill, so fill means selected-vs-unselected and only that. No Bootstrap
   equivalent: the states, hover and focus-visible all need fixed alpha values
   measured against the banner rather than theme tokens. */
.detail-hero__versions {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  padding: 0.1875rem 0.3125rem;
  border-radius: 999px;
  background-color: rgba(0, 0, 0, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.3);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.detail-hero__versions-label {
  padding-left: 0.5rem;
  padding-right: 0.25rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* 0.75 over the well's #616161-equivalent fill clears AA for this weight while
     staying quieter than the pills it labels. */
  color: rgba(255, 255, 255, 0.75);
}

/* Unselected: transparent, white label. Reads as "off" but plainly clickable
   because it lives inside the well and lights up on hover. */
.detail-hero__version {
  padding: 0.1875rem 0.625rem;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--bs-white);
  background-color: transparent;
  text-decoration: none;
  transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out;
}

.detail-hero__version:hover,
.detail-hero__version:focus {
  color: var(--bs-white);
  background-color: rgba(255, 255, 255, 0.22);
}

.detail-hero__version:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 2px;
}

/* The ONE selected treatment: a solid white chip — the highest-contrast element
   in the well, and the only filled one. `aria-current="page"` in the template
   carries the same fact for screen readers. Hover/focus are pinned to the same
   values so the selected pill never momentarily reads as unselected. */
.detail-hero__version.is-selected,
.detail-hero__version.is-selected:hover,
.detail-hero__version.is-selected:focus {
  color: var(--bs-dark);
  background-color: var(--bs-white);
}

/* ── Lessons: type-icon chip — only the fixed 40×40 size needs CSS (Bootstrap
   has no fixed-px width/height utility); flex/rounded/centering are utilities. ── */
.tw-lesson__icon {
  width: 40px;
  height: 40px;
}

/* Grab cursor on SortableJS drag handles (`.drag-handle` is the selector
   sortable_controller hands to SortableJS, so the name is fixed).
   `:not(.cursor-pointer)` is load-bearing: this sheet is emitted from the
   layout's `yield :head` AFTER custom.css, so a bare `.drag-handle` would
   out-rank the equally-specific `.cursor-pointer` utility and override a
   cursor an author set deliberately. */
.drag-handle:not(.cursor-pointer) {
  cursor: grab;
}

/* ── Roles "Add" reveal — pure-CSS checkbox disclosure (no native marker) ── */
.tw-reveal {
  display: none;
}

.tw-reveal-toggle:checked~.tw-reveal {
  display: block;
}

/* NOTE: `.border-dashed` is intentionally NOT defined here — it's a common,
   module-independent utility in custom.css (loaded app-wide). The Enrollments
   info tip pairs it with `.border`. */

/* Extra-small button for compact Q&A actions (Bootstrap's smallest built-in is
   btn-sm). The matching `.badge-xs` lives app-wide in custom.css. */
.btn-xs {
  --bs-btn-padding-y: .15rem;
  --bs-btn-padding-x: .5rem;
  --bs-btn-font-size: .72rem;
}

/* ── Add-role picker modal — fixed avatar tile + scrollable results list.
   Bootstrap has no fixed-px or max-height utility. ───────────────────────── */

/* Dialog width per the design (Bootstrap presets are 300/500/800/1140). */
@media (min-width: 576px) {
  .tw-picker-dialog {
    max-width: 440px;
  }
}

/* The theme tints .modal-body light grey (custom.css sets it !important); here
   the search bar and the flush results list should share one uniform (white)
   background, so override the tint back to the modal surface. */
.tw-picker-dialog .modal-body {
  background-color: transparent !important;
}

.tw-picker-avatar {
  width: 34px;
  height: 34px;
}

.tw-picker-results {
  max-height: 260px;
  overflow-y: auto;
}

/* Segmented toggle — Bootstrap's checked state is a solid fill; the mockup
   wants a light primary tint on the active option. */
.tw-picker-toggle .btn-check:checked+.btn {
  background-color: var(--bs-primary-bg-subtle);
  border-color: var(--bs-primary);
  color: var(--bs-on-subtle-primary, var(--bs-primary));
}

/* Search field with the magnifier inside the input (no separate addon box). */
.tw-picker-search {
  position: relative;
}

.tw-picker-search .tw-picker-search-ico {
  position: absolute;
  left: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--bs-tertiary-color);
  pointer-events: none;
}

.tw-picker-search .form-control {
  padding-left: 2.1rem;
}

/* ── Enrollments row actions — revealed on row hover / keyboard focus ────── */
.tw-row-actions {
  opacity: 0;
  transition: opacity .12s ease;
}

tr:hover .tw-row-actions,
tr:focus-within .tw-row-actions,
.tw-row-actions.show {
  opacity: 1;
}

/* ── List-row ⋮ menu — courses/_row_actions AND learning_paths/index ─────────
   The wrapper must be a stacking context above the row's stretched-link (z-1)
   and level with the row's Manage link (z-2), and that context traps the
   dropdown-menu — so the wrapper's z-index is what the OPEN menu competes with.
   Raise it only while open: a permanent z-3 on every row put later rows' ⋮
   buttons on top of an earlier row's open menu. Same :has() shape as
   fixes/dropdown_fixes.css.

   Named for courses because that is where it was found, but the learning-paths
   list is its twin and uses the SAME class — do not fork a `.tw-path-row-menu`.
   Both host pages must load this sheet (each has its own stylesheet_link_tag);
   the class alone does nothing without it, which is how the paths list briefly
   shipped an inert `.tw-row-menu` after a master merge. */
.tw-course-row-menu {
  position: relative;
  z-index: 2;
}

.tw-course-row-menu:has(.dropdown-menu.show) {
  z-index: 3;
}

/* ── Create/Edit Course form — rules with no Bootstrap utility equivalent.
   (Loaded on new/edit via the stylesheet_link_tag in those views.) ───────── */

/* Selectable option cards (Delivery Mode, Proof scope): active state driven
   purely off the hidden radio — no JS. Mirrors .tw-picker-toggle above. */
.tw-option-card .btn-check:checked+.card {
  border-color: var(--bs-primary);
  background-color: var(--bs-primary-bg-subtle);
}

/* Fixed max-widths for the media/intro fields (Bootstrap has no max-width
   utility at these sizes). */
.tw-media-max {
  max-width: 380px;
}

.tw-intro-max {
  max-width: 460px;
}

/* NOTE: `.min-w-0` is intentionally NOT defined here — it's an app-wide utility
   in custom.css, which both layouts that serve this sheet load via shared/_head
   (application for the workspace, minimal for the player, quiz and preview). */

/* Quill editor inner content rounded to match the container's rounded bottom. */
.quill-editor-wrapper .quill-editor-container,
.quill-editor-wrapper .quill-editor-container .ql-editor {
  border-bottom-left-radius: 0.5rem;
  border-bottom-right-radius: 0.5rem;
}

/* The last element of each form section card sits flush with the card padding
   (the Quill editor / last custom field otherwise add an extra mb-3). */
form .card-body>*:last-child {
  margin-bottom: 0 !important;
}

/* Tom Select selected chips in the brand color. */
.ts-wrapper.multi .item {
  background: rgba(var(--bs-primary-rgb), 0.08);
  color: var(--bs-on-subtle-primary, var(--bs-primary));
  border: 1px solid rgba(var(--bs-primary-rgb), 0.25);
  border-radius: var(--bs-border-radius-sm);
}

.ts-wrapper.multi .item .remove {
  color: var(--bs-on-subtle-primary, var(--bs-primary));
  border-left-color: rgba(var(--bs-primary-rgb), 0.25);
}

/* Common pagination tidy for any training table's card footer. The shared
 * Kaminari paginator renders a plain `ul.pagination`, which inherits the
 * list-reboot bottom margin; zero it so the footer bar stays compact. Apply
 * `.training-table-footer` to the table card's `.card-footer`. */
.training-table-footer .pagination {
  margin-bottom: 0;
  /* Wrap the page links. The footer is justify-content-between, so on a narrow
     viewport the paginator sits at the right edge; a wide link set (a table with
     thousands of pages renders ~8 links, ~341px) then runs past the viewport —
     measured 9px of horizontal PAGE scroll at 375, which the pre-footer layout
     did not have. Wrapping keeps it inside the card.
     (No justify-content here — a more specific app rule centers .pagination and
     wins, so declaring it would be dead CSS.) */
  flex-wrap: wrap;
}

/* ── Instructor-led sessions & roster ─────────────────────────────────────
   Only fixed-pixel sizes for small fixed widgets live here — a category
   Bootstrap has no utility for (w-* and h-* are percentage-only) and which the
   codebase already sets with fixed px on these exact widgets (the thin
   capacity/progress bars in enrollments/index and course_assignments/index
   ship width/height in px inline). Mirrors the .tw-lesson__icon precedent
   above. Everything else — spacing, border, radius, background, ALL font sizes
   (via .small) and every width constraint (via grid cols and w-* percentages) —
   is a Bootstrap utility in the templates. The lone custom color, the
   proof-dropzone 4%-opacity tint (below the bg-opacity scale), uses var(--bs-*)
   so it stays brand/dark-mode aware. */

/* session date chip — fixed width so learner/roster rows align */
.tw-date-chip {
  width: 52px;
}

/* capacity progress bar (sessions tab) — fixed px like enrollments/index.html.erb */
.tw-cap-bar {
  width: 120px;
  height: 6px;
}

/* thin progress bar height (learner session rows) */
.tw-bar-6 {
  height: 6px;
}

/* Choose-a-Session modal row (My Learning v5): lays out as date chip · details ·
   capacity · CTA. The capacity/CTA columns use % so they scale with the modal's
   (variable) width, with a min-width floor so the "N left" label and the button
   text ("Registration closed") never clip; details keeps a min-width so it never
   collapses. */
.tw-session-details {
  min-width: 200px;
}

.tw-session-cap {
  width: 16%;
  min-width: 100px;
}

.tw-session-cta {
  width: 19%;
  min-width: 138px;
}

/* ── Learning-path builder / detail fixed-size utilities (replace inline styles;
   dynamic progress-bar widths stay inline per Bootstrap's own pattern) ─────── */
.tw-min-w-120 {
  min-width: 120px;
}

.tw-select-max {
  max-width: 320px;
}

/* 5:3 hero aspect ratio (no Bootstrap ratio-* preset for it) */
.tw-ratio-5x3 {
  --bs-aspect-ratio: 60%;
}

/* Clickable <details> summary with no default disclosure marker (the platform
   <details>/<summary> pattern, as a class instead of an inline style). */
.tw-summary {
  cursor: pointer;
  list-style: none;
}

.tw-summary::-webkit-details-marker {
  display: none;
}

/* Faint primary wash for the proof dropzone — 4% opacity is below the
   bg-opacity utility scale (10/25/…), so it's the one custom color kept here.
   Border/radius/padding/weight all come from Bootstrap utilities. */
.tw-proof-dropzone {
  background: rgba(var(--bs-primary-rgb), .04);
}

/* ===========================================================================
 * Immersive course player (learner lesson viewer + author preview).
 * Layout dimensions only — colors, typography, spacing come from Bootstrap /
 * AdminKit utility classes and theme CSS variables on the markup. Everything is
 * scoped under `.course-player` so it can't leak into the rest of the app.
 * =========================================================================== */

/* Outline rail (LHS) — ~20% of the page, full height. Slides out on collapse;
   margin-left matches the flex-basis so it hides exactly and the content pane
   expands. The body has overflow hidden so it never adds a horizontal scrollbar. */
.course-player__rail {
  flex: 0 0 20%;
  align-self: stretch;
  transition: margin-left 0.25s ease;
}

.course-player.rail-collapsed .course-player__rail {
  margin-left: -20%;
}

/* "On this page" table of contents (RHS) — ~15% of the page, text lessons only. */
/* ~15% of the page from the right. Viewport-relative (vw) because the TOC is
   nested inside the content row, so a plain % would be 15% of that row, not the page. */
.course-player__toc-rail {
  flex: 0 0 15vw;
  min-width: 160px;
  align-self: stretch;
}

/* Native disclosure (video transcript) shows a pointer on its summary. */
.course-player details summary {
  cursor: pointer;
}

/* Outline rows sit on the rail's own (container) colour — no dividers, rounded,
   with a subtle hover. The current row is tinted with a primary left accent bar. */
.course-player__lesson {
  padding: 0.625rem 0.75rem;
  /* Match the theme's standard radius (buttons, cards, etc.) for consistency. */
  border-radius: var(--bs-border-radius);
  color: var(--bs-body-color);
  background-color: transparent;
  transition: background-color 0.1s ease;
}

.course-player__lesson:hover {
  background-color: rgba(var(--bs-primary-rgb), 0.06);
}

.course-player__rail .course-player__lesson--current,
.course-player__rail .course-player__lesson--current:hover {
  background-color: var(--bs-primary-bg-subtle);
  box-shadow: inset 3px 0 0 var(--bs-primary);
}

/* The current lesson's title is .text-primary ON that subtle fill — the ink the
   theme clamps for exactly that pairing (4.46:1 with plain .text-primary). */
.course-player__rail .course-player__lesson--current .text-primary {
  color: var(--bs-on-subtle-primary, #1f4f99) !important;
}

/* SCORM content: center the launch card both horizontally and vertically in the
   content pane (the pane has a definite height via flex-grow, so min-height 100%
   fills it and the flex centering takes over). */
.course-player__content--center {
  min-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* SCORM launch card — a centered, generously padded card (matches the mockup:
   ~560px wide, 16px radius, roomy padding). Border + soft shadow come from the
   `card border shadow-sm` utilities on the element. */
.course-player__scorm-card {
  max-width: 560px;
  margin-inline: auto;
  border-radius: var(--bs-border-radius-xl);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.06);
}

.course-player__scorm-card .card-body {
  padding: 2.5rem;
}

/* Outline meta line ("type · duration") — a touch smaller than the title.
   Matches the small-text sizes the app uses elsewhere (custom.css). */
.course-player__rail-meta {
  font-size: 0.625rem;
}

/* Slim course-progress sliver in the rail header. Higher specificity + !important
   so it beats AdminKit's default `.progress` height (which otherwise wins). */
.course-player .course-player__progress {
  height: 0.375rem !important;
}

/* Top-bar progress mirror: hidden while the rail (with its own progress) is open,
   revealed only once the rail collapses so the count stays visible. */
.course-player__topbar-progress {
  display: none;
}

.course-player.rail-collapsed .course-player__topbar-progress {
  display: flex;
}

.course-player__topbar-progress .course-player__progress {
  width: 6rem;
}

/* Measure-limited, centered editorial reading column for the content pane. */
.course-player__reading {
  max-width: 760px;
  margin-left: auto;
  margin-right: auto;
}

/* A video and its chapters (course_player/_content_video, _video_chapters).
   Narrow screen: the list follows the video. Wide screen: it stands beside the
   player and is exactly as tall as it, so the list and the chapter being played
   stay in view while the film runs — under the video they were below the fold
   before the film had started (owner, 2026-10-12). */
.course-player__chapters {
  margin-top: 1rem;
}

.course-player__chapters-panel {
  overflow: hidden;
}

/* The outline rail's density, so a film's nine or ten chapters fit beside it
   without the list scrolling. `em`, not `rem`: this page's text is smaller than
   the root size, and the list-group's own rem padding made every row 50px tall.
   The side padding is the heading's (px-3), so the times line up under it. */
.course-player__chapter.list-group-item {
  padding: 0.5em 1rem;
}

/* The chapter being played (the video-player controller sets `active`): the
   current lesson's own treatment in the outline rail — a tint, an accent bar and
   the ink the theme clamps for that tint — plus weight, so it is not told by
   colour alone. Bootstrap's solid brand fill is too loud for a list beside a film. */
.course-player__chapter.list-group-item.active {
  background-color: var(--bs-primary-bg-subtle);
  border-color: var(--bs-list-group-border-color);
  box-shadow: inset 3px 0 0 var(--bs-primary);
  color: var(--bs-on-subtle-primary, #1f4f99);
  font-weight: 600;
}

/* The time is .text-body-secondary; on the tint it takes the row's own ink. */
.course-player__chapter.active .course-player__chapter-time {
  color: inherit !important;
}

/* From 1280px. Between there and 1440px the video gives up some width to the
   list (636px wide at 1280 with the outline open, its full 760px from 1440, or
   at any of these widths once the outline is closed). Narrower than 1280px the
   video would be too small to follow, so the list goes under it. */
@media (min-width: 1280px) {
  /* The column grows by the list's width, and what follows the video keeps
     the reading measure. */
  .course-player__reading:has(.course-player__video-stage--chapters) {
    max-width: calc(760px + 1.25rem + 19rem);
  }

  .course-player__reading:has(.course-player__video-stage--chapters)>.course-player__below-video {
    max-width: 760px;
  }

  .course-player__video-stage--chapters {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 19rem;
    column-gap: 1.25rem;
  }

  /* Out of the flow, so the row is as tall as the video and no taller; the
     list scrolls inside the panel when it has more chapters than fit. */
  .course-player__video-stage--chapters .course-player__chapters {
    position: relative;
    margin-top: 0;
    min-height: 0;
  }

  .course-player__video-stage--chapters .course-player__chapters-panel {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
  }

  .course-player__video-stage--chapters .course-player__chapter-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
  }
}

/* Learner-facing lesson description (course_player/_lesson_description). simple_format
   emits paragraphs, so drop the trailing one's margin and let the wrapper own the gap. */
.course-player__lesson-description>p:last-child {
  margin-bottom: 0;
}

/* Scrollable video transcript panel. */
.course-player__transcript {
  max-height: 300px;
  overflow-y: auto;
}

/* Inline PDF.js viewer for document lessons. The viewer fills the content pane so
   ONLY the page stack scrolls — no second, outer scrollbar. The :has() rules make
   the height cascade (pane → container → reading → card) apply only on lessons that
   actually render the viewer, leaving other lesson types to scroll normally. */
.course-player__content:has(.training-pdf-viewer)>.container-fluid,
.course-player__content:has(.training-pdf-viewer)>.container-fluid>.course-player__reading {
  height: 100%;
}

/* The reading column can also carry the lesson description above the viewer, so it
   is a flex column and the viewer card takes whatever height is left rather than a
   flat 100% (which would overflow by the description's height). */
.course-player__content:has(.training-pdf-viewer)>.container-fluid>.course-player__reading {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.card.training-pdf-viewer {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.training-pdf-viewer>.card-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* The page canvases themselves are `.pdf-viewer__pages` / `.pdf-viewer__page`,
   styled globally in custom.css — the pdf-viewer Stimulus controller is shared
   with Mango Spend receipts, so its own markup can't live in a Training-only
   stylesheet. Only the course-player HEIGHT CASCADE above is Training's. */

/* Fixed-width label/progress gutters for the review-distribution rows
   (public course page), the learner-progress table, and the analytics course
   table. Widths are in rem so they scale with root font size / browser zoom
   rather than being pinned to px. */
.tw-w-25 {
  /* rating count gutter (~24px) */
  width: 1.5rem;
}

.tw-w-50 {
  /* "N stars" label gutter (~48px) */
  width: 3rem;
}

.tw-w-60 {
  /* learner-progress mini bar (~60px) */
  width: 3.75rem;
}

.tw-progress-80 {
  /* analytics progress cell (~80x20px) */
  width: 5rem;
  height: 1.25rem;
}
/* ── Layouts that follow the CONTENT box, not the window ────────────────────
   ISS-20260929-300-53D7C6. Bootstrap's col-md-* / col-xl-* ask how wide the
   WINDOW is. From 1200px up the Ask AI panel docks beside the page and takes
   its width (ask_ai_clean.css leaves the page 780-1020px), so the window still
   answers "xl" while a Training page's content box is ~650-700px: My Learning's
   25% right rail fell to ~145px and wrapped session titles a few letters per
   line, the 3- and 4-up course grids squeezed every card to ~150px, and four
   KPI tiles shared ~690px, leaving an icon tile's value ~40px. These layouts
   measure the box they are in instead, so a page lays out by the room it
   actually has — panel open or closed, any window. None of them is a grid the
   Bootstrap utilities can express: those are viewport media queries by
   definition.

   Only the KPI tile rows and the rating summary are SIZE CONTAINERS (container
   queries): a container is the containing block and stacking context of every
   absolute or fixed descendant, which would trap a Bootstrap modal inside the
   row under its own backdrop, and those two hold none. The card grid and the
   two column splits get the same behaviour from flex-wrap and min-widths
   instead, because they DO hold such things — the About tab's Rate & Review
   modal, the Automation builder's sticky rail.

   rem here is 17.6px, not 16: custom.css sets `html { font-size: 110% }`
   platform-wide. Every threshold below was first written as 16px arithmetic,
   so each one landed 10% wide — My Learning's rail stacked at a 1200px window,
   the About rail stacked at a 1440px one with the panel docked, the catalog
   held 3-up at 1470. The values are now that arithmetic divided by 1.1, and
   the px each comment quotes is what the browser measures (2026-10-01). */

/* Card grid: as many columns as fit at --tw-card-min each, never more than
   --tw-card-cols (the old col-xl count), each column an equal share. Cells
   stretch, so an h-100 card fills its row. 15rem (264px) is the narrowest a My
   Learning card keeps its footer (due/overdue chip + action button) on one line
   — measured: at 258px "Resume lesson" and "View session" break onto two lines.
   min(100%, …) lets one card shrink below that in a box narrower than it
   rather than overflow it. A child that must span the row (an empty state)
   takes .tw-card-grid__full. Items hidden with .d-none drop out of the flow. */
.tw-card-grid {
  --tw-card-min: 15rem;
  --tw-card-cols: 3;
  --tw-card-gap: 1.5rem;
  display: grid;
  gap: var(--tw-card-gap);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, max(var(--tw-card-min), (100% - (var(--tw-card-cols) - 1) * var(--tw-card-gap)) / var(--tw-card-cols))), 1fr));
}

/* The 4-up grids (Course Catalog, Partner Courses, the Team Dashboard's people)
   carry compact cards with no chip-and-button footer to hold on one line —
   catalog cards measured clean down to 200px — so they take a 13.6rem (240px)
   minimum: 4-up from a ~1040px grid (a 1470px window, panel closed), 3-up at a
   1200px one, 2-up with the panel docked. */
.tw-card-grid--4 {
  --tw-card-cols: 4;
  --tw-card-min: 13.6rem;
}

/* Two panels side by side (the Training dashboard's Recent Enrollments · Draft
   Courses) while each gets 22rem (387px) — a 1200px window and up with the
   panel closed — and stacked with the Ask AI panel docked, where col-lg-6 left
   each ~346px and the enrollments table scrolled its Enrolled column away. */
.tw-card-grid--2 {
  --tw-card-cols: 2;
  --tw-card-min: 22rem;
}

.tw-card-grid--tight {
  --tw-card-gap: 1rem;
}

.tw-card-grid > .tw-card-grid__full {
  grid-column: 1 / -1;
}

/* Main column beside a narrow quarter rail (My Learning: courses + My Sessions /
   My Records; the Automation builder: form + Automation Stats). 75 / 25 while
   the quarter is at least 10.25rem (180px) of content; below that — a row under
   ~827px, which is where the old col-xl-9 / col-xl-3 split began at a 1200px
   window (that window's row is ~845px, so the pair stays side by side there) —
   the rail no longer fits beside the main column, wraps below it and, like the
   main column, grows to the full row, exactly as these pages already stacked
   below their breakpoint. Plain flex-wrap, not a container: see above. */
.row.tw-cq-split > .tw-cq-main {
  flex: 3 1 75%;
  width: auto;
  min-width: 0;
}

.row.tw-cq-split > .tw-cq-rail {
  flex: 1 1 25%;
  width: auto;
  min-width: calc(10.25rem + var(--bs-gutter-x, 1.5rem));
}

/* Main column beside a one-third rail that carries the page's call to action or
   its facts — the course / learning-path About and Overview tabs (description
   and reviews beside the Start · Continue · Enroll · Buy card, or the Info
   list). The rail takes a third of the row but never less than 15.5rem (273px)
   of content, so its buttons and label/value rows keep their width; the main
   column takes the rest and never less than 18.25rem (321px). When both no
   longer fit — a row under ~647px, about what the old col-lg-8 / col-lg-4 split
   had at a 992px window — the rail wraps below and both take the full row, as
   before. They do NOT stack at the ~690px the docked Ask AI panel leaves at a
   1440px window: that would push the rail's call to action below the reviews.
   Plain flex-wrap: the main column holds the Rate & Review modal (see above). */
.row.tw-cq-aside > .tw-cq-main {
  flex: 999 1 0%;
  width: auto;
  min-width: min(100%, calc(18.25rem + var(--bs-gutter-x, 1.5rem)));
}

.row.tw-cq-aside > .tw-cq-rail {
  flex: 1 1 max(33.33333333%, calc(15.5rem + var(--bs-gutter-x, 1.5rem)));
  width: auto;
}

/* KPI tile rows (Total · Active · Completed · Overdue …): one tile per line in
   a narrow box, two from a 21.8rem (384px) row, four from 43.6rem (768px, a
   192px tile) — the col-sm-6 / col-xl-3 intent, measured on the row: a 1200px
   window's ~845px row is 4-up with room to spare for a scrollbar. The tiles are
   plain col-12; the row sizes them. Four-tile rows only. */
.tw-stat-row {
  container: tw-stat / inline-size;
}

@container tw-stat (min-width: 21.8rem) {
  .row.tw-stat-row > * {
    flex: 0 0 auto;
    width: 50%;
  }
}

@container tw-stat (min-width: 43.6rem) {
  .row.tw-stat-row > * {
    width: 25%;
  }
}

/* Three-tile rows (a bundle's Original · Bundle · Savings price, a purchase's
   Progress · Lessons · Time, a promo code's usage): one tile per line, three
   from a 30rem (528px) row — a 176px tile, room for a display-6 "12h 30m". Never
   2 + 1: a lone third tile at half width reads as a missing fourth. They sit in
   the main column of a tw-cq-aside split, which is ~390px with the Ask AI panel
   docked, where col-md-4 by the window had left each ~120px (finding #5439). */
@container tw-stat (min-width: 21.8rem) {
  .row.tw-stat-row.tw-stat-row--3 > * {
    width: 100%;
  }
}

@container tw-stat (min-width: 30rem) {
  .row.tw-stat-row.tw-stat-row--3 > * {
    width: 33.33333333%;
  }
}

/* A list's filter bar (search · selects · dates · Filter): as many equal
   columns as fit at 12rem (211px) each, so a select keeps its longest option
   readable and the fields wrap onto a second line rather than squeeze — and a
   field that wraps alone keeps its column's width instead of stretching across
   the row. Buttons and a checkbox (.tw-filter-bar__auto) sit at their natural
   width in their cell. The old row g-3 + col-md-2 split by the WINDOW left each
   select ~95px with the Ask AI panel docked (finding #5439). A field hidden with
   [hidden] or .d-none drops out of the flow. */
.tw-filter-bar {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 12rem), 1fr));
  align-items: end;
  gap: 1rem;
}

.tw-filter-bar > * {
  min-width: 0;
}

.tw-filter-bar > .tw-filter-bar__auto {
  justify-self: start;
}

/* A report's Filter / Reset / Export CSV / Schedule group. Four buttons in one
   12rem cell wrapped every label onto two lines (69px-tall buttons at any
   desktop width), so the group takes the whole row under the fields and wraps
   as whole buttons, never inside one. */
.tw-filter-bar > .tw-filter-bar__actions {
  grid-column: 1 / -1;
}

.tw-filter-bar__actions .btn {
  white-space: nowrap;
}

/* Label-over-value facts (a courseware package's Standard · Status · Files …, a
   learner's Status · Score · Time on that module): as many columns as fit at
   9rem (158px) each — six across a wide page, two in the 480px drawer, one in
   a narrower box — instead of col-6 col-md-3 by the window, which kept four
   across inside the drawer at any desktop width. A fact that must span the row
   (a long bookmark) takes .tw-facts__full. Plain grid, not a container. */
.tw-facts {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
}

.tw-facts > .tw-facts__full {
  grid-column: 1 / -1;
}

/* The Ratings & Reviews summary — the average beside the 5→1 bars — sized by
   its own card, not the window: inside a narrow About column the average's
   third was ~90px and its stars wrapped. Stacked (average above the bars) below
   a 23.6rem (416px) row; the divider only exists while they sit side by side. */
.tw-rating-summary {
  container: tw-rating / inline-size;
}

.tw-rating-summary > .tw-rating-average {
  margin-bottom: 1rem;
}

@container tw-rating (min-width: 23.6rem) {
  .tw-rating-summary > .tw-rating-average {
    flex: 0 0 auto;
    width: 33.33333333%;
    margin-bottom: 0;
    border-inline-end: var(--bs-border-width) solid var(--bs-border-color);
  }

  .tw-rating-summary > .tw-rating-bars {
    flex: 0 0 auto;
    width: 66.66666667%;
  }
}
