/* =============================================================================
   cst-components.css — shared component CSS for what Tabler does not cover
   Phase 3 Step 5.

   Every component here is one that docs/inventory/components.md marks
   "needs original design". Anything Tabler already provides is NOT here — it
   is restyled through tokens in cst-tokens.css and specced in
   docs/style-guide/03-components.md.

   THE RULE FOR THIS FILE
     Zero colour, size or spacing literals. Every value is a token.
     docs/tools/check-no-literals.py enforces it and must pass before commit.
     The only allowances are 0, 1px (a hairline border — borders are 1px
     everywhere by definition), percentages, unitless numbers, and the
     Bootstrap breakpoints inside a media query, which CSS cannot tokenise.

     If a value has no token, add one to cst-tokens.css. Do not inline it.

   DENSITY
     Nothing here asks which density it is in. Components read --cst-text-base,
     --cst-table-row-height and friends; `.cst-dense` on an ancestor remaps
     those names (D15). One rule, both densities.

   SPECS
     docs/style-guide/04-patterns.md
   ============================================================================= */


/* =============================================================================
   1. DATA TABLE

   Designed against the two real tables named in the directive, not a fixture:

     company/roster/index.cfm  #rosterActive — Name, Email, Location, Seat,
                               Licenses, Progress, Last Active. Already carries
                               aria-sort and a <button> in the header, which is
                               the correct accessible shape, so this spec adopts
                               it rather than inventing one.
     admin/customers/index.cfm #maintbl — ID, Username, Name, Phone, State,
                               Active, Validated. No sort at all, a
                               presentational width="100%" attribute, and the
                               two status columns rendering bare "Y" / "N".

   `.card-table` (91 uses in admin, plus company's) is the assumed context: a
   table that fills a card and takes its outer border from the card.
   ============================================================================= */

/* GLOBAL TABLE TREATMENT (Phase 5, step 4 — developer's call).
   The header/body/hover rules below alias Tabler's `.table` onto the .cst-table
   look so every Tabler-classed data table across public, company and admin
   renders identically without adding .cst-table per page. Keyed on `.table`
   specifically: the course subsystem's slide tables and legacy layout tables are
   BARE <table> (no .table class) and are therefore untouched, as is anything
   relying on inline styles (email receipts). Only header/body/hover are aliased
   — the sub-element classes (__num/__status/__row--inactive/stack) stay opt-in.

   NB: the base rule is NOT aliased onto `.table`. Its `border-collapse:separate`
   (needed only for .cst-table's sticky-header variant) would double every cell
   border on `.table-bordered`; leaving Tabler's `collapse` in place keeps those
   grids at 1px. Non-bordered tables look identical under either model. */
.cst-table {
	width: 100%;
	border-collapse: separate;   /* so a sticky header can carry its own border */
	border-spacing: 0;
	/* 14px cells, not the 17px --cst-text-base body size: the type-scale comment
	   in cst-tokens.css marks table cells as the dense 14px step, and the header
	   already uses --cst-text-sm (14px). Base-size cells rendered a data table
	   noticeably too large. */
	font-size: var(--cst-text-sm);
	color: var(--cst-ink);
}

/* --- Header ---------------------------------------------------------------
   Sentence case, not uppercase: these are nouns, and the audit found column
   labels already written that way. Weight carries the rank instead. */
.cst-table > thead > tr > th,
.table > thead > tr > th {
	height: var(--cst-table-row-height);
	padding-inline: var(--cst-table-cell-pad-x);
	/* --tblr-light (#f6f8fb), NOT the brand --cst-band (#f2f2ec warm off-white):
	   most admin DataTables never adopted .cst-table and so show Tabler's own
	   light thead, leaving the few .cst-table tables looking tannish next to
	   them. Standardising on the light Tabler surface so every table header
	   matches (developer's call — --cst-band is intentionally abandoned for
	   tables here). */
	background: var(--tblr-light);
	color: var(--cst-ink);
	font-size: var(--cst-text-sm);
	font-weight: var(--cst-weight-bold);
	letter-spacing: var(--cst-tracking-none);
	/* Sentence case (03-components.md): weight carries the rank, not caps. Set
	   explicitly so it wins over Tabler's .table thead uppercase on tables that
	   also carry the bare .table class (Phase 5 auto-skins add .cst-table but
	   leave .table in place). */
	text-transform: none;
	text-align: left;
	white-space: nowrap;
	border-block-end: 1px solid var(--cst-border);
}

/* --- Body ------------------------------------------------------------------
   Zebra striping is off; hover is the row affordance. Striping plus hover
   makes the hovered row ambiguous, which is why .table-striped is deprecated
   in 03-components.md. */
.cst-table > tbody > tr > td,
.table > tbody > tr > td {
	height: var(--cst-table-row-height);
	padding-inline: var(--cst-table-cell-pad-x);
	border-block-start: 1px solid var(--cst-border-subtle);
	vertical-align: middle;
}

/* Hover uses the same tint as a selected row (--cst-accent-pale), per the
   developer's call — a cool green that sits with the light header, rather than
   the warm --cst-band. Hover and selected therefore read the same. */
.cst-table > tbody > tr:hover > td,
.table > tbody > tr:hover > td { background: var(--cst-accent-pale); }

/* A row that carries data-href is clickable whole (cst-datatables.js). Signal it
   with the pointer cursor; the green hover above already marks it. */
tr[data-href] { cursor: pointer; }

.cst-table > tbody > tr[aria-selected="true"] > td {
	background: var(--cst-accent-pale);
}

/* A row the user cannot act on. Not `disabled` — a row is not a control. */
.cst-table > tbody > tr.cst-table__row--inactive > td { color: var(--cst-muted); }

/* --- Numeric and status columns -------------------------------------------
   Numerals align on the decimal and use tabular figures so columns of digits
   scan as columns. The audit found `Seat` and `Licenses` centred in company's
   roster; centring numbers defeats comparison. */
