/* ─────────────────────────────────────────────────────────────────────────
   AssurKit — design tokens (ported from cpeio.online's tokens.css)

   Source: https://github.com/audit-brands/slim_lms — assets/css/tokens.css
   Ported 2026-05-23 per kanban card #72. Verbatim copy with one adjustment:
   font URLs were `/assets/fonts/geist/...`; AssurKit serves fonts at
   `/fonts/geist/...` (no `/assets` segment in our public-root layout).

   Architecture (unchanged from source):
     - Brand hue 245 (sky blue) anchors the entire palette. Change one
       number at :root --brand-hue to re-key everything.
     - Loaded AFTER Bulma. Tokens redirect Bulma's color modifiers
       (is-primary, is-link, is-info, is-light, is-success, is-warning,
       is-danger) to OKLCH-based tonal pairs. Structural Bulma classes
       (flex, grid, columns) are untouched.
     - Geist + Geist Mono variable woff2 fonts self-hosted at
       /fonts/geist/. License (OFL) in /fonts/geist/LICENSE.txt.
     - data-theme="dark" toggle wins over prefers-color-scheme — see the
       small inline script in layouts/base.html.twig.

   When tweaking the palette: prefer changing tokens here rather than
   restating component-level color in app.css. Anything component-
   specific belongs in app.css and should consume `var(--color-...)`
   instead of hardcoding hex.
   ───────────────────────────────────────────────────────────────────────── */

