/* ==========================================================================
   Spartan CDL Training - design system
   Same brand system as the Spartan Command Portal (SCG Tech Solutions
   palette): dark-first, Void Black backgrounds, Crimson Core as the single
   accent, Neon Red for active states only, flat solid fills (no gradients).
   Motion respects prefers-reduced-motion.
   ========================================================================== */

:root {
  /* Primary */
  --void-black: #0A0A0A;
  --crimson: #CC0000;
  --white: #FFFFFF;
  /* Secondary */
  --neon-red: #FF1A1A;
  --surface: #1A1A1A;
  --deep-crimson: #2A0000;
  --steel: #3D3D3D;
  --silver: #C0C0C0;
  /* Red scale */
  --red-900: #4D0000;
  --red-700: #800000;
  --red-400: #E62020;
  --red-200: #FF6666;
  --red-100: #FFB3B3;

  --radius: 12px;
  --sidebar-w: 248px;
  --sidebar-w-collapsed: 68px;
  --font: "Segoe UI", system-ui, -apple-system, sans-serif;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { font-family: var(--font); color: var(--white); background: var(--void-black); }
a { color: var(--neon-red); }
code { background: var(--void-black); border: 1px solid var(--steel); padding: 2px 6px; border-radius: 4px; font-size: 0.9em; }

/* ---------- entrance animation shared by all pages -------------------- */
@keyframes pageEnter {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}
.page-enter { animation: pageEnter 0.45s cubic-bezier(0.22, 1, 0.36, 1) both; }

/* ==========================================================================
   Public pages (login / signup / verify / reset)
   ========================================================================== */
.public-body {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  background: var(--void-black);
  padding: 24px;
}
/* Drifting ember-field built from box-shadows - solid dots, pure CSS */
.stars { position: fixed; inset: 0; pointer-events: none; overflow: hidden; }
.stars::before, .stars::after {
  content: ""; position: absolute; width: 2px; height: 2px; border-radius: 50%;
  background: transparent;
  box-shadow:
    8vw 12vh 0 rgba(204,0,0,.55), 22vw 68vh 0 rgba(255,255,255,.16),
    35vw 30vh 0 rgba(204,0,0,.4), 48vw 82vh 0 rgba(255,255,255,.18),
    61vw 18vh 0 rgba(204,0,0,.5), 74vw 55vh 0 rgba(255,255,255,.14),
    87vw 38vh 0 rgba(204,0,0,.55), 15vw 90vh 0 rgba(255,255,255,.18),
    93vw 74vh 0 rgba(204,0,0,.35), 55vw 8vh 0 rgba(255,26,26,.6);
  animation: starDrift 60s linear infinite;
}
.stars::after { animation-duration: 90s; transform: translateY(-30vh); opacity: .6; }
@keyframes starDrift { to { transform: translateY(-100vh); } }

.public-wrap { width: 100%; max-width: 460px; position: relative; z-index: 1; text-align: center; }
.public-wrap.wide { max-width: 560px; }
/* Long-form prose on a pre-authentication page: the public view of /guide. Wide
   enough to read a document in, and left-aligned inside the card - the sign-in
   card centres its text, which is wrong for paragraphs and lists. */
.public-wrap--doc { max-width: 940px; }
.public-wrap--doc .public-card { text-align: left; }
.public-logo { width: 340px; max-width: 92%; height: auto; margin-bottom: 20px; }
/* Logo: the base wordmark image (training-logo.svg) with the portal's own name
   overlaid in red where the old "CDL" sat. The overlay is an inline SVG in the
   same 820×240 space; an invisible "SPARTAN " reserves the exact width so the
   name flows into the gap (see brandLogo() in views/helpers.js). */
.brand-logo { position: relative; display: inline-block; line-height: 0; }
.brand-logo__base { display: block; width: 100%; height: auto; }
.brand-logo__name { position: absolute; top: 0; left: 0; width: 100%; height: auto; pointer-events: none; }
.public-logo-link { display: inline-block; margin-bottom: 20px; }
.brand-logo--public { width: 340px; max-width: 92%; }
.brand-logo--sidebar { width: 100%; }
.public-card {
  background: var(--surface); text-align: left;
  border: 1px solid var(--crimson); border-radius: var(--radius);
  padding: 36px; box-shadow: 0 24px 60px rgba(0,0,0,.6);
}
.public-title { margin: 0 0 6px; font-size: 24px; }
.public-sub { margin: 0 0 22px; color: var(--silver); font-size: 14px; line-height: 1.55; }
.public-links { margin-top: 18px; font-size: 14px; text-align: center; }
/* Guide pointer under the sign-in / activation card. Quiet by default - it is an
   offer of help, not a call to action competing with the form's own button. */
.public-help { margin: 16px 0 0; font-size: 13px; color: var(--silver); text-align: center; }
.public-help a { color: var(--silver); text-decoration: underline; text-underline-offset: 3px; }
.public-help a:hover, .public-help a:focus-visible { color: var(--neon-red); }
.public-foot { margin-top: 16px; font-size: 11px; letter-spacing: 1.5px; color: #6d6d6d; text-transform: uppercase; }
.public-foot strong { color: var(--silver); font-weight: 600; }

/* ---------- forms ------------------------------------------------------ */
.stack { display: flex; flex-direction: column; gap: 16px; }
.stack.narrow { max-width: 320px; }
label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--silver); }
input, select {
  font: inherit; color: var(--white); background: var(--void-black);
  border: 1px solid var(--steel); border-radius: 8px; padding: 11px 13px;
  transition: border-color .2s, box-shadow .2s;
}
input:focus, select:focus {
  outline: none; border-color: var(--crimson);
  box-shadow: 0 0 0 3px rgba(204,0,0,.30);
}
/* Checkboxes and radios are controls, not text fields: keep them native-sized
   and free of the text-input border/padding/background and focus glow, which
   otherwise stretch them into a full-width bar that pushes the label below. */
