:root {
  --red: #e3001b;          /* sampled straight from the logo artwork */
  --red-hover: #b80016;
  --red-soft: #fdeaec;
  --ink: #111114;
  --ink-strong: #08080a;   /* headings, stat numbers, nav labels */
  --ink-2: #33373a;
  --muted: #6d7175;
  --muted-2: #8c9196;
  --border: #e3e3e3;
  --border-strong: #c9cccf;
  --bg: #f1f1f1;
  --surface: #ffffff;
  --dark: #1a1a1a;
  --success: #007a3d;
  --success-soft: #ddf4e4;
  --warning: #8a6116;
  --warning-soft: #fcf1cd;
  --info: #1f5199;
  --info-soft: #e4f0fd;
  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 16px;
  --shadow-xs: 0 1px 2px rgba(26, 26, 26, 0.05);
  --shadow-sm: 0 2px 8px rgba(26, 26, 26, 0.06), 0 1px 2px rgba(26, 26, 26, 0.05);
  --shadow-md: 0 12px 28px rgba(26, 26, 26, 0.09), 0 2px 6px rgba(26, 26, 26, 0.05);
  --shadow-lg: 0 28px 60px rgba(26, 26, 26, 0.14), 0 4px 12px rgba(26, 26, 26, 0.06);
  --sidebar-w: 252px;
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-display: "Oswald", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-tagline: "Baloo 2", "Inter", sans-serif;
  --transition: 140ms ease;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-font-smoothing: antialiased; }
body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  line-height: 1.55;
  font-size: 14px;
}

a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
/* All headings carry the logo's voice: condensed caps, near-black, bold,
   and sheared to the right like its lettering. (skewX shears on vertical
   position only, so left- and centre-aligned headings both stay put.) */
h1, h2, h3, h4 {
  margin: 0 0 0.4em;
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 1.25;
  font-weight: 700;
  color: var(--ink-strong);
  transform: skewX(-8deg);
}
p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

.text-link {
  color: var(--red);
  font-weight: 600;
  transition: color var(--transition);
}
.text-link:hover { color: var(--red-hover); text-decoration: underline; }

/* Brand mark — the real steering-wheel/pin artwork (extracted from the
   source logo) paired with a wordmark that echoes its bold, condensed,
   right-leaning lettering, and a rounded tagline face like the original. */
.brand-lockup { display: inline-flex; align-items: center; gap: 0.65rem; }
.brand-icon { flex-shrink: 0; display: block; }
.brand-text { display: flex; flex-direction: column; line-height: 1.05; }
.brand-word {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  transform: skewX(-10deg);
  white-space: nowrap;
}
.brand-word strong { color: var(--ink); font-weight: 700; }
.brand-word em { color: var(--red); font-weight: 700; font-style: normal; }
.brand-tagline {
  display: block;
  font-family: var(--font-tagline);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink);
  margin-top: 0.3rem;
  letter-spacing: 0.005em;
  white-space: nowrap;
}

/* Buttons — rectangular, restrained, Polaris-style */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 0.6rem 1.1rem;
  font-family: var(--font-display);
  font-size: 0.86rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition), box-shadow var(--transition);
  white-space: nowrap;
}
.btn:focus-visible { outline: 2px solid var(--red); outline-offset: 1px; }
.btn-primary {
  background: var(--red);
  color: #fff;
  box-shadow: 0 4px 12px rgba(216, 39, 28, 0.22);
}
.btn-primary:hover { background: var(--red-hover); box-shadow: 0 6px 16px rgba(216, 39, 28, 0.28); transform: translateY(-1px); }
.btn-ghost {
  background: transparent;
  color: #fff;
  border-color: rgba(255, 255, 255, 0.35);
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.08); }
.btn-outline {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-xs);
}
.btn-outline:hover { background: #fafafa; border-color: var(--muted-2); }
.btn-sm { padding: 0.42rem 0.85rem; font-size: 0.8rem; }
.btn-block { width: 100%; }
.btn-google {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  margin-bottom: 1.1rem;
}
.btn-google svg { width: 18px; height: 18px; flex-shrink: 0; }
.auth-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 1.1rem;
  color: var(--muted-2);
  font-size: 0.78rem;
}
.auth-divider::before,
.auth-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* Flash */
.flash-wrap {
  position: fixed;
  top: 5rem;
  right: 1rem;
  z-index: 2000;
  display: grid;
  gap: 0.6rem;
  max-width: 360px;
}
.flash {
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-md);
  font-size: 0.88rem;
  animation: toastIn 0.25s ease;
}
.flash-success { border-left: 3px solid var(--success); }
.flash-danger { border-left: 3px solid var(--red); }
.flash-warning { border-left: 3px solid var(--warning); }