.cst-table__num {
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.cst-table__status { white-space: nowrap; }

/* --- Stack (reflow) ---------------------------------------------------------
   Below `md` the rows become labelled blocks instead of scrolling sideways.
   Each cell supplies its own label via `data-label`; the first cell takes
   `.cst-table__stack-title` and leads the block without one.

   Specified in 03-components.md and 04-patterns.md §1 but never implemented
   here. The working version was an inline <style> block in
   includes/header/company_header.cfm (~line 155), so it reached company's
   pages only and was invisible to every CSS-file search — including the one
   that first reported it as an undefined no-op class. Ported to the component
   layer unchanged in behaviour, with its seven literals replaced by tokens.

   The header block stays until every company table carries this class; 19 of
   them still say `.table-stack`. Removing it before then would un-reflow them.

   COMPLEMENTARY TO .table-responsive, not an alternative to it. The guide's
   "don't use both" rule is about one table doing both things at one viewport.
   Here the reflow owns everything below md and the scroll fallback owns the
   widths above it where a dense 7-column table can still overrun. */
@media (max-width: 767.98px) {
	.cst-table-stack > thead { display: none; }

	/* `:not([hidden])` is load-bearing, not defensive. `display: block` on a
	   row overrides the user agent's `[hidden] { display: none }`, so a
	   filtered-out row would reappear at exactly the widths where the reflow
	   applies. Observed on the roster: the script's "no matches" row sat
	   visible under the list on a phone. */
	.cst-table-stack,
	.cst-table-stack > tbody,
	.cst-table-stack > tbody > tr:not([hidden]),
	.cst-table-stack > tbody > tr > td {
		display: block;
		width: 100%;
	}

	/* A hidden row must not leave its separator behind either. */
	.cst-table-stack > tbody > tr:not([hidden]) {
		border-block-end: 1px solid var(--cst-border);
		padding-block: var(--cst-space-2) var(--cst-space-3);
	}
	.cst-table-stack > tbody > tr:not([hidden]):last-child { border-block-end: 0; }

	/* Height comes from the row token at table density; a stacked block sizes
	   to its content instead. */
	.cst-table-stack > tbody > tr > td {
		height: auto;
		border: 0;
		padding-block: var(--cst-space-1);
		padding-inline: var(--cst-table-cell-pad-x);
		display: flex;
		align-items: baseline;
		gap: var(--cst-stack-gap);
		text-align: left;
	}

	.cst-table-stack > tbody > tr > td::before {
		content: attr(data-label);
		flex: 0 0 var(--cst-stack-label-width);
		font-size: var(--cst-text-caption);
		font-weight: var(--cst-weight-medium);
		letter-spacing: var(--cst-tracking-wide);
		text-transform: uppercase;
		color: var(--cst-muted);
	}

	/* No label, no gutter. `attr(data-label)` on a cell that has none resolves
	   to an empty string, which still lays out as an 8.5rem flex item and
	   indents the cell against every other row. Covers both cases that
	   legitimately carry no label: the leading title cell, and the
	   whole-table empty-state cell. */
	.cst-table-stack > tbody > tr > td:not([data-label])::before { content: none; }

	/* The first cell leads the block: larger, and no label above it. */
	.cst-table-stack > tbody > tr > td.cst-table__stack-title a {
		font-size: var(--cst-text-lg);
	}

	/* A spanning cell is a message about the table, not a field in a row, so
	   it keeps the block layout and its own padding. */
	.cst-table-stack > tbody > tr > td[colspan] {
		display: block;
		padding-inline: 0;
	}
}

/* --- Initially hidden, revealed by script ----------------------------------
   For a region a page hides on load and a script later reveals with jQuery
   .show() (which writes an inline `display`). Bootstrap's .d-none carries
   !important, so .show() cannot override it and the region never appears; this
   is a plain class of normal specificity that an inline style beats. Not a
   substitute for the `hidden` attribute — use `hidden` when a script toggles
   it with the attribute or the .hidden property, and this only when the
   reveal is jQuery .show()/.hide(). */
.cst-js-hidden { display: none; }

/* --- Bounded scrolling pane -------------------------------------------------
   A list long enough to need its own scroll inside a card. The two on the
   assign screens set max-height inline at 15rem and 380px; one height serves
   both, which is the point of having a class at all. */
.cst-scroll-pane {
	max-height: var(--cst-scroll-pane-max);
	overflow-y: auto;
}

/* --- Full-bleed page body ---------------------------------------------------
   Tabler's .page-body carries margin-top/bottom of --tblr-page-padding-y. That
   is right for a normal page, whose content sits in a card on the page
   background, and wrong for a full-bleed one, whose first child is a coloured
   band meant to start at the header's bottom rule. The margin showed as a strip
   of page background between the two, and the same strip again between the last
   band and the footer.

   templates/header.cfm has emitted this class on p_fullbleed pages since the
   band layout landed, but the rule was never written, so the modifier did
   nothing and the gap stayed. Only the marketing home page sets p_fullbleed
   today; every other page keeps Tabler's spacing untouched.

   Equal specificity to .page-body (one class each) - this wins on order, since
   cst-components.css loads after tabler.min.css. */
.page-body--flush {
	margin-block: 0;
}

/* --- Sticky purchase card ---------------------------------------------------
   A summary/purchase card that stays in view as the page beside it scrolls
   (store/product). Tabler's .sticky-top pins to top:0; this offsets it below
   the header instead. */
.cst-sticky-card {
	position: sticky;
	top: var(--cst-space-6);
}

/* --- Course-card cover image ------------------------------------------------
   A fixed-height, cover-cropped thumbnail at the top of a course card, so a
   row of cards aligns. Was style="height:140px;object-fit:cover" on the img. */
.cst-card-img {
	height: var(--cst-card-img-height);
	object-fit: cover;
}

/* --- Course-card thumbnail --------------------------------------------------
   The same picture as .cst-card-img, but beside the details instead of above
   them (My Courses). A square box with both dimensions fixed, so the text
   column starts at the same x on every card in the grid and the titles line
   up; object-fit crops whatever shape the source image is.

   flex-shrink:0 is load-bearing, not defensive. This sits in a flex row next
   to a text column that can be long, and a flex item's default is to shrink -
   without it a card with a wordy title squeezes its own thumbnail narrower
   than its neighbours'. */
.cst-card-thumb {
	flex: 0 0 auto;
	width: var(--cst-card-thumb-size);
	height: var(--cst-card-thumb-size);
	object-fit: cover;
	border-radius: var(--cst-radius);
}

/* --- Photo-edit badge -------------------------------------------------------
   The small camera button pinned to the corner of an editable avatar
   (profile page). A <label> for a visually-hidden file input, so clicking it
   opens the picker. Overlaps the avatar, so it is absolutely positioned within
   an ancestor that establishes positioning. */
.cst-photo-badge {
	position: absolute;
	inset-block-end: var(--cst-space-1);
	inset-inline-end: var(--cst-space-1);
	width: var(--cst-photo-badge-size);
	height: var(--cst-photo-badge-size);
	border-radius: 50%;
	background: var(--cst-surface);
	border: 1px solid var(--cst-border);
	box-shadow: var(--cst-shadow-overlay);
	display: flex;
	align-items: center;
	justify-content: center;
	cursor: pointer;
	color: var(--cst-ink-soft);
	font-size: var(--cst-text-sm);
}

/* --- Icon medallion ---------------------------------------------------------
   A circular badge holding an icon rather than initials. Built by rebinding
   Tabler's own avatar variables instead of declaring width/height/background,
   so it keeps every behaviour .avatar already has. Use as
   `class="avatar rounded-circle cst-medallion"`.

   Recurs: the renew screen has one, and includes/footer/company_footer.cfm
   builds three more of the same shape for its social links. */
.cst-medallion {
	--tblr-avatar-size: var(--cst-medallion-size);
	--tblr-avatar-bg:   var(--cst-ink);
	--tblr-avatar-icon-size: var(--cst-text-2xl);
	color: var(--cst-surface);
}

/* --- A control that should not stretch --------------------------------------
   A field sitting in a flex toolbar row, where `width: 100%` would push the
   controls beside it off the line. The date-range picker on the subscription
   history screen set max-width inline. */
.cst-field-inline { max-width: var(--cst-measure-xs); }

/* --- Company logo preview --------------------------------------------------
   company/info/ previews the logo exactly as the header will render it, and
   the two cases are shaped differently: a real upload keeps its aspect ratio
   inside a height cap, the placeholder is a fixed square. That was a ternary
   building one of two inline style strings — two states, so two classes. */
.cst-logo-preview {
	max-width: 100%;
	max-height: var(--cst-space-9);
	width: auto;
	height: auto;
}

.cst-logo-preview--placeholder {
	width: var(--cst-logo-placeholder-size);
	height: var(--cst-logo-placeholder-size);
	max-height: none;
	object-fit: contain;
	border: 1px solid var(--cst-border);
	border-radius: var(--cst-radius);
	background: var(--cst-surface);
}

/* --- Language card ---------------------------------------------------------
   Admin's content editors (course/product versions, tests, evaluations) show
   one card per language, colour-coded by the language's own DB colour so an
   editor scanning several cards can tell them apart. The colour is dynamic
   data, so it arrives as the --cst-lang-rgb custom property set inline on the
   card; these classes consume it. The tint is that colour at ~13% (the old
   markup used an "22" hex-alpha suffix; color-mix expresses the same without
   string-building a colour). */
.cst-lang-card { border-inline-start: 4px solid var(--cst-lang-rgb); }

.cst-lang-card__tint {
	background-color: color-mix(in srgb, var(--cst-lang-rgb) 13%, transparent);
}

/* --- Nested rows -----------------------------------------------------------
   A course listed under its section is indented one step from the section
   heading above it. company/site/subscription/customer/ set this as an inline
   `padding-left` computed to "1.5rem" or "0" — two states, which is a class,
   not a value.

   Indent is added to the cell's normal inline padding rather than replacing
   it, so the first-child padding .card-table applies still holds. */
.cst-table__indent { padding-inline-start: calc(var(--cst-table-cell-pad-x) + var(--cst-space-5)); }

/* --- Sort ------------------------------------------------------------------
   The whole header is the control, so the hit area is the cell. Company's
   existing <button class="rs-sort"> pattern, kept, restyled, and given an
   affordance that does not depend on colour. */
.cst-table__sort {
	display: inline-flex;
	align-items: center;
	gap: var(--cst-space-1);
	width: 100%;
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	color: inherit;
	text-align: inherit;
	cursor: pointer;
}

/* Codepoints verified against @tabler/icons-webfont 3.19.0, the build the
   headers load. The first three written here were from a different
   version and all resolved to something else: \ea4d is
   ti-building-hospital, and the ascending arrow was pointing down
   because \ea5f is ti-chevron-DOWN. Check the font, not the memory. */
.cst-table__sort::after {
	font-family: "tabler-icons";
	content: "\eb1d";                       /* ti-selector — unsorted */
	opacity: 0.35;
	font-size: var(--cst-text-sm);
}

.cst-table > thead > tr > th[aria-sort="ascending"] .cst-table__sort::after {
	content: "\ea62";                       /* ti-chevron-up */
	opacity: 1;
}

.cst-table > thead > tr > th[aria-sort="descending"] .cst-table__sort::after {
	content: "\ea5f";                       /* ti-chevron-down */
	opacity: 1;
}

/* --- Selection column ------------------------------------------------------ */
.cst-table__select {
	width: var(--cst-row-action-size);
	padding-inline-start: var(--cst-table-cell-pad-x);
	padding-inline-end: 0;
}

/* --- Row actions (D19) -----------------------------------------------------
   .btn-icon.btn-sm is the primitive. This wrapper keeps a row's actions on one
   line, right-aligned, and stops the column growing with the button count. */
.cst-table__actions {
	width: 0;                                /* shrink-to-fit */
	white-space: nowrap;
	text-align: right;
	padding-inline-end: var(--cst-table-cell-pad-x);
}

.cst-table__actions > .btn + .btn { margin-inline-start: var(--cst-space-1); }

/* Actions stay visible. Revealing them on row hover hides them from keyboard
   and touch users entirely, which is why this is a rule and not an option. */

/* --- Sticky header ---------------------------------------------------------
   Opt-in: only useful where the table is taller than the viewport. */
.cst-table--sticky > thead > tr > th {
	position: sticky;
	top: 0;
	z-index: 1;
}

/* --- Footer ---------------------------------------------------------------- */
.cst-table-footer {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--cst-stack-gap);
	padding: var(--cst-stack-gap) var(--cst-table-cell-pad-x);
	border-block-start: 1px solid var(--cst-border);
	color: var(--cst-muted);
	font-size: var(--cst-text-sm);
}

