/*
  Components — buttons, cards, badges, and the platform's one signature
  device: bracketed, monospace data tags (see tokens.css). Used for stat
  counters, program duration/module-count badges, and reference/matricule
  numbers — anywhere a number is the point, not decoration.
*/

/* --- Buttons --------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: var(--text-sm);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color var(--transition-fast), color var(--transition-fast),
    border-color var(--transition-fast), transform var(--transition-fast);
}
.btn:active {
  transform: translateY(1px);
}
/* A DISABLED BUTTON MUST LOOK DISABLED (15 Sep... 17 Sep 2026). Until
   today `.btn` had no `:disabled` rule at all, so a disabled primary
   button rendered in full BISOFTECH orange, indistinguishable from one
   that works — a dead control wearing the most inviting colour on the
   page. Found by rendering the final-results screen with nothing ready
   to publish and looking at it.

   `.icon-btn:disabled` (dashboard.css) has had this since Day 26; the
   base button never got it. Opacity plus the not-allowed cursor rather
   than a flat grey, so the label stays legible and the button is still
   obviously the same control, just unavailable. `:active` is cancelled
   too — a button that presses down but does nothing is worse than one
   that does not move. */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}
.btn:disabled:active,
.btn[aria-disabled="true"]:active { transform: none; }

.btn--primary {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}
.btn--primary:hover {
  background: var(--color-accent-dark);
  color: var(--color-accent-contrast);
}

.btn--outline {
  background: transparent;
  border-color: var(--color-border);
  color: var(--color-ink);
}
.btn--outline:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.btn--ghost-on-dark {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.35);
  color: var(--color-on-dark);
}
.btn--ghost-on-dark:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.btn--sm {
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
}

/* Log out is a POST (Django's LogoutView has refused GET since 5.0, and a
   plain <a> returned 405 — see templates/partials/logout_form.html). The
   form is only a transport; it must not disturb the layout of the button
   row or nav list it sits in. */
.logout-form {
  display: inline-flex;
  margin: 0;
}
.mobile-nav__list .logout-form {
  display: block;
}
/* Makes a <button> read as one of the plain nav anchors around it.
   Mirrors `.mobile-nav__list a` in layout.css — kept here, beside the
   form it belongs to, rather than bolted onto that selector. */
.logout-form__link {
  display: block;
  width: 100%;
  padding: var(--space-2) 0;
  background: none;
  border: 0;
  text-align: left;
  font: inherit;
  font-weight: 500;
  color: var(--color-ink);
  cursor: pointer;
}
.logout-form__link:hover {
  color: var(--color-accent);
}

/* --- Cards ------------------------------------------------------------------ */
.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-base), transform var(--transition-base);
}
.card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}
.card__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-accent-text);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: var(--space-2);
  display: block;
}
:root[data-theme="dark"] .card__eyebrow {
  color: var(--color-accent);
}

/* Program card — module count / duration / price, matching the confirmed
   bisoftech.com card structure (BRANDING.md). */
.program-card__meta {
  display: flex;
  gap: var(--space-4);
  margin-block: var(--space-3);
}
.program-card__price {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.program-card__price .was {
  text-decoration: line-through;
  color: var(--color-muted);
  font-size: var(--text-sm);
}
.program-card__price .now {
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--color-primary);
}
:root[data-theme="dark"] .program-card__price .now {
  color: var(--color-accent);
}


/* --- Stat card (26 Aug 2026) -----------------------------------------------
   BISOFTECH asked for the statistics to sit on "beautiful cards". The
   card is what changed; the NUMBER did not. The bracketed IBM Plex Mono
   numeral is described in tokens.css as the platform's one signature
   typographic device, and it survives intact inside the card — dropping
   it to chase a generic dashboard look would have retired the only thing
   that makes this site's data displays recognisably BISOFTECH's.

   Structure: an icon plate, the bracketed value, a label, and an
   optional caption. The left accent rail is 3px and is the card's only
   saturated element; everything else is surface, hairline and ink, so a
   row of eight cards reads as a row and not as a fairground.
--------------------------------------------------------------------------- */
.stat-card {
  position: relative;
  /* The card measures itself, so the numeral inside can size to the
     space it actually has rather than to the viewport. Five cards on a
     1200px homepage row leave ~160px of content width each — not enough
     for "[ 1250+ ]" at --text-3xl in monospace, which `nowrap` then
     clipped. A media query cannot see this: the same component is four
     cards in a narrower dashboard column, where the big numeral fits
     fine. */
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-5) var(--space-5) var(--space-6);
  /* A wash of the card's own colour rather than flat white — Day 35,
     BISOFTECH asked for colour here. It fades out by 65% so the label
     underneath still sits on plain surface and keeps its contrast. */
  background: linear-gradient(155deg, var(--tint-accent) 0%, var(--color-surface) 65%);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast),
    border-color var(--transition-fast);
}
.stat-card::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 4px;
  background: linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-dark) 100%);
}
.stat-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--color-accent);
}
/* Someone who has asked the OS to stop animating things did not ask for
   a card that leaps when the pointer crosses it. */
@media (prefers-reduced-motion: reduce) {
  .stat-card { transition: none; }
  .stat-card:hover { transform: none; }
}

/* Variant: an IDENTIFIER, not a number. A matricule number
   (BISOFTECH-2026-SD-0007) is 22 characters — at --text-3xl in monospace
   with `white-space: nowrap` it was clipped to "BISOFTEC…" on the
   student's own dashboard, which is the one place it has to be readable
   and copyable. Smaller, and allowed to wrap, because the value is a
   string someone reads character by character rather than a magnitude
   they take in at a glance. */
.stat-card--code .stat-card__value {
  font-size: var(--text-lg);
  white-space: normal;
  /* `break-word`, not `anywhere`: break at the hyphens the identifier
     already has, rather than mid-segment. */
  overflow-wrap: break-word;
  line-height: 1.25;
}
/* An identifier keeps the monospace face: a matricule is read and
   typed character by character, which is the one job proportional type
   is bad at. It loses the brackets with everything else. */
.stat-card--code .stat-card__value { font-family: var(--font-mono); font-weight: 600; }
@container (min-width: 210px) {
  /* Override the numeral upgrade — an identifier never wants it. */
  .stat-card--code .stat-card__value { font-size: var(--text-lg); }
}
.stat-card--code::before { background: var(--color-primary); }
.stat-card--code .stat-card__icon { color: var(--color-primary); background: var(--tint-primary); }

/* Variant: rail in navy rather than orange, for the "live from the
   database" blocks — it distinguishes measured figures from the
   promotional ones at a glance, without a second typeface or a badge. */