/* Self-hosted Geist (Vercel, OFL). Latin coverage; variable axis 100–900. */
@font-face {
  font-family: "Geist";
  src: url("/fonts/geist/Geist-Variable.woff2") format("woff2-variations"),
       url("/fonts/geist/Geist-Variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Geist Mono";
  src: url("/fonts/geist/GeistMono-Variable.woff2") format("woff2-variations"),
       url("/fonts/geist/GeistMono-Variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ─── Token primitives (light theme defaults) ────────────────────────────── */

:root {
  /* Brand hue primitive — anchors logo sky-blue. Every neutral and accent
     derives from this value. To re-key the palette, change this number. */
  --brand-hue: 245;

  /* Shell metrics. The navbar height was a 52px literal in app.css; a sticky
     header made it load-bearing, because the sidebar pins directly beneath the
     header and the shell's min-height is measured from it.
     The inherited 52px was WRONG: the navbar renders 57px. The sidebar was
     tucking 5px under the header. `.navbar` now takes its min-height from this
     token (see app.css), so the number and the rendered height cannot drift
     apart again — which is the whole reason a literal in two places was a
     problem. Card #693. */
  --nav-height: 57px;
  --rail-width: 64px;

  /* Neutrals — Stripe-restraint mechanic, tinted toward brand hue.
     Pure black and pure white are forbidden (PRODUCT.md anti-references). */
  --color-paper-cream:    oklch(98%   0.005  var(--brand-hue));  /* primary surface */
  --color-paper-elevated: oklch(99.5% 0.004  var(--brand-hue));  /* card / elevated surface */
  --color-paper-band:     oklch(95.5% 0.008  var(--brand-hue));  /* navbar / footer band — slightly creamier than body */
  --color-graphite:       oklch(22%   0.012  var(--brand-hue));  /* primary text */
  --color-graphite-soft:  oklch(38%   0.012  var(--brand-hue));  /* secondary text, metadata */
  --color-hairline:       oklch(88%   0.006  var(--brand-hue));  /* border, divider */
  --color-hairline-strong:oklch(78%   0.008  var(--brand-hue));  /* border-emphasis, focus group */
  --color-border-control: oklch(60%   0.012  var(--brand-hue));  /* control boundary — 3.74:1, WCAG 1.4.11 */

  /* Sky-action accent — recognizably the logo blue, deepened for AA contrast.
     The accent appears on ≤10% of any view (DESIGN.md "The One Voice Rule"). */
  --color-sky-action:       oklch(52% 0.18 245);  /* primary buttons, focus rings, active state */
  --color-sky-action-hover: oklch(46% 0.20 245);  /* hover/active depth */
  --color-sky-action-deep:  oklch(48% 0.20 245);  /* inline accent text on cream (4.5:1 AA) */
  --color-sky-action-soft:  oklch(94% 0.04 245);  /* subtle wash, badge background */
  --color-sky-action-on:    oklch(99% 0.005 var(--brand-hue));  /* text/icon ON sky-action */

  /* Functional colors — tonal siblings, paired text/icon affordance required.
     No color-only signaling (DESIGN.md a11y). */
  --color-success-deep: oklch(46% 0.10 145);
  --color-success-soft: oklch(94% 0.04 145);
  --color-warning-deep: oklch(50% 0.12 75);
  --color-warning-soft: oklch(95% 0.05 75);
  --color-danger-deep:  oklch(48% 0.16 25);
  --color-danger-soft:  oklch(94% 0.05 25);
  --color-info-deep:    var(--color-sky-action-deep);
  --color-info-soft:    var(--color-sky-action-soft);

  /* Semantic surface roles — consume these in components, not the primitives.
     Lets dark-theme override flow through without restating component styles. */
  --color-surface:          var(--color-paper-cream);
  --color-surface-elevated: var(--color-paper-elevated);
  --color-surface-band:     var(--color-paper-band);
  --color-text:             var(--color-graphite);
  --color-text-soft:        var(--color-graphite-soft);
  --color-text-on-action:   var(--color-sky-action-on);
  --color-border:           var(--color-hairline);
  --color-border-strong:    var(--color-hairline-strong);
  --color-border-input:     var(--color-border-control);

  /* Type families. System fallbacks load instantly; Geist swaps in on arrival. */
  --font-sans: "Geist", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* Type scale — 1.25 ratio (major third). Display fluid; UI/body fixed rem. */
  --type-display-size:    clamp(2.5rem, 1.5rem + 4vw, 4.25rem);
  --type-display-weight:  600;
  --type-display-line:    1.05;
  --type-display-track:   -0.022em;

  --type-headline-size:   clamp(1.75rem, 1.4rem + 1.6vw, 2.25rem);
  --type-headline-weight: 600;
  --type-headline-line:   1.15;
  --type-headline-track:  -0.014em;

  --type-title-size:      1.25rem;     /* 20px */
  --type-title-weight:    600;
  --type-title-line:      1.3;
  --type-title-track:     -0.008em;

  --type-body-size:       1rem;        /* 16px */
  --type-body-weight:     400;
  --type-body-line:       1.55;
  --type-body-track:      0;

  --type-label-size:      0.8125rem;   /* 13px */
  --type-label-weight:    500;
  --type-label-line:      1.4;
  --type-label-track:     0;

  --type-caption-size:    0.75rem;     /* 12px */
  --type-caption-weight:  500;
  --type-caption-line:    1.45;
  --type-caption-track:   0;

  --type-mono-size:       0.9375rem;   /* 15px — slightly under body for visual balance */
  --type-mono-weight:     400;
  --type-mono-line:       1.5;
  --type-mono-track:      0;

  /* Reading measure — body prose stays in the comfortable band. */
  --measure-prose: 70ch;

  /* Spacing scale — 4pt base, varied for rhythm (NOT uniform). */
  --space-3xs: 0.25rem;   /*  4px */
  --space-2xs: 0.5rem;    /*  8px */
  --space-xs:  0.75rem;   /* 12px */
  --space-sm:  1rem;      /* 16px */
  --space-md:  1.5rem;    /* 24px */
  --space-lg:  2rem;      /* 32px */
  --space-xl:  3rem;      /* 48px */
  --space-2xl: 4rem;      /* 64px */
  --space-3xl: 6rem;      /* 96px */

  /* Radii — small restrained set. */
  --radius-sm:   4px;
  --radius-md:   6px;
  --radius-lg:   8px;
  --radius-xl:   12px;
  --radius-pill: 9999px;  /* only for genuine pills (chips, badges) */

  /* Border presets. */
  --border-hairline:        1px solid var(--color-border);
  --border-hairline-strong: 1px solid var(--color-border-strong);

  /* Motion easings — exponential ease-out only. No bounce, no elastic.
     Duration values are defined per-use, not as tokens. */
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out-expo:  cubic-bezier(0.16, 1, 0.3, 1);

  /* Focus ring — functional, used only on :focus-visible. */
  --focus-ring: 0 0 0 3px oklch(60% 0.18 245 / 0.35);
}

/* ─── Dark theme — explicit toggle wins over system preference ──────────── */

:root[data-theme="dark"] {
  --color-paper-cream:    oklch(18% 0.012 var(--brand-hue));   /* slate */
  --color-paper-elevated: oklch(22% 0.014 var(--brand-hue));
  --color-paper-band:     oklch(14% 0.014 var(--brand-hue));   /* deeper band — receives content above */
  --color-graphite:       oklch(94% 0.005 var(--brand-hue));   /* bone */
  --color-graphite-soft:  oklch(76% 0.008 var(--brand-hue));
  --color-hairline:       oklch(28% 0.012 var(--brand-hue));
  --color-hairline-strong:oklch(38% 0.014 var(--brand-hue));
  --color-border-control: oklch(52% 0.012 var(--brand-hue));

  --color-sky-action:       oklch(70% 0.16 245);
  --color-sky-action-hover: oklch(78% 0.16 245);
  --color-sky-action-deep:  oklch(76% 0.18 245);
  --color-sky-action-soft:  oklch(28% 0.06 245);
  --color-sky-action-on:    oklch(15% 0.012 var(--brand-hue));

  --color-success-deep: oklch(72% 0.14 145);
  --color-success-soft: oklch(28% 0.06 145);
  --color-warning-deep: oklch(76% 0.14 75);
  --color-warning-soft: oklch(28% 0.06 75);
  --color-danger-deep:  oklch(72% 0.16 25);
  --color-danger-soft:  oklch(28% 0.07 25);

  /* Light text on dark reads as heavier; reduce body weight one notch. */
  --type-body-weight: 380;

  --focus-ring: 0 0 0 3px oklch(70% 0.16 245 / 0.45);
}

/* ─── Base element styling ─────────────────────────────────────────────── */

html {
  background-color: var(--color-surface);
  color: var(--color-text);
}

body {
  font-family: var(--font-sans);
  font-feature-settings: "kern" 1;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Heading line-break quality — variable-font browsers will balance long titles. */
h1, h2, h3, h4, h5, h6,
.title, .subtitle {
  text-wrap: balance;
}

/* Prose paragraphs avoid orphans. */
p {
  text-wrap: pretty;
}

/* Mono utility for structured data — credit hours, course codes, certificate
   IDs, regulatory citations, ISO timestamps (DESIGN.md "Mono-for-Data Rule"). */
.is-mono,
.font-mono,
code,
kbd,
samp {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Visually-hidden but accessible to assistive tech. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ─── Bulma color-class redirection ─────────────────────────────────────── */
/* Existing markup uses `is-primary`, `is-link`, `is-info` etc. Redirect those
   to token-based values so legacy templates inherit the new palette without
   a class-name migration. Bulma's structural styling (flex, grid helpers,
   sizing) continues unchanged. */

.button.is-primary,
.button.is-link {
  background-color: var(--color-sky-action);
  border-color: var(--color-sky-action);
  color: var(--color-text-on-action);
  transition: background-color 0.18s var(--ease-out-quart),
              border-color   0.18s var(--ease-out-quart);
}
.button.is-primary:hover,
.button.is-link:hover {
  background-color: var(--color-sky-action-hover);
  border-color:     var(--color-sky-action-hover);
  color:            var(--color-text-on-action);
}
.button.is-primary:focus-visible,
.button.is-link:focus-visible,
.button.is-light:focus-visible,
.button:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.button.is-light {
  background-color: var(--color-surface-elevated);
  border-color:     var(--color-border);
  color:            var(--color-text);
}
.button.is-light:hover {
  background-color: var(--color-sky-action-soft);
  border-color:     var(--color-border-strong);
  color:            var(--color-text);
}

/* Navbar — Bulma's whitesmoke is too close to our cream surface. Use the
   band token so the nav reads as its own paper band, with a hairline below
   defining the edge (no shadow — flat-by-default). */
.navbar.is-light {
  background-color: var(--color-surface-band);
  color:            var(--color-text);
  border-bottom:    var(--border-hairline);
  box-shadow:       none;
}
.navbar.is-light .navbar-item,
.navbar.is-light .navbar-link {
  color: var(--color-text);
  background-color: transparent;
  transition: background-color 0.15s var(--ease-out-quart),
              color            0.15s var(--ease-out-quart);
}
.navbar.is-light .navbar-item:hover,
.navbar.is-light .navbar-link:hover,
.navbar.is-light .navbar-item.is-active,
.navbar.is-light .navbar-link.is-active {
  background-color: var(--color-sky-action-soft);
  color:            var(--color-sky-action-deep);
}
.navbar.is-light .navbar-burger {
  color: var(--color-text);
}
.navbar.is-light .navbar-burger:hover {
  background-color: var(--color-sky-action-soft);
}
/* Mobile: the dropdown menu surface should match the band, not Bulma white. */
@media (max-width: 1023px) {
  .navbar.is-light .navbar-menu {
    background-color: var(--color-surface-band);
    border-top: var(--border-hairline);
    box-shadow: none;
  }
}

/* Form fields — global Bulma redirects so every form across cpeio inherits.
   Surface-scoped overrides (e.g. .catalog-filters .input) still win on
   specificity. */
.input,
.textarea,
.select select {
  background-color: var(--color-surface);
  /* A control boundary is an affordance, not a divider: WCAG 1.4.11 holds it to
     3:1. --color-hairline measures 1.36:1, which is right for a row separator
     and wrong here. See DESIGN.md §3. */
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  box-shadow: none;
  transition: border-color 0.15s var(--ease-out-quart);
}
.input::placeholder,
.textarea::placeholder {
  color: var(--color-text-soft);
}
.input:hover,
.textarea:hover,
.select select:hover {
  border-color: var(--color-border-strong);
}
.input:focus,
.input:focus-visible,
.textarea:focus,
.textarea:focus-visible,
.select select:focus,
.select select:focus-visible {
  outline: none;
  border-color: var(--color-sky-action);
  box-shadow: var(--focus-ring);
}
.input:disabled,
.textarea:disabled,
.select select:disabled {
  background-color: var(--color-surface-band);
  color: var(--color-text-soft);
  cursor: not-allowed;
}
.label {
  font-family: var(--font-sans);
  font-size: var(--type-label-size);
  font-weight: var(--type-label-weight);
  color: var(--color-text);
  margin-bottom: var(--space-3xs);
}
.help {
  font-family: var(--font-sans);
  font-size: var(--type-caption-size);
  color: var(--color-text-soft);
  margin-top: var(--space-3xs);
}
.checkbox,
.radio {
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  color: var(--color-text);
}
.checkbox input[type="checkbox"],
.radio input[type="radio"] {
  accent-color: var(--color-sky-action);
  margin-right: var(--space-3xs);
}

/* Default .tag (no color modifier) and .tag.is-light — neutral pill on the
   surface-band tone with hairline border. Placed BEFORE the colored variants
   so source-order precedence keeps .tag.is-info (etc) winning when both
   classes are applied (e.g. <span class="tag is-info is-light">). */
.tag,
.tag.is-light {
  background-color: var(--color-surface-band);
  color:            var(--color-text);
  border:           var(--border-hairline);
  font-family:      var(--font-sans);
}

.tag.is-info,
.tag.is-link {
  background-color: var(--color-sky-action-soft);
  color:            var(--color-sky-action-deep);
  border-color:     transparent;
}
.tag.is-success {
  background-color: var(--color-success-soft);
  color:            var(--color-success-deep);
  border-color:     transparent;
}
.tag.is-warning {
  background-color: var(--color-warning-soft);
  color:            var(--color-warning-deep);
  border-color:     transparent;
}
.tag.is-danger {
  background-color: var(--color-danger-soft);
  color:            var(--color-danger-deep);
  border-color:     transparent;
}
/* When .is-light is paired with a color modifier, fall through to the colored
   variant — keep the soft-tonal fill rather than the neutral surface-band. */
.tag.is-light.is-info,
.tag.is-light.is-link {
  background-color: var(--color-sky-action-soft);
  color:            var(--color-sky-action-deep);
  border-color:     transparent;
}
.tag.is-light.is-success {
  background-color: var(--color-success-soft);
  color:            var(--color-success-deep);
  border-color:     transparent;
}
.tag.is-light.is-warning {
  background-color: var(--color-warning-soft);
  color:            var(--color-warning-deep);
  border-color:     transparent;
}
.tag.is-light.is-danger {
  background-color: var(--color-danger-soft);
  color:            var(--color-danger-deep);
  border-color:     transparent;
}

/* Progress bars — Bulma .progress redirected to tokens. Sky-action fill on
   a hairline-strong track. No shadow, no border-radius beyond pill so the
   bar reads as a clean rule, not a card. */
progress.progress {
  height: 6px;
  background-color: var(--color-hairline-strong);
  border-radius: var(--radius-pill);
  border: none;
  box-shadow: none;
  overflow: hidden;
}
progress.progress::-webkit-progress-bar {
  background-color: var(--color-hairline-strong);
  border-radius: var(--radius-pill);
}
progress.progress::-webkit-progress-value {
  background-color: var(--color-sky-action);
  border-radius: var(--radius-pill);
  transition: width 0.3s var(--ease-out-quart);
}
progress.progress::-moz-progress-bar {
  background-color: var(--color-sky-action);
  border-radius: var(--radius-pill);
}

/* Ad unit card — overrides the .box chrome from the partials/ad_sidebar.twig
   wrapper so ads render as a clean hairline-bordered surface on every page
   that includes the partial. */
.ad-unit-card.box,
.ad-unit-card {
  background-color: var(--color-surface-elevated);
  border: var(--border-hairline);
  border-radius: var(--radius-md);
  box-shadow: none;
  padding: var(--space-2xs);
}
.ad-unit-card img {
  display: block;
  border-radius: var(--radius-sm);
}

/* Modal — Bulma .modal redirected to tokens. Used by base.twig
   confirmDeleteModal and any surface that opens a modal-card. */
.modal {
  align-items: center;
  display: none;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
  position: fixed;
  inset: 0;
  z-index: 40;
}
.modal.is-active {
  display: flex;
}
.modal-background {
  position: absolute;
  inset: 0;
  background-color: oklch(0% 0 0 / 0.55);
}
.modal-card {
  position: relative;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 4rem);
  width: 640px;
  max-width: calc(100vw - 2rem);
  background-color: var(--color-surface-elevated);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: 0 16px 48px oklch(0% 0 0 / 0.18);
}
.modal-card-head,
.modal-card-foot {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  padding: var(--space-md) var(--space-lg);
  background-color: var(--color-surface-elevated);
  position: relative;
}
.modal-card-head {
  border-bottom: var(--border-hairline);
  justify-content: space-between;
}
.modal-card-foot {
  border-top: var(--border-hairline);
  gap: var(--space-2xs);
  justify-content: flex-end;
}
.modal-card-title {
  font-family: var(--font-sans);
  font-size: var(--type-title-size);
  font-weight: var(--type-title-weight);
  letter-spacing: var(--type-title-track);
  color: var(--color-text);
  margin: 0;
  flex: 1;
}
.modal-card-body {
  background-color: var(--color-surface-elevated);
  flex: 1;
  overflow: auto;
  padding: var(--space-md) var(--space-lg);
}
.modal .delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-pill);
  border: var(--border-hairline);
  background-color: transparent;
  cursor: pointer;
  position: relative;
  color: var(--color-text-soft);
  font-size: 0;
  transition: background-color 0.15s var(--ease-out-quart),
              color            0.15s var(--ease-out-quart);
}
.modal .delete::before,
.modal .delete::after {
  content: "";
  position: absolute;
  width: 0.875rem;
  height: 1px;
  background-color: currentColor;
  top: 50%;
  left: 50%;
}
.modal .delete::before { transform: translate(-50%, -50%) rotate(45deg); }
.modal .delete::after  { transform: translate(-50%, -50%) rotate(-45deg); }
.modal .delete:hover {
  color: var(--color-text);
  background-color: var(--color-sky-action-soft);
  border-color: var(--color-sky-action-deep);
}

/* Breadcrumb — Bulma .breadcrumb redirected to inline-text pattern with
   hairline below. Used by surfaces that haven't adopted a scoped breadcrumb
   class. Surface-specific patterns (.cd-breadcrumb, .lp-breadcrumb,
   .adm-breadcrumb, .in-breadcrumb, etc) win on specificity. */
.breadcrumb {
  font-family: var(--font-sans);
  font-size: var(--type-label-size);
  color: var(--color-text-soft);
  margin: 0 0 var(--space-md);
  padding-bottom: var(--space-2xs);
  border-bottom: var(--border-hairline);
}
.breadcrumb ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
  gap: 0;
}
.breadcrumb li {
  display: inline-flex;
  align-items: center;
}
.breadcrumb li + li::before {
  content: "→";
  margin: 0 var(--space-3xs);
  color: var(--color-border-strong);
  font-family: var(--font-sans);
}
.breadcrumb li a {
  color: var(--color-sky-action-deep);
  text-decoration: none;
  padding: 0;
  display: inline;
}
.breadcrumb li a:hover {
  color: var(--color-sky-action);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.breadcrumb li.is-active a {
  color: var(--color-text);
  pointer-events: none;
}

/* Tabs — Bulma .tabs redirected to underlined-active pattern. Hairline at the
   bottom of the tab bar; active tab gets a 2px sky-action bottom border + deep
   text. Drops the .is-boxed chrome (we deliberately don't read that modifier).
   Used by instructor course editor and inbox; cascades wherever .tabs renders. */
.tabs {
  border-bottom: var(--border-hairline);
  margin-bottom: var(--space-lg);
  font-family: var(--font-sans);
  overflow-x: auto;
}
.tabs ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  border-bottom: none;
}
.tabs li {
  margin-bottom: -1px;
  display: inline-flex;
}
.tabs li a {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2xs) var(--space-md);
  font-size: var(--type-label-size);
  font-weight: 500;
  color: var(--color-text-soft);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  transition: color        0.15s var(--ease-out-quart),
              border-color 0.15s var(--ease-out-quart);
}
.tabs li a:hover {
  color: var(--color-text);
  background-color: transparent;
}
.tabs li.is-active a {
  color: var(--color-sky-action-deep);
  border-bottom-color: var(--color-sky-action);
}
.tabs li a:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
/* Bulma's .is-boxed modifier should yield to the underlined pattern. */
.tabs.is-boxed li a,
.tabs.is-boxed li.is-active a {
  background-color: transparent;
  border-radius: 0;
  border-left: none;
  border-right: none;
  border-top: none;
}