/* --- Loading ---------------------------------------------------------------
   A skeleton, not a spinner: the shape of the result is known, so showing it
   avoids a layout jump when the data lands. See §7. */
.cst-table__loading > td { padding-block: var(--cst-space-2); }


/* =============================================================================
   2. DATATABLES ADAPTER

   DataTables 1.13.8 injects its own length, filter, info and paginate controls
   (docs/inventory/unthemeable.md §9). Since D30 the headers load the BOOTSTRAP
   5 INTEGRATION build, which renders those controls as Bootstrap components
   that Tabler already themes — so almost nothing needs restyling here. What is
   left is the wrapper typography and one suppression.

   Scoped to .cst-dt so it only applies where we opt in — DataTables is on 52
   admin call sites and this must not change them until Phase 4 says so.
   ============================================================================= */

.cst-dt .dataTables_wrapper { color: var(--cst-ink); }

.cst-dt .dataTables_length,
.cst-dt .dataTables_filter,
.cst-dt .dataTables_info,
.cst-dt .dataTables_paginate {
	font-size: var(--cst-text-sm);
	color: var(--cst-muted);
}

/* The control rules that were here are GONE, and their absence is the point
   of D30.

   Against the plain build, the filter input, length select and pager carried
   DataTables' own classes and had to be restyled here by hand. The Bootstrap 5
   integration renders the same controls as real Bootstrap components —
   `.form-control.form-control-sm` on the filter, `.form-select.form-select-sm`
   on the length menu, and `<ul class="pagination"><li class="page-item">
   <a class="page-link">` for the pager — every one of which Tabler already
   themes from `--tblr-*`. Keeping the old rules would have overridden that
   with a hand-rolled copy, which is the thing this phase is removing.

   Two of them would also have been silently wrong: `.paginate_button` is now
   the `<li>` rather than the control, so the padding and border would have
   drawn a second box around the real `.page-link`; and the current page is
   marked `.active`, not `.current`, so that rule matched nothing at all. */