@keyframes toastIn {
  from { opacity: 0; transform: translateY(-6px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Public shell / marketing pages */
.public-page { background: var(--surface); }
.public-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem clamp(1.25rem, 4vw, 3rem);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.nav-links { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; justify-content: flex-end; }
/* Logo + "Pricing / Sign in / Create School" stop fitting on one line
   around 480px — left as flex-wrap's default behavior, the "Create
   School" button was stranding itself indented under the wrapped nav
   items instead of the row properly breaking. Force the links onto
   their own full-width, centered second row instead. */
@media (max-width: 480px) {
  .public-nav { flex-wrap: wrap; row-gap: 0.6rem; justify-content: center; }
  .nav-links { width: 100%; justify-content: center; }
}
.nav-text-link {
  padding: 0.5rem 0.85rem;
  color: var(--muted);
  font-weight: 600;
  font-size: 0.88rem;
  border-radius: var(--radius);
  transition: color var(--transition), background var(--transition);
}
.nav-text-link:hover { color: var(--ink); background: var(--bg); }

/* Marketing hero — confident type on a soft brand-tinted wash, with a
   tilted product-preview card for depth. Calm, not glowing. */
.hero {
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  align-items: center;
  gap: 3.5rem;
  padding: clamp(3.5rem, 8vw, 7rem) clamp(1.25rem, 4vw, 3rem);
  background:
    radial-gradient(ellipse 900px 500px at 85% -10%, rgba(216, 39, 28, 0.07), transparent 60%),
    radial-gradient(ellipse 700px 500px at -10% 110%, rgba(216, 39, 28, 0.05), transparent 60%),
    var(--surface);
}
.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.35rem 0.75rem;
  border-radius: 999px;
  background: var(--red-soft);
  color: var(--red);
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 1.25rem;
}
.hero h1 {
  font-size: clamp(2.4rem, 4.6vw, 3.6rem);
  max-width: 13ch;
  margin-bottom: 1.1rem;
  letter-spacing: -0.03em;
  font-weight: 800;
}
.hero-lead {
  color: var(--muted);
  font-size: clamp(1.02rem, 1.4vw, 1.15rem);
  max-width: 46ch;
  margin-bottom: 2.1rem;
}
.hero-copy { min-width: 0; }
.hero-cta { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.hero-cta .btn { padding: 0.75rem 1.4rem; font-size: 0.92rem; }
.hero-cta .btn-primary { box-shadow: 0 10px 24px rgba(216, 39, 28, 0.22); }

/* How-it-works steps */
.steps-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.5rem;
  max-width: 1080px;
  margin: 0 auto;
}
.step-card { text-align: center; padding: 0 0.5rem; }
.step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-bottom: 1rem;
  border-radius: 50%;
  background: var(--red);
  color: #fff;
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 700;
}
.step-card h3 { font-size: 1.05rem; margin-bottom: 0.4rem; }
.step-card p { color: var(--muted); font-size: 0.9rem; }

/* Final CTA band */
.cta-band {
  text-align: center;
  background:
    radial-gradient(ellipse 700px 400px at 50% -20%, rgba(216, 39, 28, 0.1), transparent 60%),
    var(--surface);
}
.cta-band h2 { font-size: clamp(1.6rem, 2.6vw, 2.2rem); margin-bottom: 0.6rem; }
.cta-band p { color: var(--muted); max-width: 46ch; margin: 0 auto 1.75rem; }

/* Hero visual — a flat, realistic product-screenshot style preview,
   gently tilted in 3D for depth on wide screens. */
.hero-visual { position: relative; z-index: 1; }
.hero-preview {
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  transform: perspective(1400px) rotateY(-6deg) rotateX(2deg);
  transition: transform 400ms ease;
}
.hero-preview:hover { transform: perspective(1400px) rotateY(-2deg) rotateX(1deg); }
.hero-preview-bar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--border);
  background: #fafafa;
}
.hero-preview-bar span {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--border-strong);
}
.hero-preview-bar span:first-child { background: #e0645b; }
.hero-preview-bar span:nth-child(2) { background: #e0b84f; }
.hero-preview-bar span:nth-child(3) { background: #63b57a; }
.hero-preview-body { padding: 1.35rem; }
/* This card is a fixed, illustrative 3-item mockup, not a real dashboard
   with a variable stat count — the shared .stats grid's mobile override
   (repeat(2, 1fr), added for real dashboards elsewhere) leaves this card's
   3rd tile stranded alone in column 1 of its own row instead of centered.
   Flexbox instead of grid here specifically so a wrapped trailing item
   centers on its own line via justify-content, at every width. */
.hero-preview .stats {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin-bottom: 1.1rem;
}
.hero-preview .stat { flex: 1 1 130px; max-width: 160px; padding: 0.9rem 1rem; box-shadow: none; }
.hero-preview .stat .value { font-size: 1.5rem; }
.hero-preview-chart {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  height: 46px;
  margin-bottom: 1.1rem;
  padding: 0 0.1rem;
}
.hero-preview-chart span {
  flex: 1;
  border-radius: 3px 3px 0 0;
  background: var(--red-soft);
}
.hero-preview-chart span.up { background: var(--red); }
.hero-preview-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}
.hero-preview-row:last-child { border-bottom: 0; }
.hero-preview-row .who { font-weight: 600; }
.hero-preview-row .meta { color: var(--muted); font-size: 0.8rem; }

/* Drive scene — a looping illustration of a car driving up the road to a
   destination marked by the real pin/steering-wheel icon. */
.drive-scene {
  position: relative;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  padding: 1.5rem 1.5rem 1.1rem;
}
.drive-scene-svg { width: 100%; height: auto; display: block; overflow: visible; }
.drive-dash {
  stroke-dasharray: 20 16;
  animation: driveDash 1s linear infinite;
}
.drive-car {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: driveCar 4s cubic-bezier(0.45, 0, 0.4, 1) infinite;
}
@keyframes driveDash {
  from { stroke-dashoffset: 72; }
  to { stroke-dashoffset: 0; }
}
@keyframes driveCar {
  0%   { transform: translateY(0) scale(1); opacity: 1; }
  62%  { transform: translateY(-208px) scale(0.4); opacity: 1; }
  76%  { transform: translateY(-224px) scale(0.32); opacity: 0; }
  77%  { transform: translateY(0) scale(1); opacity: 0; }
  94%  { transform: translateY(0) scale(1); opacity: 0; }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}
.drive-destination {
  position: absolute;
  top: 0.6rem;
  left: 50%;
  width: 54px;
  height: auto;
  transform: translateX(-50%);
  filter: drop-shadow(0 6px 10px rgba(26, 26, 26, 0.18));
}
.drive-badge {
  display: block;
  text-align: center;
  margin-top: 0.5rem;
  font-family: var(--font-display);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
  .drive-dash, .drive-car { animation: none; }
}

.section {
  padding: clamp(3rem, 6vw, 5rem) clamp(1.25rem, 4vw, 3rem);
}
.section-muted { background: var(--bg); }
.section-head {
  max-width: 620px;
  margin-bottom: 2.5rem;
}
.section-head.center { margin-inline: auto; text-align: center; }
.section-kicker {
  color: var(--red);
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 0.65rem;
}
.section-head h2 { font-size: clamp(1.6rem, 2.6vw, 2.2rem); }
.section-head p { color: var(--muted); font-size: 1rem; }

.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.1rem;
}
.feature-card {
  padding: 1.5rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
  transition: transform var(--transition), border-color var(--transition), box-shadow var(--transition);
}
.feature-card:hover {
  transform: translateY(-3px);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
}
.feature-icon {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  background: var(--red-soft);
  color: var(--red);
  margin-bottom: 0.9rem;
}
.feature-icon.icon-blue { background: var(--info-soft); color: var(--info); }
.feature-icon.icon-green { background: var(--success-soft); color: var(--success); }
.feature-icon.icon-amber { background: var(--warning-soft); color: var(--warning); }
.feature-card h3 { font-size: 1.02rem; margin-bottom: 0.4rem; }
.feature-card p { color: var(--muted); font-size: 0.9rem; }
@media (max-width: 640px) {
  /* Matches the "How it works" step-cards' always-centered layout — those
     already read fine centered at every width, these read as left-shoved
     once the grid drops to one column on a phone. Icon is a fixed-size
     block box, so text-align on the card isn't enough to center it too —
     needs its own margin: auto. */
  .feature-card { text-align: center; }
  .feature-icon { margin-left: auto; margin-right: auto; }
}
@media (max-width: 640px) {
  /* Same left-shoved issue as .feature-card above, on the /pricing plan
     cards — heading, price, and blurb center; the feature bullet list
     keeps its own inline text-align:left (set in pricing.html) since a
     centered multi-line bullet list reads as ragged, not tidy. */
  .price-card { text-align: center; }
}

.site-footer {
  padding: 2rem clamp(1.25rem, 4vw, 3rem);
  background: var(--surface);
  border-top: 1px solid var(--border);
  color: var(--muted);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}
.powered-link {
  display: inline-block;
  text-decoration: none;
  transition: opacity var(--transition);
}
.powered-link:hover { opacity: 0.75; }
.powered-label {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 0.5rem;
}
.powered-img {
  width: 84px;
  max-width: 40%;
  height: auto;
  margin: 0 auto;
  border-radius: 50%;
}

/* Auth — split screen: branded visual panel + form panel */
.auth-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
}
.auth-visual {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  padding: 3rem 2.5rem;
  background:
    radial-gradient(circle at 15% 15%, rgba(255, 255, 255, 0.08), transparent 45%),
    linear-gradient(150deg, #1a1a1c 0%, #2a1414 55%, var(--red) 130%);
  color: #fff;
  text-align: center;
  overflow: hidden;
}
.auth-visual-brand { display: inline-flex; }
.auth-visual-brand .brand-word strong,
.auth-visual-brand .brand-word em { color: #fff; }
.auth-visual-brand .brand-tagline { color: rgba(255, 255, 255, 0.75); }
.auth-visual-scene {
  width: min(260px, 70%);
}
.auth-visual-heading {
  font-family: var(--font-display);
  font-size: 1.6rem;
  line-height: 1.25;
  max-width: 360px;
  margin: 0;
}
.auth-visual-tagline {
  color: rgba(255, 255, 255, 0.78);
  max-width: 340px;
  font-size: 0.95rem;
  margin: 0;
}
.auth-form-side {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2.5rem 1.5rem;
  background: var(--surface);
}
.auth-mobile-brand { display: none; }
.auth-card { width: min(400px, 100%); }
.auth-card.auth-card-wide { width: min(480px, 100%); }
.auth-card h1 { font-size: 1.6rem; text-align: center; }
.auth-card .sub { color: var(--muted); margin-bottom: 1.75rem; font-size: 0.92rem; text-align: center; }
.auth-footer {
  margin-top: 1.25rem;
  font-size: 0.86rem;
  color: var(--muted);
  text-align: center;
}

@media (max-width: 900px) {
  .auth-shell { grid-template-columns: 1fr; }
  .auth-visual { display: none; }
  .auth-form-side { padding: 2.5rem 1.25rem; }
  .auth-mobile-brand { display: inline-flex; margin-bottom: 1.75rem; }
}

/* Forms */
.form-group { margin-bottom: 1rem; }
.form-group label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink-2);
  margin-bottom: 0.35rem;
}
.form-control {
  width: 100%;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.6rem 0.75rem;
  font: inherit;
  font-size: 0.88rem;
  color: var(--ink);
  background: #fff;
  transition: border-color var(--transition), box-shadow var(--transition);
}
.form-control:focus {
  outline: none;
  border-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-soft);
}
.form-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.88rem;
  color: var(--muted);
  margin: 0.9rem 0 1.1rem;
}
.form-error { color: var(--red); font-size: 0.78rem; margin-top: 0.3rem; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }

/* A single text input plus one small action button on the same line
   (e.g. "add a rubric criterion", "download a CSV template" style rows) —
   distinct from .form-row (two roughly-equal fields): here the input
   should flex to fill the space and the button stay its own natural
   width, on desktop AND on mobile (just stacked, full-width, on mobile —
   see the max-width:820px block below). Without min-width:0 on the input,
   a flex item's default min-width:auto lets a long placeholder push the
   row wider than its container instead of letting the input shrink. */
.inline-add-row { display: flex; gap: 0.5rem; align-items: flex-start; }
.inline-add-row input { flex: 1; min-width: 0; }
.inline-add-row button { flex-shrink: 0; }

/* Mock-test question options, on the learner's take-test screen. */
.quiz-options { display: grid; gap: 0.6rem; }
.quiz-option {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 0.9rem;
  transition: border-color var(--transition), box-shadow var(--transition);
}
.quiz-option:has(input:checked) { border-color: var(--red); box-shadow: 0 0 0 3px var(--red-soft); }
.quiz-option input { flex-shrink: 0; }

/* App shell */
.app-shell { display: flex; min-height: 100vh; background: var(--bg); }

/* Mobile top bar (hamburger + centered brand + bell/avatar) and the
   sidebar-as-drawer it opens — both no-ops on desktop (display:none /
   the drawer transform never applies there), fully defined here so the
   @media block below only has to turn them on. */
.mobile-topbar { display: none; }
.sidebar-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(8, 8, 10, 0.45);
  z-index: 40;
  opacity: 0;
  transition: opacity var(--transition);
}
.sidebar-backdrop.is-open { opacity: 1; }
.sidebar-close { display: none; }

.sidebar {
  width: var(--sidebar-w);
  padding: 1rem 0.75rem;
  background: var(--surface);
  border-right: 1px solid var(--border);
  box-shadow: 1px 0 0 rgba(26, 26, 26, 0.02), var(--shadow-xs);
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  z-index: 10;
}
.sidebar-brand { display: flex; align-items: center; margin: 0.15rem 0.5rem 0.4rem; }
.sidebar-school {
  margin: 0 0.25rem 0.1rem;
  padding: 0.6rem 0.7rem;
  border-radius: var(--radius);
  background: var(--bg);
  font-size: 0.76rem;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.35;
}
.sidebar-school strong {
  display: block;
  color: var(--ink);
  font-size: 0.84rem;
  font-weight: 700;
  margin-bottom: 0.05rem;
}
.sidebar nav { display: grid; gap: 0.1rem; flex: 1; }
.nav-item {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius);
  color: var(--ink-strong);
  font-size: 0.86rem;
  font-weight: 600;
  transition: background var(--transition), color var(--transition);
}
/* Only the label leans — the icon and the active pill stay square, which
   is why the text is wrapped rather than skewing the whole row. */