/* Pagination — Bulma's .pagination redirected to tokens. Numerals use mono
   with tabular figures so the pagination row doesn't reflow width across
   page changes. */
.pagination .pagination-link,
.pagination .pagination-previous,
.pagination .pagination-next {
  background-color: transparent;
  border: var(--border-hairline);
  color: var(--color-text);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  border-radius: var(--radius-sm);
  transition: background-color 0.15s var(--ease-out-quart),
              border-color    0.15s var(--ease-out-quart),
              color           0.15s var(--ease-out-quart);
}
.pagination .pagination-previous,
.pagination .pagination-next {
  font-family: var(--font-sans);
  font-variant-numeric: normal;
}
.pagination .pagination-link:hover,
.pagination .pagination-previous:hover,
.pagination .pagination-next:hover {
  background-color: var(--color-sky-action-soft);
  border-color:     var(--color-sky-action-deep);
  color:            var(--color-sky-action-deep);
}
.pagination .pagination-link.is-current {
  background-color: var(--color-sky-action);
  border-color:     var(--color-sky-action);
  color:            var(--color-text-on-action);
}
.pagination .pagination-link:focus-visible,
.pagination .pagination-previous:focus-visible,
.pagination .pagination-next:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}
.pagination .pagination-ellipsis {
  color: var(--color-text-soft);
}