/* DataTables writes its own sort indicators as background images on the th.
   Suppress them; the ::after affordance in §1 is the spec, and it responds to
   aria-sort, which DataTables also maintains. */
.cst-dt table.dataTable > thead > tr > th { background-image: none; }

/* scrollX sizes the table to its CONTENT width and writes that as an inline
   width, so a table whose columns don't fill the card is left narrower than its
   container. Force the scroll head/body tables to at least the container width —
   they fill when narrow, and still scroll horizontally when the content is
   genuinely wider (inline width > 100%). A data table should always span its
   container.

   scrollX is now OFF by default (cst-datatables.js), so this applies only to the
   handful of very wide report tables that opt back in. Keep it: those are
   exactly the tables that get filtered down to a few narrow rows and would
   otherwise collapse. */
.dataTables_scrollHead table.dataTable,
.dataTables_scrollBody table.dataTable {
	min-width: 100%;
}


/* =============================================================================
   3. FORM LAYOUT AND VALIDATION (D16)

   D16: Tabler stock controls plus tokens — no original control design. What is
   original is the LAYOUT and the VALIDATION DISPLAY, because the application
   has 577 form labels in admin alone with no shared vertical rhythm, and
   admin's .required / .help-text / .error-message vocabulary is the only
   validation display that exists anywhere.

   Those three class names are kept (they are the only prior art) and renamed to
   the cst- prefix, extended with success and inline-hint states.
   ============================================================================= */

/* --- The row ---------------------------------------------------------------
   One field is one .cst-field. The rhythm comes from the container, not from a
   margin on each field, so a field can be added or removed without touching
   its neighbours. */
.cst-form {
	display: flex;
	flex-direction: column;
	gap: var(--cst-stack-gap);
}

.cst-field {
	display: flex;
	flex-direction: column;
	gap: var(--cst-space-1);
}

/* --- Label ---------------------------------------------------------------- */
.cst-label {
	font-size: var(--cst-text-sm);
	font-weight: var(--cst-weight-bold);
	color: var(--cst-ink);
}

/* Required marker. An asterisk alone is not an accessible signal, so the
   control also carries `required` and the legend explains the convention once
   per form — see 04-patterns.md §2. */
.cst-required {
	margin-inline-start: var(--cst-space-1);
	color: var(--tblr-danger-text-emphasis);
	font-weight: var(--cst-weight-bold);
}

/* Optional marker, for the inverse convention: where most fields are optional,
   marking the few required ones is noise. Use one convention per form. */
.cst-optional {
	margin-inline-start: var(--cst-space-1);
	color: var(--cst-muted);
	font-weight: var(--cst-weight-normal);
}

/* --- Help text -------------------------------------------------------------
   Sits ABOVE the control when it is an instruction the user needs before
   typing, and below when it is a note about what was typed. The default is
   below; .cst-help-text--lead moves it above. */
.cst-help-text {
	font-size: var(--cst-text-caption);
	color: var(--cst-muted);
}

.cst-help-text--lead { order: -1; }

/* --- Validation messages ---------------------------------------------------
   State colours are FILLS AND BORDERS, never text (01-foundations §1.4), so
   both messages use the -text-emphasis variants, which clear 10:1 on white. */
.cst-error-message,
.cst-success-message {
	display: flex;
	align-items: flex-start;
	gap: var(--cst-space-1);
	font-size: var(--cst-text-caption);
}

.cst-error-message   { color: var(--tblr-danger-text-emphasis); }
.cst-success-message { color: var(--tblr-success-text-emphasis); }

/* A field in error gets a left rule as well as the message, so the error is
   locatable when scanning a long form and not carried by colour alone. */
.cst-field--invalid {
	padding-inline-start: var(--cst-space-3);
	border-inline-start: 1px solid var(--tblr-danger);
}

/* --- Horizontal variant ----------------------------------------------------
   Label beside the control, for the dense admin filter bars where a stacked
   label doubles the height of a one-line form. Only at md and up; below that
   it stacks, because a 1fr/2fr split at phone width leaves no room for either. */
.cst-form--horizontal .cst-field { gap: var(--cst-space-1); }

@media (min-width: 768px) {
	.cst-form--horizontal .cst-field {
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
		align-items: baseline;
		column-gap: var(--cst-space-4);
	}
	.cst-form--horizontal .cst-label { text-align: right; }
	/* Messages and help text align with the control, not the label. */
	.cst-form--horizontal .cst-help-text,
	.cst-form--horizontal .cst-error-message,
	.cst-form--horizontal .cst-success-message { grid-column: 2; }
}

/* --- Inline variant --------------------------------------------------------
   Controls on one line, labels visually hidden. For a filter bar where each
   control's purpose is obvious from its content. The label element still
   exists — .visually-hidden, never removed. */
.cst-form--inline {
	flex-direction: row;
	flex-wrap: wrap;
	align-items: flex-end;
}

/* --- Fieldset -------------------------------------------------------------- */
.cst-fieldset {
	display: flex;
	flex-direction: column;
	gap: var(--cst-stack-gap);
	padding: 0;
	margin: 0;
	border: 0;
}

.cst-legend {
	padding: 0;
	margin-block-end: var(--cst-space-2);
	font-size: var(--cst-text-lg);
	font-weight: var(--cst-weight-bold);
	color: var(--cst-ink);
}

/* --- Form actions ---------------------------------------------------------
   Secondary first, primary last, right-aligned: the primary sits nearest the
   thumb and nearest the end of the reading order. Reverses to full-width
   stacked below sm. */
.cst-form-actions {
	display: flex;
	justify-content: flex-end;
	gap: var(--cst-space-3);
	padding-block-start: var(--cst-stack-gap);
	border-block-start: 1px solid var(--cst-border-subtle);
}

@media (max-width: 575.98px) {
	.cst-form-actions { flex-direction: column-reverse; }
	.cst-form-actions > .btn { width: 100%; }
}


/* =============================================================================
   4. EMPTY STATES (D21)

   One pattern, three sizes. Zero exist across 279 admin pages today; six exist
   application-wide.

   Anatomy is fixed: icon, one-line heading, one-line body, optional single
   primary action. Nothing else — an empty state that needs two paragraphs is
   documentation in the wrong place.
   ============================================================================= */

.cst-empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	gap: var(--cst-space-2);
	color: var(--cst-muted);
}

.cst-empty__icon  { color: var(--cst-border); }
.cst-empty__title { color: var(--cst-ink); font-weight: var(--cst-weight-bold); }
.cst-empty__body  { max-width: var(--cst-measure-sm); }
.cst-empty__action { margin-block-start: var(--cst-space-2); }

/* Size 1 — inside a table body. Must not change the table's width, so it is
   the cell that spans, not this. Kept short: a tall empty state in a table
   pushes the footer off screen. */
.cst-empty--table {
	padding-block: var(--cst-space-7);
}
.cst-empty--table .cst-empty__title { font-size: var(--cst-text-base); }
.cst-empty--table .cst-empty__body  { font-size: var(--cst-text-sm); }