.stat-card--live {
  background: linear-gradient(155deg, var(--tint-primary) 0%, var(--color-surface) 65%);
}
.stat-card--live::before {
  background: linear-gradient(180deg, var(--color-primary-light) 0%, var(--color-primary-dark) 100%);
}
.stat-card--live .stat-card__icon { color: var(--color-primary); background: var(--tint-primary); }

.stat-card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  background: var(--tint-accent);
  color: var(--color-accent);
  flex: none;
}
.stat-card__icon svg { width: 20px; height: 20px; }

.stat-card__value {
  /* BRACKETS GONE, DISPLAY FACE IN, WEIGHT UP — BISOFTECH's call
     (Day 35). They were `[ 1250+ ]` in IBM Plex Mono at 600, which is
     the heaviest weight this project loads that face in. Poppins goes
     to 800, and a headline figure on a marketing page wants to be the
     heaviest thing in its card, not a code sample. The brackets were
     there to frame a monospace numeral; nothing left to frame. */
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--color-primary);
  white-space: nowrap;
  /* Proportional figures deliberately: tabular-nums gives every digit
     the width of a zero, which makes a large standalone number like 121
     look gappy. Tabular is for columns that must align — see the
     dashboard tables. */
}
:root[data-theme="dark"] .stat-card__value { color: var(--color-on-dark); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .stat-card__value { color: var(--color-on-dark); }
}
/* Room for the full-size numeral: 210px of card is enough for the
   longest realistic figure. Below that it stays at --text-2xl rather
   than being clipped. */
@container (min-width: 210px) {
  .stat-card__value { font-size: var(--text-3xl); }
}


.stat-card__label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--color-ink);
}
.stat-card__caption {
  font-size: var(--text-xs);
  color: var(--color-muted);
  margin: 0;
}

/* A responsive row of stat cards. `auto-fit` + `minmax` rather than a
   fixed column count, because the same component carries 4 cards on the
   dashboard and 5 on the homepage, and a hardcoded grid--5 collapses
   badly at exactly the width a projector in a lecture hall uses. */
.stat-row {
  display: grid;
  /* 200px: measured against the real homepage figures, not guessed.
     (It was set for the old bracketed monospace numeral and kept when
     that went — the label "Internships Provided" needs it anyway.) */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
}

/* --- Data tag / bracket badge (signature element) -------------------------- */
/* Renders content wrapped in square brackets, monospace — used for stat
   counters ("[ 75+ ] Total Students") and data badges ("[ 12 ] Modules",
   "[ 12 weeks ]"). Brackets are generated content, not literal characters
   in the markup, so screen readers don't announce "bracket bracket". */
.data-tag {
  font-family: var(--font-mono);
  font-weight: 600;
  color: var(--color-primary);
  white-space: nowrap;
}
:root[data-theme="dark"] .data-tag {
  color: var(--color-accent);
}
.data-tag::before { content: "[ "; opacity: 0.5; }
.data-tag::after { content: " ]"; opacity: 0.5; }

.stat {
  text-align: left;
}
.stat__value {
  font-family: var(--font-mono);
  font-size: var(--text-3xl);
  font-weight: 700;
  color: var(--color-primary);
  display: block;
}
:root[data-theme="dark"] .stat__value {
  color: var(--color-accent);
}
.stat__value::before { content: "[ "; opacity: 0.4; font-weight: 400; }
.stat__value::after { content: " ]"; opacity: 0.4; font-weight: 400; }
.stat__label {
  font-size: var(--text-sm);
  color: var(--color-muted);
}

/* --- Badges (status pills — approved/pending/rejected, used from Week 3) -- */
.badge {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.badge--pending { background: #FFF3DA; color: #8A5A00; }
.badge--approved { background: #E3F5EC; color: #146840; }
.badge--rejected { background: #FBE7E7; color: #A32B2B; }
.badge--muted { background: var(--color-surface-alt); color: var(--color-muted); }

/* --- Hero (structural pattern only — real homepage content lands Day 10) -- */
.hero {
  background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
  color: var(--color-on-dark);
}
.hero .container {
  padding-block: var(--space-12);
}
.hero h1 {
  color: var(--color-on-dark);
  max-width: 20ch;
}
.hero p.lead {
  color: rgba(255, 255, 255, 0.8);
  max-width: 42ch;
}
.hero__actions {
  display: flex;
  gap: var(--space-4);
  margin-top: var(--space-6);
  flex-wrap: wrap;
}

/* --- Hero slider (Day 10 — admin-manageable, FR-WEB-03) --------------------- */
.hero-slider {
  position: relative;
  min-height: 480px;
  overflow: hidden;
}
.hero-slider__slide {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-color: var(--color-primary); /* shows while the image loads */
  opacity: 0;
  transition: opacity 800ms ease;
  display: flex;
  align-items: center;
}
.hero-slider__slide.is-active {
  opacity: 1;
  z-index: 1;
}
.hero-slider__overlay {
  width: 100%;
  background: linear-gradient(
    90deg,
    rgba(11, 21, 38, 0.88) 0%,
    rgba(11, 21, 38, 0.5) 60%,
    rgba(11, 21, 38, 0.1) 100%
  );
  padding-block: var(--space-12);
}
.hero-slider__overlay h1 {
  color: var(--color-on-dark);
  max-width: 20ch;
}
.hero-slider__overlay p.lead {
  color: rgba(255, 255, 255, 0.85);
  max-width: 42ch;
}
.hero-slider__dots {
  position: absolute;
  z-index: 2;
  bottom: var(--space-5);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: var(--space-2);
}
.hero-slider__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.4);
  cursor: pointer;
  padding: 0;
}
.hero-slider__dot.is-active {
  background: var(--color-accent);
}

/* --- Promo video (Day 11 — FR-CMS-03) --------------------------------------- */
.promo-video {
  max-width: 900px;
  margin-inline: auto;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
}
.promo-video video {
  width: 100%;
  display: block;
  background: var(--color-primary-dark); /* visible while the video buffers */
}
.promo-video__caption {
  text-align: center;
  color: var(--color-muted);
  font-size: var(--text-sm);
  margin: var(--space-3) 0 0;
}

/* --- Theme toggle button ----------------------------------------------------- */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: transparent;
  cursor: pointer;
  font-size: var(--text-base);
  line-height: 1;
}
.theme-toggle:hover {
  border-color: var(--color-accent);
}

/* --- Forms ------------------------------------------------------------------ */
.form-group {
  margin-bottom: var(--space-5);
}
.form-label {
  display: block;
  font-weight: 600;
  font-size: var(--text-sm);
  margin-bottom: var(--space-2);
}
.input {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  transition: border-color var(--transition-fast);
}
.input:focus {
  outline: none;
  border-color: var(--color-accent);
}
.form-help {
  font-size: var(--text-xs);
  color: var(--color-muted);
  margin-top: var(--space-2);
}
.form-error {
  font-size: var(--text-xs);
  color: var(--color-danger);
  margin-top: var(--space-2);
}
.form-error ul {
  list-style: none;
  padding: 0;
  margin: 0;
}
.checkbox-list ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.checkbox-list label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 400;
  cursor: pointer;
}