.nav-label {
  display: inline-block;
  transform: skewX(-8deg);
}
.nav-item svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
  opacity: 0.75;
}
.nav-badge {
  margin-left: auto;
  font-size: 0.6rem;
  padding: 0.1rem 0.4rem;
  flex-shrink: 0;
}
.nav-item:hover { background: var(--bg); }
.nav-item.active {
  color: var(--red);
  background: var(--red-soft);
  font-weight: 600;
}
.nav-item.active svg { opacity: 1; }
.sidebar-footer {
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
}

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.95rem 1.5rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.topbar-copy h1 {
  font-size: 1.15rem;
  margin: 0;
}
.topbar-copy p {
  margin: 0.15rem 0 0;
  color: var(--muted);
  font-size: 0.84rem;
}
.topbar-user {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.notification-bell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  color: var(--ink-2);
}
.notification-bell:hover { background: var(--bg); }
.notification-bell svg { width: 20px; height: 20px; }
.notification-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: 8px;
  background: var(--red);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}
.user-chip {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.3rem 0.7rem 0.3rem 0.3rem;
  border-radius: var(--radius);
  background: var(--bg);
}
.user-meta { line-height: 1.2; }
.user-meta strong {
  display: block;
  font-size: 0.82rem;
}
.user-meta span {
  color: var(--muted);
  font-size: 0.74rem;
  font-weight: 500;
  text-transform: capitalize;
}

/* Avatar dropdown — see partials/macros.html's user_menu() macro. A small
   anchored popover (not the app's <dialog> modal pattern) toggled by
   .is-open, see app.js's ".user-menu-trigger" handler. */
.user-menu { position: relative; display: flex; }
.user-menu-trigger {
  display: flex;
  padding: 0;
  border: none;
  background: none;
  border-radius: 50%;
  cursor: pointer;
}
.user-menu-trigger:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
.user-menu-panel {
  display: none;
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  min-width: 210px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 0.4rem;
  z-index: 40;
}
.user-menu.is-open .user-menu-panel { display: block; }
.user-menu-header {
  padding: 0.5rem 0.6rem 0.6rem;
  margin-bottom: 0.3rem;
  border-bottom: 1px solid var(--border);
}
.user-menu-header strong { display: block; font-size: 0.85rem; }
.user-menu-header span {
  font-size: 0.74rem;
  color: var(--muted);
  text-transform: capitalize;
}
.user-menu-item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.6rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  color: var(--ink);
}
.user-menu-item:hover { background: var(--bg); }
.user-menu-item svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--muted); }

.content {
  padding: 1.25rem 1.5rem 2rem;
}

/* Dashboard components */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 0.9rem;
  margin-bottom: 1.25rem;
}
.stat {
  position: relative;
  overflow: hidden;
  padding: 1.1rem 1.2rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
  transition: box-shadow var(--transition), transform var(--transition);
}
.stat:hover { box-shadow: var(--shadow-sm); transform: translateY(-2px); }
.stat::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--border-strong);
}
.stat:has(.value.accent)::before { background: var(--red); }
.stat .label {
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 500;
}
.stat .value {
  font-family: var(--font-display);
  font-size: 1.95rem;
  font-weight: 700;
  color: var(--ink-strong);
  letter-spacing: -0.01em;
  margin-top: 0.3rem;
  transform: skewX(-8deg);
}
/* Card numbers all read dark black; the red lives in the card's top rule
   so the accent still registers without tinting the figure itself. */
.stat .value.accent { color: var(--ink-strong); }
.stat-clickable {
  font: inherit;
  text-align: left;
  cursor: pointer;
  width: 100%;
}
.stat-clickable.is-active {
  border-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-soft);
}
.stat-clickable.is-active::before { background: var(--red); }

@media (max-width: 820px) {
  /* A single full-width stat card on a phone is mostly empty space with
     its number pinned to the top-left corner — a tighter 2-up grid with
     centered content reads as an actual tile instead. Left as-is for
     .stat-clickable (admin's telemetry filter tiles), which isn't part
     of this pass. */
  .stats { grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
  .stat:not(.stat-clickable) { padding: 0.9rem 0.7rem; text-align: center; }
  .stat:not(.stat-clickable) .value { font-size: 1.5rem; }
}

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-xs);
  padding: 1.25rem;
  margin-bottom: 1.1rem;
}
.panel-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.6rem 1rem;
  margin-bottom: 1rem;
}
.panel-head h2 { font-size: 1rem; margin: 0; }
.panel-sub { color: var(--muted); font-size: 0.88rem; margin-top: -0.3rem; margin-bottom: 0.9rem; }

.table-wrap { overflow-x: auto; border-radius: var(--radius); border: 1px solid var(--border); box-shadow: var(--shadow-xs); }
table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
  background: #fff;
}
table.data th, table.data td {
  text-align: left;
  padding: 0.75rem 0.9rem;
  border-bottom: 1px solid var(--border);
}
table.data th {
  background: #fafafa;
  color: var(--muted);
  font-family: var(--font-display);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
table.data tr:last-child td { border-bottom: 0; }
table.data tbody tr { transition: background var(--transition); }
table.data tbody tr:hover { background: #fafafa; }

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.22rem 0.55rem;
  border-radius: 4px;
  font-size: 0.74rem;
  font-weight: 600;
  background: var(--bg);
  color: var(--ink-2);
}
.badge-green { background: var(--success-soft); color: var(--success); }
.badge-red { background: var(--red-soft); color: var(--red); }
.badge-amber { background: var(--warning-soft); color: var(--warning); }
.badge-blue { background: var(--info-soft); color: var(--info); }

/* Booking approval */
.await-hint {
  display: inline-block;
  margin-left: 0.4rem;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.respond-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0 0.25rem 0.25rem 0;
}
.respond-reason {
  width: 150px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.36rem 0.55rem;
  font: inherit;
  font-size: 0.8rem;
}
.respond-reason:focus {
  outline: none;
  border-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-soft);
}
.approval-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--border);
}
.approval-row:last-child { border-bottom: 0; }
.approval-row .when { font-weight: 600; }
.approval-row .who { color: var(--muted); font-size: 0.84rem; }
.panel-flag {
  border-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-soft);
}

.progress {
  height: 8px;
  border-radius: 999px;
  background: #e9e9ec;
  overflow: hidden;
}
/* background-size is stretched to the full track width (100% ÷ fill%), so a
   part-filled bar only reveals the bright left end of the ramp and deepens
   toward near-black red as it reaches completion. max() guards 0%. */
.progress-bar {
  height: 100%;
  border-radius: inherit;
  background-image: linear-gradient(
    90deg,
    #ff6a5a 0%,
    #f22d1e 32%,
    var(--red) 62%,
    #9b0012 86%,
    #63000b 100%
  );
  background-size: calc(100% * 100 / max(var(--pct, 100), 1)) 100%;
  background-repeat: no-repeat;
  transition: width 400ms ease;
}

/* Avatars */
.avatar {
  display: inline-grid;
  place-items: center;
  flex-shrink: 0;
  width: var(--avatar-size, 32px);
  height: var(--avatar-size, 32px);
  border-radius: 50%;
  overflow: hidden;
  background: var(--bg);
  box-shadow: inset 0 0 0 1px rgba(8, 8, 10, 0.08);
}
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar-placeholder { background: var(--red-soft); }
.avatar-placeholder svg {
  width: 76%;
  height: 76%;
  fill: var(--red);
  opacity: 0.55;
  transform: translateY(6%);
}
.person { display: inline-flex; align-items: center; gap: 0.55rem; }
.person-name { font-weight: 600; }
.profile-photo-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  border-radius: var(--radius);
  background: var(--bg);
}
.profile-photo-row .who { color: var(--muted); font-size: 0.84rem; }
.instructor-contact { display: flex; align-items: center; gap: 1rem; padding: 1rem; border-radius: var(--radius); background: var(--bg); }
.instructor-contact .who { color: var(--muted); font-size: 0.84rem; margin-bottom: 0.5rem; }
.instructor-contact .contact-links { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.photo-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.photo-controls > strong { flex-basis: 100%; }
.photo-controls > .who { flex-basis: 100%; margin-top: -0.35rem; }

/* Click-the-avatar upload: the native file input is hidden, the avatar is
   the affordance, and a camera badge signals it's editable. */
.avatar-upload-form { display: inline-flex; margin: 0; }
.avatar-upload {
  position: relative;
  display: inline-flex;
  cursor: pointer;
  border-radius: 50%;
  transition: transform var(--transition);
}
.avatar-upload:hover { transform: translateY(-1px); }
.avatar-upload:hover .avatar { box-shadow: inset 0 0 0 2px var(--red); }
.avatar-upload:focus-within .avatar { box-shadow: inset 0 0 0 2px var(--red); }
.avatar-cam {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 40%;
  min-width: 15px;
  max-width: 24px;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--red);
  color: #fff;
  border: 2px solid var(--surface);
  transition: background var(--transition);
}
.avatar-cam svg { width: 62%; height: 62%; }
.avatar-upload:hover .avatar-cam { background: var(--red-hover); }
/* Vehicles */
.car-svg { display: block; height: auto; flex-shrink: 0; overflow: visible; }
.car-svg .car-body {
  transition: transform 0.35s cubic-bezier(.34,1.56,.64,1), filter 0.35s ease;
  transform-box: fill-box;
  transform-origin: 50% 100%;
}
.car-svg .car-wheel {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}
/* Hovering the tile/card "starts the engine": the car lifts slightly with a
   deeper shadow (like it's about to pull off) and the wheels spin — a plain
   circle rotating shows nothing, which is why wheel() draws a hub cross. */