/* Size 2 — inside a card body. */
.cst-empty--card {
	padding-block: var(--cst-space-8);
}
.cst-empty--card .cst-empty__title { font-size: var(--cst-text-lg); }
.cst-empty--card .cst-empty__body  { font-size: var(--cst-text-base); }

/* Size 3 — a whole page or region with nothing in it. */
.cst-empty--page {
	padding-block: var(--cst-space-9);
}
.cst-empty--page .cst-empty__title { font-size: var(--cst-text-xl); }
.cst-empty--page .cst-empty__body  { font-size: var(--cst-text-base); }


/* =============================================================================
   5. TAB OVERFLOW (D20)

   Horizontal scroll with edge fades at lg and up; a <select> below lg.

   Admin's customer sub-nav is 12 tabs with no responsive treatment at all —
   below md it wraps into a block of links. Twelve is past the point where a
   scrolling strip helps, which is why 04-patterns.md §4 also proposes a
   grouping.
   ============================================================================= */

.cst-tabs-scroll { position: relative; }

.cst-tabs-scroll__viewport {
	display: flex;
	overflow-x: auto;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
}

.cst-tabs-scroll__viewport::-webkit-scrollbar { display: none; }

/* The fades are the only affordance that there is more to see, so they are
   driven by data attributes the script sets — not by :hover, which touch and
   keyboard users never trigger. */
.cst-tabs-scroll::before,
.cst-tabs-scroll::after {
	content: "";
	position: absolute;
	inset-block: 0;
	width: var(--cst-space-7);
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.15s;
	z-index: 1;
}

.cst-tabs-scroll::before {
	inset-inline-start: 0;
	background: linear-gradient(to right, var(--cst-surface), transparent);
}

.cst-tabs-scroll::after {
	inset-inline-end: 0;
	background: linear-gradient(to left, var(--cst-surface), transparent);
}

.cst-tabs-scroll[data-overflow-start="true"]::before { opacity: 1; }
.cst-tabs-scroll[data-overflow-end="true"]::after    { opacity: 1; }

/* Below lg the strip is replaced by a select. Both are always in the DOM; only
   one is displayed, so no JS is needed to switch and the select still submits
   if the script never runs. */
.cst-tabs-select { display: block; }
.cst-tabs-scroll { display: none; }

@media (min-width: 992px) {
	.cst-tabs-select { display: none; }
	.cst-tabs-scroll { display: block; }
}


/* =============================================================================
   6. STAT TILE

   Company's pattern, tokenised. .stat-value in cst-utilities.css already
   provides the figure sizes; this is the tile around it, which company builds
   ad hoc on four dashboards.
   ============================================================================= */

.cst-stat {
	display: flex;
	flex-direction: column;
	gap: var(--cst-space-1);
}

.cst-stat__label {
	font-size: var(--cst-text-caption);
	font-weight: var(--cst-weight-bold);
	letter-spacing: var(--cst-tracking-wide);
	text-transform: uppercase;
	color: var(--cst-muted);
}

.cst-stat__value {
	font-size: var(--cst-text-2xl);
	font-weight: var(--cst-weight-bold);
	line-height: var(--cst-leading-none);
	letter-spacing: var(--cst-tracking-tight);
	font-variant-numeric: tabular-nums;
	color: var(--cst-ink);
}

/* The unit or denominator rides with the figure at body size, so "18" reads as
   the number and "of 24" as its context. */
.cst-stat__unit {
	font-size: var(--cst-text-base);
	font-weight: var(--cst-weight-normal);
	letter-spacing: var(--cst-tracking-none);
	color: var(--cst-muted);
}

/* Direction of travel. Never the only signal — the delta text carries it too. */
.cst-stat__delta { font-size: var(--cst-text-sm); }
.cst-stat__delta--up   { color: var(--tblr-success-text-emphasis); }
.cst-stat__delta--down { color: var(--tblr-danger-text-emphasis); }
.cst-stat__delta--flat { color: var(--cst-muted); }

/* A row of tiles. Wraps rather than scrolling: a dashboard figure that is off
   screen is not a dashboard figure. */
.cst-stat-row {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
	gap: var(--cst-stack-gap);
}


/* =============================================================================
   7. CHART WRAPPER (D22)

   ApexCharts via Tabler's integration. The series palette, grid, axis, label
   and tooltip colours live in cst-tokens.css; the options object that feeds
   them to ApexCharts is assets/js/cst-charts.js.

   This is the frame: a card header, a fixed-height container so the card does
   not jump as data loads, and a legend that reads as text.
   ============================================================================= */

.cst-chart {
	display: flex;
	flex-direction: column;
	gap: var(--cst-stack-gap);
}

/* A height is required. ApexCharts renders into whatever box it is given, and
   an auto-height container collapses to nothing before the first render. */
.cst-chart__canvas {
	position: relative;
	min-height: var(--cst-space-9);
	width: 100%;
}

/* --- Legend ---------------------------------------------------------------
   Rendered as markup rather than by ApexCharts, so it is real text: selectable,
   translatable, and reachable by a screen reader. ApexCharts' own legend is
   canvas-adjacent SVG with no reliable reading order. */
.cst-chart__legend {
	display: flex;
	flex-wrap: wrap;
	gap: var(--cst-space-2) var(--cst-space-4);
	font-size: var(--cst-text-caption);
	color: var(--cst-muted);
}

.cst-chart__key {
	display: inline-flex;
	align-items: center;
	gap: var(--cst-space-1);
}

.cst-chart__swatch {
	width: var(--cst-space-3);
	height: var(--cst-space-3);
	border-radius: var(--cst-radius-sm);
	flex: none;
}

.cst-chart__swatch--1 { background: var(--cst-chart-1); }
.cst-chart__swatch--2 { background: var(--cst-chart-2); }
.cst-chart__swatch--3 { background: var(--cst-chart-3); }
.cst-chart__swatch--4 { background: var(--cst-chart-4); }
.cst-chart__swatch--5 { background: var(--cst-chart-5); }
.cst-chart__swatch--6 { background: var(--cst-chart-6); }
.cst-chart__swatch--7 { background: var(--cst-chart-7); }
.cst-chart__swatch--8 { background: var(--cst-chart-8); }

/* --- Series fills outside the canvas ---------------------------------------
   A stacked .progress that encodes the SAME series as a chart on the same
   screen has to use the same slots, or the page says two different things with
   two different colours. company/roster/person/ does exactly this: a donut of
   Completed / In progress / Not started, and a per-product progress bar
   splitting the first two.

   The WIDTH of a progress bar stays an inline style — it is a data value, it
   cannot be a class, and 03-components.md's Progress anatomy specifies
   `.progress-bar` with `style="width: N%"`. Only the colour comes from here. */
.cst-chart-fill--1 { background-color: var(--cst-chart-1); }
.cst-chart-fill--2 { background-color: var(--cst-chart-2); }
.cst-chart-fill--8 { background-color: var(--cst-chart-8); }