/* --- Alerts (Django messages framework) -------------------------------------- */
.alert {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  border: 1px solid transparent;
}
/* SELF-CONTAINED PAIRS, both halves hardcoded (Day 35). The
   backgrounds here have never had a dark-mode variant — an alert is a
   pale box in either theme — so the text beside them must not come from
   a token that flips: the dark-mode green is 1.9:1 on this pale green.
   Measured: 6.02:1 and 6.01:1. */
.alert--error { background: #FBE7E7; color: #A32B2B; border-color: #F3C6C6; }
.alert--success { background: #E3F5EC; color: #146840; border-color: #C3E9D6; }
.alert--info { background: #E6F1FB; color: #0C447C; border-color: #C6E0F7; }
.alert--warning { background: #FFF3DA; color: #8A5A00; border-color: #F5DFA8; }

/* A LINK INSIDE AN ALERT (15 Sep 2026). Found by screenshotting the
   hire screen's "set it on the post" link and not being able to see it:
   the global anchor colour and .alert--info's own text colour are both
   dark navy blue, so the link was the same ink as the sentence around
   it. A link nobody can see is a dead control.

   `color: inherit` on purpose, NOT a fifth colour: each alert variant's
   pair is measured (6.02:1, 6.01:1 …) and a link that kept its own hue
   would leave that measurement behind. The underline and the weight are
   what say "link" — which is the more robust signal anyway, since it
   survives greyscale and colour-blindness.

   The dark twin is not optional. `:root[data-theme="dark"] a:not(.btn)`
   is (0,2,1) and would outrank `.alert a` at (0,1,1), repainting these
   links in the dark-mode orange on a pale box — the same cascade trap
   this stylesheet has now been caught by four times. */
.alert a,
:root[data-theme="dark"] .alert a:not(.btn) {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  font-weight: 600;
}

/* --- Auth pages (login) --------------------------------------------------- */
.auth-page {
  max-width: 420px;
  margin-inline: auto;
}

/* --- Gallery (Day 13 — FR-CMS-02) -------------------------------------------- */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}
@media (min-width: 640px) {
  .gallery-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1024px) {
  .gallery-grid { grid-template-columns: repeat(4, 1fr); }
}
.gallery-grid__item {
  display: block;
  padding: 0;
  border: none;
  border-radius: var(--radius-md);
  overflow: hidden;
  cursor: pointer;
  aspect-ratio: 1 / 1;
  background: var(--color-surface-alt);
}
.gallery-grid__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-base);
}
.gallery-grid__item:hover img {
  transform: scale(1.05);
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0, 0, 0, 0.9);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
}
.lightbox[hidden] {
  display: none;
}
.lightbox img {
  max-width: 100%;
  max-height: 80vh;
  border-radius: var(--radius-md);
}
.lightbox__caption {
  color: var(--color-on-dark);
  margin-top: var(--space-4);
  text-align: center;
}
.lightbox__close {
  position: absolute;
  top: var(--space-5);
  right: var(--space-5);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.15);
  color: var(--color-on-dark);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.lightbox__close:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* --- CMS management screens (Day 13) ------------------------------------------ */
.manage-table {
  width: 100%;
  border-collapse: collapse;
}
.manage-table th,
.manage-table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  border-bottom: 1px solid var(--color-border);
}
.manage-table th {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-muted);
}
.manage-table img.thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: var(--radius-sm);
}

/* --- Step progress (Day 15 — multi-step application form) ------------------- */
.step-progress {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-6);
  flex-wrap: wrap;
}
.step-progress__step {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: 600;
  background: var(--color-surface-alt);
  color: var(--color-muted);
}
.step-progress__step.is-current {
  background: var(--color-primary);
  color: var(--color-on-dark);
}
.step-progress__step.is-done {
  /* Both halves fixed, like the alerts above: this pale green has no
     dark-mode variant, so the text on it must not come from a token
     that does. */
  background: #E3F5EC;
  color: #146840;
}
.step-progress__number {
  font-family: var(--font-mono);
}

/* --- Review queue & record detail (Day 19 — admissions review dashboard) ----
   Built generically (not named `admissions-*`) because the same three
   shapes recur in every remaining admin module: a filter bar over a list,
   pagination under it, and a label/value record view. HR's instructor
   applications (Week 5) and Finance's payment records (Week 5) reuse these
   rather than each adding a near-identical block. ------------------------ */
.filter-bar {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.filter-bar__grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .filter-bar__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .filter-bar__grid { grid-template-columns: repeat(3, 1fr); }
}
.filter-bar .form-group { margin-bottom: 0; }

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-5);
  flex-wrap: wrap;
}
.pagination__status {
  color: var(--color-muted);
  font-size: var(--text-sm);
}

/* Label/value pairs for a single record. `dt`/`dd` rather than a table:
   this is a description list, and it reflows to one column on mobile
   without the horizontal scroll a table would need. */
.detail-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-1) var(--space-4);
  margin: 0;
}
@media (min-width: 480px) {
  .detail-list { grid-template-columns: minmax(9rem, auto) 1fr; }
}
.detail-list dt {
  color: var(--color-muted);
  font-size: var(--text-sm);
  font-weight: 600;
}
.detail-list dd {
  margin: 0 0 var(--space-3);
  overflow-wrap: anywhere;
}
@media (min-width: 480px) {
  .detail-list dd { margin-bottom: var(--space-2); }
}

.document-list {
  list-style: none;
  padding: 0;
  margin: var(--space-2) 0 0;
}
.document-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}
.document-list li:last-child { border-bottom: 0; }

.review-photo {
  max-width: 180px;
  width: 100%;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
}

/* --- Decision panel (Day 20 — approve/reject on an application) ------------ */
.decision-panel {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}
@media (min-width: 768px) {
  .decision-panel { grid-template-columns: 1fr 1fr; gap: var(--space-8); }
  /* A divider between the two columns rather than a border on one form —
     keeps the rule from appearing above/below them when they stack on
     mobile. */
  .decision-panel > form:last-child {
    border-left: 1px solid var(--color-border);
    padding-left: var(--space-8);
  }
}