/* Notifications are redirected in the utility block near the end of this file,
   where the `.is-light` combinations live too. They were declared twice for a
   while on 2026-08-24; two places defining one thing is the drift that caused
   card #688 in the first place, so they are consolidated to one. */

/* Inline accent links inherit the deep accent for AA contrast on cream. */
a {
  color: var(--color-sky-action-deep);
  text-decoration-color: color-mix(in oklch, var(--color-sky-action-deep) 35%, transparent);
  text-underline-offset: 2px;
  transition: color 0.15s var(--ease-out-quart);
}
a:hover {
  color: var(--color-sky-action);
  text-decoration-color: currentColor;
}

/* Hairline border default for cards (when surfaces choose tokens over Bulma). */
.has-token-border {
  border: var(--border-hairline);
  border-radius: var(--radius-md);
}

/* ─── Surface components ───────────────────────────────────────────────── */

/* Course-thumbnail fallback. Used in any card grid where a course may lack a
   thumbnail_url. Pairs with Bulma's <figure class="image is-16by9"> wrapper;
   `.has-ratio` inherits Bulma's absolute-fill positioning. The fallback is a
   tonal band with a hairline border (no gradient — anti-reference) and a
   single uppercased title-initial in display sans, restrained color. */
.course-thumbnail-fallback {
  background-color: var(--color-surface-band);
  border: var(--border-hairline);
  display: flex;
  align-items: center;
  justify-content: center;
}
.course-thumbnail-fallback__initial {
  font-family: var(--font-sans);
  font-size: clamp(2rem, 4.5vw, 3.25rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--color-text-soft);
  font-feature-settings: "kern" 1;
}