/* --- Text equivalent -------------------------------------------------------
   A chart is an image of data. The table behind it is not optional; it is
   visually hidden by default and revealed by the toggle, so the data is always
   reachable without a screen reader too. */
.cst-chart__data[hidden] { display: none; }


/* =============================================================================
   8. SKELETON

   For a region whose SHAPE is known before its content — a table body, a list
   of cards. A spinner says "something is happening"; a skeleton says "this is
   what is arriving", and avoids the layout jump when it does.

   Use a spinner instead when the shape is unknown or the wait is under a
   second.
   ============================================================================= */

.cst-skeleton {
	background: var(--cst-band);
	border-radius: var(--cst-radius-sm);
	color: transparent;
	user-select: none;
}

/* Sized from the text step it stands in for, so a skeleton line occupies
   exactly the height its content will. */
.cst-skeleton--text  { height: var(--cst-text-base); }
.cst-skeleton--title { height: var(--cst-text-lg); }
.cst-skeleton--block { height: var(--cst-table-row-height); }

/* Varying widths so a stack of lines reads as prose rather than as a bar
   chart. Applied by nth-child so the markup stays clean. */
.cst-skeleton-lines > .cst-skeleton { margin-block-end: var(--cst-space-2); }
.cst-skeleton-lines > .cst-skeleton:nth-child(3n+1) { width: 92%; }
.cst-skeleton-lines > .cst-skeleton:nth-child(3n+2) { width: 100%; }
.cst-skeleton-lines > .cst-skeleton:nth-child(3n+3) { width: 64%; }

.cst-skeleton--pulse { animation: cst-skeleton-pulse 1.4s ease-in-out infinite; }

@keyframes cst-skeleton-pulse {
	0%, 100% { opacity: 1; }
	50%      { opacity: 0.55; }
}

/* The global prefers-reduced-motion block in cst-public.css already disables
   animation; this is belt-and-braces for a page that does not load that file. */
@media (prefers-reduced-motion: reduce) {
	.cst-skeleton--pulse { animation: none; }
}


/* =============================================================================
   9. SIDEBAR / MULTI-LEVEL NAV

   The learner area is the only section with a sidebar today
   (templates/account_sidenav.cfm — bespoke .snlink and .largerText, styled in
   an 18-line inline block). This replaces it and gives admin and company a
   pattern for the day a section grows past a top navbar.

   Two levels only. A third level is a page, not a nav item.
   ============================================================================= */

.cst-sidenav {
	display: flex;
	flex-direction: column;
	gap: var(--cst-space-1);
}

.cst-sidenav__section {
	padding-inline: var(--cst-space-3);
	padding-block: var(--cst-space-2) var(--cst-space-1);
	font-size: var(--cst-text-caption);
	font-weight: var(--cst-weight-bold);
	letter-spacing: var(--cst-tracking-wide);
	text-transform: uppercase;
	color: var(--cst-muted);
}

.cst-sidenav__link {
	display: flex;
	align-items: center;
	gap: var(--cst-space-2);
	min-height: var(--cst-table-row-height);
	padding-inline: var(--cst-space-3);
	border-radius: var(--cst-radius);
	color: var(--cst-ink);
	font-size: var(--cst-text-base);
	text-decoration: none;
}

.cst-sidenav__link:hover { background: var(--cst-band); }

/* Current page. Carries a left rule as well as the fill, so the state does not
   rest on colour alone. aria-current="page" is the source of truth. */
.cst-sidenav__link[aria-current="page"] {
	background: var(--cst-accent-pale);
	color: var(--cst-accent-hover);
	font-weight: var(--cst-weight-bold);
	box-shadow: inset var(--cst-space-1) 0 0 0 var(--cst-accent);
}

/* Second level. Indented to the depth of the parent's icon so the hierarchy
   reads without a connector line. */
.cst-sidenav__sub {
	display: flex;
	flex-direction: column;
	gap: var(--cst-space-1);
	margin-inline-start: var(--cst-space-6);
}

.cst-sidenav__sub .cst-sidenav__link { font-size: var(--cst-text-sm); }


/* =============================================================================
   10. PRINT

   Certificates, receipts and reports print. Nothing in the application has a
   print treatment beyond `d-print-none` (175 uses in admin, 3 in company),
   which only hides chrome — it does not make the remaining page printable.
   ============================================================================= */

@media print {
	/* Ink on paper. Backgrounds do not print reliably and cost the reader
	   toner for no information. */
	.cst-print-reset,
	.cst-print-reset * {
		background: transparent;
		color: var(--cst-ink);
		box-shadow: none;
	}

	/* A card's border is its only remaining definition once the fill is gone. */
	.cst-print-reset .card { border: 1px solid var(--cst-ink); }

	/* Tables must not break a row across pages, and the header repeats. */
	.cst-table { break-inside: auto; }
	.cst-table > thead { display: table-header-group; }
	.cst-table > tbody > tr { break-inside: avoid; }

	/* Anything interactive is meaningless on paper. */
	.cst-print-hide,
	.cst-table__actions,
	.cst-table__select,
	.cst-form-actions { display: none; }

	/* A link's destination is invisible in print, so print it. Skips in-page
	   and javascript: hrefs, which would be noise. */
	.cst-print-reset a[href^="http"]::after { content: " (" attr(href) ")"; }

	.cst-print-break-before { break-before: page; }
	.cst-print-break-avoid  { break-inside: avoid; }
}


/* =============================================================================
   11. BACK-OFFICE NAVBAR (D35)

   The manager and admin header bars, on a light ground matching public's.

   Both were `--cst-ink` navy with white text: company set it as an inline
   style on the <header>, admin as `.navbar-lms` in admin_styles.css. Two
   copies of one treatment, neither reachable from the token layer in any
   useful way, and the pair of them disagreed with the public header on the
   same site.

   Defined once here because both back-office headers already load this file,
   and because Step 7 consolidates the three templates — having the styling
   already shared makes that a template merge rather than a style merge.

   Public's `.cst-hdr` in cst-header.css is NOT folded in. It is a different
   bar with a search field, a cart and a taller fixed height; what it shares
   with these two is the palette, which comes from the same tokens either way.

   CONTRAST, all against --cst-surface #fff:
     --cst-ink       #16263c   15.3:1   brand label, user name
     --cst-ink-soft  #34486a    9.1:1   nav links, icons, toggler
     --cst-accent    #006624    7.4:1   hover and active
   The old bar carried white on #16263c at the same 15.3:1, so nothing loses
   contrast in the swap.
   ============================================================================= */

.cst-navbar {
	background: var(--cst-surface);
	border-bottom: 1px solid var(--cst-border);
	min-height: var(--cst-navbar-min-height);
}

/* The brand lockup: logo plus, on company, a "Manager Portal" label that was
   .text-white in the markup. */
.cst-navbar .navbar-brand { color: var(--cst-ink); }