/* ---------------------------------------------------------------------------
   Course catalogue — Day 34
--------------------------------------------------------------------------- */

.catalogue-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}
.catalogue-filters .input { flex: 1 1 14rem; min-width: 0; }

.episode-list {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
  counter-reset: none;
}
.episode {
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.episode__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}
.episode__title { font-weight: 600; color: var(--color-ink); }
.episode__length { color: var(--color-muted); font-size: var(--text-sm); margin-left: auto; }
.episode__desc {
  margin: var(--space-2) 0 var(--space-3);
  color: var(--color-muted);
  font-size: var(--text-sm);
}
/* Text, not a disabled button: "there is nothing yet" is a different
   statement from "you may not". Same rule as Day 29's join link. */
.episode__note { color: var(--color-muted); font-size: var(--text-sm); font-style: italic; }

@media (max-width: 34rem) {
  .episode__length { margin-left: 0; width: 100%; }
}

/* ------------------------------------------------------------------
   The floating calls-to-action (Day 35)
   ------------------------------------------------------------------
   Two pills, bottom-right, above the page: "Applications open" (navy)
   and "We're hiring" (orange). See the partial for which hides where.

   ONE COMPONENT, TWO VARIANTS, because there will be a third one day
   and the second was already a copy waiting to happen.

   FLOATING, IN THE LITERAL SENSE. `position: fixed` puts them above the
   page; the shadow — a warm or cool one thrown by the pill's own colour,
   over a neutral one for depth — is what makes them read as lifted
   rather than stuck to the corner. The slow halo behind the orange one
   is the only thing on the site that moves on its own, and it stays
   exclusive to it: two pulsing buttons would be a fairground.

   THEY DO NOT CHANGE SHAPE ON A PHONE, they change ROW. Side by side
   they are wider than a 420px screen, so below 34rem the stack becomes
   a column and the hiring button — the one that comes and goes — sits
   nearest the corner.

   z-index sits above the sticky header (100) and below the mobile nav
   drawer (300): the drawer must be able to cover them.
------------------------------------------------------------------ */
.fab-stack {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: 200;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-3);
  max-width: calc(100vw - 2 * var(--space-5));
  animation: fab-in 300ms ease-out 900ms both;
}
@keyframes fab-in {
  from { opacity: 0; transform: translateY(0.75rem) scale(0.96); }
  to   { opacity: 1; transform: none; }
}

.fab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 1.25rem 0.5rem 0.5rem;
  border-radius: 999px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1rem;
  line-height: 1;
  white-space: nowrap;
  transition: box-shadow 180ms ease, transform 180ms ease;
}

/* THE LABEL COLOUR, STATED OUT LOUD. `.block-link` (Day 27) and the
   global `a:not(.btn)` rule are both (0,1,1) and outrank a single
   component class, which left these labels following the page's ink —
   dark in light mode, light in dark mode, on the same coloured pill.
   Two classes is (0,2,0) and wins in every theme. */
.fab-stack .fab,
.fab-stack .fab:hover,
:root[data-theme="dark"] .fab-stack .fab {
  color: #FFFFFF;
  text-decoration: none;
}

.fab:hover,
.fab:focus-visible { transform: translateY(-3px); }
.fab:active { transform: translateY(-1px); }

.fab__icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.22);
}
.fab__icon .icon { width: 1.25rem; height: 1.25rem; }

/* --- the intake: navy, and the quieter of the two ------------------ */
.fab--apply {
  background: linear-gradient(135deg, var(--color-primary-light) 0%, var(--color-primary) 55%, var(--color-primary-dark) 100%);
  box-shadow: 0 10px 26px rgba(1, 74, 144, 0.34), 0 2px 8px rgba(20, 33, 61, 0.22);
  /* A hairline in BOTH themes, not just dark. Every page on this site
     opens on a navy hero, and a navy pill floating over navy is an
     outline away from invisible. On a white background the shadow does
     the work and this is not noticeable. */
  border: 1px solid rgba(255, 255, 255, 0.28);
}
.fab--apply:hover,
.fab--apply:focus-visible {
  box-shadow: 0 16px 34px rgba(1, 74, 144, 0.42), 0 3px 10px rgba(20, 33, 61, 0.26);
}
/* --- hiring: orange, and the one that goes away -------------------- */
.fab--hiring {
  background: linear-gradient(135deg, #FF7A1A 0%, var(--color-accent) 45%, var(--color-accent-dark) 100%);
  /* Warm shadow first, neutral second: the orange has to look like it
     is casting the light, or the pill reads as a sticker. */
  box-shadow: 0 10px 26px rgba(255, 106, 0, 0.38), 0 2px 8px rgba(20, 33, 61, 0.22);
}
.fab--hiring:hover,
.fab--hiring:focus-visible {
  box-shadow: 0 16px 34px rgba(255, 106, 0, 0.46), 0 3px 10px rgba(20, 33, 61, 0.26);
}

/* The halo. Behind the hiring pill only, never over it, and slow enough
   (4s) to register as "alive" rather than "blinking at you". */
.fab--hiring::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--color-accent);
  opacity: 0.55;
  z-index: -1;
  animation: fab-halo 4s ease-out infinite;
}
@keyframes fab-halo {
  0%        { transform: scale(1);    opacity: 0.45; }
  55%, 100% { transform: scale(1.22); opacity: 0; }
}

@media (max-width: 34rem) {
  .fab-stack {
    right: var(--space-4);
    bottom: var(--space-4);
    max-width: calc(100vw - 2 * var(--space-4));
    gap: var(--space-2);
  }
  .fab { font-size: 0.9375rem; padding-right: 1.1rem; }
  .fab__icon { width: 2rem; height: 2rem; }
}

@media (prefers-reduced-motion: reduce) {
  .fab-stack { animation: fab-in 1ms linear 900ms both; }
  .fab--hiring::before { animation: none; opacity: 0; }
  .fab:hover,
  .fab:focus-visible,
  .fab:active { transform: none; }
}

/* ------------------------------------------------------------------
   Detail pages — the public "one thing, explained" layout (Day 35)
   ------------------------------------------------------------------
   Written for the careers post page and immediately reused by the
   program page, which is the whole reason the class names below say
   `page-hero` and `chip` rather than `job-`: a navy hero, a column of
   cards, and a sticky facts panel is not a fact about recruitment.
   Anything genuinely careers-only keeps its `job-` prefix further down.

   COLOUR IS RATIONED. Navy for the post itself, orange for the one
   thing the visitor is meant to do, amber for a deadline inside its
   last week, and grey for every other fact. A page where everything is
   coloured tells the reader nothing about what matters.
------------------------------------------------------------------ */
.back-link {
  display: inline-block;
  font-size: var(--text-sm);
  margin-bottom: var(--space-4);
}