/* ─── Reduced motion — respect system preference everywhere ──────────────── */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ─── Bulma text + background utility redirection ────────────────────────── */
/* Bulma 1.x ships its own prefers-color-scheme support, so `has-text-grey` and
   friends resolve against BULMA's notion of the colour scheme rather than ours.
   Where its assumption and our surface disagree, contrast collapses. A sweep on
   2026-08-23 measured has-text-grey failing on 24 of 24 pages (949 occurrences,
   worst 3.68:1) and the has-text-*-dark family at 1.05:1.
   Redirecting here fixes every occurrence at once, in one file, without
   touching templates the backend sessions are concurrently editing.
   Cards #685, #687. DESIGN.md §7 bans reaching for these in new markup. */

.has-text-grey,
.has-text-grey-light,
.has-text-grey-dark,
.has-text-grey-darker,
.has-text-grey-lighter {
  color: var(--color-text-soft) !important;
}

.has-text-black,
.has-text-black-bis,
.has-text-black-ter,
.has-text-dark {
  color: var(--color-text) !important;
}

.has-text-danger,
.has-text-danger-dark  { color: var(--color-danger-deep)  !important; }
.has-text-warning,
.has-text-warning-dark { color: var(--color-warning-deep) !important; }
.has-text-success,
.has-text-success-dark { color: var(--color-success-deep) !important; }
.has-text-info,
.has-text-info-dark,
.has-text-link,
.has-text-link-dark    { color: var(--color-sky-action-deep) !important; }