.vehicle-tile:hover .car-svg .car-body,
.vehicle-card:hover .car-svg .car-body,
.stage-content-view:hover .car-svg .car-body {
  transform: translateY(-2px);
  filter: drop-shadow(0 4px 3px rgba(0, 0, 0, 0.25));
}
.vehicle-tile:hover .car-svg .car-wheel,
.vehicle-card:hover .car-svg .car-wheel {
  animation: car-wheel-spin 0.5s linear infinite;
}
@keyframes car-wheel-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .car-svg .car-body,
  .car-svg .car-wheel { transition: none; animation: none !important; }
}
.vehicle-card { display: inline-flex; align-items: center; gap: 0.85rem; }
.vehicle-meta { display: flex; flex-direction: column; gap: 0.2rem; align-items: flex-start; }
.vehicle-name { font-weight: 600; }
.vehicle-spec { color: var(--muted); font-size: 0.78rem; }
/* SA-style number plate */
.plate {
  display: inline-block;
  padding: 0.12rem 0.5rem;
  border: 1.5px solid var(--ink-strong);
  border-radius: 4px;
  background: #fff;
  color: var(--ink-strong);
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.vehicle-tile {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
}
.vehicle-tile.is-down { opacity: 0.6; }
.vehicle-status-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.3rem;
}
.vehicle-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.6rem;
}
.vehicle-meta form:has(.btn-link-danger) { margin-top: 0.5rem; }
.vehicle-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 0.9rem;
}

/* Journey road — a winding, game-map-style path through the curriculum,
   with the real brand pin driving up to wherever the learner has reached. */
.journey-panel { overflow: visible; }
.journey-hero { max-width: 420px; margin: 0 auto; text-align: center; }
/* Two geometries, one CSS switch: vertical for narrow viewports (matches
   the app-shell's own mobile breakpoint below), horizontal for wide ones. */