/* --- the post, on the brand navy --- */
.page-hero {
  border-radius: var(--radius-lg);
  padding: var(--space-8) var(--space-6);
  background:
    radial-gradient(120% 140% at 100% 0%, var(--color-primary-light) 0%, transparent 55%),
    linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
  color: var(--color-on-dark);
  box-shadow: var(--shadow-md);
}
.page-hero__eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
  margin-bottom: var(--space-3);
}
.page-hero__title {
  margin: 0;
  color: var(--color-on-dark);
  font-size: clamp(1.75rem, 1.2rem + 2vw, 2.5rem);
  line-height: 1.15;
}
.page-hero__summary {
  margin: var(--space-3) 0 0;
  max-width: 60ch;
  /* Its own colour pair: this sits on an always-dark surface, so it
     must not inherit a token that flips with the theme — the mistake
     the footer shipped with for weeks (see test_stylesheets.py). */
  color: rgba(255, 255, 255, 0.86);
}

/* --- the fact chips --- */
.chips {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-5) 0 0;
  padding: 0;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.75rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.92);
  font-size: var(--text-sm);
  font-weight: 500;
}
.chip .icon { width: 1rem; height: 1rem; flex: none; }
.chip--open   { background: rgba(255, 255, 255, 0.16); }
/* Amber, and only here: a deadline inside its last week is the one
   fact on this page that changes what the reader should do today. */
.chip--urgent {
  background: #FFC857;
  border-color: #FFC857;
  color: #4A3200;
  font-weight: 700;
}
.chip--closed {
  background: rgba(0, 0, 0, 0.28);
  border-color: rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.78);
}

/* --- the two columns --- */
.detail-layout {
  display: grid;
  gap: var(--space-6);
  margin-top: var(--space-6);
  align-items: start;
}
.detail-main { display: grid; gap: var(--space-6); min-width: 0; }
.detail-aside { display: grid; gap: var(--space-5); }

@media (min-width: 64rem) {
  .detail-layout { grid-template-columns: minmax(0, 1fr) 20rem; }
  .detail-aside { position: sticky; top: var(--space-6); }
}

/* Cards on this page do not lift on hover — none of them is a link,
   and a card that moves under the cursor promises a click. */
.job-card:hover,
.job-apply:hover,
.job-closed:hover,
.facts-card:hover,
.job-note:hover { transform: none; box-shadow: var(--shadow-sm); }

/* --- responsibilities / requirements --- */
.check-list {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}
.check-list li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
  color: var(--color-ink);
}
.check-list .icon {
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.15rem;
  color: var(--color-accent);
  flex: none;
}

/* --- the form --- */
.job-apply {
  /* The one card the whole page exists to reach, marked as such. */
  border-top: 3px solid var(--color-accent);
}
.job-apply__head { margin-bottom: var(--space-5); }
.job-apply__title { margin: 0 0 var(--space-2); }
.job-apply__lead { margin: 0; color: var(--color-muted); max-width: 58ch; }

.job-fields {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 40rem) {
  .job-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .job-field--wide { grid-column: 1 / -1; }
}
.job-field label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  margin-bottom: var(--space-2);
}
.job-field__req { color: var(--color-accent-text); margin-left: 0.15rem; }
:root[data-theme="dark"] .job-field__req { color: var(--color-accent); }
.job-field--error .input { border-color: var(--color-danger); }

/* A file input cannot be restyled, only dressed: the button half is
   ours, the filename half is the browser's, and that is fine — people
   recognise their own operating system's file picker. */
.job-form input[type="file"] {
  padding: var(--space-3);
  font-size: var(--text-sm);
  cursor: pointer;
}
.job-form input[type="file"]::file-selector-button {
  margin-right: var(--space-3);
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-accent-text);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}
.job-form input[type="file"]::file-selector-button:hover {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}
:root[data-theme="dark"] .job-form input[type="file"]::file-selector-button {
  color: var(--color-accent);
}

.job-form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}
.job-form__note {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-muted);
}

.job-closed { border-top: 3px solid var(--color-border); }
.job-closed p { color: var(--color-muted); }
.job-closed .btn { margin-top: var(--space-4); }

/* --- the sidebar --- */
.facts { margin: var(--space-3) 0 0; }
.facts > div {
  display: grid;
  grid-template-columns: minmax(0, 7rem) minmax(0, 1fr);
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}
.facts > div:last-child { border-bottom: 0; }
.facts dt {
  font-size: var(--text-sm);
  color: var(--color-muted);
}
.facts dd {
  margin: 0;
  font-weight: 600;
  color: var(--color-ink);
}
.facts__cta { width: 100%; justify-content: center; margin-top: var(--space-4); }

.job-note p { margin: var(--space-3) 0 0; color: var(--color-muted); font-size: var(--text-sm); }
.job-note__quiet { font-style: italic; }

@media (max-width: 34rem) {
  .page-hero { padding: var(--space-6) var(--space-5); }
  .facts > div { grid-template-columns: minmax(0, 1fr); gap: 0.15rem; }
}

/* --- Careers: the confirmation screen ------------------------------------- */
.job-done { text-align: center; margin-bottom: var(--space-6); }
.job-done__mark {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  margin: 0 auto var(--space-4);
  border-radius: 50%;
  /* Fixed, not the token: in dark mode `--color-success` is a LIGHT
     green (it has to be, to be readable on a dark page) and a white
     tick on it measures 1.4:1 — an invisible tick on the one screen
     whose whole job is to say "we have it". */
  background: #177349;
  color: #FFFFFF;
}
.job-done__mark .icon { width: 2rem; height: 2rem; }
.job-done__title { margin: 0; }
.job-done__sub { margin: var(--space-2) 0 0; color: var(--color-muted); }

.job-ref { text-align: center; border-top: 3px solid var(--color-accent); }
.job-ref__number {
  font-family: var(--font-mono);
  font-size: clamp(1.5rem, 1rem + 2.5vw, 2rem);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--color-ink);
  margin: var(--space-2) 0 0;
  /* Long enough to matter, short enough to read aloud down a phone. */
  word-break: break-all;
}
.job-ref__hint { margin: var(--space-3) 0 0; color: var(--color-muted); font-size: var(--text-sm); }