/* Tinted row/panel backgrounds. Bulma's *-light backgrounds stay light in our
   dark theme while our text tokens go light, giving 1.01:1. Card #688. */
.has-background-danger-light  { background-color: var(--color-danger-soft)  !important; }
.has-background-warning-light { background-color: var(--color-warning-soft) !important; }
.has-background-success-light { background-color: var(--color-success-soft) !important; }
.has-background-info-light,
.has-background-link-light    { background-color: var(--color-sky-action-soft) !important; }
.has-background-light         { background-color: var(--color-surface-band)  !important; }

/* A tinted row must also own the colour of everything inside it, or its links
   and cells keep resolving against the page surface they are no longer on. */
.has-background-danger-light,
.has-background-warning-light,
.has-background-success-light,
.has-background-info-light,
.has-background-link-light,
.has-background-light {
  color: var(--color-text);
}
.has-background-danger-light a:not(.button),
.has-background-warning-light a:not(.button),
.has-background-success-light a:not(.button),
.has-background-info-light a:not(.button),
.has-background-link-light a:not(.button),
.has-background-light a:not(.button) {
  color: var(--color-sky-action-deep);
}

/* ─── Notifications and messages ─────────────────────────────────────────── */
/* Same failure as the tinted rows: Bulma paints a light panel, our text token
   goes light on it. Card #688. Tonal sibling pairs, per DESIGN.md §3. */