input[type="checkbox"], input[type="radio"] {
  width: auto; margin: 0; padding: 0; border: 0; border-radius: 0;
  background: none; box-shadow: none; accent-color: var(--crimson); flex: 0 0 auto;
}
input[type="checkbox"]:focus, input[type="radio"]:focus { box-shadow: none; }
input[type="checkbox"]:focus-visible, input[type="radio"]:focus-visible {
  outline: 2px solid var(--crimson); outline-offset: 2px;
}
.code-input { letter-spacing: 8px; font-size: 20px; text-align: center; font-variant-numeric: tabular-nums; }
.field-hint { font-size: 11.5px; color: #8f8f8f; }
.radio-row { display: flex; flex-direction: row; gap: 18px; align-items: center; }
.radio-row label { flex-direction: row; align-items: center; gap: 7px; color: var(--white); font-size: 14px; }
.radio-row input { accent-color: var(--crimson); }

.btn {
  font: inherit; font-weight: 600; cursor: pointer; border-radius: 8px;
  border: 1px solid transparent; padding: 11px 20px; text-decoration: none;
  display: inline-block; text-align: center;
  transition: transform .15s ease, background .2s ease, border-color .2s ease, color .2s ease;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-primary { background: var(--crimson); color: var(--white); }
.btn-primary:hover { background: var(--neon-red); }
.btn-ghost { background: transparent; color: var(--white); border-color: var(--steel); }
.btn-ghost:hover { border-color: var(--crimson); color: var(--neon-red); }
.btn-warn { background: transparent; color: var(--red-200); border-color: var(--red-200); }
.btn-warn:hover { background: var(--deep-crimson); }
.btn-danger { background: transparent; color: var(--neon-red); border-color: var(--red-700); }
.btn-danger:hover { background: var(--crimson); color: var(--white); }
.btn-block { width: 100%; }
.btn-small { padding: 6px 12px; font-size: 13px; }
.btn-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
/* Buttons/forms side by side inside a table cell: keep them on one line and let
   each button hold its own label rather than wrapping mid-word. (.stack is a
   16px-gap COLUMN — wrong for an actions cell.) */
/* Buttons/forms side by side inside a table cell. NOWRAP on purpose: the cell is
   narrow in a wide table, and wrapping put "Revoke" on a second line under the
   history link. The enclosing .table-wrap scrolls horizontally, so keeping the
   row on one line costs at most a scrollbar. */
.row-actions { display: flex; flex-wrap: nowrap; align-items: center; gap: 6px; white-space: nowrap; }
.row-actions > * { flex: 0 0 auto; }
.row-actions .btn { white-space: nowrap; }
.inline-form { display: inline-block; }
/* Compliance registries: wide tables scroll in place; inactive/voided rows
   read as history without disappearing (they are records, not clutter). */
.table-scroll { overflow-x: auto; }
.row-inactive td, .doc-voided td { opacity: 0.55; }
.doc-actions { white-space: nowrap; }
.doc-actions .void-reason, .inline-form .void-reason { width: 110px; font-size: 12px; padding: 4px 6px; }
/* BTW skill checklists: two checkboxes per skill (practiced / proficient) */
.skill-set { border: 1px solid var(--red-900); border-radius: 8px; padding: 10px 12px; }
.skill-set legend { font-size: 12px; letter-spacing: 1px; text-transform: uppercase; color: var(--silver); padding: 0 6px; }
/* The §380.715(b) skill grid: one row per skill, Practiced / Proficient /
   On-record as headed columns (replaced the wrapping inline-flex .skill-box). */
.skill-grid { width: 100%; border-collapse: collapse; font-size: 13px; }
.skill-grid thead th { font-size: 11px; letter-spacing: 1px; text-transform: uppercase; color: var(--silver); text-align: left; padding: 4px 10px; border-bottom: 1px solid var(--steel); }
.skill-grid thead th + th, .skill-grid .skill-cell { text-align: center; }
.skill-grid .skill-name { font-weight: 400; text-align: left; padding: 5px 10px; border-bottom: 1px solid var(--red-900); }
.skill-grid .skill-name label { cursor: pointer; }
.skill-grid .skill-cell { padding: 5px 10px; border-bottom: 1px solid var(--red-900); }
.skill-grid tbody tr:last-child .skill-name, .skill-grid tbody tr:last-child .skill-cell { border-bottom: 0; }
.skill-grid input[type="checkbox"] { accent-color: var(--crimson); width: 16px; height: 16px; }
.skill-grid .skill-done { color: var(--neon-red); font-weight: 700; }
/* The phase heading a run of skills sits under (pre-trip, backing, and so on).
   A checklist worked down beside a truck is scanned by phase, not read as one
   list of thirty rows. */
.skill-grid tr.skill-group th {
  text-align: left; padding: 10px 10px 4px; font-size: 11px; letter-spacing: 1px;
  text-transform: uppercase; color: var(--neon-red); font-weight: 700; border-bottom: 1px solid var(--steel);
}
.skill-grid tbody:first-of-type tr.skill-group th { padding-top: 4px; }
.skill-grid .skill-note-cell { text-align: left; }
.skill-grid input.skill-note { width: 100%; min-width: 140px; font-size: 12px; padding: 3px 6px; }

/* Session list: skills in words — proficient badges wrap, context underneath */
.skills-cell { min-width: 260px; }
.skills-cell .badge { display: inline-block; margin: 1px 4px 1px 0; }
.skills-cell > div + div { margin-top: 3px; }

/* Training-log roster: attendance / BTW entry dialogs */
.entry-modal { border: 0; padding: 0; background: transparent; max-width: 560px; width: 94%; color: var(--white); }
.entry-modal--wide { max-width: 760px; }
.entry-modal::backdrop { background: rgba(0, 0, 0, .62); }
.entry-modal__box { display: flex; flex-direction: column; gap: 12px; background: var(--surface); border: 1px solid var(--steel); border-radius: var(--radius); padding: 22px; max-height: 86vh; overflow-y: auto; }
.entry-modal__box h3 { margin: 0; font-size: 17px; letter-spacing: .3px; }
.entry-modal__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 2px; }
.entry-signature { font-size: 12px; }
.training-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---------- TPR Reporting ----------------------------------------------------
   The transport banner must be unmistakable: TEST and LIVE reach the same FMCSA
   host and differ only in the credential the deployment holds, so the mode is
   said in colour as well as words. Findings are the reasons a filing cannot
   leave yet (blocking) or deserves a look (advisory); the request tables are
   the exact bytes. */
.card-mode { border-width: 2px; }
.card-mode-off { border-color: var(--steel); }
.card-mode-test { border-color: var(--red-400); background: var(--void-black); }
.card-mode-live { border-color: var(--neon-red); box-shadow: 0 0 0 3px rgba(204, 0, 0, .25); }
.card-mode-broken { border-color: var(--red-700); background: var(--deep-crimson); }
.tpr-tabs { margin-bottom: 14px; }
.tab-count { display: inline-block; min-width: 18px; padding: 0 6px; margin-left: 6px; border-radius: 9px; background: var(--deep-crimson); color: var(--white); font-size: 11px; text-align: center; }
.finding-list { list-style: none; margin: 10px 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.finding { border: 1px solid var(--steel); border-left-width: 3px; border-radius: 8px; padding: 8px 12px; font-size: 14px; line-height: 1.5; }
.finding-blocking { border-left-color: var(--crimson); }
.finding-advisory { border-left-color: var(--red-400); color: var(--silver); }
.finding a { margin-left: 8px; color: var(--neon-red); white-space: nowrap; }
.tpr-request th { text-align: left; white-space: nowrap; color: var(--silver); font-weight: 600; }
.tpr-request code { color: var(--white); }
.tpr-clocks { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; font-size: 14px; margin: 6px 0 10px; }
.tpr-clock-date { white-space: nowrap; }
.tpr-bytes { white-space: pre-wrap; word-break: break-all; font-size: 12px; background: var(--void-black); border: 1px solid var(--steel); border-radius: 8px; padding: 10px 12px; margin: 6px 0; max-height: 240px; overflow: auto; }
.attempt-log { margin-top: 8px; }
.attempt-log summary { cursor: pointer; color: var(--neon-red); font-size: 13px; }
.attempt-log .attempt { border-top: 1px solid var(--steel); padding: 8px 0; }
.inline-check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--silver); margin-right: 8px; }
.inline-check input { accent-color: var(--crimson); }
/* Transmission outcomes (services/tprClient.js OUTCOMES), rendered with statusBadge(). */
.badge-accepted, .badge-accepted_with_warning { color: var(--white); border-color: var(--silver); }
.badge-rejected { color: var(--red-200); border-color: var(--red-700); }
.badge-retryable { color: var(--silver); border-color: var(--steel); }
.badge-unknown { color: var(--red-200); border-color: var(--red-400); background: var(--deep-crimson); }

/* Inspector View: student list beside the record panel */
.inspector-split { display: grid; grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); gap: 18px; align-items: start; }
@media (max-width: 860px) { .inspector-split { grid-template-columns: 1fr; } }
.inspector-list { background: var(--surface); border: 1px solid var(--steel); border-radius: var(--radius); padding: 10px; display: flex; flex-direction: column; gap: 4px; }
.inspector-search { display: flex; gap: 8px; margin-bottom: 8px; }
.inspector-search input[type="search"] { flex: 1; min-width: 0; }
.inspector-row { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: 8px; text-decoration: none; color: var(--white); }
.inspector-row:hover { background: var(--void-black); }
.inspector-row.active { background: var(--void-black); border-left: 3px solid var(--crimson); }
.inspector-row-name { font-weight: 600; }
.inspector-detail { min-width: 0; }
.card-subtitle { font-size: 15px; margin: 18px 0 8px; }
.form-row--tight label { min-width: 90px; }
/* completion gates + compliance alerts */
.gate-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--red-900); font-size: 14px; }
.gate-row:last-of-type { border-bottom: none; }
.alert-list, .report-links { margin: 6px 0; padding-left: 20px; line-height: 1.8; }
.btn:disabled { background: var(--steel); color: var(--silver); cursor: not-allowed; }
.btn-disabled { opacity: .4; pointer-events: none; }

/* Pagination controls */
.pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 16px; }
.pager-info { color: var(--silver); font-size: 13px; font-variant-numeric: tabular-nums; }

.flash { padding: 11px 14px; border-radius: 8px; font-size: 14px; margin-bottom: 16px; animation: pageEnter .35s ease both; }
.flash-ok { background: var(--surface); border: 1px solid var(--silver); color: var(--white); }
.flash-err { background: var(--deep-crimson); border: 1px solid var(--red-700); color: var(--neon-red); }

.qr-wrap { text-align: center; margin: 18px 0; }
.qr-wrap img { border-radius: 10px; border: 6px solid var(--white); }
.manual-key { margin: 0 0 18px; font-size: 13px; color: var(--silver); }
.manual-key code { word-break: break-all; }

/* ==========================================================================
   App shell - animated sidebar navigation
   ========================================================================== */
.app-body { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-w); flex-shrink: 0; display: flex; flex-direction: column;
  background: var(--void-black);
  border-right: 1px solid var(--steel);
  position: sticky; top: 0; height: 100vh;
  transition: margin-left .3s cubic-bezier(0.22, 1, 0.36, 1),
              width .25s cubic-bezier(0.22, 1, 0.36, 1);
}
/* The logo IS the home link. */
.sidebar-brand {
  display: block; padding: 14px 14px 10px;
  border-bottom: 1px solid var(--steel);
  transition: background .2s ease;
}
.sidebar-brand:hover { background: var(--deep-crimson); }
.sidebar-logo { width: 100%; height: auto; display: block; border-radius: 6px; }