.job-next { margin-top: var(--space-6); }
.job-next .btn { margin-top: var(--space-4); }

/* ------------------------------------------------------------------
   Programs — the department list and the program page (Day 35)
   ------------------------------------------------------------------
   The program page reuses the detail-page layout above (`page-hero`,
   `detail-layout`, `check-list`, `facts-card`); what follows is only
   what those two pages add.
------------------------------------------------------------------ */

/* --- the department cards ----------------------------------------- */
.dept-grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 52rem) { .dept-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.dept-card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: box-shadow var(--transition-base);
}
.dept-card:hover { box-shadow: var(--shadow-md); }

/* The navy band is what makes a department read as a place rather than
   a heading — the same device as the hero, one size down. */
.dept-card__head {
  padding: var(--space-5) var(--space-6);
  background:
    radial-gradient(120% 160% at 100% 0%, var(--color-primary-light) 0%, transparent 60%),
    linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
  color: var(--color-on-dark);
}
.dept-card__code {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: var(--space-2);
}
.dept-card__name {
  margin: 0;
  font-size: var(--text-xl);
  color: var(--color-on-dark);
}
.dept-card__count {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  /* Its own colour pair: this sits on an always-navy surface and must
     not inherit a token that flips with the theme. */
  color: rgba(255, 255, 255, 0.78);
}

.dept-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--space-5) var(--space-6) var(--space-6);
}
.dept-card__desc {
  margin: 0 0 var(--space-4);
  color: var(--color-muted);
  font-size: var(--text-sm);
}

/* --- a program, as a row ------------------------------------------- */
.program-rows {
  list-style: none;
  /* Top-aligned, NOT pushed to the bottom of the card. Bottom-aligning
     them made a department with one program show a hand-width of empty
     white above its only row, which reads as something failing to load
     rather than as tidy alignment. */
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.program-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  transition: border-color var(--transition-fast), background-color var(--transition-fast),
    transform var(--transition-fast);
}
/* The whole row is the link, so nothing inside it may turn link-blue —
   or, in dark mode, orange. Note the THIRD selector: the dark theme's
   own anchor rule is `:root[data-theme="dark"] a:not(.btn)` at (0,2,1),
   which quietly outranks a two-class fix that looks perfectly sound in
   light mode. Caught in a dark render, for the third time on this
   project (Day 27's inbox, Day 35's hiring pill, this). */
.program-rows .program-row,
.program-rows .program-row:hover,
:root[data-theme="dark"] .program-rows .program-row,
:root[data-theme="dark"] .program-rows .program-row:hover {
  color: var(--color-ink);
  text-decoration: none;
}
.program-row:hover {
  border-color: var(--color-accent);
  background: var(--color-surface-alt);
  transform: translateX(2px);
}
.program-row__name { font-weight: 600; min-width: 0; }
.program-row__meta {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  white-space: nowrap;
}
.program-row__weeks { color: var(--color-muted); }
.program-row__price { font-weight: 600; color: var(--color-accent-text); }
:root[data-theme="dark"] .program-row__price { color: var(--color-accent); }
.program-row__go { display: grid; place-items: center; color: var(--color-muted); }
.program-row__go .icon { width: 1.1rem; height: 1.1rem; }
.program-row:hover .program-row__go { color: var(--color-accent); }

@media (max-width: 34rem) {
  .program-row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: var(--space-2);
  }
  .program-row__name { grid-column: 1 / -1; }
  .program-row__go { justify-self: end; }
}

/* --- the price chip on the program hero ---------------------------- */
.chip--price { font-weight: 700; }

/* --- the curriculum outline ---------------------------------------- */
.module-list {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-3);
  counter-reset: none;
}
.module {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-4);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.module__number {
  display: grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  background: var(--color-surface-alt);
  color: var(--color-primary);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 600;
}
:root[data-theme="dark"] .module__number { color: var(--color-on-dark); }
.module__body { min-width: 0; }
.module__title { margin: 0; font-weight: 600; color: var(--color-ink); }
.module__desc {
  margin: var(--space-2) 0 0;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.facts__note {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--color-muted);
  text-align: center;
}

/* "There is nothing here yet" is a sentence, not an empty box. Used by
   both program pages; the same reasoning as the Day 29 join link. */
.empty-note {
  margin: var(--space-3) 0 0;
  color: var(--color-muted);
  font-style: italic;
}

/* ------------------------------------------------------------------
   The public pages: features, certifications, contact (Day 35)
   ------------------------------------------------------------------
   Everything here is shared by About, Internships, Certification Exams
   and Contact, which is why none of the names says which page it came
   from. They sit on top of the detail-page layout further up.
------------------------------------------------------------------ */

.section-title { margin: var(--space-2) 0 var(--space-3); }
.prose { color: var(--color-muted); max-width: 68ch; margin: 0; }
.prose + .prose { margin-top: var(--space-4); }
.prose + .check-list { margin-top: var(--space-4); }

/* --- a reason to choose this place, as a card --------------------- */
.feature-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--space-5);
}
@media (min-width: 44rem) { .feature-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 68rem) { .feature-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* Three across even on a medium screen, for pages with exactly three. */
@media (min-width: 52rem) { .feature-grid--wide { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.feature {
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-base), border-color var(--transition-base);
}
.feature:hover { box-shadow: var(--shadow-md); border-color: var(--color-accent); }
.feature__icon {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  margin-bottom: var(--space-4);
  border-radius: var(--radius-md);
  /* A tint of the accent rather than the accent itself: six solid
     orange squares down a page is a page nobody can read. */
  background: rgba(255, 106, 0, 0.12);
  color: var(--color-accent-dark);
}
:root[data-theme="dark"] .feature__icon {
  background: rgba(255, 106, 0, 0.18);
  color: var(--color-accent);
}
.feature__icon .icon { width: 1.5rem; height: 1.5rem; }
.feature__title { margin: 0 0 var(--space-2); font-size: var(--text-lg); }
.feature__text { margin: 0; color: var(--color-muted); font-size: var(--text-sm); }

/* --- the mission, said once and given room ------------------------ */
.mission {
  padding: var(--space-8) var(--space-6);
  border-radius: var(--radius-lg);
  text-align: center;
  background:
    radial-gradient(120% 160% at 0% 0%, var(--color-primary-light) 0%, transparent 55%),
    linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
  color: var(--color-on-dark);
}
.mission__eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: var(--space-4);
}
.mission__text {
  margin: 0 auto;
  max-width: 54ch;
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 0.9rem + 1vw, 1.5rem);
  line-height: 1.5;
  /* Its own colour pair: always-navy surface, must not inherit a token
     that flips with the theme. */
  color: rgba(255, 255, 255, 0.94);
}