.notification {
  background-color: var(--color-surface-band);
  color:            var(--color-text);
  border:           var(--border-hairline);
  border-radius:    var(--radius-md);
  box-shadow:       none;
}
.notification.is-light { background-color: var(--color-surface-band); color: var(--color-text); }

.notification.is-info,
.notification.is-link,
.notification.is-primary,
.notification.is-light.is-info,
.notification.is-light.is-link,
.notification.is-light.is-primary {
  background-color: var(--color-sky-action-soft);
  color:            var(--color-sky-action-deep);
  border-color:     transparent;
}
.notification.is-success,
.notification.is-light.is-success {
  background-color: var(--color-success-soft);
  color:            var(--color-success-deep);
  border-color:     transparent;
}
.notification.is-warning,
.notification.is-light.is-warning {
  background-color: var(--color-warning-soft);
  color:            var(--color-warning-deep);
  border-color:     transparent;
}
.notification.is-danger,
.notification.is-light.is-danger {
  background-color: var(--color-danger-soft);
  color:            var(--color-danger-deep);
  border-color:     transparent;
}

/* Inherit, so a link or bold run inside a toned notification does not fall back
   to the page text colour and lose its contrast against the tint. */
.notification a:not(.button),
.notification strong,
.notification .delete { color: inherit; }