.journey-orient-desktop { display: none; }
@media (min-width: 821px) {
  .journey-orient-mobile { display: none; }
  .journey-orient-desktop { display: block; max-width: 900px; }
}
.journey-map-wrap {
  position: relative;
  width: 100%;
  margin: 70px auto 0;
  /* The top margin is real headroom above the road's own coordinate box
     — not padding, since the SVG/nodes are absolutely positioned with
     inset:0, whose containing block is the padding box, so ancestor
     padding never actually pushes them down. Without it, a unit banner
     or the "you are here" car anchored to a node near y=0 of the SVG
     viewBox has nowhere to render and ends up under the sticky app
     header instead. */
  /* A soft sky-to-ground backdrop behind the road, with a few low, soft
     "hill" blobs layered in near the bottom — turns a plain white
     rectangle into a little scene, rather than lines floating on nothing. */
  background:
    radial-gradient(120% 40% at 12% 96%, rgba(58, 166, 97, 0.22), transparent 70%),
    radial-gradient(90% 35% at 88% 100%, rgba(47, 143, 78, 0.2), transparent 70%),
    radial-gradient(70% 30% at 50% 92%, rgba(58, 166, 97, 0.16), transparent 70%),
    linear-gradient(180deg, #eef4fb 0%, #f7f7f5 65%, #f2efe8 100%);
  border-radius: var(--radius-lg);
}
/* Roadside scenery — small decorative sprites scattered in the outer
   margins (see journey_scenery() macro); z-index below both the road and
   the nodes so they read as backdrop, never competing with the actual
   route for attention. */
.journey-scenery { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.scenery-item { position: absolute; width: 34px; opacity: 0.9; filter: drop-shadow(0 2px 2px rgba(20,20,10,0.12)); }
.scenery-item svg { width: 100%; height: auto; display: block; }
.scenery-item.scenery-sm { width: 24px; opacity: 0.75; }
.scenery-item.scenery-waterfall { width: 30px; }
.scenery-item.scenery-dog, .scenery-item.scenery-person { width: 30px; opacity: 0.8; }
@media (max-width: 480px) {
  .scenery-item { width: 24px; }
  .scenery-item.scenery-sm { width: 18px; }
}
.journey-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; z-index: 1; }
/* Shoulder — a lighter, wider stroke laid down first so a sliver of it
   peeks out either side of the asphalt on top, reading as a road edge/curb
   rather than the path just stopping. */
.journey-road-shoulder {
  fill: none;
  stroke: #cfd3d8;
  stroke-width: 21;
  stroke-linecap: round;
}
.journey-road-bg {
  fill: none;
  stroke: #4a4e57;
  stroke-width: 17;
  stroke-linecap: round;
}
.journey-road-fill {
  fill: none;
  stroke: var(--red);
  stroke-width: 17;
  stroke-linecap: round;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}
/* Centre line — the same path again, on top of both the asphalt and the
   red "travelled" fill, so the road reads as a road (lane markings) along
   its whole length rather than just a solid coloured stroke. */
.journey-road-center {
  fill: none;
  stroke: #fff6d9;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-dasharray: 9 11;
  opacity: 0.85;
}
.journey-hero.is-playing .journey-road-fill {
  animation: journeyReveal 2.6s cubic-bezier(0.3, 0.7, 0.3, 1) forwards;
}
@keyframes journeyReveal {
  to { stroke-dashoffset: calc(100 - var(--journey-target-num)); }
}
@media (prefers-reduced-motion: reduce) {
  .journey-hero.is-playing .journey-road-fill {
    animation: none;
    stroke-dashoffset: calc(100 - var(--journey-target-num));
  }
}

/* Position is driven from JS as plain left/top percentages (see app.js),
   not CSS offset-path — see the note in journey_map.py for why. This also
   means there's no @supports fallback to worry about: left/top percent
   positioning works everywhere. */
.journey-car {
  position: absolute;
  top: 0;
  left: 0;
  width: 32px;
  z-index: 3;
  /* Sits just above whichever node it's driven to, not on top of it — the
     badge circle (up to 54px) plus its label live below this point, so a
     fixed clearance here (not a %, which is relative to the car's own
     tiny size) is what actually keeps them apart regardless of node size. */
  transform: translate(-50%, calc(-100% - 34px));
  filter: drop-shadow(0 6px 8px rgba(8, 8, 10, 0.28));
}
.journey-car img { width: 100%; height: auto; display: block; }

.journey-node {
  position: absolute;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  transform: translate(-50%, -50%);
}
/* Chunky "pressed-button" nodes: a hard-edged, unblurred bottom rim in a
   darker shade of the fill (not a blurred box-shadow) is what actually
   reads as 3D depth rather than a flat disc floating on a soft shadow —
   the soft shadow after it is only for lift off the backdrop. */
.journey-node-badge {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  position: relative;
  background: radial-gradient(circle at 32% 26%, #ffffff, #eef0f2 75%);
  color: var(--muted-2);
  font-family: var(--font-display);
  font-weight: 700;
  box-shadow: 0 5px 0 #c7cbd0, 0 7px 10px rgba(20, 22, 26, 0.18);
  transition: transform var(--transition);
}
.journey-node-badge svg { width: 19px; height: 19px; }
.journey-node.status-completed .journey-node-badge {
  color: #fff;
  background: radial-gradient(circle at 32% 26%, #6bd39a, var(--success) 78%);
  box-shadow: 0 5px 0 #2f9e63, 0 7px 10px rgba(20, 22, 26, 0.2);
}
.journey-node.status-current .journey-node-badge {
  width: 60px;
  height: 60px;
  color: #fff;
  background: radial-gradient(circle at 32% 26%, #ff8a72, #ff4630 78%);
  box-shadow: 0 6px 0 #c22415, 0 10px 16px rgba(20, 22, 26, 0.3), 0 0 0 0 rgba(255, 70, 48, 0.5);
  animation: journeyPulse 1.7s ease-in-out infinite;
}
@keyframes journeyPulse {
  0%, 100% { box-shadow: 0 6px 0 #c22415, 0 10px 16px rgba(20, 22, 26, 0.3), 0 0 0 0 rgba(255, 70, 48, 0.5); }
  50% { box-shadow: 0 6px 0 #c22415, 0 10px 16px rgba(20, 22, 26, 0.3), 0 0 0 13px rgba(255, 70, 48, 0); }
}
.journey-node.status-locked .journey-node-badge {
  color: var(--muted);
  background: radial-gradient(circle at 32% 26%, #f4f5f6, #e2e4e7 78%);
  box-shadow: 0 4px 0 #c7cbd0;
  opacity: 0.75;
}
.journey-node.is-final .journey-node-badge {
  width: 62px;
  height: 62px;
  font-size: 1.4rem;
  background: radial-gradient(circle at 32% 26%, #ffe9a8, #e0a100 78%);
  box-shadow: 0 6px 0 #a97800, 0 10px 16px rgba(20, 22, 26, 0.25);
}
/* Unit banner — a small pill anchored above the first node of each
   roughly-even third of the stage list (see journey_map.unit_starts), so
   the path reads as named chunks instead of one continuous line. */
/* Styled after a highway guide sign (green plate, white border/text, a
   short post underneath planting it into the road) rather than a plain
   pill — a small extra bit of "road" theming at each named section. */
.journey-unit-banner {
  position: absolute;
  /* Clears the "you are here" car marker, which can be anchored to this
     same node and already occupies the space immediately above the
     badge — see .journey-car's own offset in the comment above it. */
  bottom: calc(100% + 2.75rem);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 0.3rem 0.7rem;
  border-radius: 4px;
  background: #1f6a3d;
  border: 2px solid #fff;
  outline: 1.5px solid #1f6a3d;
  color: #fff;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  box-shadow: var(--shadow-sm);
  z-index: 4;
}
.journey-unit-banner::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  width: 3px;
  height: 0.65rem;
  background: #9a9d9f;
  transform: translateX(-50%);
}
.journey-node-label {
  max-width: 10ch;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--ink-strong);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}
.journey-node.status-current .journey-node-label { color: var(--red); }
.journey-node.status-locked .journey-node-label { color: var(--muted); }
.journey-node-score {
  display: inline-block;
  margin-top: 0.15rem;
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
  background: var(--success-soft);
  color: var(--success);
  font-size: 0.64rem;
  font-weight: 700;
}
.journey-replay { margin-top: 1rem; }

@media (max-width: 480px) {
  .journey-node-label { font-size: 0.66rem; max-width: 7ch; }
}

.journey-graduated-banner {
  max-width: 420px;
  margin: 0 auto 1.1rem;
  padding: 1rem 1.2rem;
  border-radius: var(--radius);
  background: linear-gradient(135deg, #fff4d6, #ffe4a8);
  border: 1px solid #e0a100;
  text-align: center;
}
.journey-graduated-banner strong { display: block; font-size: 1.05rem; margin-bottom: 0.2rem; }
.journey-graduated-banner p { margin: 0; color: var(--ink-2); font-size: 0.88rem; }

/* Confetti — plain absolutely-positioned pieces the JS spawns and falls
   via this one keyframe, cleaned out of the DOM once each finishes rather
   than looping, so it reads as a one-off celebration, not decoration. */
.confetti-piece {
  position: fixed;
  top: -12px;
  width: 8px;
  height: 14px;
  z-index: 999;
  pointer-events: none;
  animation: confettiFall linear forwards;
}
@keyframes confettiFall {
  0% { transform: translateY(0) rotate(0deg); opacity: 1; }
  100% { transform: translateY(105vh) rotate(600deg); opacity: 0.9; }
}
@media (prefers-reduced-motion: reduce) {
  .confetti-piece { display: none; }
}

/* Rulebook */
.rule-list { display: grid; gap: 0.9rem; }
.rule-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}
.rule-copy { flex: 1; min-width: 0; }
.rule-copy strong { display: block; margin-bottom: 0.2rem; }
.rule-copy .who { margin: 0; }
.rule-input { width: 100px; flex-shrink: 0; }
.rule-list input[type="text"].rule-input { width: 160px; }

/* Toggle switch */
.rule-toggle { position: relative; display: inline-block; flex-shrink: 0; }
.rule-toggle input {
  position: absolute;
  opacity: 0;
  width: 44px; height: 24px;
  margin: 0;
  cursor: pointer;
}
.rule-toggle-track {
  display: block;
  width: 44px; height: 24px;
  border-radius: 999px;
  background: var(--border-strong);
  transition: background var(--transition);
}
.rule-toggle-track::after {
  content: "";
  position: absolute;
  top: 3px; left: 3px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-xs);
  transition: transform var(--transition);
}
.rule-toggle input:checked + .rule-toggle-track { background: var(--red); }
.rule-toggle input:checked + .rule-toggle-track::after { transform: translateX(20px); }
.rule-toggle input:focus-visible + .rule-toggle-track { outline: 2px solid var(--red); outline-offset: 2px; }

.photo-cell { display: inline-flex; align-items: center; gap: 0.6rem; }
.photo-cell form { margin: 0; }
.btn-link-danger {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
}
.btn-link-danger:hover { color: var(--red); }
.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;
}

.tabs {
  display: flex;
  gap: 0.2rem;
  margin-bottom: 1.1rem;
  border-bottom: 1px solid var(--border);
  width: 100%;
  overflow-x: auto;
}
.tab {
  padding: 0.6rem 0.9rem;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--transition), border-color var(--transition);
}
.tab:hover { color: var(--ink); }
.tab.active {
  color: var(--red);
  border-color: var(--red);
}

/* Journey roadmap — a clean vertical stepper, numbered circles on a thin
   line, in the same restrained style as the rest of the app. */
/* Same road language as the journey hero above it (asphalt + dashed
   centre line) but laid out so the sign badge sits in its OWN gutter
   column, to the side of each card — never on top of the card's own
   text — regardless of how wide the badge itself needs to be. */
.roadmap {
  display: grid;
  gap: 0.75rem;
  position: relative;
  padding-left: 3.1rem;
  counter-reset: stage;
}
.roadmap::before {
  content: "";
  position: absolute;
  left: 1.15rem;
  top: 0.5rem;
  bottom: 0.5rem;
  width: 10px;
  border-radius: 5px;
  background: #4a4e57;
}
.roadmap::after {
  content: "";
  position: absolute;
  left: calc(1.15rem + 4px);
  top: 0.5rem;
  bottom: 0.5rem;
  width: 2px;
  background-image: repeating-linear-gradient(to bottom, #fff6d9 0, #fff6d9 8px, transparent 8px, transparent 16px);
}
.road-item {
  position: relative;
  padding: 0.95rem 1.1rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  counter-increment: stage;
}
/* The sign itself — a plain circle by default (locked), a yellow
   diamond warning-sign for the stage in progress, switching to a solid
   green circle + check the moment it's completed. Centred on the mini
   road above, sized and positioned to clear the card's own left edge so
   the stage name is never fought over. */
.road-item::before {
  content: counter(stage);
  position: absolute;
  left: -2.6rem;
  top: 0.75rem;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 0.72rem;
  font-weight: 700;
  background: var(--surface);
  color: var(--muted-2);
  border: 2px solid var(--border);
  box-shadow: var(--shadow-sm);
  z-index: 1;
}
.road-item.completed { border-color: var(--success-soft); }
.road-item.completed::before {
  content: "✓";
  background: var(--success);
  color: #fff;
  border-color: var(--success);
  border-radius: 50%;
  transform: none;
}
.road-item.current {
  border-color: var(--red);
}
.road-item.current::before {
  /* Rotated square = diamond warning-sign shape; the counter-rotation on
     a nested pseudo isn't available on ::before itself, so the number is
     rendered as a background-less circle centred inside the diamond
     instead — keeps the digit upright without a second pseudo-element. */
  width: 24px;
  height: 24px;
  border-radius: 5px;
  background: #ffcc00;
  color: #111;
  border-color: #111;
  transform: rotate(45deg);
  font-size: 0;
}
.road-item.current::after {
  content: counter(stage);
  position: absolute;
  left: -2.55rem;
  top: 0.82rem;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  font-size: 0.72rem;
  font-weight: 700;
  color: #111;
  z-index: 2;
}
.road-item.locked { opacity: 0.65; }
.road-item h3 { font-size: 0.92rem; margin-bottom: 0.2rem; font-weight: 600; }
.road-item p { margin: 0; color: var(--muted); font-size: 0.86rem; }

.wizard-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}
.wizard-step {
  padding: 0.7rem 0.6rem;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--muted);
  text-align: center;
}
.wizard-step.active {
  background: var(--red-soft);
  border-color: var(--red);
  color: var(--red);
}
.wizard-step.done {
  background: var(--success-soft);
  border-color: var(--success-soft);
  color: var(--success);
}

.empty {
  text-align: center;
  padding: 2.25rem 1rem;
  color: var(--muted);
  font-size: 0.9rem;
  border-radius: var(--radius);
  background: var(--bg);
  border: 1px dashed var(--border-strong);
}

.pricing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.1rem;
}
.price-card {
  padding: 1.75rem 1.5rem;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
}
/* Bullet lists vary in length between tiers — pin the Subscribe/Current
   plan control to the bottom of every card so they line up in a row
   regardless of how many features a given tier lists. */
.price-card > form,
.price-card > button {
  margin-top: auto;
}
.price-card.featured {
  border-color: var(--red);
  box-shadow: var(--shadow-sm);
}
.price-card h3 { margin-bottom: 0.65rem; font-size: 1.05rem; }
.price {
  font-family: var(--font-display);
  font-size: 2.2rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: 0.65rem;
}
.price span { font-size: 0.9rem; color: var(--muted); font-weight: 500; }
.price-card p { color: var(--muted); margin-bottom: 1.35rem; font-size: 0.92rem; }

.page-grid { display: grid; gap: 1.1rem; }
.page-grid.two { grid-template-columns: 1.1fr 0.9fr; }
.form-panel { max-width: 680px; }

@media (max-width: 980px) {
  .hero { grid-template-columns: 1fr; }
  .hero-visual { order: -1; }
  .hero-preview { transform: none; }
  /* Below the two-column split, .hero-copy's children were keeping their
     desktop left alignment — with only one column left, that reads as
     "shoved to one side" rather than a deliberate mobile layout. h1/
     .hero-lead both cap their own width (max-width: 13ch / 46ch) for
     desktop line-length, so text-align alone doesn't center them as
     blocks — need margin auto too. */
  .hero-copy { text-align: center; }
  .hero h1, .hero-lead { margin-left: auto; margin-right: auto; }
  .hero-cta { justify-content: center; }
  .page-grid.two { grid-template-columns: 1fr; }
}

@media (max-width: 820px) {
  .app-shell { flex-direction: column; }

  /* Hamburger + centered brand + bell/avatar, sticky at the very top —
     replaces the desktop topbar's user info (hidden below) so the two
     don't show redundant bell/avatar copies at once. */
  .mobile-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.6rem 0.9rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 30;
  }
  .mobile-menu-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    color: var(--ink-strong);
    flex-shrink: 0;
  }
  .mobile-menu-btn:hover { background: var(--bg); }
  .mobile-menu-btn svg { width: 22px; height: 22px; }
  .mobile-topbar-brand { flex-shrink: 0; }
  .mobile-topbar-actions {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
  }

  /* The sidebar becomes an off-canvas drawer: fixed, off-screen until
     .is-open, sliding in over a dimmed backdrop rather than pushing the
     page content down the way stacking it inline used to. */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    width: 280px;
    max-width: 85vw;
    height: 100vh;
    z-index: 50;
    transform: translateX(-100%);
    transition: transform var(--transition);
    box-shadow: var(--shadow-lg);
  }
  .sidebar.is-open { transform: translateX(0); }
  .sidebar-backdrop.is-open { display: block; pointer-events: auto; }
  /* Icon + label centered as a group within each row, matching the rest
     of the mobile-centered layout — left as the desktop sidebar's own
     left-aligned default everywhere outside this drawer-mode breakpoint.
     A nav-badge's margin-left:auto (e.g. "Coming soon") still overrides
     this to pin itself to the row's far edge, same as before. */
  .nav-item { justify-content: center; }
  .sidebar-brand { justify-content: center; }
  .sidebar-school { text-align: center; }
  .sidebar-close {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    color: var(--muted);
  }
  .sidebar-close svg { width: 18px; height: 18px; }

  /* Bell/avatar now live in .mobile-topbar; the page heading (h1 + subtitle)
     stays in the original topbar so every page's own title still shows. */
  .topbar-user { display: none; }
  /* With .topbar-user hidden, .topbar-copy is the only thing left in the
     row — centering it here, plus every panel heading below it, applies
     app-wide across every admin/instructor/learner page in one place,
     since they all share these same few classes rather than each having
     their own per-page heading markup. */
  .topbar { justify-content: center; }
  .topbar-copy { text-align: center; }
  .panel-head { justify-content: center; text-align: center; }
  /* Descendant, not just direct-child — a second heading inside a panel
     (e.g. learner/dashboard.html's "Upcoming lessons", one div deeper
     than "Progress overview" above it) was being missed by ">". */
  .panel h2 { text-align: center; }
  .panel-sub { text-align: center; }
  /* Announcement banner (title/body/author row) and the instructor-
     contact card (avatar + name/specialty + phone/WhatsApp links) both
     kept their desktop left-to-right row alignment on mobile — same
     "shoved to one side" issue as everything above. */
  .announcement-banner { text-align: center; }
  .announcement-author { justify-content: center; }
  .instructor-contact { flex-direction: column; text-align: center; }
  .instructor-contact .contact-links { justify-content: center; }

  .form-row { grid-template-columns: 1fr; }
  .inline-add-row { flex-direction: column; align-items: stretch; }
  .topbar { padding-inline: 1rem; }
  .content { padding-inline: 1rem; }

  /* Opt-in table -> stacked-card conversion (add class="responsive-table"
     and a data-label on each <td>) — for tables too column-heavy for
     horizontal scrolling to be a real answer, not every table in the app.
     A sub-row with no data-label (e.g. a colspan detail row) just renders
     as plain block content, no empty label line, since the ::before rule
     below only matches td[data-label]. */
  .responsive-table thead { display: none; }
  .responsive-table, .responsive-table tbody, .responsive-table tr, .responsive-table td {
    display: block;
    width: 100%;
  }
  .responsive-table tr {
    padding: 0.85rem;
    border-bottom: 1px solid var(--border);
  }
  .responsive-table tr:last-child { border-bottom: none; }
  .responsive-table tr.sub-row {
    margin-top: -0.85rem;
    padding-top: 0;
    background: var(--bg);
  }
  /* Qualified as "table.responsive-table td" rather than plain
     ".responsive-table td" — every table this applies to also carries
     the base "data" class, whose own "table.data td" rule (padding +
     border-bottom, for the desktop layout) has HIGHER specificity
     (table+class+element beats class+element) and was winning even
     here, leaving every field's border-bottom AND desktop-sized padding
     in place instead of the intended one-border-per-lesson, tight-
     stacked-field look — every field on every stacked card was getting
     its own divider line, not just each row. */
  table.responsive-table td {
    padding: 0.3rem 0;
    border: none;
    text-align: center;
  }
  /* A trailing action column (e.g. "Start lesson" / "Complete" / "Edit
     score") often renders nothing at all for some row statuses — genuinely
     empty (no data-label, no child content) rather than just visually
     blank, so it can be collapsed instead of leaving a padded gap between
     rows on mobile. */
  .responsive-table td:empty {
    padding: 0;
  }
  .responsive-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--muted);
    margin-bottom: 0.15rem;
  }

  /* Start lesson / Complete / Route / Cancel — a phone is the whole point
     of these (an instructor mid-lesson, one-handed), so on mobile they get
     full-width, comfortably tall touch targets stacked in one column
     instead of the small inline row that's fine on desktop. */
  .lesson-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
    margin-top: 0.3rem;
  }
  .lesson-actions .btn {
    width: 100%;
    padding: 0.85rem 1rem;
    font-size: 0.88rem;
  }
  /* Cancel-with-reason (cancel_button() macro) reads as one deliberate
     "danger zone" instead of a bare input + underlined link floating in
     space — the tinted/bordered wrapper groups the two controls as a
     single flow, and a solid (not outline) button on the actual
     destructive step reads as more real than the link-styled default,
     which was designed for a dense admin table row, not a standalone
     mobile action. */
  .lesson-actions .respond-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
    margin: 0;
    width: 100%;
    padding: 0.75rem;
    border: 1px solid var(--red-soft);
    border-radius: var(--radius);
    background: var(--red-soft);
  }
  .lesson-actions .respond-reason {
    width: 100%;
    padding: 0.65rem 0.75rem;
    font-size: 0.85rem;
    background: #fff;
  }
  .lesson-actions .btn-link-danger {
    padding: 0.65rem 1rem;
    border: none;
    border-radius: var(--radius);
    background: var(--red);
    color: #fff;
    text-decoration: none;
    text-align: center;
  }
  .lesson-actions .btn-link-danger:hover { color: #fff; background: var(--red-hover); }
  .lesson-actions .who {
    text-align: center;
  }
  .shift-slot .btn-link-danger { padding: 0.4rem 0.5rem; }

  /* Study material row: title + actions sit side by side on desktop, but
     that non-wrapping flex row squeezes the title to near-zero width on a
     phone just to keep Download/Publish/Delete on one line. Let the row
     wrap and give the actions their own full-width line below the title. */
  .material-row {
    flex-wrap: wrap;
  }
  .material-actions {
    flex-basis: 100%;
    flex-wrap: wrap;
  }

  /* Vehicle tile: a fixed 96px car icon beside the meta column (name,
     plate, spec, badges, action buttons, delete link) is fine on desktop,
     but on a phone that fixed-width icon leaves very little room for all
     that meta content. Stack icon above meta instead, so the meta column
     gets the full tile width. */
  .vehicle-tile {
    flex-direction: column;
    align-items: stretch;
  }

  /* Report bar rows (Revenue / Instructor utilization / Learner pipeline on
     the Reports page): the 90px/1fr/80px grid gets cramped once the label
     column has to share a phone-width row with a track and a value — stack
     label+value on one line, with the bar track given its own full-width
     line below instead.
     Selector is qualified as ".panel .report-bar-row" (rather than plain
     ".report-bar-row") to out-specificity the unconditional base rule
     further down this file — that base rule also sets grid-template-columns,
     and being later in source order it would otherwise win the cascade over
     an equal-specificity rule in here even while this media query is
     active, silently undoing the mobile override. */
  .panel .report-bar-row {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "label value"
      "track track";
    row-gap: 0.3rem;
  }
  .panel .report-bar-row > div:first-child { grid-area: label; }
  .report-bar-row .report-bar-track { grid-area: track; }
  .report-bar-row .report-bar-value { grid-area: value; }

  /* Profile photo row (photo_manager() macro — admin/instructor learner
     detail pages, profile.html): a non-wrapping flex row with the avatar
     thumbnail beside the name/status text. photo-controls has its own
     flex-wrap:wrap, but as a nowrap flex ITEM of this row its default
     min-width:auto still reports its unwrapped max-content width, which
     overflows the panel (and the whole page) on a phone. Letting the row
     itself wrap stacks the avatar above the text instead. */
  .profile-photo-row {
    flex-wrap: wrap;
    justify-content: center;
    text-align: center;
  }
  /* .photo-controls' name/status lines are flex-basis:100% so they already
     inherited the centered text-align above, but the optional "Remove
     photo" button (only rendered when the user already has a photo) has
     no such override and defaulted to flex-start — sitting flush against
     the left edge while the text above it was centered. */
  .photo-controls {
    justify-content: center;
  }

  /* Grid items default to min-width:auto, which means their intrinsic
     content size (e.g. an unconstrained table inside a .table-wrap) can
     force the whole shared 1fr column — and the page itself — wider than
     the viewport, no matter what overflow-x:auto is set on a descendant.
     min-width:0 lets the column actually shrink to the viewport so
     .table-wrap's own horizontal scroll (or responsive-table stacking)
     can do its job instead of being overridden by the grid sizing pass. */
  .page-grid > .panel {
    min-width: 0;
  }
}