.sidebar-nav { flex: 1; padding: 14px 10px; overflow-y: auto; }
.nav-section {
  font-size: 11px; letter-spacing: 2px; text-transform: uppercase;
  color: var(--silver); padding: 16px 10px 8px;
}
.nav-empty { color: var(--silver); font-size: 13px; padding: 8px 12px; line-height: 1.6; }

/* Nav items: sliding crimson rail + icon lift on hover, staggered entrance */
.nav-item {
  position: relative; display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; margin: 2px 0; border-radius: 8px;
  color: var(--silver); text-decoration: none; font-size: 14px; font-weight: 500;
  overflow: hidden;
  transition: color .2s ease, background .2s ease, padding-left .25s cubic-bezier(0.22, 1, 0.36, 1);
  animation: navSlideIn .5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes navSlideIn { from { opacity: 0; transform: translateX(-16px); } to { opacity: 1; transform: none; } }
.sidebar-nav .nav-item:nth-of-type(1) { animation-delay: .05s; }
.sidebar-nav .nav-item:nth-of-type(2) { animation-delay: .10s; }
.sidebar-nav .nav-item:nth-of-type(3) { animation-delay: .15s; }
.sidebar-nav .nav-item:nth-of-type(4) { animation-delay: .20s; }
.sidebar-nav .nav-item:nth-of-type(5) { animation-delay: .25s; }
.sidebar-nav .nav-item:nth-of-type(6) { animation-delay: .30s; }
.sidebar-nav .nav-item:nth-of-type(7) { animation-delay: .35s; }
.sidebar-nav .nav-item:nth-of-type(8) { animation-delay: .40s; }
.sidebar-nav .nav-item:nth-of-type(9) { animation-delay: .45s; }
.sidebar-nav .nav-item:nth-of-type(10) { animation-delay: .50s; }
.sidebar-nav .nav-item:nth-of-type(11) { animation-delay: .55s; }

.nav-item::before {          /* the sliding crimson rail */
  content: ""; position: absolute; left: 0; top: 20%; bottom: 20%;
  width: 3px; border-radius: 3px; background: var(--crimson);
  transform: scaleY(0); transform-origin: center;
  transition: transform .25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.nav-item:hover { color: var(--white); background: var(--deep-crimson); padding-left: 16px; }
.nav-item:hover::before { transform: scaleY(0.7); background: var(--neon-red); }
.nav-item.active { color: var(--neon-red); background: var(--deep-crimson); }
.nav-item.active::before { transform: scaleY(1); }
/* Portal Settings mode: the way back to training reads as an exit, not another
   destination — brighter than its siblings, separated from the admin items. */
.nav-item--back { color: var(--white); font-weight: 600; margin-bottom: 6px; border-bottom: 1px solid var(--red-900); border-radius: 8px 8px 0 0; }
.nav-icon { font-size: 17px; transition: transform .25s cubic-bezier(0.34, 1.56, 0.64, 1); }
.nav-item:hover .nav-icon { transform: translateY(-2px) scale(1.15); }
.nav-glyph-img { width: 18px; height: 18px; object-fit: contain; display: block; }
.frame-glyph { width: 20px; height: 20px; object-fit: contain; vertical-align: -4px; }
/* tiny progress ring next to enrolled-course nav items */
.nav-progress { margin-left: auto; font-size: 10.5px; color: var(--silver); font-variant-numeric: tabular-nums; }
.nav-item .nav-done { margin-left: auto; color: var(--neon-red); font-size: 12px; }

/* ---------- account popup ---------------------------------------------- */
.sidebar-footer { padding: 14px; border-top: 1px solid var(--steel); }
.account-menu { position: relative; }
.user-chip {
  display: flex; align-items: center; gap: 10px; font-size: 13px; width: 100%;
  background: transparent; border: 1px solid transparent; border-radius: 8px;
  color: var(--white); padding: 8px; cursor: pointer; font-family: inherit;
  transition: background .2s ease, border-color .2s ease;
}
.user-chip:hover, .user-chip.open { background: var(--deep-crimson); border-color: var(--steel); }
.user-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; text-align: left; }
.user-chip-caret { color: var(--silver); font-size: 11px; transition: transform .2s ease; }
.user-chip.open .user-chip-caret { transform: rotate(180deg); }
.user-avatar {
  width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; font-weight: 700; color: var(--white);
  background: var(--crimson);
}
.account-popup {
  position: absolute; bottom: calc(100% + 10px); left: 0; right: 0; z-index: 30;
  background: var(--surface); border: 1px solid var(--crimson); border-radius: 10px;
  box-shadow: 0 14px 34px rgba(0,0,0,.6); overflow: hidden;
  animation: pageEnter .2s ease both;
}
.account-popup-id {
  display: flex; flex-direction: column; gap: 2px; padding: 12px 14px;
  border-bottom: 1px solid var(--steel); font-size: 13px;
}
.account-popup-id span { color: var(--silver); font-size: 12px; word-break: break-all; }
.account-popup-item {
  display: block; width: 100%; padding: 11px 14px; font-size: 14px; text-align: left;
  color: var(--white); text-decoration: none; background: none; border: 0;
  cursor: pointer; font-family: inherit; transition: background .15s ease;
}
.account-popup-item:hover { background: var(--deep-crimson); color: var(--neon-red); }

/* ---------- collapse control -------------------------------------------
   Chevron only, against the sidebar's right edge and ABOVE the footer's
   divider - a flex child of .sidebar, so it stays pinned there instead of
   scrolling with the nav. A definite width keeps align-items:stretch from
   widening it, and margin-left:auto pushes it across the cross axis. Its
   name lives in aria-label/title, not in text. */
.nav-collapse {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 34px; height: 30px; margin: 0 14px 10px auto; padding: 0;
  background: transparent; border: 1px solid transparent; border-radius: 8px;
  color: var(--silver); cursor: pointer;
  font-family: inherit; font-size: 12.5px;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.nav-collapse:hover { background: var(--deep-crimson); border-color: var(--steel); color: var(--white); }
.nav-collapse-icon { display: grid; place-items: center; font-size: 15px; }
.nav-collapse-icon::before { content: "\00AB"; }   /* « */

/* ---------- collapsed (icons-only) sidebar -----------------------------
   #navCollapse (public/js/portal.js) toggles .collapsed and writes the
   training_nav cookie, which the server reads back so the sidebar renders
   collapsed on the FIRST paint - see sidebarCollapsed() in views/i18n.js.

   Labels are hidden visually rather than with display:none so screen readers
   still announce them, and the hover flyout is painted from data-label.
   The flyout MUST be position:fixed: .sidebar-nav (overflow-y:auto) and
   .nav-item (overflow:hidden) would both clip an absolute one. Leaving
   top/bottom at auto keeps a fixed box at its static position, so it lines up
   with its own row while escaping every ancestor's clip.

   Desktop only - below 861px the hamburger owns the sidebar (see the 860px
   query), so the whole feature stays out of the mobile slide-in's way.
   ---------------------------------------------------------------------- */
@media (min-width: 861px) {
  /* position:sticky makes the sidebar its own stacking context, so the flyouts'
     z-index only sorts them inside it - the whole rail has to outrank the
     topbar (5) and the page content, which come later in tree order. */
  .sidebar.collapsed { width: var(--sidebar-w-collapsed); z-index: 25; }

  /* Crop the wordmark to its square helmet mark (x -4..224 of the 820-wide
     viewBox) instead of squeezing seven letters into 68px. */
  /* flex so the flyout's static position centres on the mark, the same way it
     does inside a nav row (a block container would drop it below the logo) */
  .sidebar.collapsed .sidebar-brand { padding: 10px 8px; display: flex; align-items: center; }
  .sidebar.collapsed .brand-logo--sidebar { overflow: hidden; border-radius: 6px; }
  .sidebar.collapsed .brand-logo__base { width: 353%; }
  .sidebar.collapsed .brand-logo__name { display: none; }

  /* Section headings become hairline dividers (the first one would just
     double the brand's bottom border). */
  .sidebar.collapsed .sidebar-nav { padding: 10px 8px; }
  .sidebar.collapsed .nav-section {
    font-size: 0; padding: 0; height: 1px; margin: 14px 6px 10px;
    background: var(--steel); overflow: hidden;
  }
  .sidebar.collapsed .sidebar-nav > .nav-section:first-child { display: none; }
  .sidebar.collapsed .nav-empty { display: none; }

  .sidebar.collapsed .nav-item,
  .sidebar.collapsed .nav-item:hover,
  .sidebar.collapsed .user-chip { justify-content: center; gap: 0; padding: 10px 0; }
  /* no right edge worth hugging in a 68px rail - centre it on the icon column */
  .sidebar.collapsed .nav-collapse { margin-inline: auto; }
  /* An element with a transform animation in effect - navSlideIn, even after it
     finishes, because fill-mode is `both` - becomes a containing block for
     fixed descendants, which puts the flyout back inside this item's
     overflow:hidden and the nav's scroll clip. Drop both when collapsed: there
     are no labels left to slide in. */
  .sidebar.collapsed .nav-item { animation: none; overflow: visible; }
  .sidebar.collapsed .sidebar-footer { padding: 10px 8px; }
  /* completed courses keep their tick as a corner badge */
  .sidebar.collapsed .nav-item .nav-done {
    position: absolute; top: 3px; right: 7px; margin: 0; font-size: 10px;
  }

  .sidebar.collapsed .nav-label,
  .sidebar.collapsed .nav-progress,
  .sidebar.collapsed .user-chip-name,
  .sidebar.collapsed .user-chip-caret {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }

  .sidebar.collapsed .nav-collapse-icon::before { content: "\00BB"; }   /* » */
  /* the popup has no room to sit under a 52px chip - fly it out to the right */
  .sidebar.collapsed .account-popup { left: 0; right: auto; min-width: 236px; }

  .sidebar.collapsed .sidebar-brand::after,
  .sidebar.collapsed .nav-item::after,
  .sidebar.collapsed .user-chip::after,
  .sidebar.collapsed .nav-collapse::after {
    content: attr(data-label);
    position: fixed; left: calc(var(--sidebar-w-collapsed) + 9px);
    z-index: 40; pointer-events: none;
    padding: 7px 11px; border-radius: 8px;
    background: var(--surface); border: 1px solid var(--crimson);
    box-shadow: 0 10px 26px rgba(0,0,0,.6);
    color: var(--white); font-size: 13px; font-weight: 500; white-space: nowrap;
    opacity: 0; transform: translateX(-6px);
    transition: opacity .15s ease, transform .15s ease;
  }
  .sidebar.collapsed .sidebar-brand:hover::after,
  .sidebar.collapsed .sidebar-brand:focus-visible::after,
  .sidebar.collapsed .nav-item:hover::after,
  .sidebar.collapsed .nav-item:focus-visible::after,
  .sidebar.collapsed .user-chip:hover::after,
  .sidebar.collapsed .user-chip:focus-visible::after,
  .sidebar.collapsed .nav-collapse:hover::after,
  .sidebar.collapsed .nav-collapse:focus-visible::after { opacity: 1; transform: none; }
  /* the flyout would sit on top of the open account menu */
  .sidebar.collapsed .user-chip.open::after { display: none; }
}
@media (max-width: 860px) { .nav-collapse { display: none; } }

/* ---------- top bar + content ----------------------------------------- */
.main-column { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.topbar {
  display: flex; align-items: center; gap: 14px; padding: 14px 26px;
  border-bottom: 1px solid var(--steel);
  background: var(--void-black);
  position: sticky; top: 0; z-index: 5;
}
.topbar-head { display: flex; flex-direction: row; align-items: center; flex-wrap: wrap; gap: 6px 14px; min-width: 0; }
.topbar-title { font-size: 17px; margin: 0; font-weight: 600; letter-spacing: .3px; }
.topbar-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 11.5px; color: var(--silver); padding-left: 14px; border-left: 1px solid var(--steel); }
.topbar-meta .tm-label { text-transform: uppercase; letter-spacing: 1px; font-size: 9.5px; color: rgba(192,192,192,.55); margin-right: 4px; }
.topbar-meta .tm-sep { color: rgba(192,192,192,.35); }

/* Verse of the day (src/views/dailyVerse.js), in the empty width beside the
   meta row. It must not change the bar's HEIGHT - --guide-jump and every other
   in-page jump offset is hand-measured against this bar - and two nested boxes
   are what make that structural rather than a matter of the text happening to
   be short enough:

   .topbar-verse is IN FLOW, so it reserves its own column and cannot overlap
   the meta row the way an absolute box could, and did, at 1280. Everything
   inside it is out of flow, so its base size is 0 both ways: no height, and no
   width taken from .topbar-head, which still wraps exactly where it always did
   (measured: head 703px and bar height identical with and without the verse,
   375px to 1920px). Both declarations are load-bearing - with a zero base size
   and no flex-grow the column has no width at all, and without relative the
   line resolves against the sticky bar and spans the whole of it.

   .tv-line is OUT OF FLOW, so no wrap can add height: 52px either way, against
   58.7px with the same text in flow. It cannot grow to fit either, so clipping
   is what keeps an over-budget verse off the bar, and test/dailyVerse.test.js
   holds the list to the 2 lines this box shows (measured: a 400px box at 11px
   takes 142 characters before a third line).

   Hidden below 1420px, where the meta row leaves less than that 400px - it
   wraps to two and three rows as the viewport narrows and takes the whole bar
   with it, and growing the bar to compensate is the one thing this must not do.

   Colour: --crimson is only 3.4:1 on --void-black, under AA at this size, so
   the verse takes the brighter reds off the same scale. */
.topbar-verse { flex: 1; position: relative; }
.tv-line {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  width: 100%; max-height: 30px; overflow: hidden;
  text-align: right; font-size: 11px; line-height: 1.35; font-style: italic;
}
.tv-text { color: var(--neon-red); }
.tv-ref { color: var(--red-200); font-style: normal; white-space: nowrap; }
.tv-ver { font-size: 9.5px; letter-spacing: 1px; text-transform: uppercase; }
@media (max-width: 1420px) { .topbar-verse { display: none; } }

/* Language selector - lives as a tm-item in the topbar meta row and in the
   public-page footer. Styled to read like the other tm-items (mini uppercase
   label + value); the OK button is the zero-JS fallback (portal.js hides it
   and auto-submits on change). */
.tm-lang { display: inline-flex; align-items: center; gap: 6px; }
.tm-lang .tm-label { display: inline; }
.tm-lang-select {
  font: inherit; font-size: 11.5px; color: var(--silver);
  background: var(--void-black); border: 1px solid var(--steel);
  border-radius: 6px; padding: 2px 6px; cursor: pointer;
}
.tm-lang-select:hover, .tm-lang-select:focus { border-color: var(--crimson); outline: none; color: var(--white); }
.tm-lang-go {
  font: inherit; font-size: 10.5px; color: var(--silver);
  background: transparent; border: 1px solid var(--steel);
  border-radius: 6px; padding: 2px 8px; cursor: pointer;
}
.tm-lang-go:hover { border-color: var(--crimson); color: var(--white); }
.public-lang { margin-top: 14px; display: flex; justify-content: center; }
.public-lang .tm-label {
  text-transform: uppercase; letter-spacing: 1px; font-size: 9.5px;
  color: rgba(192,192,192,.55);
}

.hamburger {
  display: none; flex-direction: column; gap: 4px; background: none; border: 0;
  cursor: pointer; padding: 6px;
}
.hamburger span { width: 20px; height: 2px; background: var(--white); border-radius: 2px; transition: transform .25s, opacity .25s; }
.hamburger.open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.hamburger.open span:nth-child(2) { opacity: 0; }
.hamburger.open span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.content { padding: 26px; max-width: 1200px; width: 100%; }
.content.full-bleed { max-width: none; padding: 0; display: flex; flex-direction: column; flex: 1; }

/* ==========================================================================
   Course panels - frosted-glass tiles on Void Black (same treatment as the
   Command Deck) with progress bars for enrolled courses.
   ========================================================================== */
.section-title {
  font-size: 13px; letter-spacing: 2.5px; text-transform: uppercase;
  color: var(--crimson); margin: 30px 0 14px;
}
.section-title:first-child { margin-top: 0; }
.tool-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 18px; }
.tool-card {
  position: relative; overflow: hidden;
  display: block; padding: 22px; min-height: 160px;
  text-decoration: none; color: var(--white);
  border-radius: 20px;
  background: rgba(26, 26, 26, 0.55);
  -webkit-backdrop-filter: blur(12px) saturate(120%);
  backdrop-filter: blur(12px) saturate(120%);
  border: 1px solid rgba(204, 0, 0, 0.45);
  box-shadow: 0 10px 34px rgba(0,0,0,.5), 0 0 16px rgba(204,0,0,.20),
              inset 0 1px 0 rgba(255,255,255,.14);
  transition: transform .25s cubic-bezier(0.22, 1, 0.36, 1), border-color .25s, box-shadow .25s;
  animation: pageEnter .5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.tool-card::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 2;
  background: linear-gradient(135deg, rgba(255,255,255,.12) 0%, rgba(255,255,255,0) 45%);
}
/* Entrance stagger. Matched on the GRID ITEM, not on .tool-card: a
   CDL-regulated tile wraps its card in .course-tile (see below), so the card is
   no longer the grid's nth child and every wrapped tile would otherwise share
   the first delay. */
.tool-grid > :nth-child(1) .tool-card, .tool-grid > .tool-card:nth-child(1) { animation-delay: .05s; }
.tool-grid > :nth-child(2) .tool-card, .tool-grid > .tool-card:nth-child(2) { animation-delay: .11s; }
.tool-grid > :nth-child(3) .tool-card, .tool-grid > .tool-card:nth-child(3) { animation-delay: .17s; }
.tool-grid > :nth-child(4) .tool-card, .tool-grid > .tool-card:nth-child(4) { animation-delay: .23s; }
.tool-grid > :nth-child(5) .tool-card, .tool-grid > .tool-card:nth-child(5) { animation-delay: .29s; }
.tool-grid > :nth-child(6) .tool-card, .tool-grid > .tool-card:nth-child(6) { animation-delay: .35s; }
.tool-card:hover {
  transform: translateY(-4px);
  border-color: var(--crimson);
  box-shadow: 0 16px 40px rgba(0,0,0,.6), 0 0 24px rgba(204,0,0,.45),
              inset 0 1px 0 rgba(255,255,255,.18);
}
/* A course whose content is not mounted: shown so the student can see their
   enrollment and progress, but not a link — /learn would refuse it. Dimmed and
   inert rather than hidden, because hiding a paid course looks like data loss. */
.tool-card--disabled {
  cursor: not-allowed; opacity: .55; border-color: var(--steel);
  filter: grayscale(80%);
}
.tool-card--disabled:hover { transform: none; border-color: var(--steel); box-shadow: none; }
.nav-item--disabled { cursor: not-allowed; opacity: .5; }
.nav-item--disabled:hover { color: var(--silver); background: transparent; padding-left: 12px; }
.nav-item--disabled:hover::before { transform: scaleY(0); }

.tool-card-bg {
  position: absolute; right: -12px; bottom: -30px; font-size: 150px; line-height: 1;
  opacity: .10; pointer-events: none; user-select: none; z-index: 0;
  transition: opacity .25s ease, transform .25s ease;
}
.tool-card:hover .tool-card-bg { opacity: .16; transform: scale(1.05) rotate(-4deg); }
.tool-card-bg--img {
  width: 150px; height: auto; max-height: 150px; object-fit: contain;
  right: -8px; bottom: -18px; font-size: 0;
}
/* ---------- CDL details panel ----------------------------------------------
   A CDL-regulated course tile reveals its program-completion requirements on
   hover, and on keyboard focus of the tile link (:focus-within) so the panel is
   not mouse-only. It has to be a SIBLING of .tool-card inside this wrapper
   rather than a child: .tool-card sets overflow:hidden for its background
   glyph, which would clip a panel that overhangs the tile.

   pointer-events stay off even when shown — the panel is read-only, and letting
   it swallow clicks would block the tiles it overhangs. The wrapper is the grid
   item, so it must fill the track or the tile inside it shrinks. */
.course-tile { position: relative; display: flex; }
.course-tile > .tool-card { flex: 1 1 auto; }
.cdl-pop {
  position: absolute; z-index: 30; left: 0; right: 0; top: calc(100% + 8px);
  min-width: 260px; padding: 14px 16px;
  border-radius: 16px; border: 1px solid var(--crimson);
  background: var(--surface);
  box-shadow: 0 18px 44px rgba(0,0,0,.72), 0 0 22px rgba(204,0,0,.35);
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity .18s ease, transform .18s ease, visibility .18s;
  pointer-events: none;
}
.course-tile:hover .cdl-pop,
.course-tile:focus-within .cdl-pop { opacity: 1; visibility: visible; transform: translateY(0); }
.cdl-pop-head {
  font-weight: 700; font-size: 14px; margin-bottom: 8px;
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
}
.cdl-pop .gate-row { font-size: 13px; padding: 5px 0; }
.cdl-pop p { margin: 10px 0 0; font-size: 12px; }
/* The panel always opens DOWNWARD. An earlier version flipped the grid's last
   tiles upward to avoid running off the bottom of the page, keyed on
   :nth-last-child — which is wrong: document order says nothing about vertical
   position, so in a multi-column grid where every tile shares one row it threw
   the panel ~380px above the top of the viewport, where it cannot be scrolled
   to at all. Off the bottom is recoverable by scrolling; off the top is not. */

.tool-card-body { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 8px; height: 100%; }
.tool-name { font-weight: 700; font-size: 17px; }
.tool-desc { color: var(--silver); font-size: 13px; line-height: 1.5; flex: 1; }
.tool-meta { color: var(--silver); font-size: 11px; letter-spacing: 1px; display: flex; gap: 10px; align-items: center; }
.price-tag { color: var(--white); font-weight: 700; font-size: 15px; letter-spacing: 0; }
.price-tag small { color: var(--silver); font-weight: 400; }

/* progress bar (course cards, transcript) */
.progress-track {
  height: 7px; border-radius: 4px; background: var(--void-black);
  border: 1px solid var(--steel); overflow: hidden; position: relative;
}
.progress-fill { height: 100%; background: var(--crimson); border-radius: 4px; transition: width .4s cubic-bezier(0.22, 1, 0.36, 1); }
.progress-fill.done { background: var(--neon-red); }
/* 5%-step widths: the strict CSP (style-src 'self') forbids inline styles */
.pw-0{width:0}.pw-5{width:5%}.pw-10{width:10%}.pw-15{width:15%}.pw-20{width:20%}
.pw-25{width:25%}.pw-30{width:30%}.pw-35{width:35%}.pw-40{width:40%}.pw-45{width:45%}
.pw-50{width:50%}.pw-55{width:55%}.pw-60{width:60%}.pw-65{width:65%}.pw-70{width:70%}
.pw-75{width:75%}.pw-80{width:80%}.pw-85{width:85%}.pw-90{width:90%}.pw-95{width:95%}.pw-100{width:100%}
.progress-line { display: flex; align-items: center; gap: 10px; font-size: 12px; color: var(--silver); }
.progress-line .progress-track { flex: 1; }
.progress-pct { font-variant-numeric: tabular-nums; min-width: 34px; text-align: right; }

.empty-state { text-align: center; padding: 70px 20px; color: var(--silver); line-height: 1.7; }
.empty-icon { font-size: 44px; display: block; margin-bottom: 12px; }

/* ---------- course frame (secure iframe) ------------------------------- */
.tool-frame-bar {
  display: flex; align-items: center; gap: 14px; padding: 10px 16px;
  border-bottom: 1px solid var(--steel); background: var(--surface);
}
.tool-frame-title { font-weight: 600; font-size: 14px; }
.tool-frame-title em { color: var(--silver); font-style: normal; font-size: 12px; }
.tool-frame-note { margin-left: auto; font-size: 11px; color: #7d7d7d; letter-spacing: .5px; }
.tool-frame { flex: 1; width: 100%; border: 0; min-height: 70vh; background: var(--void-black); }
.tool-frame.light { background: #fff; }

/* ---------- cards / tables / detail ------------------------------------ */
.card {
  background: var(--surface); border: 1px solid var(--steel); border-radius: var(--radius);
  padding: 22px; margin-bottom: 20px;
}
.card.featured { border-color: var(--crimson); }
.card.card-alert { background: var(--deep-crimson); border-color: var(--red-700); }
.card-title { margin: 0 0 16px; font-size: 16px; letter-spacing: .4px; }
/* A card header that carries an action alongside its title — the record's own
   control (deactivate/reactivate an instructor or vehicle) belongs at the top
   right of the panel, not below the edit form where it reads as part of saving.
   The title keeps its own bottom margin, so the row only aligns the two. */
.card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.card-head .card-title { flex: 1 1 auto; }
/* margin-left:auto, not only justify-content: on a very narrow panel the action
   wraps onto its own row, where space-between no longer positions anything —
   this keeps it against the right edge either way. */
.card-head > form { margin-left: auto; }
/* Reason-capture popup (styled <dialog>) for audited actions like certificate
   revoke/authorize — replaces the inline table-cell textbox. */
.reason-modal { border: 0; padding: 0; background: transparent; max-width: 460px; width: 92%; color: var(--white); }
.reason-modal::backdrop { background: rgba(0, 0, 0, .62); }
.reason-modal__box { display: flex; flex-direction: column; gap: 12px; background: var(--surface); border: 1px solid var(--steel); border-radius: var(--radius); padding: 22px; }
.reason-modal__title { margin: 0; font-size: 17px; letter-spacing: .3px; }
.reason-modal__msg { margin: 0; color: var(--silver); font-size: 14px; line-height: 1.5; }
.reason-modal__label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--silver); }
.reason-modal__input { font: inherit; color: var(--white); background: var(--void-black); border: 1px solid var(--steel); border-radius: 8px; padding: 10px 12px; width: 100%; box-sizing: border-box; resize: vertical; min-height: 76px; }
.reason-modal__input:focus { outline: none; border-color: var(--crimson); box-shadow: 0 0 0 3px rgba(204, 0, 0, .30); }
.reason-modal__err { margin: 0; color: var(--crimson); font-size: 13px; }
.reason-modal__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 2px; }
/* Course management: section headings + question/test editor */
.section-head { color: var(--crimson); margin: 24px 0 8px; font-size: 14px; letter-spacing: .3px; }
.q-edit { border: 1px solid var(--steel); border-radius: 10px; padding: 12px 16px; margin: 14px 0; }
.q-edit legend { padding: 0 8px; color: var(--silver); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .5px; }
.q-edit .block { margin: 10px 0; }
.q-edit textarea { width: 100%; box-sizing: border-box; resize: vertical; }
.q-choices { margin: 10px 0; display: flex; flex-direction: column; gap: 4px; }
.q-choice { flex-direction: row; align-items: center; gap: 10px; margin: 4px 0; }
.q-choice input[type="text"] { flex: 1; }
.q-choice input[type="radio"] { accent-color: var(--crimson); width: 18px; height: 18px; flex: 0 0 auto; }
/* Instructor notes (admin): read-only list of append-only notes + add form. */
.note-list { list-style: none; margin: 10px 0 0; padding: 0; }
.note-item {
  border: 1px solid var(--steel); border-left: 3px solid var(--crimson);
  border-radius: 8px; padding: 10px 14px; margin: 0 0 10px;
}
.note-meta { color: var(--silver); font-size: 12px; margin-bottom: 6px; }
.note-body { white-space: pre-wrap; line-height: 1.55; }
.muted { color: var(--silver); font-size: 13px; }
.ok-note { color: var(--white); } .warn-note { color: var(--red-200); }