/* One logo height for all three headers (02-chrome.md §1, D35). Was 34px
   (public, natural), unset (company) and a triple-!important 45px (admin via
   .navbar-lms). 40px is the settled compromise, matching --cst-table-row-height.
   No !important needed now that .navbar-lms is gone. */
.cst-brand-logo { height: 40px; width: auto; max-width: none; }

/* Below the md breakpoint the logo has to be capped by WIDTH, not height.
   CSTLogo.png is 1108x148 - a very wide wordmark - so at height:40px it renders
   299.5px wide. The navbar container is .container-xl.flex-nowrap, so nothing in
   the bar may wrap, and max-width:none above stops the image shrinking: logo
   (299.5) + brand margin (8) + cart (~40 + 16) + toggler (~46) + the container's
   own 8px gutters came to ~425px against a 390px phone. That ~35px of overflow
   is what let every public page scroll and drift sideways on a phone.

   min() rather than a flat cap because a flat one still overflows the narrow
   end: 220px fits a 390px screen but not a 320px one. 52vw keeps the lockup
   proportional to whatever the screen actually is, and the px value stops it
   growing back to full size just under the breakpoint. height:auto lets the
   aspect ratio follow the width; min-width:0 on the anchor is what allows the
   flex item to give up the space at all. */
@media (max-width: 767.98px) {
	.cst-navbar .navbar-brand { min-width: 0; }
	.cst-brand-logo { height: auto; width: auto; max-width: min(220px, 52vw); }
}

.cst-navbar .nav-link,
.cst-navbar .nav-link-icon { color: var(--cst-ink-soft); }

/* Hover and active are the same pair public uses: accent text on the band.
   The old bar tinted with rgba(255,255,255,.1) and .15, which only reads on a
   dark ground. */
.cst-navbar .nav-link:hover,
.cst-navbar .nav-link:focus-visible {
	color: var(--cst-accent);
	background-color: var(--cst-band);
	border-radius: var(--cst-radius);
}

.cst-navbar .nav-link.active,
.cst-navbar .nav-item.active .nav-link,
.cst-navbar .nav-item.show .nav-link {
	color: var(--cst-accent);
	background-color: var(--cst-band);
	border-radius: var(--cst-radius);
}

.cst-navbar .navbar-toggler {
	border-color: var(--cst-border);
	color: var(--cst-ink-soft);
}

/* Tabler 1.0.0 does NOT draw the hamburger from --tblr-navbar-toggler-icon-bg,
   despite declaring it. It redefines .navbar-toggler-icon further down as a
   2px `background: currentColor` bar with ::before and ::after for the other
   two strokes — which is also how it animates into a cross. So the glyph
   simply inherits `color` from .navbar-toggler above, and needs no rule of
   its own to recolour.

   Company's markup carried `filter: invert(1)` to make that dark bar light
   against the navy header. The filter is gone with the navy; this only stops
   it coming back. */
.cst-navbar .navbar-toggler-icon { filter: none; }

.cst-navbar .avatar {
	background-color: var(--cst-band);
	color: var(--cst-ink-soft);
}

.cst-navbar .navbar-user-name { color: var(--cst-ink); }

/* --- The mobile slide-in panel --------------------------------------------
   Below md (matching navbar-expand-md) the collapsed nav becomes a full-height
   overlay that slides in from the right. Bootstrap's collapse plugin drives it;
   header.cfm's JS toggles body.cst-nav-open for the backdrop + scroll lock.

   Recreated here (rescoped from the old #cst-navbar id to the shared
   .cst-navbar-panel class) after the source rules were lost with cst-header.css
   in Stage 6 — without them Bootstrap's default .navbar-collapse jammed the
   panel to the right and it was unreachable on a phone. */
@media (max-width: 767.98px) {
	.cst-navbar-panel.collapse,
	.cst-navbar-panel.collapsing {
		position: fixed;
		top: 0;
		right: 0;
		bottom: 0;
		width: 80%;
		max-width: 320px;
		z-index: 1045;
		background-color: var(--cst-surface);
		border-inline-start: 1px solid var(--cst-border);
		overflow-y: auto;
		box-shadow: -2px 0 12px rgb(0 0 0 / 18%);
	}

	/* .collapse hides via display:none; keep it visible while collapsing so the
	   slide has something to animate. */
	.cst-navbar-panel.collapsing {
		display: block !important;
		transform: translateX(100%);
		transition: transform .25s ease-out;
	}
	.cst-navbar-panel.collapse:not(.show) { display: none; }
	.cst-navbar-panel.collapse.show { display: block; transform: translateX(0); }

	body.cst-nav-open { overflow: hidden; }
	body.cst-nav-open::after {
		content: '';
		position: fixed;
		inset: 0;
		background: rgb(0 0 0 / 50%);
		z-index: 1040;
	}

	/* The panel's own heading row. Was .border-white .border-opacity-25. */
	.cst-navbar-panel__head {
		border-block-end: 1px solid var(--cst-border);
		color: var(--cst-ink);
	}

	/* In the mobile slide-in, Bootstrap forces an in-navbar .dropdown-menu to
	   transparent so the bar shows through, which would leave items on the
	   panel's own surface. Force them to the panel's ink/accent scheme. Only
	   admin currently has nav-group dropdowns, but this belongs to the shared
	   header (migrated from .navbar-lms in admin_styles.css, Step 6e). */
	.cst-navbar .navbar-nav .dropdown-menu {
		background-color: transparent !important;
		border: none;
		box-shadow: none;
		padding-left: 1rem;
	}
	.cst-navbar .navbar-nav .dropdown-menu .dropdown-item {
		color: var(--cst-ink-soft) !important;
	}
	.cst-navbar .navbar-nav .dropdown-menu .dropdown-item:hover,
	.cst-navbar .navbar-nav .dropdown-menu .dropdown-item:focus,
	.cst-navbar .navbar-nav .dropdown-menu .dropdown-item.active {
		color: var(--cst-accent) !important;
		background-color: var(--cst-band) !important;
	}
}


/* =============================================================================
   12. TOAST

   The feedback contract's "something happened" channel (02-chrome.md §6):
   toast for save and completion, modal for destructive confirmation, inline
   validation for a field, alert for a region in an error state.

   SEVERITY IS A BORDER AND AN ICON, NOT A TINTED SURFACE

   Admin's showToast() mapped severity onto Tabler's literal tint classes —
   bg-blue-lt / bg-green-lt / bg-yellow-lt / bg-red-lt — none of which route
   through the token layer, so every admin toast was off-brand by
   construction. It also meant the body text sat on a different background per
   severity, with a different contrast ratio each time.

   Here the surface is always --cst-toast-bg and severity is carried by the
   left border and the icon, so the text keeps one known ratio.

   WHY THE -text-emphasis VARIANTS

   Measured against white, Tabler's stock state colours are not safe for a
   3px border or an icon, both of which are graphical objects needing 3:1
   under WCAG 1.4.11:

       --tblr-success   #2fb344   2.74:1   FAILS
       --tblr-warning   #f76707   3.04:1   scrapes
       --tblr-info      #4299e1   3.05:1   scrapes
       --tblr-danger    #d63939   4.66:1   passes

   The -text-emphasis variants clear 10:1 across all four, so they are used
   for both the accent and the glyph. One rule, four severities, no exception
   for the one that fails.
   ============================================================================= */