/* Instructor weekly shifts */
.shift-week {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 0.6rem;
}
.shift-day {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.7rem;
  background: var(--bg);
}
.shift-day-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.8rem;
  margin-bottom: 0.5rem;
}
.shift-slot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 0.8rem;
  margin-bottom: 0.35rem;
}
.shift-slot form { margin: 0; }
.shift-slot-off { color: var(--muted); justify-content: center; background: transparent; border-style: dashed; }

/* Curriculum builder */
/* One swimlane per license-category — a separate track so an added-on
   program (forklift, motorbike...) doesn't read as a stray entry tacked
   onto the end of the main curriculum's numbering. */
.stage-lane + .stage-lane { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px dashed var(--border); }
.stage-lane-title { font-size: 0.95rem; margin-bottom: 0.75rem; }
.stage-list { display: grid; gap: 0.75rem; }
.stage-card {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.6rem 0.85rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  transition: box-shadow var(--transition), opacity var(--transition), border-color var(--transition);
}
.stage-card[draggable="true"] { cursor: grab; }
.stage-card.is-dragging { opacity: 0.4; }
.stage-card.is-drag-over { border-color: var(--red); box-shadow: 0 0 0 3px var(--red-soft); }
.stage-drag-handle {
  flex-shrink: 0;
  color: var(--muted-2);
  font-weight: 700;
  letter-spacing: -0.15em;
  padding-top: 0.1rem;
  cursor: grab;
  user-select: none;
}
.stage-index {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--bg);
  color: var(--ink-2);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.82rem;
}
/* flex-basis 200px (not the default 0%) is what makes this the item that
   wraps onto its own line first when .stage-card runs out of room — the
   drag handle/index/actions around it are all flex-shrink:0 and stay put. */