.table-wrap { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.data-table th {
  text-align: left; padding: 9px 12px; color: var(--silver); font-weight: 600;
  border-bottom: 1px solid var(--steel); font-size: 12px; letter-spacing: 1px; text-transform: uppercase;
}
.data-table td { padding: 10px 12px; border-bottom: 1px solid var(--steel); vertical-align: top; }
.data-table tbody tr { transition: background .15s; }
.data-table tbody tr:hover { background: var(--deep-crimson); }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table th.num { text-align: right; }
/* Single-line cells for timestamps, ids and IPs — without this the browser
   squeezes short columns until a date wraps to four lines (the audit log did
   exactly that), quadrupling every row. The wrapping columns (detail, actor)
   absorb the width instead; .table-wrap scrolls if it ever runs out. */
.data-table .nowrap { white-space: nowrap; }
.data-table tr.course-missing { opacity: .7; }

/* Course catalog management (admin): one aligned grid per course. Columns:
   identity | status | enrolled | revenue | tuition | actions. */
.cat-list { margin: 4px 0 14px; }
.cat-row {
  display: grid;
  grid-template-columns: minmax(220px, 1.5fr) 100px 64px 90px 168px minmax(196px, auto);
  gap: 8px 14px; align-items: center;
  padding: 12px 12px; border-bottom: 1px solid var(--steel);
}
.cat-row:not(.cat-head):hover { background: var(--deep-crimson); }
.cat-row.course-missing { opacity: .75; }
.cat-head {
  font-size: 11px; text-transform: uppercase; letter-spacing: 1px;
  color: rgba(192,192,192,.6); padding-top: 2px; padding-bottom: 8px;
}
.cat-row .num { text-align: right; font-variant-numeric: tabular-nums; }
.cat-course { min-width: 0; }
.cat-name { line-height: 1.35; }
.cat-path { margin-top: 3px; font-size: 11.5px; }
.cat-path code { font-size: 11px; padding: 1px 5px; }
.cat-cell { font-size: 13.5px; }
.cat-price { display: flex; gap: 6px; align-items: center; }
.cat-price input {
  width: 88px; font: inherit; font-size: 13px; color: var(--white);
  background: var(--void-black); border: 1px solid var(--steel);
  border-radius: 6px; padding: 6px 8px; text-align: right;
  font-variant-numeric: tabular-nums;
}
.cat-price input:focus { outline: none; border-color: var(--crimson); }
.cat-actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
.cat-empty { padding: 18px 4px; }
@media (max-width: 1100px) {
  .cat-head { display: none; }
  .cat-row { grid-template-columns: 1fr 1fr; }
  .cat-row .cat-course { grid-column: 1 / -1; }
  .cat-row .cat-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

.detail-grid { display: grid; grid-template-columns: 150px 1fr; gap: 8px 16px; margin: 0 0 16px; font-size: 14px; }
.detail-grid dt { color: var(--silver); } .detail-grid dd { margin: 0; }

.form-row { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
.form-row label { min-width: 160px; flex: 1; }
.form-row label.compact { min-width: 110px; flex: 0 1 140px; }
.form-row .btn { flex-shrink: 0; }

.badge {
  display: inline-block; padding: 3px 10px; border-radius: 20px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .4px;
  background: var(--void-black); color: var(--silver); border: 1px solid var(--steel);
}
.badge-active, .badge-activated, .badge-delivered, .badge-succeeded { color: var(--white); border-color: var(--silver); }
.badge-completed { color: var(--neon-red); border-color: var(--crimson); background: var(--deep-crimson); }
.badge-pending_verification, .badge-pending_invitation, .badge-pending_activation, .badge-sent, .badge-draft { color: var(--silver); border-color: var(--steel); }
.badge-suspended, .badge-expired, .badge-expired_invitation, .badge-declined { color: var(--red-200); border-color: var(--red-700); }
/* an expiration inside its warning window — urgent but not yet lapsed */
.badge-warn { color: var(--red-200); border-color: var(--red-400); }
.badge-disabled, .badge-revoked, .badge-refunded, .badge-reversed { color: var(--neon-red); background: var(--deep-crimson); border-color: var(--red-700); }

.invite-block { border: 1px solid var(--steel); border-radius: 10px; padding: 14px; margin-bottom: 12px; }
.invite-head { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 8px; font-size: 13px; }
.event-list { margin: 10px 0 0; padding-left: 20px; font-size: 13px; color: var(--silver); line-height: 1.8; }
details summary { cursor: pointer; color: var(--neon-red); font-size: 13px; }

.steps { line-height: 1.9; font-size: 14px; color: var(--silver); }
.split-row { display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap; margin-bottom: 16px; }
.split-row .steps { flex: 1 1 260px; margin: 0; }
.split-row form { flex-shrink: 0; }

/* Segmented toggle (tabs) */
.seg-toggle { display: inline-flex; border: 1px solid var(--steel); border-radius: 8px; overflow: hidden; margin-bottom: 14px; }
.seg-toggle a {
  padding: 8px 16px; font-size: 13px; font-weight: 600; text-decoration: none;
  color: var(--silver); background: var(--void-black); transition: background .15s, color .15s;
}
.seg-toggle a + a { border-left: 1px solid var(--steel); }
.seg-toggle a:hover { color: var(--white); background: var(--deep-crimson); }
.seg-toggle a.on { background: var(--crimson); color: var(--white); }

.host-tag {
  display: inline-block; margin-left: 8px; padding: 2px 9px; border-radius: 12px;
  background: var(--deep-crimson); color: var(--neon-red); border: 1px solid var(--red-700);
  font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
}

/* ==========================================================================
   Checkout
   ========================================================================== */
.checkout-grid { display: grid; grid-template-columns: 1fr 340px; gap: 20px; align-items: start; }
.order-summary { position: sticky; top: 90px; }
.order-lines { display: flex; flex-direction: column; gap: 10px; font-size: 14px; margin-bottom: 14px; }
.order-line { display: flex; justify-content: space-between; gap: 12px; color: var(--silver); }
.order-line strong { color: var(--white); font-weight: 600; }
.order-line.total { border-top: 1px solid var(--steel); padding-top: 12px; font-size: 17px; }
.order-line.total strong { font-size: 20px; }
.order-line .off { color: var(--red-200); }
.card-row { display: flex; gap: 12px; }
.card-row label { flex: 1; }
.secure-note { display: flex; gap: 8px; align-items: flex-start; font-size: 12px; color: #8f8f8f; line-height: 1.5; margin-top: 14px; }
.discount-applied {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--void-black); border: 1px dashed var(--crimson); border-radius: 8px;
  padding: 6px 10px; font-size: 13px; color: var(--white);
}
.testcard-hint { font-size: 11.5px; color: #7a7a7a; margin-top: 10px; line-height: 1.6; }
.testcard-hint code { font-size: 11px; }

/* ==========================================================================
   Transcript
   ========================================================================== */
.transcript-course { margin-bottom: 26px; }
.transcript-head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 18px; margin-bottom: 12px; }
.transcript-head h3 { margin: 0; font-size: 17px; }
/* Per-course record actions: sit at the right, vertically centered with the
   course title/meta (no top margin, unlike the generic .btn-row). */
.head-actions { margin-left: auto; display: flex; gap: 10px; align-items: center; }
.transcript-module { margin: 14px 0 4px; font-size: 12.5px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--silver); }
.transcript-module .seat-note { text-transform: none; letter-spacing: .3px; color: #8f8f8f; }
.titem-row td:first-child { width: 30px; text-align: center; }
.titem-pass { color: var(--white); } .titem-fail { color: var(--red-200); }
/* A lesson under way: neither the finished white nor the failed red — the
   student is mid-module and the row now says so with its slide count. */
.titem-progress { color: var(--silver); }
.grade-chip { font-variant-numeric: tabular-nums; font-weight: 600; }
.cert-line {
  border: 1px solid var(--crimson); background: var(--deep-crimson); border-radius: 10px;
  padding: 14px 18px; margin-top: 14px; display: flex; gap: 12px; align-items: center; font-size: 14px;
}

/* ==========================================================================
   Admin dashboard stat tiles
   ========================================================================== */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 14px; margin-bottom: 22px; }
.stat-tile {
  background: var(--surface); border: 1px solid var(--steel); border-radius: var(--radius);
  padding: 16px 18px; animation: pageEnter .5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.stat-grid .stat-tile:nth-child(2) { animation-delay: .06s; }
.stat-grid .stat-tile:nth-child(3) { animation-delay: .12s; }
.stat-grid .stat-tile:nth-child(4) { animation-delay: .18s; }
.stat-grid .stat-tile:nth-child(5) { animation-delay: .24s; }
.stat-label { font-size: 11px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--silver); margin-bottom: 6px; }
.stat-value { font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; }
.stat-value small { font-size: 13px; color: var(--silver); font-weight: 400; }
.stat-tile.accent { border-color: var(--crimson); }
.stat-tile.accent .stat-value { color: var(--neon-red); }
/* The trend line under a dashboard figure ("12 joined in 30 days") — context
   for the number above it, deliberately quieter than the .stat-label. */
.stat-foot { margin-top: 5px; font-size: 11px; line-height: 1.35; color: var(--silver); }

/* The CDL dashboard's competency matrix: one column per appendix skill, so the
   headers run vertically or twenty long labels would push the table to several
   screens wide. The scroll container still owns any remaining overflow. */
.skill-matrix th.skill-col { vertical-align: bottom; padding: 6px 4px; }
.skill-matrix .skill-col-label {
  writing-mode: vertical-rl; transform: rotate(180deg);
  max-height: 180px; overflow: hidden; text-overflow: ellipsis;
  font-weight: 500; white-space: nowrap; display: inline-block;
}
.skill-matrix td.skill-cell { text-align: center; padding: 4px; }
.skill-matrix .skill-done-mark { color: var(--white); font-weight: 700; }

/* ---------- responsive -------------------------------------------------- */
@media (max-width: 980px) {
  .checkout-grid { grid-template-columns: 1fr; }
  .order-summary { position: static; }
}
@media (max-width: 860px) {
  .hamburger { display: flex; }
  .sidebar {
    position: fixed; z-index: 20; margin-left: calc(-1 * var(--sidebar-w));
    box-shadow: 0 0 40px rgba(0,0,0,.7);
  }
  .sidebar.open { margin-left: 0; }
  .detail-grid { grid-template-columns: 1fr; gap: 2px 0; }
  .detail-grid dt { margin-top: 8px; }
}

/* ==========================================================================
   User guide (/guide - src/routes/guide.js, rendered from USER_GUIDE.md by
   src/views/markdown.js). Reached from the 📖 entry in the account menu.
   Long-form prose, so it gets a narrower measure and a looser line height than
   the rest of the portal, plus a sticky contents rail.
   ========================================================================== */

/* --guide-jump is how far a jumped-to target sits from the top of the viewport.
   It has to clear the sticky top bar, whose height is NOT a constant:
   .topbar-meta wraps as the viewport narrows (measured with the full meta row -
   70px at 1280 and 860, 98px at 700, 121px at 600, 144px at 375), so the offset
   steps with it at the breakpoints below. Erring generous is deliberate - too
   much leaves a gap above the heading, too little hides it behind the bar. */
.guide { display: grid; grid-template-columns: 236px minmax(0, 1fr); gap: 32px; align-items: start;
  --guide-jump: 80px; }
@media (max-width: 860px) { .guide { --guide-jump: 112px; } }
@media (max-width: 620px) { .guide { --guide-jump: 136px; } }
@media (max-width: 460px) { .guide { --guide-jump: 160px; } }

/* The contents rail tracks the reader down a long document, clearing the top bar
   by the same offset its links scroll to. */
.guide-toc {
  position: sticky; top: var(--guide-jump); max-height: calc(100vh - 118px); overflow-y: auto;
  border-left: 1px solid var(--steel); padding-left: 16px;
}
.guide-toc-head {
  margin: 0 0 10px; font-size: 11px; letter-spacing: 2px;
  text-transform: uppercase; color: var(--silver);
}
.guide-toc-list { list-style: none; margin: 0; padding: 0; font-size: 13px; line-height: 1.45; }
.guide-toc-list a { display: block; padding: 4px 0; color: var(--silver); text-decoration: none; }
.guide-toc-list a:hover, .guide-toc-list a:focus-visible { color: var(--neon-red); }
/* Depth is relative to the shallowest heading that survived filtering, so these
   rules hold whichever level a guide starts its parts at. */
.guide-toc-d0 { margin-top: 10px; }
.guide-toc-d0 > a { color: var(--white); font-weight: 600; }
.guide-toc-d0:first-child { margin-top: 0; }
.guide-toc-d1 { padding-left: 12px; }
.guide-toc-d2 { padding-left: 24px; }

/* This is a DOCUMENT, not a dashboard, and it was originally styled like one -
   21px part headings, a tiny uppercase treatment for sub-headings, and the admin
   grid's borders on its tables. The scale below is closer to how a Markdown
   reader sets the same file: parts that read as chapter openers, real
   sub-headings, and fully ruled tables. Body copy is a shade off pure white so
   that **bold** has somewhere brighter to go. */
.guide-doc { min-width: 0; max-width: 800px; font-size: 15px; line-height: 1.7; color: #E6E6E6; }
.guide-doc p { margin: 0 0 16px; }
.guide-doc strong { color: var(--white); font-weight: 600; }
.guide-doc em { color: #F0F0F0; }
/* Shown when the reader's language has no generated guide yet and English is
   standing in - see routes/guide.js. */
.guide-fallback {
  margin: 0 0 22px; padding: 10px 14px; font-size: 13px;
  color: var(--silver); background: var(--surface);
  border-left: 3px solid var(--steel); border-radius: 0 8px 8px 0;
}
.guide-h {
  scroll-margin-top: var(--guide-jump);
  color: var(--white); font-weight: 600; letter-spacing: 0; text-wrap: balance;
}
.guide-h1 {
  font-size: 27px; line-height: 1.25; margin: 46px 0 18px; padding-bottom: 10px;
  border-bottom: 1px solid var(--steel);
}
.guide-h2 { font-size: 19.5px; line-height: 1.3; margin: 34px 0 12px; }
.guide-h3 { font-size: 16px; margin: 26px 0 8px; }
.guide-h4 { font-size: 14.5px; margin: 22px 0 6px; color: var(--silver); }
.guide-doc > .guide-h:first-child { margin-top: 0; }
/* The `---` separators between parts. Roomier than the heading's own rule so the
   two never read as a double line. */
.guide-rule { border: 0; border-top: 1px solid var(--steel); margin: 38px 0 0; }
.guide-list { margin: 0 0 16px; padding-left: 26px; }
.guide-list li { margin-bottom: 8px; }
.guide-list li::marker { color: var(--silver); }
.guide-list .guide-list { margin: 8px 0 2px; }
.guide-note {
  margin: 0 0 16px; padding: 12px 16px;
  background: var(--surface); border-left: 3px solid var(--crimson);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.guide-note > :last-child { margin-bottom: 0; }
/* A wide table scrolls inside its own box rather than widening the page. */
.guide-table-wrap { overflow-x: auto; margin: 0 0 20px; }
/* Fully ruled, standalone - NOT built on .data-table. That class is the admin
   grid: uppercase tracked headers and bottom borders only, which is right for a
   list of records and wrong for a reference table of prose, where the cell
   boundaries are what let you read across a row. */
.guide-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.guide-table th, .guide-table td {
  border: 1px solid var(--steel); padding: 9px 13px;
  text-align: left; vertical-align: top; min-width: 110px;
}
.guide-table th {
  background: var(--surface); color: var(--white); font-weight: 600;
  font-size: 13.5px; letter-spacing: 0; text-transform: none;
}
.guide-table tbody tr:hover { background: rgba(204, 0, 0, .10); }
.ta-center { text-align: center; }
.ta-right { text-align: right; }
/* Zero-height <a id> targets the guide pins onto sections whose titles it does
   not want its cross-links to depend on. */
.guide-anchor { display: block; height: 0; scroll-margin-top: var(--guide-jump); }

/* No contents rail: the public view is a handful of onboarding steps, so the
   route omits the rail rather than printing a list as long as the document. */
.guide--solo { grid-template-columns: minmax(0, 1fr); }
.guide--solo .guide-doc { max-width: none; }

/* A 27px chapter opener is right in a desktop column and shouty in a phone one,
   so the document scale steps down with the viewport. */
@media (max-width: 620px) {
  .guide-doc { font-size: 14.5px; }
  .guide-h1 { font-size: 23px; margin-top: 36px; }
  .guide-h2 { font-size: 18px; }
  .guide-h3 { font-size: 15.5px; }
  .guide-table th, .guide-table td { padding: 8px 10px; }
}

@media (max-width: 1000px) {
  .guide { grid-template-columns: minmax(0, 1fr); gap: 20px; }
  .guide-toc {
    position: static; max-height: none; overflow: visible;
    border-left: 0; border-bottom: 1px solid var(--steel);
    padding: 0 0 16px;
  }
}

/* ---------- print (transcripts + the user guide) ------------------------- */
@media print {
  body { background: #fff; color: #000; }
  .sidebar, .topbar, .btn, .no-print { display: none !important; }
  .guide { display: block; }
  .guide-toc, .guide-fallback { display: none; }
  .guide-doc { max-width: none; color: #000; }
  .guide-doc strong, .guide-doc em, .guide-h { color: #000; }
  .guide-h1, .guide-h2 { border-color: #999; }
  .guide-h4 { color: #333; }
  .guide-h, .guide-note, .guide-table-wrap { break-inside: avoid; }
  .guide-note { background: #fff; border-left-color: #666; }
  .guide-rule { border-top-color: #bbb; }
  /* .guide-table is standalone now, so it needs its own print colours rather
     than inheriting the data grid's below. */
  .guide-table th, .guide-table td { color: #000; border-color: #999; }
  .guide-table th { background: #f2f2f2; }
  .guide-table tbody tr:hover { background: none; }
  .content { max-width: none; padding: 0; }
  .card { border-color: #999; background: #fff; color: #000; break-inside: avoid; }
  .card-title, .transcript-head h3 { color: #000; }
  .muted, .transcript-module, .data-table th, .data-table td { color: #222; }
  .data-table th, .data-table td { border-color: #bbb; }
  .badge { border-color: #666; color: #000; background: #fff; }
  .progress-track { border-color: #999; }
  .progress-fill { background: #666; }
}

/* ---------- accessibility ----------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- setup wizard (routes/onboarding.js) --------------------------
   Moved out of an inline <style> block: the portal CSP is style-src 'self'
   with no 'unsafe-inline', so inline styles never applied and the wizard
   rendered unstyled. See test/csp.test.js. */
.ob-wrap { max-width: 640px; }
.ob-wrap--narrow { max-width: 560px; }
.ob-card { text-align: left; }
.ob-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.ob-field { display: flex; flex-direction: column; gap: 6px; font-size: .86rem; }
.ob-field.full { grid-column: 1 / -1; }
.ob-field input {
  padding: 9px 11px; border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, .18);
  background: rgba(255, 255, 255, .05); color: inherit;
}
.ob-section { margin: 18px 0 6px; font-weight: 600; opacity: .85; }
.ob-actions { display: flex; gap: 12px; margin-top: 20px; }
.ob-note { padding: 11px 13px; border-radius: 8px; margin: 14px 0; font-size: .88rem; }
.ob-note.err { background: rgba(220, 60, 60, .15); border: 1px solid rgba(220, 60, 60, .4); }
.ob-note.ok { background: rgba(60, 180, 110, .15); border: 1px solid rgba(60, 180, 110, .4); }
.ob-hint { opacity: .65; font-size: .8rem; margin-top: 2px; }
.ob-meta { opacity: .7; font-size: .82rem; margin-top: 6px; }

@media (max-width: 560px) {
  .ob-grid { grid-template-columns: 1fr; }
}

/* ---------- Database & Deployment (routes/adminDeployment.js) ------------
   Same story as .ob-* above — this page was styled entirely with inline
   attributes and therefore rendered unstyled under the CSP.
   Only LAYOUT lives here now. It previously carried its own type scale (rem,
   against the portal's px) and its own input skin (transparent fill, grey
   border, no focus ring), which is why the page read as a different product:
   labels and fields now inherit the global `label` / `input` rules, and the
   headings use .card-title like every other card. */
.dep-card { margin-bottom: 20px; }
.dep-hint { color: var(--silver); font-size: 13px; line-height: 1.6; margin: 0 0 4px; }
.dep-kv { border-collapse: collapse; margin-top: 8px; }
.dep-kv th {
  text-align: left; padding: 4px 16px 4px 0; font-weight: 500;
  color: var(--silver); font-size: 13px;
}
.dep-kv td { padding: 4px 0; }
.dep-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; max-width: 640px; margin-top: 12px; }
.dep-narrow { max-width: 360px; margin-top: 12px; }
.dep-actions { margin-top: 18px; }
.dep-word { color: var(--crimson); }

@media (max-width: 560px) {
  .dep-grid { grid-template-columns: 1fr; }
}

/* Long opaque values (user agents, tokens) in diagnostic tables. Referenced by
   routes/troubleshooting.js, which previously named a class that did not exist. */
.mono-cell {
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: .78rem; opacity: .8;
  max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Modifiers replacing former inline style attributes (see test/csp.test.js). */
.form-row--wrap { flex-wrap: wrap; align-items: flex-end; }
.card-title--flush { margin: 0; }

/* Launch-expired page served by routes/content.js. It previously carried an
   inline <style> block; the page loads portal.css so the rules belong here. */
.launch-expired {
  margin: 0; min-height: 100vh; padding: 24px;
  display: flex; align-items: center; justify-content: center; text-align: center;
  background: #0A0A0A; color: #F2F3F7;
  font-family: "Segoe UI", system-ui, sans-serif;
}
.launch-expired__box { max-width: 420px; }
.launch-expired__box h1 { color: #FF1A1A; font-size: 20px; margin: 0 0 10px; }
.launch-expired__box p { color: #C0C0C0; line-height: 1.6; font-size: 14px; }
.launch-expired__box .btn { margin-top: 18px; }

/* Course builder (Admin → Courses → Create course). */
.option-grid { border: 1px solid var(--border, #2a2a2a); border-radius: 8px; padding: 12px 16px; display: grid; gap: 10px; }
.option-grid legend { padding: 0 6px; font-weight: 600; }
label.check { display: flex; gap: 10px; align-items: flex-start; font-weight: 400; }
label.check input { margin-top: .25em; flex: none; }
.job-log {
  background: #0A0A0A; color: #C0C0C0; border: 1px solid #2a2a2a; border-radius: 8px;
  padding: 12px; font-size: .8rem; line-height: 1.5; max-height: 16rem; overflow: auto;
  white-space: pre-wrap; overflow-wrap: break-word;
}

/* --- Practice app: the per-course "Practice on your phone" page ---------- */
/* Three QR tiles side by side (two store listings + the pairing code), wrapping
   to one column on a phone. The pairing code is the larger one on purpose: it
   is the one scanned from a laptop screen across a desk. */
.qr-row { display: flex; flex-wrap: wrap; gap: 24px; justify-content: center; align-items: flex-start; margin: 12px 0 4px; }
.qr-tile { flex: 1 1 200px; max-width: 260px; text-align: center; }
.qr-tile h3 { margin: 0 0 4px; font-size: 15px; }
.qr-tile .qr-wrap { margin: 8px 0; }
.qr-tile .qr-wrap img { width: 160px; height: 160px; }
.qr-step { margin: 22px 0 6px; font-size: 16px; color: var(--silver); letter-spacing: 0.5px; }
.qr-pair .qr-wrap img { width: 240px; height: 240px; }
/* The typed fallback under the pairing QR: collapsed by default, the code set
   large and spaced the way the confirmation-code email prints its digits. */
.pair-manual { margin: 14px auto 6px; max-width: 420px; }
.pair-manual summary { cursor: pointer; color: var(--silver); font-size: 14px; }
.pair-code { text-align: center; font-size: 34px; letter-spacing: 8px; font-weight: 800; margin: 10px 0; }