.message           { background-color: transparent; }
.message-body {
  background-color: var(--color-surface-band);
  border:           var(--border-hairline);
  border-radius:    var(--radius-md);
  color:            var(--color-text);
}
.message.is-info    .message-body { background-color: var(--color-sky-action-soft); color: var(--color-sky-action-deep); border-color: transparent; }
.message.is-success .message-body { background-color: var(--color-success-soft);    color: var(--color-success-deep);    border-color: transparent; }
.message.is-warning .message-body { background-color: var(--color-warning-soft);    color: var(--color-warning-deep);    border-color: transparent; }
.message.is-danger  .message-body { background-color: var(--color-danger-soft);     color: var(--color-danger-deep);     border-color: transparent; }

/* A tinted table ROW is not a tinted chip ─────────────────────────────── */
/* Same token, very different area, so the same strength reads far louder
   across a full-width row than inside a badge. On /projects an overdue row was
   already carrying an "Overdue" chip and a red date, so the tint was the third
   encoding of one fact and the loudest of the three.
   Halved toward the page surface: still scannable as a band, no longer the
   thing your eye lands on first. DESIGN.md, the Earned Salience Rule.

   Mixed in OKLAB, not OKLCH. OKLCH is polar, so color-mix interpolates the HUE
   ANGLE between the two colours: danger sits at 25 and the surface at 245, 220
   degrees apart, and the shortest arc between them runs through magenta. The
   overdue row came out pink. OKLAB is the same perceptual space in rectangular
   coordinates, so mixing moves in a straight line and the hue stays put. */
tr.has-background-danger-light  { background-color: color-mix(in oklab, var(--color-danger-soft)     55%, var(--color-surface)) !important; }
tr.has-background-warning-light { background-color: color-mix(in oklab, var(--color-warning-soft)    55%, var(--color-surface)) !important; }
tr.has-background-success-light { background-color: color-mix(in oklab, var(--color-success-soft)    55%, var(--color-surface)) !important; }
tr.has-background-info-light,
tr.has-background-link-light    { background-color: color-mix(in oklab, var(--color-sky-action-soft) 55%, var(--color-surface)) !important; }