.cst-toast {
	background: var(--cst-toast-bg);
	border: 1px solid var(--cst-toast-border);
	border-radius: var(--cst-radius);
	box-shadow: var(--cst-shadow-toast);
	color: var(--cst-ink);
	font-size: var(--cst-text-sm);
	/* The accent lives on the inline-start edge; declared here so every
	   severity inherits the width and only the colour changes. */
	border-inline-start: var(--cst-toast-accent-width) solid var(--cst-border);
}

.cst-toast .toast-body {
	display: flex;
	align-items: flex-start;
	gap: var(--cst-space-2);
	padding: var(--cst-space-3) var(--cst-space-4);
}

/* The glyph takes the severity colour; the message does not. */
.cst-toast__icon { flex: none; line-height: inherit; }

.cst-toast--success { border-inline-start-color: var(--tblr-success-text-emphasis); }
.cst-toast--danger  { border-inline-start-color: var(--tblr-danger-text-emphasis); }
.cst-toast--warning { border-inline-start-color: var(--tblr-warning-text-emphasis); }
.cst-toast--info    { border-inline-start-color: var(--tblr-info-text-emphasis); }

.cst-toast--success .cst-toast__icon { color: var(--tblr-success-text-emphasis); }
.cst-toast--danger  .cst-toast__icon { color: var(--tblr-danger-text-emphasis); }
.cst-toast--warning .cst-toast__icon { color: var(--tblr-warning-text-emphasis); }
.cst-toast--info    .cst-toast__icon { color: var(--tblr-info-text-emphasis); }

/* A danger toast does not auto-dismiss, so it needs a way out. */
.cst-toast .btn-close { margin-inline-start: auto; }


/* =============================================================================
   13. COURSE ACTION (Tier C)

   The action button on a learner's course card, state-coded on purpose. This
   is the documented exception to "buttons carry no state colour"
   (03-components.md): a learner scans a list of cards by the action colour
   before reading the label — done vs resume vs take-the-test vs get-your-
   certificate — so the colour is doing real work here that it is not doing on
   an ordinary form button.

   Built by rebinding Tabler's button variables, the same way .btn-secondary
   and .cst-navbar are, so every size, focus ring and disabled behaviour Tabler
   derives keeps working. Each fill's white label clears 4.5:1 (cst-tokens.css),
   which the stock state colours do not.

   Rendered by templates/queries/gets/get_Course_Status_And_Action_v2.cfm.

   Disabled states (Not Available, Not Passed) are NOT here: they stay Tabler's
   .btn-secondary / .btn-danger disabled, which already read correctly greyed.
   ============================================================================= */

.cst-course-action {
	--tblr-btn-color:              var(--cst-surface);
	--tblr-btn-hover-color:        var(--cst-surface);
	--tblr-btn-active-color:       var(--cst-surface);
	--tblr-btn-border-color:       transparent;
	--tblr-btn-hover-border-color: transparent;
	--tblr-btn-active-border-color: transparent;
}

.cst-course-action--start  { --tblr-btn-bg: var(--cst-course-start);  --tblr-btn-hover-bg: var(--cst-course-start-hover);  --tblr-btn-active-bg: var(--cst-course-start-hover); }
.cst-course-action--resume { --tblr-btn-bg: var(--cst-course-resume); --tblr-btn-hover-bg: var(--cst-course-resume-hover); --tblr-btn-active-bg: var(--cst-course-resume-hover); }
.cst-course-action--test   { --tblr-btn-bg: var(--cst-course-test);   --tblr-btn-hover-bg: var(--cst-course-test-hover);   --tblr-btn-active-bg: var(--cst-course-test-hover); }
.cst-course-action--eval   { --tblr-btn-bg: var(--cst-course-eval);   --tblr-btn-hover-bg: var(--cst-course-eval-hover);   --tblr-btn-active-bg: var(--cst-course-eval-hover); }
.cst-course-action--done   { --tblr-btn-bg: var(--cst-course-done);   --tblr-btn-hover-bg: var(--cst-course-done-hover);   --tblr-btn-active-bg: var(--cst-course-done-hover); }
.cst-course-action--cert   { --tblr-btn-bg: var(--cst-course-cert);   --tblr-btn-hover-bg: var(--cst-course-cert-hover);   --tblr-btn-active-bg: var(--cst-course-cert-hover); }


/* =============================================================================
   14. COUNT PILL (.cst-count)

   "How many are in here" appears all over admin and was written four ways:
   a parenthetical in twelve tab labels — Products (7) — a bg-secondary badge in
   admin/menus, a bg-azure-lt badge in the reports categories table, and plain
   sentence text in the coupon product picker. Same fact, four treatments, and
   two of them used the deprecated literal tints (03-components.md › Badge).

   This is the one treatment. It is deliberately NOT a status badge: a count is
   a quantity, not a state, so it takes the neutral band surface and never a
   semantic tint. A number that does carry meaning — seats over allotment,
   people with no access — stays a .badge with the tint that says so.

   Sizing note: min-width plus tabular figures keep 1 and 11 the same width, so
   a column of pills does not jitter as counts change.
   ============================================================================= */

.cst-count {
	display: inline-block;
	/* The pill almost always trails a label — "Products 7" — and a bare text
	   space is too tight to read as a separate object. The gap is part of the
	   component so call sites do not each guess at an ms-* utility. A pill that
	   stands alone (its own table cell) takes --flush to drop it. */
	margin-inline-start: var(--cst-space-2);
	min-width: var(--cst-space-4);
	padding-inline: var(--cst-space-2);
	border: 1px solid var(--cst-border-subtle);
	border-radius: var(--cst-radius-pill);
	background: var(--cst-band);
	color: var(--cst-muted);
	font-size: var(--cst-text-xs);
	font-weight: var(--cst-weight-medium);
	font-variant-numeric: tabular-nums;
	line-height: 1.7;   /* unitless multiplier, not a length — no token needed */
	text-align: center;
	white-space: nowrap;
}

/* Standalone: the pill is the whole content of its cell or block, so the
   leading gap would just push it out of column alignment. */
.cst-count--flush { margin-inline-start: 0; }

/* Zero is rendered, not hidden: a missing pill reads as "not loaded yet",
   which is a different claim from "none". It just recedes. */
.cst-count--zero {
	color: var(--cst-muted);
	opacity: .65;
}

/* On a dark or active surface (an active tab, the navbar) the pill inherits
   the surrounding ink rather than carrying its own light chip. */
.nav-link.active > .cst-count,
.cst-count--inherit {
	background: transparent;
	border-color: currentColor;
	color: inherit;
}