/* --- certifications ------------------------------------------------ */
.cert-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  margin-top: var(--space-5);
}
.cert-card {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  padding: var(--space-5);
  text-align: center;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}
.cert-card__badge { height: 4rem; width: auto; object-fit: contain; }
.cert-card__badge--fallback {
  display: grid;
  place-items: center;
  width: 4rem;
  border-radius: 50%;
  background: var(--color-surface-alt);
  color: var(--color-primary);
}
:root[data-theme="dark"] .cert-card__badge--fallback { color: var(--color-on-dark); }
.cert-card__badge--fallback .icon { width: 1.75rem; height: 1.75rem; }
.cert-card__provider {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-accent-text);
}
:root[data-theme="dark"] .cert-card__provider { color: var(--color-accent); }
.cert-card__name { margin: 0; font-size: var(--text-base); }

/* --- contact ------------------------------------------------------- */
.contact-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: minmax(0, 1fr);
  margin: var(--space-6) 0;
}
@media (min-width: 52rem) { .contact-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.contact-card {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-base), border-color var(--transition-base),
    transform var(--transition-base);
}
/* Two of the three are links (tel:, mailto:) and the third is an
   address, so the colour has to be stated — and stated for dark mode
   too, where `:root[data-theme="dark"] a:not(.btn)` at (0,2,1) would
   otherwise turn the whole card orange. */
.contact-grid .contact-card,
.contact-grid .contact-card:hover,
:root[data-theme="dark"] .contact-grid .contact-card,
:root[data-theme="dark"] .contact-grid .contact-card:hover {
  color: var(--color-ink);
  text-decoration: none;
}
a.contact-card:hover {
  border-color: var(--color-accent);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}
.contact-card__icon {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-md);
  background: rgba(1, 74, 144, 0.10);
  color: var(--color-primary);
  margin-bottom: var(--space-2);
}
:root[data-theme="dark"] .contact-card__icon {
  background: rgba(255, 255, 255, 0.10);
  color: var(--color-on-dark);
}
.contact-card__icon .icon { width: 1.4rem; height: 1.4rem; }
.contact-card__value {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-lg);
  line-height: 1.3;
  word-break: break-word;
}
.contact-card__note { color: var(--color-muted); font-size: var(--text-sm); }

/* ------------------------------------------------------------------
   The home page (Day 35)
   ------------------------------------------------------------------
   Section heads, the sidebar, the four steps and the closing band. The
   cards themselves are the shared `.card`, `.feature` and `.stat-card`
   components — this is what the home page adds around them.
------------------------------------------------------------------ */

.stat-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-5);
}
.stat-head__caption { color: var(--color-muted); font-size: var(--text-sm); }

/* --- a Read More that looks like the end of a thought --------------- */
.read-more {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.read-more .icon { width: 1rem; height: 1rem; transition: transform var(--transition-fast); }
.read-more:hover .icon { transform: translateX(3px); }
/* The plain one carries no button chrome — inside a step card the whole
   card is already a block, and a second bordered button in it would
   read as a second thing to do. */
.read-more--plain {
  font-weight: 600;
  font-size: var(--text-sm);
  margin-top: var(--space-4);
}

/* --- the sidebar --------------------------------------------------- */
.sidebar-lead {
  margin: var(--space-3) 0 0;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.side-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
}
.side-link .icon { width: 1rem; height: 1rem; flex: none; }

.side-menu {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  gap: 1px;
}
.side-menu li + li { border-top: 1px solid var(--color-border); }
.side-menu a {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background-color var(--transition-fast), padding-left var(--transition-fast);
}
/* Two classes AND a dark-theme twin: the theme's own anchor rule is
   (0,2,1) and would otherwise repaint this whole menu accent-orange
   after dark. */
.side-menu a,
:root[data-theme="dark"] .side-menu a { color: var(--color-ink); text-decoration: none; }
.side-menu a:hover,
:root[data-theme="dark"] .side-menu a:hover {
  background: var(--color-surface-alt);
  padding-left: var(--space-3);
  color: var(--color-accent-text);
}
:root[data-theme="dark"] .side-menu a:hover { color: var(--color-accent); }
.side-menu .icon { width: 1.1rem; height: 1.1rem; flex: none; color: var(--color-primary); }
:root[data-theme="dark"] .side-menu .icon { color: var(--color-accent); }

.side-contact__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  word-break: break-word;
}
.side-contact .side-contact__row,
:root[data-theme="dark"] .side-contact .side-contact__row {
  color: var(--color-ink);
  text-decoration: none;
}
.side-contact .side-contact__row:hover { color: var(--color-accent-text); }
:root[data-theme="dark"] .side-contact .side-contact__row:hover { color: var(--color-accent); }
.side-contact__row .icon { width: 1.1rem; height: 1.1rem; flex: none; color: var(--color-primary); }
/* Small enough that a 27-character email address fits the sidebar
   column on one line. At the row's own size it broke as "…gmail.co / m",
   which reads as a typo rather than a wrap. Measured, not guessed:
   0.8125rem still left one character over. */
.side-contact__row span { font-size: 0.75rem; overflow-wrap: anywhere; }
:root[data-theme="dark"] .side-contact__row .icon { color: var(--color-accent); }
.side-contact__hours {
  margin: var(--space-4) 0 0;
  color: var(--color-muted);
  font-size: var(--text-xs);
}

/* --- the four steps ------------------------------------------------ */
.step-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 44rem) { .step-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 72rem) { .step-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.step {
  display: flex;
  flex-direction: column;
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--transition-base), border-color var(--transition-base);
}
.step:hover { box-shadow: var(--shadow-md); border-color: var(--color-accent); }
.step__number {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  margin-bottom: var(--space-4);
  border-radius: 50%;
  background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
  color: #FFFFFF;
  font-family: var(--font-display);
  font-weight: 800;
}
.step__title { margin: 0 0 var(--space-2); font-size: var(--text-lg); }
.step__text { margin: 0; color: var(--color-muted); font-size: var(--text-sm); }
.step .read-more { margin-top: auto; padding-top: var(--space-4); }

/* --- the closing band ---------------------------------------------- */
.closing-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  margin-top: var(--space-6);
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 160% at 0% 0%, var(--color-primary-light) 0%, transparent 55%),
    linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
  color: var(--color-on-dark);
}
.closing-cta__eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: var(--space-2);
}
.closing-cta__text {
  margin: 0;
  max-width: 48ch;
  /* Its own colour pair: always-navy surface. */
  color: rgba(255, 255, 255, 0.90);
}
.closing-cta__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }

/* ------------------------------------------------------------------
   Sessions on the public site (Day 38)
   ------------------------------------------------------------------
   An intake card. The code is the loudest thing on it after the name,
   because a student quotes it on their final-project application and on
   exam papers, and this page is where they first see it.
------------------------------------------------------------------ */
.session-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: minmax(0, 1fr);
  margin-bottom: var(--space-8);
}
@media (min-width: 52rem) { .session-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 72rem) { .session-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.session-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: box-shadow var(--transition-base);
}
.session-card:hover { box-shadow: var(--shadow-md); }

.session-card__head {
  padding: var(--space-5) var(--space-6);
  background:
    radial-gradient(120% 160% at 100% 0%, var(--color-primary-light) 0%, transparent 60%),
    linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
  color: var(--color-on-dark);
}
.session-card__code {
  display: inline-block;
  padding: 0.2rem 0.6rem;
  margin-bottom: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.14em;
}
.session-card__name { margin: 0; color: var(--color-on-dark); font-size: var(--text-xl); }
.session-card__when {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  /* Its own pair: always-navy surface. */
  color: rgba(255, 255, 255, 0.82);
}

.session-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--space-5) var(--space-6) var(--space-6);
}
.session-card__label {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-accent-text);
}
.session-card__programs {
  list-style: none;
  margin: var(--space-3) 0 var(--space-5);
  padding: 0;
  display: grid;
  gap: var(--space-2);
}
.session-card__programs a {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
}
/* Two classes and a dark twin — the theme's own anchor rule is (0,2,1). */
.session-card__programs a,
:root[data-theme="dark"] .session-card__programs a { color: var(--color-ink); text-decoration: none; }
.session-card__programs a:hover,
:root[data-theme="dark"] .session-card__programs a:hover { color: var(--color-accent-text); }
.session-card__programs .icon { width: 1rem; height: 1rem; flex: none; color: var(--color-accent); }
.session-card__cta { margin-top: auto; width: 100%; justify-content: center; }

/* ===========================================================================
   Internship cards (17 Sep 2026)
   ========================================================================
   The public Internships page listed no actual placements until today —
   four true statements about the institute and nothing a visitor could
   act on. These are the cards that fixed that.

   A CLOSED placement is dimmed, not hidden. Somebody who saw it last
   week and comes back deserves to be told it closed rather than shown a
   gap they will read as "I imagined it" (the Day 33 careers rule). */
.internship-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.internship-card__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.internship-card__logo {
  width: 48px;
  height: 48px;
  object-fit: contain;
  flex: none;
}
/* The organisation's initial, for a placement with no logo yet. Not a
   broken image and not a blank box — the card is still a real
   opportunity without one. */
.internship-card__monogram {
  width: 48px;
  height: 48px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-md);
  background: var(--color-primary);
  color: var(--color-on-dark);
  font-weight: 700;
  font-size: var(--text-lg);
}
.internship-card__title {
  margin: 0;
  font-size: var(--text-lg);
  line-height: 1.25;
}
.internship-card__org { margin: 0; color: var(--color-muted); font-size: var(--text-sm); }
.internship-card__summary { margin: 0; }
.internship-card__facts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-muted);
}
.internship-card__facts li { display: flex; align-items: center; gap: var(--space-2); }
.internship-card__facts .icon { width: 16px; height: 16px; flex: none; }
.internship-card__closed {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-muted);
  font-style: italic;
}
.internship-card--closed { opacity: 0.72; }
/* The card is a column flexbox, so a stretched button is the default and
   a full-width orange bar under every placement is what you get. The
   action sits at the bottom, at its own width. `margin-top: auto` keeps
   the buttons on a row of cards aligned when the summaries differ in
   length — which they always do. */
.internship-card .btn {
  align-self: flex-start;
  margin-top: auto;
}
/* In dark mode the navy monogram disappears into the card. The accent
   is the surface there, and `--color-accent-contrast` is the token that
   already carries the measured text colour for it — invented one
   (`--color-on-accent`) on the first attempt, and the undefined-token
   guard in tests/test_stylesheets.py caught it before it shipped as a
   silently dropped declaration. */
:root[data-theme="dark"] .internship-card__monogram {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}

/* ===========================================================================
   Zoom call cards (17 Sep 2026)
   ========================================================================
   The public schedule, grouped by programme and then by day.

   ⚠️ NOTHING HERE STYLES A JOIN LINK BY DEFAULT. The button is rendered
   only where the view says the person may have it (Day 29: the link is
   a credential). A CSS rule that made one appear would be a security
   bug wearing a stylesheet. */
.zoom-programme { margin-top: var(--space-8); }
.zoom-programme__title {
  margin: 0;
  font-size: var(--text-2xl);
  color: var(--color-primary);
}
.zoom-programme__dept {
  margin: 0 0 var(--space-4);
  color: var(--color-muted);
  font-size: var(--text-sm);
}
.zoom-day {
  margin: var(--space-5) 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-muted);
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-3);
}
.zoom-card {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;
  overflow: hidden;
}
.zoom-card__cover {
  display: block;
  width: 100%;
  height: 150px;
  object-fit: cover;
}
/* A class nobody uploaded a picture for still gets a real cover: the
   programme's initial on the brand navy. Not a broken image icon and
   not an empty band. */
.zoom-card__cover--fallback {
  display: grid;
  place-items: center;
  background: var(--color-primary);
  color: var(--color-on-dark);
  font-weight: 700;
  font-size: 3rem;
  line-height: 1;
}
.zoom-card__body {
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1;
}
.zoom-card__title { margin: 0; font-size: var(--text-lg); line-height: 1.25; }
.zoom-card__facts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-muted);
}
.zoom-card__facts li { display: flex; align-items: center; gap: var(--space-2); }
.zoom-card__facts .icon { width: 16px; height: 16px; flex: none; }
.zoom-card__desc { margin: 0; font-size: var(--text-sm); }
.zoom-card__note,
.zoom-card__cancelled {
  margin: auto 0 0;
  font-size: var(--text-sm);
  color: var(--color-muted);
}
.zoom-card__cancelled { color: var(--color-danger); font-weight: 600; }
.zoom-card .btn { align-self: flex-start; margin-top: auto; }
.zoom-card--cancelled .zoom-card__cover { filter: grayscale(1); opacity: 0.6; }

:root[data-theme="dark"] .zoom-programme__title { color: var(--color-accent); }
:root[data-theme="dark"] .zoom-card__cover--fallback {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}