.stage-body { flex: 1 1 200px; min-width: 0; }
.stage-body h3 { margin-bottom: 0.15rem; }
.stage-body p { color: var(--muted); font-size: 0.88rem; margin-bottom: 0.5rem; }
.stage-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}
.stage-actions form { margin: 0; display: inline-flex; }
.btn-icon {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--ink-2);
  font-weight: 700;
  cursor: pointer;
  transition: background var(--transition);
}
.btn-icon:hover { background: var(--bg); }
.btn-icon:disabled { opacity: 0.35; cursor: not-allowed; }

/* Competency chips (read-only display, e.g. on the stage card) */
.chip-row { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.chip {
  display: inline-flex;
  align-items: center;
  padding: 0.22rem 0.65rem;
  border-radius: 999px;
  background: var(--red-soft);
  color: var(--red-hover);
  font-size: 0.76rem;
  font-weight: 600;
}
/* Marks the correct option on a mock-test question card. */
.chip-correct { background: var(--success-soft); color: var(--success); }

/* Chip input editor — progressively enhances a plain text field (see
   app.js). Falls back to a normal text input with no JS. */
.chip-editor {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: #fff;
}
.chip-editor:focus-within { border-color: var(--red); box-shadow: 0 0 0 3px var(--red-soft); }
.chip-editor .chip { gap: 0.35rem; padding-right: 0.4rem; }
.chip-editor .chip button {
  border: 0;
  background: none;
  color: inherit;
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  opacity: 0.7;
}
.chip-editor .chip button:hover { opacity: 1; }
.chip-editor-input {
  flex: 1;
  min-width: 120px;
  border: 0;
  outline: none;
  font: inherit;
  padding: 0.2rem;
}

.plain-list { margin: 0.5rem 0 0; padding-left: 1.2rem; color: var(--muted); }
.plain-list li { margin-bottom: 0.4rem; }
.plain-list code,
.who code { background: var(--bg); padding: 0.1rem 0.35rem; border-radius: 3px; font-size: 0.85em; }

/* Cross-page links (bookings -> vehicle/instructor/learner). Landing on an
   anchored row/tile scrolls it into view below the sticky topbar and briefly
   highlights it, so "click a name, land on its record" is unmistakable. */
.vehicle-tile,
.road-item,
tr[id] {
  scroll-margin-top: 96px;
}
.vehicle-tile:target,
.road-item:target {
  animation: target-ring 2.4s ease;
}
@keyframes target-ring {
  0%, 15% { box-shadow: 0 0 0 3px var(--red); }
  100% { box-shadow: 0 0 0 3px transparent; }
}
tr:target td {
  animation: target-bg 2.4s ease;
}
@keyframes target-bg {
  0%, 15% { background: var(--red-soft); }
  100% { background: transparent; }
}

/* Announcements */
.announcement-list { display: flex; flex-direction: column; gap: 0.6rem; }
.announcement-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.85rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.announcement-row.is-expired { opacity: 0.6; }
.announcement-text { margin: 0.4rem 0 0; color: var(--muted); font-size: 0.9rem; white-space: pre-line; }
.announcement-banner {
  padding: 0.9rem 1rem;
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--red);
  border-radius: var(--radius-sm);
  background: var(--red-soft);
  margin-bottom: 1rem;
}
.announcement-banner + .announcement-banner { margin-top: -0.4rem; }
.announcement-banner strong { display: block; margin-bottom: 0.2rem; }
.announcement-banner p { margin: 0; color: var(--ink-2); font-size: 0.9rem; white-space: pre-line; }
.announcement-author {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-top: 0.5rem;
  font-size: 0.82rem;
  color: var(--muted);
}
.announcement-author-role {
  margin-left: 0.4rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--border-strong);
  font-size: 0.75rem;
}

/* Notifications */
.notification-list { display: flex; flex-direction: column; gap: 0.4rem; }
.notification-row {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.notification-row.is-unread { border-color: var(--red); background: var(--red-soft); }
.notification-link {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  flex: 1;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
.notification-link:hover { text-decoration: underline; }
.notification-dismiss-form { flex-shrink: 0; margin: 0; }
.notification-dismiss {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.15rem 0.4rem;
  border-radius: var(--radius-sm);
}
.notification-dismiss:hover { background: var(--bg); color: var(--ink); }
.notification-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 0.4rem;
  flex-shrink: 0;
  background: var(--red);
  visibility: hidden;
}
.notification-row.is-unread .notification-dot { visibility: visible; }
.notification-body p { margin: 0.25rem 0; color: var(--muted); font-size: 0.9rem; }

/* Trial countdown banner */
.trial-banner {
  padding: 0.65rem 1rem;
  margin-bottom: 1rem;
  border-radius: var(--radius-sm);
  background: var(--info-soft, var(--bg));
  color: var(--ink-2);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
}
.trial-banner.is-urgent { background: var(--red-soft); color: var(--red); }

/* First-time guided tour */
.tour-overlay {
  position: fixed;
  inset: 0;
  z-index: 4000;
}
.tour-spotlight {
  position: fixed;
  border-radius: 10px;
  box-shadow: 0 0 0 9999px rgba(15, 15, 17, 0.6);
  transition: all 0.25s ease;
  pointer-events: none;
}
.tour-card {
  position: fixed;
  width: 280px;
  max-width: calc(100vw - 32px);
  padding: 1rem 1.1rem;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-md);
  transition: top 0.25s ease, left 0.25s ease;
}
.tour-card strong { display: block; font-size: 1rem; margin-bottom: 0.4rem; }
.tour-card p { margin: 0 0 0.9rem; color: var(--muted); font-size: 0.88rem; }
.tour-actions { display: flex; align-items: center; gap: 0.5rem; }
.tour-counter { font-size: 0.78rem; color: var(--muted); margin-right: auto; }

/* Floating support bubble — links out to Empath Technology Solutions'
   own ticket system, on every page (added once in base.html rather than
   per-template) so help is always one tap away regardless of which
   role/page a user is on. z-index sits above ordinary content but below
   the tour overlay/modals (4000+), so it doesn't fight either of those
   for attention while they're active. */
.support-bubble {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 900;
  display: flex;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  box-shadow: var(--shadow-lg);
  transition: transform var(--transition);
}
.support-bubble:hover { transform: scale(1.06); }
.support-bubble img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
/* Small chat-icon badge overlaid on the logo's corner — the logo alone
   just reads as branding, not as an affordance to click for help; a
   speech-bubble badge is the widely-recognized "this opens support"
   signal (Intercom/Zendesk-style widgets). */
.support-bubble-badge {
  position: absolute;
  right: -4px;
  bottom: -4px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--red);
  color: #fff;
  border: 2px solid #fff;
}
.support-bubble-badge svg { width: 12px; height: 12px; }
/* Contact-us label — hidden by default, slides in on hover/focus (desktop)
   or briefly on its own once per session via .is-peeking (app.js), since
   touch devices have no persistent hover state to reveal it otherwise. */
.support-bubble-label {
  position: absolute;
  right: calc(100% + 0.6rem);
  top: 50%;
  transform: translateY(-50%) translateX(6px);
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  background: var(--ink, #1a1a1a);
  color: #fff;
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: var(--shadow-md);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition), transform var(--transition);
}
.support-bubble:hover .support-bubble-label,
.support-bubble:focus-visible .support-bubble-label,
.support-bubble.is-peeking .support-bubble-label,
.support-bubble.is-revealed .support-bubble-label {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
}
@media (max-width: 820px) {
  /* Clear of the mobile-topbar and, for a logged-in learner/instructor,
     any bottom-of-screen controls — smaller and nudged up slightly.
     The label itself stays functional here (not display:none) — it's
     how a touch device ever sees the "contact us" message at all, via
     .is-peeking (app.js) rather than :hover, which touch has none of. */
  .support-bubble { width: 46px; height: 46px; right: 1rem; bottom: 1rem; }
  .support-bubble-badge { width: 18px; height: 18px; }
  .support-bubble-badge svg { width: 10px; height: 10px; }
  .support-bubble-label { font-size: 0.76rem; padding: 0.45rem 0.75rem; max-width: 60vw; white-space: normal; text-align: right; }
}

/* Generic modal — native <dialog>, reusable anywhere via
   data-open-modal="the-dialog-id" on a trigger button. */
.app-modal {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(560px, calc(100vw - 2rem));
  max-height: min(640px, calc(100vh - 2rem));
  overflow-y: auto;
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-md);
}
.app-modal::backdrop {
  background: rgba(20, 20, 22, 0.55);
}
.app-modal-close-form {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  margin: 0;
}
.app-modal-close {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.app-modal-close:hover { background: var(--bg); }
.app-modal .btn { white-space: normal; text-align: center; }

/* Reports — plain CSS bar rows, no chart library */
.report-bar-row {
  display: grid;
  grid-template-columns: 90px 1fr 80px;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.6rem;
  font-size: 0.88rem;
}
.report-bar-track {
  height: 10px;
  border-radius: 999px;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  overflow: hidden;
}
.report-bar-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--red);
}
.report-bar-value { text-align: right; color: var(--muted); }

/* Practice route (real Leaflet map, see route_map() / app.js) */
.route-map {
  width: 100%;
  max-width: 640px;
  background: var(--bg);
  border: 1px solid var(--border-strong);
}

/* Turn-by-turn-style step list under a route map (see route_map() macro) */
.route-steps {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  max-width: 640px;
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.route-step {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.route-step:last-child { border-bottom: none; }
.route-step:hover { background: var(--bg); }
.route-step.is-active { background: var(--red-soft); }
.route-step-index {
  flex-shrink: 0;
  width: 1.6rem;
  height: 1.6rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ink);
  color: #fff;
  border-radius: 50%;
  font-size: 0.72rem;
  font-weight: 700;
}
.route-step.is-active .route-step-index { background: var(--red); }
.route-step-time { font-weight: 600; font-size: 0.85rem; white-space: nowrap; }

/* Vehicle monitoring (telematics + dashcam) */
tr.is-incident-row td { background: var(--red-soft); }
.dashcam-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
}
.dashcam-tile {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.75rem;
  background: var(--surface);
}

/* Study materials */
.material-list { display: flex; flex-direction: column; gap: 0.6rem; }
.material-row {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.material-row.is-draft { border-style: dashed; opacity: 0.75; }
.material-icon { font-size: 1.5rem; line-height: 1; }
.material-body { flex: 1; min-width: 0; }
.material-desc { margin: 0.3rem 0 0; color: var(--muted); font-size: 0.88rem; }
.material-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

/* Rich content editor for curriculum stages */
.rich-editor {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface);
}
.rich-editor-toolbar {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.45rem;
  background: var(--bg);
  border-bottom: 1px solid var(--border-strong);
  flex-wrap: wrap;
}
.rich-editor-sep {
  width: 1px;
  align-self: stretch;
  background: var(--border-strong);
  margin: 0 0.15rem;
}
.rich-editor-surface {
  min-height: 160px;
  padding: 0.75rem 0.9rem;
  font: inherit;
  color: var(--ink);
  outline: none;
}
.rich-editor-surface:empty::before {
  content: "Write the lesson content here…";
  color: var(--muted-2);
}
.rich-editor-surface p { margin: 0 0 0.75rem; }
.rich-editor-surface ul,
.rich-editor-surface ol { margin: 0 0 0.75rem 1.2rem; }
.rich-editor-surface h4 { margin: 0.9rem 0 0.5rem; }

/* Rendered stage content (learner-facing + admin preview) */
.stage-content-view p { margin: 0 0 0.85rem; }
.stage-content-view ul,
.stage-content-view ol { margin: 0 0 0.85rem 1.2rem; }
.stage-content-view a { color: var(--red); }
.video-embed {
  position: relative;
  width: 100%;
  max-width: 640px;
  aspect-ratio: 16 / 9;
  margin: 0.5rem 0 1rem;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #000;
}
.video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Fleet map */
.fleet-map-layout {
  display: grid;
  grid-template-columns: 260px 1fr;
  gap: 1rem;
}
@media (max-width: 720px) {
  .fleet-map-layout { grid-template-columns: 1fr; }
}
.fleet-map-canvas {
  height: 560px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  z-index: 0; /* keeps Leaflet's own z-indexed panes below the app sidebar/topbar */
}
.fleet-map-sidebar { display: flex; flex-direction: column; min-height: 0; }
.fleet-map-search {
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: 0.86rem;
  margin-bottom: 0.6rem;
}
.vehicle-search-list {
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
  max-height: 420px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.vehicle-search-list li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  font-size: 0.86rem;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.vehicle-search-list li:last-child { border-bottom: 0; }
.vehicle-search-list li:hover { background: #fafafa; }
.vehicle-search-list li strong { display: block; }
.vehicle-search-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--muted-2);
  flex-shrink: 0;
}
.map-legend {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.78rem;
  color: var(--muted);
}
.map-legend-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-right: 0.4rem;
}
.fleet-map-pin-wrap { background: transparent !important; border: none !important; }
.fleet-map-pin {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  width: 40px;
  cursor: pointer;
  transition: transform var(--transition);
}
.fleet-map-pin:hover { transform: scale(1.15); }
.fleet-map-pin .car-svg {
  pointer-events: none;
  /* No background chip behind the car, so it needs its own contrast
     against whatever the map tile underneath happens to be. */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55));
}
.fleet-map-pin-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  flex-shrink: 0;
}

/* Curriculum template picker */
.template-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1rem;
}
.template-card { display: flex; flex-direction: column; }
.template-stage-list {
  margin: 0 0 1.1rem;
  padding-left: 1.2rem;
  color: var(--ink-2);
  font-size: 0.86rem;
  line-height: 1.7;
  flex: 1;
}

/* Booking date-availability calendar (booking-scheduler.js) — a custom
   popover replacing a plain <input type="date"> so unavailable days can be
   shown disabled, which a native date input's own calendar can't do. */
.scheduler-date { position: relative; }
.scheduler-date-input { display: none; }
.scheduler-date-trigger {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 0.6rem 0.75rem;
  font: inherit;
  font-size: 0.88rem;
  color: var(--ink);
  background: #fff;
  cursor: pointer;
}
.scheduler-date-trigger:focus-visible {
  outline: none;
  border-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-soft);
}
.scheduler-date-trigger .placeholder { color: var(--muted-2); }
.scheduler-calendar {
  position: absolute;
  z-index: 40;
  top: calc(100% + 0.35rem);
  left: 0;
  width: 280px;
  max-width: 90vw;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.16);
  padding: 0.85rem;
}
.scheduler-calendar[hidden] { display: none; }
.scheduler-cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.6rem; }
.scheduler-cal-head span { font-weight: 600; font-size: 0.86rem; }
.scheduler-cal-head button {
  border: none;
  background: var(--bg);
  border-radius: var(--radius-sm);
  width: 28px;
  height: 28px;
  cursor: pointer;
  font-size: 0.95rem;
  color: var(--ink);
}
.scheduler-cal-head button:disabled { opacity: 0.35; cursor: default; }
.scheduler-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.scheduler-cal-grid .dow {
  text-align: center;
  font-size: 0.66rem;
  color: var(--muted-2);
  font-weight: 600;
  padding-bottom: 0.3rem;
}
.scheduler-cal-day {
  border: none;
  background: transparent;
  border-radius: var(--radius-sm);
  aspect-ratio: 1;
  font-size: 0.82rem;
  color: var(--ink);
  cursor: pointer;
}
.scheduler-cal-day:hover:not(:disabled) { background: var(--bg); }
.scheduler-cal-day.is-selected { background: var(--red); color: #fff; }
.scheduler-cal-day.is-today:not(.is-selected) { box-shadow: inset 0 0 0 1px var(--red); }
.scheduler-cal-day:disabled { color: var(--muted-2); opacity: 0.4; cursor: not-allowed; }
.scheduler-cal-day.is-blank { visibility: hidden; cursor: default; }
.scheduler-cal-loading { text-align: center; color: var(--muted); font-size: 0.8rem; padding: 0.5rem 0; }

/* Direct-message thread — a plain chat-bubble list, no libraries. */
.chat-thread {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-height: 60vh;
  overflow-y: auto;
  padding-bottom: 0.5rem;
}
.chat-bubble {
  align-self: flex-start;
  max-width: 75%;
  background: var(--bg);
  border-radius: var(--radius-lg);
  border-bottom-left-radius: 4px;
  padding: 0.6rem 0.9rem;
}
.chat-bubble p { margin: 0 0 0.2rem; white-space: pre-line; word-break: break-word; }
.chat-bubble .who { font-size: 0.72rem; }
.chat-bubble.is-mine {
  align-self: flex-end;
  background: var(--red-soft);
  border-radius: var(--radius-lg);
  border-bottom-right-radius: 4px;
}

/* Inline booking-form errors that must stay visible while the New Booking
   modal is still open — a plain flash-after-redirect (the old behavior)
   closed the modal along with the page navigation, so a rejection like
   "end time must be after start time" only ever appeared after the form
   itself had already vanished from view. */
.modal-inline-error {
  display: none;
  background: var(--red-soft);
  color: var(--red);
  border-radius: var(--radius);
  padding: 0.6rem 0.75rem;
  font-size: 0.86rem;
  margin-bottom: 0.9rem;
}
.modal-inline-error.is-visible { display: block; }
