/* ==========================================================================
   LEECOI — Components
   ==========================================================================

   LOAD ORDER (fixed, and the reason the stylesheet needs no `!important`):

     1. variables.css   tokens and surface scopes
     2. globals.css     element selectors only
     3. components.css  this file, single-class selectors
     4. animations.css  keyframes and reveal states
     5. utilities.css   single-purpose overrides, last so they win on order
     6. pages/<page>.css  page-scoped composition only

   RULES OF THIS FILE

   - One class per selector. No element selectors, no descendant chains deeper
     than a state or a direct child. Everything sits at 0,1,0 specificity so
     conflicts are resolved by source order rather than escalation.
   - Colour comes from the surface tokens (`--ink`, `--accent`, `--line`, ...)
     and never from the raw palette. That is what makes each component work
     unchanged on a dark hero and a light content band.
   - A component owns its internal spacing. It never sets its own outer
     margin; the parent layout does.
   ========================================================================== */

/* ==========================================================================
   1. LAYOUT
   ========================================================================== */

/* The content rail. `--gutter` is fluid, so this is also the guarantee that
   nothing touches the viewport edge on a 320px phone. */
.container {
  width: 100%;
  max-width: var(--width-page);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container--wide {
  max-width: var(--width-wide);
}

.container--prose {
  max-width: var(--width-prose);
}

/* Vertical rhythm. Every major band is a `.section`, so the spacing between
   bands is decided in exactly one place. */
.section {
  padding-block: var(--space-section);
}

.section--tight {
  padding-block: clamp(3rem, 6vw, 4.5rem);
}

.section--flush-top {
  padding-block-start: 0;
}

.section--flush-bottom {
  padding-block-end: 0;
}

/* ---- Section heading block --------------------------------------------- */
.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  max-width: 46rem;
  margin-block-end: var(--space-2xl);
}

/* A heading block with an action parked on the far right on wide screens
   ("Explore tracks" / "View all"), stacking underneath on narrow ones. */
.section-head--split {
  max-width: none;
}

@media (min-width: 48rem) {
  .section-head--split {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-xl);
  }

  .section-head--split > :first-child {
    max-width: 46rem;
  }
}

.section-head__title {
  font-size: var(--step-d2);
  line-height: var(--step-d2-lh);
  letter-spacing: var(--step-d2-ls);
  font-weight: var(--weight-bold);
}

.section-head__lead {
  font-size: var(--step-lead);
  line-height: var(--step-lead-lh);
  letter-spacing: var(--step-lead-ls);
  color: var(--muted);
  max-width: var(--width-prose);
}

/* ==========================================================================
   2. STRUCTURAL DEVICES
   --------------------------------------------------------------------------
   The site's recurring signature at the structural level: a monospaced label
   above a heading, and hairlines that FADE at their ends so a boundary reads
   as a continuing axis rather than a closed box.

   Note on numbering: an index is only rendered where the content genuinely is
   a sequence (the 18-week phase roadmap, ordered modules). Decorative
   01 / 02 / 03 markers on unordered sections would be inventing information,
   so `.eyebrow__index` is used sparingly and by choice.
   ========================================================================== */

.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-sm);

  font-family: var(--font-mono);
  font-size: var(--step-micro);
  line-height: var(--step-micro-lh);
  letter-spacing: var(--step-micro-ls);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  color: var(--muted);
}

/* The one place a short gold mark is allowed outside the four rationed roles:
   a 2px rule reads as a tick on an axis, not as an accent colour. */
.eyebrow::before {
  content: '';
  inline-size: var(--space-lg);
  block-size: 2px;
  flex: none;
  background-color: var(--accent);
}

.eyebrow--plain::before {
  display: none;
}

.eyebrow__index {
  color: var(--accent-strong);
  font-variant-numeric: tabular-nums;
}

/* Hairline that fades at both ends. Used vertically as a phase rail. */
.rule-y {
  inline-size: 1px;
  background-image: linear-gradient(
    to bottom,
    transparent,
    var(--line-strong) 10%,
    var(--line-strong) 90%,
    transparent
  );
}

.rule-x {
  block-size: 1px;
  border: 0;
  background-image: linear-gradient(
    to right,
    var(--line-strong),
    var(--line-strong) 60%,
    transparent
  );
}

/* Graph-paper field. Deliberately faint: it should be felt underneath the
   content as plotting-surface texture, never seen as a "tech grid" motif.
   Masked so it dissolves rather than ending on a hard edge. */
.field-grid {
  position: absolute;
  inset: 0;
  z-index: var(--z-behind);
  pointer-events: none;

  /* Held well below the hairline token's own weight. At full strength the
     64px cells read as a table drawn over the hero; at this opacity the grid
     is felt as plotting-surface texture, which is the whole intent. */
  opacity: 0.5;

  background-image:
    linear-gradient(to right, var(--line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--line) 1px, transparent 1px);
  background-size: 64px 64px;

  -webkit-mask-image: radial-gradient(
    ellipse 90% 65% at 50% 0%,
    #000 15%,
    transparent 75%
  );
  mask-image: radial-gradient(
    ellipse 90% 65% at 50% 0%,
    #000 15%,
    transparent 75%
  );
}

/* ==========================================================================
   3. BUTTONS
   ========================================================================== */

.btn {
  --btn-pad-block: 0.75rem;
  --btn-pad-inline: 1.375rem;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);

  padding: var(--btn-pad-block) var(--btn-pad-inline);
  border-radius: var(--radius-md);
  border: 1px solid transparent;

  font-size: var(--step-small);
  font-weight: var(--weight-semi);
  line-height: 1.2;
  letter-spacing: -0.005em;
  white-space: nowrap;

  transition:
    background-color var(--dur-fast) var(--ease-ui),
    border-color var(--dur-fast) var(--ease-ui),
    color var(--dur-fast) var(--ease-ui),
    transform var(--dur-fast) var(--ease-ui),
    box-shadow var(--dur-fast) var(--ease-ui);
}

.btn:active {
  /* Sub-pixel on purpose: enough to feel the press, not enough to shift
     surrounding text. */
  transform: translateY(1px);
}

.btn--lg {
  --btn-pad-block: 0.9375rem;
  --btn-pad-inline: 1.75rem;
  font-size: var(--step-body);
}

.btn--sm {
  --btn-pad-block: 0.5rem;
  --btn-pad-inline: 0.875rem;
}

/* ---- Primary: gold. One of the four rationed uses of the accent. -------- */
.btn--primary {
  background-color: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}

.btn--primary:hover {
  background-color: var(--accent-press);
  border-color: var(--accent-press);
}

/* ---- Secondary: outlined, for the alternative path ---------------------- */
.btn--secondary {
  border-color: var(--control-line);
  color: var(--ink);
}

.btn--secondary:hover {
  border-color: var(--ink);
  background-color: var(--line);
}

/* ---- Ghost: lowest emphasis, for chrome such as Sign in ----------------- */
.btn--ghost {
  color: var(--muted);
}

.btn--ghost:hover {
  color: var(--ink);
  background-color: var(--line);
}

/* ---- Full width, for mobile CTAs and form submits ---------------------- */
.btn--block {
  width: 100%;
}

/* The arrow nudges on hover. It is marked aria-hidden in markup, so it must
   never be the only thing conveying that a control leads somewhere. */
.btn__arrow {
  flex: none;
  inline-size: 1em;
  transition: transform var(--dur-fast) var(--ease-ui);
}

.btn:hover .btn__arrow {
  transform: translateX(3px);
}

/* ---- Text link that behaves like a button in a heading row ------------- */
.link-action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);

  /* WCAG 2.2 SC 2.5.8 sets a 24x24 minimum for pointer targets. At the small
     type step this link's line box is 23px, and stacked in a list with a 12px
     gap it cannot claim the spacing exception either. */
  min-block-size: 1.5rem;

  color: var(--link);
  font-size: var(--step-small);
  font-weight: var(--weight-semi);
  transition: gap var(--dur-fast) var(--ease-ui);

  /* Deliberately NOT `white-space: nowrap`. A component has to survive whatever
     label it is given, and a long one ("See what an unwritten track looks
     like") pushed a 320px page into horizontal scroll. The label wraps inside
     its own flex item, so the arrow stays beside it rather than orphaning onto
     a line of its own. */
  max-inline-size: 100%;
}

/* The text is an anonymous flex item and needs permission to shrink below its
   max-content width before it will wrap at all. */
.link-action > svg {
  flex: none;
}

.link-action:hover {
  gap: var(--space-sm);
  text-decoration: underline;
}

/* ==========================================================================
   4. BADGES
   ========================================================================== */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);

  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);

  font-family: var(--font-mono);
  font-size: var(--step-micro);
  line-height: var(--step-micro-lh);
  letter-spacing: var(--step-micro-ls);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

/* A track with a published syllabus. */
.badge--ready {
  color: var(--positive);
  border-color: color-mix(in srgb, var(--positive) 35%, transparent);
}

/* A track that is announced but whose syllabus has not been written. The
   honest state, used everywhere instead of an invented module list. */
.badge--pending {
  color: var(--accent-strong);
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
  background-color: var(--accent-wash);
}

/* Small dot preceding the label, so status is not carried by colour alone
   once paired with its text. */
.badge__dot {
  inline-size: 5px;
  block-size: 5px;
  border-radius: var(--radius-pill);
  background-color: currentcolor;
  flex: none;
}

/* ---- Tag: a tool or technology name, not a status ---------------------- */
.tag {
  display: inline-flex;
  padding: 0.1875rem 0.5rem;
  border-radius: var(--radius-sm);
  background-color: var(--bg-sunk);
  border: 1px solid var(--line);

  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--muted);

  /* Deliberately NOT `white-space: nowrap`.

     Most tags are short tokens (XGBoost, HNSW) and will never wrap because they
     fit. But the syllabus data is transcribed verbatim, and some method entries
     are full phrases: "Feature selection: mutual information, chi-square, ANOVA
     F-test" is a single value. With nowrap that tag measured 472px, which became
     the min-content width of its grid column and pushed the whole page into
     horizontal scroll at 320px.

     Rendering has to cope with the content rather than the content being
     trimmed to suit the rendering. */
  max-inline-size: 100%;
  overflow-wrap: break-word;
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}

/* ==========================================================================
   5. CARDS AND PANELS
   ========================================================================== */

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);

  padding: var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  background-color: var(--panel);

  transition:
    border-color var(--dur-base) var(--ease-ui),
    transform var(--dur-base) var(--ease-ui),
    box-shadow var(--dur-base) var(--ease-ui);
}

/* Hover elevation. Applied only where the whole card is a link target, so a
   static card never implies interactivity it does not have. */
.card--link:hover {
  transform: translateY(-3px);
  border-color: var(--line-strong);
  box-shadow: var(--shadow-md);
}

/* Stretches a nested anchor to fill the card, so the entire surface is one
   click target with one accessible name, rather than a card full of
   overlapping links that a screen-reader user has to wade through. */
.card__hit::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/* When the card is one big hit area, the ring belongs on the card, not on the
   invisible stretched anchor. */
.card--link:has(.card__hit:focus-visible) {
  outline: 2px solid var(--ring);
  outline-offset: 3px;
}

.card__title {
  font-size: var(--step-d3);
  line-height: var(--step-d3-lh);
  letter-spacing: var(--step-d3-ls);
  font-weight: var(--weight-semi);
}

.card__text {
  color: var(--muted);
  font-size: var(--step-small);
  line-height: var(--step-small-lh);
}

/* Pushes a footer row to the bottom so cards of unequal text length still
   align their metadata. */
.card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-block-start: auto;
  padding-block-start: var(--space-md);
}

.card__icon {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  background-color: var(--bg-sunk);
  color: var(--link);
  margin-block-end: var(--space-xs);
}

/* ---- Panel: a larger container, for dashboards and diagrams ------------ */
.panel {
  border-radius: var(--radius-xl);
  border: 1px solid var(--line);
  background-color: var(--panel);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

/* Window bar, echoing the tooling the product is about. */
.panel__bar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-block-end: 1px solid var(--line);
  background-color: var(--bg-sunk);

  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--muted);
}

.panel__body {
  padding: var(--space-lg);
}

/* ==========================================================================
   6. GRIDS
   --------------------------------------------------------------------------
   `auto-fit` plus `minmax` rather than breakpoint-by-breakpoint column counts:
   the grid reflows at whatever width the content actually needs, which is why
   there are no card-grid media queries anywhere in this file.
   ========================================================================== */

.grid {
  display: grid;
  gap: var(--space-lg);
}

/* Card grids are marked up as lists, because a set of tracks is a list. The
   grid stretches the <li>, but a card inside it would still size to its own
   content, leaving cards in the same row at different heights. Making the item a
   grid container passes the stretch through to the card. */
.grid > li {
  display: grid;
}

.grid--cards {
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
}

.grid--wide-cards {
  grid-template-columns: repeat(auto-fit, minmax(min(21rem, 100%), 1fr));
}

.grid--halves {
  grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
}

/* ==========================================================================
   7. SITE HEADER
   --------------------------------------------------------------------------
   Fixed, transparent over a dark hero, and on scroll it acquires a blurred
   field plus a bottom hairline. The scrolled state is driven by
   `[data-scrolled]`, set once by an IntersectionObserver in navbar.js rather
   than by a scroll handler, so there is no per-frame work on the main thread.
   ========================================================================== */

.site-header {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: var(--z-header);

  /* The header sits above both dark and light bands, so it pins itself to the
     dark role tokens regardless of what it is floating over. */
  color: var(--p-white);

  transition:
    background-color var(--dur-base) var(--ease-ui),
    border-color var(--dur-base) var(--ease-ui),
    backdrop-filter var(--dur-base) var(--ease-ui);
  border-block-end: 1px solid transparent;
}

.site-header[data-scrolled] {
  background-color: rgb(11 15 25 / 0.72);
  border-block-end-color: rgb(255 255 255 / 0.08);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  backdrop-filter: blur(14px) saturate(160%);
}

/* Where backdrop-filter is unavailable the translucent fill would leave text
   sitting on top of scrolling content. Fall back to an opaque field. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header[data-scrolled] {
    background-color: rgb(11 15 25 / 0.97);
  }
}

/* While a menu is open the header is always opaque, whatever the scroll
   position, so the panel never bleeds into the hero behind it. */
.site-header[data-menu-open] {
  background-color: rgb(11 15 25 / 0.97);
  border-block-end-color: rgb(255 255 255 / 0.08);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  block-size: var(--header-h);

  /* The containing block for the dropdown panels. They anchor to this rather
     than to their own trigger, which is what keeps a wide panel on screen: the
     nav is right-aligned, so a 736px panel hung from the left edge of "Courses"
     runs past the viewport at every desktop width. */
  position: relative;
}

/* Inserted at the top of <body> by navbar.js and watched by an
   IntersectionObserver. Once it scrolls out of view the header gains
   `[data-scrolled]`. This is what replaces a scroll event listener: the
   observer fires twice in the life of the page instead of on every frame. */
.scroll-sentinel {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 1px;
  block-size: var(--header-h);
  pointer-events: none;
  visibility: hidden;
}

/* ---- Brand ------------------------------------------------------------- */
/* The wordmark is real selectable text and the aperture is a separate mark,
   rather than an SVG substituted for a letter. That keeps the anchor's
   accessible name and its crawlable text both spelling "LEECOI". */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  flex: none;
  margin-inline-end: auto;
}

.brand__mark {
  inline-size: 1.375rem;
  block-size: 1.375rem;
  flex: none;
  overflow: visible;
}

/* The converged dot at the centre of the mark is the same dot that terminates
   the progress curve on the homepage. The logo is the endpoint of the graph. */
.brand__mark-ring {
  fill: none;
  stroke: rgb(255 255 255 / 0.35);
  stroke-width: 1.5;
}

.brand__mark-arc {
  fill: none;
  stroke: var(--p-gold);
  stroke-width: 2.25;
  stroke-linecap: round;
}

.brand__mark-dot {
  fill: var(--p-gold);
}

.brand__word {
  font-size: 1.0625rem;
  font-weight: var(--weight-bold);
  letter-spacing: 0.14em;
  /* Optical correction: uppercase tracking adds space after the final letter
     that the eye reads as a gap before the next element. */
  margin-inline-end: -0.14em;
}

/* ---- Primary navigation ------------------------------------------------ */
.primary-nav {
  display: none;
}

.primary-nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

/* Deliberately not positioned: a positioned item would become the containing
   block for its panel and reintroduce the overflow described above. */
.primary-nav__item {
  display: flex;
}

.primary-nav__link {
  /* Positioned for its own underline pseudo-element, which belongs to the link
     rather than to the list item. */
  position: relative;

  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);

  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);

  font-size: var(--step-small);
  font-weight: var(--weight-medium);
  color: rgb(255 255 255 / 0.72);
  transition: color var(--dur-fast) var(--ease-ui);
}

.primary-nav__link:hover {
  color: var(--p-white);
}

/* Animated underline, drawn from the centre. Sits on a pseudo-element so it
   never affects the text baseline or the hit area. */
.primary-nav__link::after {
  content: '';
  position: absolute;
  inset-block-end: 0.125rem;
  inset-inline: var(--space-sm);
  block-size: 1.5px;
  border-radius: var(--radius-pill);

  background-color: var(--p-gold);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--dur-base) var(--ease-entrance);
}

.primary-nav__link:hover::after,
.primary-nav__link[aria-expanded='true']::after {
  transform: scaleX(1);
}

/* Active page. The second of the four rationed gold roles. `aria-current` does
   the work, so the state cannot be styled without also being announced. */
.primary-nav__link[aria-current='page'] {
  color: var(--p-white);
}

.primary-nav__link[aria-current='page']::after {
  transform: scaleX(1);
}

.primary-nav__chevron {
  inline-size: 0.625rem;
  flex: none;
  color: rgb(255 255 255 / 0.5);
  transition: transform var(--dur-base) var(--ease-ui);
}

.primary-nav__link[aria-expanded='true'] .primary-nav__chevron {
  transform: rotate(180deg);
}

/* ---- Header actions ---------------------------------------------------- */
.site-header__actions {
  display: none;
  align-items: center;
  gap: var(--space-xs);
  flex: none;
}

/* Chrome buttons pin to the dark palette because the header floats over both
   surface types and must not restyle itself mid-scroll. */
.site-header .btn--ghost {
  color: rgb(255 255 255 / 0.72);
}

.site-header .btn--ghost:hover {
  color: var(--p-white);
  background-color: rgb(255 255 255 / 0.08);
}

.site-header .btn--primary {
  background-color: var(--p-gold);
  border-color: var(--p-gold);
  color: var(--p-base);
}

.site-header .btn--primary:hover {
  background-color: var(--p-gold-deep);
  border-color: var(--p-gold-deep);
}

@media (min-width: 62rem) {
  .primary-nav,
  .site-header__actions {
    display: flex;
  }
}

/* ==========================================================================
   8. MEGA MENU
   --------------------------------------------------------------------------
   A disclosure, not a hover trap: it opens on click or Enter, closes on Escape
   and on outside click, and returns focus to its trigger. Hover opens it too
   on pointer devices, but hover is never the only way in.
   ========================================================================== */

.mega {
  position: absolute;
  z-index: var(--z-menu);

  /* 100% is the header row's own height, so the panel clears the header by a
     consistent 8px whichever trigger opened it. */
  inset-block-start: calc(100% + 0.5rem);

  /* Right edge aligns with the container's content edge, which is also the
     right edge of the Register button. Anchoring to one predictable edge means
     no panel can ever leave the viewport, at any width, with no JS measuring
     anything. */
  inset-inline-start: auto;
  inset-inline-end: var(--gutter);

  inline-size: min(46rem, calc(100% - 2 * var(--gutter)));
  padding: var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid rgb(255 255 255 / 0.1);
  background-color: var(--p-surface);
  box-shadow: var(--shadow-dark-lg);

  /* Closed state. `visibility` rather than `display` so the panel can
     transition, and JS adds `inert` so nothing inside is tabbable or
     announced while it is hidden. */
  visibility: hidden;
  opacity: 0;
  transform: translateY(-0.375rem);
  transition:
    opacity var(--dur-base) var(--ease-ui),
    transform var(--dur-base) var(--ease-entrance),
    visibility var(--dur-base) step-end;
}

.mega[data-open] {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition-timing-function: var(--ease-entrance), var(--ease-entrance),
    step-start;
}

.mega--narrow {
  inline-size: min(20rem, calc(100% - 2 * var(--gutter)));
}

.mega__grid {
  display: grid;
  gap: var(--space-lg);
}

@media (min-width: 48rem) {
  /* 14rem, not 13: the rail's links reserve space for their hover arrow even
     while it is transparent, and at 13rem "Roadmaps and guides" wrapped. */
  .mega__grid--split {
    grid-template-columns: 1fr 14rem;
  }
}

.mega__label {
  margin-block-end: var(--space-sm);
}

.mega__tracks {
  display: grid;
  gap: var(--space-2xs);
}

@media (min-width: 34rem) {
  .mega__tracks {
    grid-template-columns: 1fr 1fr;
  }
}

/* ---- A single track row inside the menu -------------------------------- */
.mega-item {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;

  padding: var(--space-sm);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  transition:
    background-color var(--dur-fast) var(--ease-ui),
    border-color var(--dur-fast) var(--ease-ui);
}

.mega-item:hover {
  background-color: rgb(255 255 255 / 0.05);
  border-color: rgb(255 255 255 / 0.1);
}

.mega-item__name {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--step-small);
  font-weight: var(--weight-semi);
  color: var(--p-white);
}

/* Flex, not inline: the status dot is a sized box, and `inline-size` has no
   effect on a non-replaced inline element, which left the dots rendering at
   zero pixels. */
.mega-item__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs);

  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--p-grey);
}

/* Status shown as a small dot plus text in the meta line, never colour alone. */
.mega-item__dot {
  inline-size: 5px;
  block-size: 5px;
  border-radius: var(--radius-pill);
  flex: none;
}

.mega-item__dot--ready {
  background-color: var(--p-positive);
}

.mega-item__dot--pending {
  background-color: var(--p-gold);
}

/* ---- The right-hand rail of the menu ----------------------------------- */
.mega__rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding-block-start: var(--space-md);
  border-block-start: 1px solid rgb(255 255 255 / 0.08);
}

@media (min-width: 48rem) {
  .mega__rail {
    padding-block-start: 0;
    padding-inline-start: var(--space-lg);
    border-block-start: 0;
    border-inline-start: 1px solid rgb(255 255 255 / 0.08);
  }
}

.mega__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);

  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);

  font-size: var(--step-small);
  color: rgb(255 255 255 / 0.78);
  transition:
    color var(--dur-fast) var(--ease-ui),
    background-color var(--dur-fast) var(--ease-ui);
}

.mega__link:hover {
  color: var(--p-white);
  background-color: rgb(255 255 255 / 0.05);
}

.mega__link-arrow {
  inline-size: 0.75rem;
  flex: none;
  opacity: 0;
  transform: translateX(-4px);
  transition:
    opacity var(--dur-fast) var(--ease-ui),
    transform var(--dur-fast) var(--ease-ui);
}

.mega__link:hover .mega__link-arrow {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   9. MOBILE NAVIGATION
   ========================================================================== */

.nav-toggle {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;   /* 44px: the minimum comfortable touch target */
  block-size: 2.75rem;
  flex: none;
  margin-inline-end: calc(var(--space-xs) * -1);
  border-radius: var(--radius-md);
  color: var(--p-white);
  transition: background-color var(--dur-fast) var(--ease-ui);
}

.nav-toggle:hover {
  background-color: rgb(255 255 255 / 0.08);
}

@media (min-width: 62rem) {
  .nav-toggle {
    display: none;
  }
}

/* Two bars that cross into an X. Animating a transform rather than swapping
   icons keeps the control's identity continuous through the change. */
.nav-toggle__bars {
  position: relative;
  inline-size: 1.125rem;
  block-size: 0.75rem;
}

.nav-toggle__bar {
  position: absolute;
  inset-inline: 0;
  block-size: 1.5px;
  border-radius: var(--radius-pill);
  background-color: currentcolor;
  transition:
    transform var(--dur-base) var(--ease-entrance),
    opacity var(--dur-fast) var(--ease-ui);
}

.nav-toggle__bar:nth-child(1) { inset-block-start: 0; }
.nav-toggle__bar:nth-child(2) { inset-block-start: 50%; }
.nav-toggle__bar:nth-child(3) { inset-block-end: 0; }

.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(1) {
  transform: translateY(0.375rem) rotate(45deg);
}

.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(2) {
  opacity: 0;
}

.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(3) {
  transform: translateY(-0.375rem) rotate(-45deg);
}

/* ---- Scrim ------------------------------------------------------------- */
.nav-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);

  background-color: rgb(7 10 18 / 0.6);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);

  visibility: hidden;
  opacity: 0;
  transition:
    opacity var(--dur-base) var(--ease-ui),
    visibility var(--dur-base) step-end;
}

.nav-scrim[data-open] {
  visibility: visible;
  opacity: 1;
  transition-timing-function: var(--ease-ui), step-start;
}

@media (min-width: 62rem) {
  .nav-scrim {
    display: none;
  }
}

/* ---- Drawer ------------------------------------------------------------ */
.mobile-nav {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: var(--z-drawer);

  display: flex;
  flex-direction: column;
  inline-size: min(22rem, 100%);
  padding: var(--space-lg) var(--gutter) var(--space-xl);

  background-color: var(--p-base);
  border-inline-start: 1px solid rgb(255 255 255 / 0.08);
  color: var(--p-white);
  overflow-y: auto;
  overscroll-behavior: contain;

  transform: translateX(100%);
  visibility: hidden;
  transition:
    transform var(--dur-base) var(--ease-entrance),
    visibility var(--dur-base) step-end;
}

.mobile-nav[data-open] {
  transform: none;
  visibility: visible;
  transition-timing-function: var(--ease-entrance), step-start;
}

@media (min-width: 62rem) {
  .mobile-nav {
    display: none;
  }
}

.mobile-nav__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  block-size: calc(var(--header-h) - var(--space-lg));
  margin-block-end: var(--space-lg);
}

.mobile-nav__close {
  display: grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  margin-inline-end: calc(var(--space-sm) * -1);
  border-radius: var(--radius-md);
  color: rgb(255 255 255 / 0.7);
}

.mobile-nav__close:hover {
  color: var(--p-white);
  background-color: rgb(255 255 255 / 0.08);
}

.mobile-nav__list {
  display: flex;
  flex-direction: column;
}

.mobile-nav__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);

  padding-block: var(--space-md);
  border-block-end: 1px solid rgb(255 255 255 / 0.08);

  font-size: 1.0625rem;
  font-weight: var(--weight-medium);
  color: rgb(255 255 255 / 0.85);
  inline-size: 100%;
}

.mobile-nav__link:hover {
  color: var(--p-white);
}

.mobile-nav__link[aria-current='page'] {
  color: var(--p-gold);
}

.mobile-nav__link-chevron {
  inline-size: 0.75rem;
  flex: none;
  color: rgb(255 255 255 / 0.45);
  transition: transform var(--dur-base) var(--ease-ui);
}

.mobile-nav__link[aria-expanded='true'] .mobile-nav__link-chevron {
  transform: rotate(180deg);
}

/* ---- Accordion panel inside the drawer --------------------------------- */
/* `grid-template-rows: 0fr -> 1fr` animates to intrinsic height without
   measuring anything in JS or hard-coding a max-height that will be wrong the
   moment the content changes. */
.mobile-nav__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-base) var(--ease-entrance);
}

.mobile-nav__panel[data-open] {
  grid-template-rows: 1fr;
}

.mobile-nav__panel-inner {
  overflow: hidden;
  min-height: 0;
}

.mobile-nav__sub {
  display: flex;
  flex-direction: column;
  padding-block: var(--space-xs);
  padding-inline-start: var(--space-sm);
  border-inline-start: 1px solid rgb(255 255 255 / 0.12);
  margin-block: var(--space-xs);
  margin-inline-start: var(--space-2xs);
  gap: 0.125rem;
}

.mobile-nav__sub-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: var(--step-small);
  color: rgb(255 255 255 / 0.72);
}

.mobile-nav__sub-link:hover {
  color: var(--p-white);
  background-color: rgb(255 255 255 / 0.05);
}

.mobile-nav__foot {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-block-start: var(--space-xl);
}

/* ==========================================================================
   10. TOOLBELT STRIP
   --------------------------------------------------------------------------
   The row of technology marks under the hero.

   Deliberately labelled as the tooling taught on the tracks, NOT as partners
   or sponsors. Naming it "trusted by" over a row of cloud-vendor logos would
   imply commercial relationships that do not exist.
   ========================================================================== */

.toolbelt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
  padding-block: var(--space-xl);
  border-block: 1px solid var(--line);
}

@media (min-width: 60rem) {
  .toolbelt {
    flex-direction: row;
    justify-content: space-between;
    gap: var(--space-2xl);
  }
}

.toolbelt__label {
  flex: none;
  text-align: center;
}

.toolbelt__items {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg) var(--space-2xl);
}

.toolbelt__item {
  display: flex;
  align-items: center;
  gap: var(--space-xs);

  font-size: var(--step-small);
  font-weight: var(--weight-medium);
  color: var(--muted);
  transition:
    color var(--dur-base) var(--ease-ui),
    opacity var(--dur-base) var(--ease-ui);
}

.toolbelt__item:hover {
  color: var(--ink);
}

.toolbelt__item svg {
  inline-size: 1.125rem;
  block-size: 1.125rem;
  flex: none;
}

/* ==========================================================================
   11. FORM FIELDS
   ========================================================================== */

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

.field__label {
  font-size: var(--step-small);
  font-weight: var(--weight-medium);
}

/* "Optional" rather than marking required fields with an asterisk: most
   fields are required, so the exception is the useful thing to label. */
.field__optional {
  color: var(--muted);
  font-weight: var(--weight-normal);
}

.field__input {
  inline-size: 100%;
  padding: 0.6875rem var(--space-md);
  border-radius: var(--radius-md);
  /* Control edges use --control-line, measured at 3:1 minimum, rather than
     the decorative hairline token. */
  border: 1px solid var(--control-line);
  background-color: var(--bg-sunk);
  color: var(--ink);
  transition:
    border-color var(--dur-fast) var(--ease-ui),
    background-color var(--dur-fast) var(--ease-ui);
}

.field__input::placeholder {
  color: var(--muted);
  opacity: 1;
}

.field__input:hover {
  border-color: var(--ink);
}

.field__input:focus-visible {
  border-color: var(--ring);
}

/* Error state is carried by the border, an icon-free message, and
   aria-invalid, so it is never colour alone. */
.field__input[aria-invalid='true'] {
  border-color: var(--critical);
}

.field__hint {
  font-size: var(--step-small);
  color: var(--muted);
}

.field__error {
  font-size: var(--step-small);
  color: var(--critical);
  font-weight: var(--weight-medium);
}

/* Off-screen bot trap. Not `display: none`, which some bots detect and skip;
   and aria-hidden plus tabindex keep it away from real assistive tech. */
.field--honeypot {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ==========================================================================
   12. SITE FOOTER
   ========================================================================== */

.site-footer {
  padding-block-start: var(--space-4xl);
  border-block-start: 1px solid var(--line);
}

.site-footer__top {
  display: grid;
  gap: var(--space-2xl);
  padding-block-end: var(--space-2xl);
}

@media (min-width: 48rem) {
  .site-footer__top {
    grid-template-columns: 1fr 1fr;
  }
}

@media (min-width: 68rem) {
  .site-footer__top {
    /* Brand column is wider than the link columns; the link columns share the
       remainder equally. */
    grid-template-columns: 18rem repeat(4, 1fr);
    gap: var(--space-xl);
  }
}

.site-footer__brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-md);
}

.site-footer__tagline {
  color: var(--muted);
  font-size: var(--step-small);
  max-width: 22rem;
}

.site-footer__col-title {
  font-family: var(--font-mono);
  font-size: var(--step-micro);
  line-height: var(--step-micro-lh);
  letter-spacing: var(--step-micro-ls);
  text-transform: uppercase;
  color: var(--ink);
  margin-block-end: var(--space-md);
}

.site-footer__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.site-footer__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);

  /* 24px minimum target height, per WCAG 2.2 SC 2.5.8. The small type step
     gives a 23px line box on its own. */
  min-block-size: 1.5rem;

  font-size: var(--step-small);
  color: var(--muted);
  transition: color var(--dur-fast) var(--ease-ui);
  /* Anchor the hit area to the text, so the row is not one wide invisible
     target spanning the whole column. */
  align-self: flex-start;
}

.site-footer__link:hover {
  color: var(--ink);
  text-decoration: underline;
}

.site-footer__social {
  display: flex;
  gap: var(--space-xs);
}

.site-footer__social-link {
  display: grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  color: var(--muted);
  transition:
    color var(--dur-fast) var(--ease-ui),
    border-color var(--dur-fast) var(--ease-ui);
}

.site-footer__social-link:hover {
  color: var(--ink);
  border-color: var(--line-strong);
}

.site-footer__social-link svg {
  inline-size: 1rem;
  block-size: 1rem;
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);

  padding-block: var(--space-lg);
  border-block-start: 1px solid var(--line);
}

.site-footer__fine {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--muted);
}

.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
}

/* ==========================================================================
   13. SLIDER
   --------------------------------------------------------------------------
   A real scroll container with scroll-snap, not a transform carousel. Native
   scrolling gives touch momentum, keyboard scrolling, and find-in-page for
   free; slider.js only drives the same scroll a finger would.
   ========================================================================== */

.slider {
  position: relative;
}

.slider__track {
  display: flex;
  gap: var(--space-lg);

  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* Stops a flick from scrolling the page behind the slider once the track
     reaches its end. */
  overscroll-behavior-x: contain;

  /* The scrollbar is redundant next to the buttons and the dots, and on
     desktop it sits awkwardly under the cards. Hidden visually only: the
     container is still a scroll container in every other respect. */
  scrollbar-width: none;
  -ms-overflow-style: none;

  /* Room for the hover lift and the focus ring, which would otherwise be
     clipped by the scroll container's own bounds. */
  padding-block: var(--space-2xs) var(--space-sm);
  padding-inline: 2px;
  margin-inline: -2px;
}

.slider__track::-webkit-scrollbar {
  display: none;
}

.slider__slide {
  flex: 0 0 auto;
  scroll-snap-align: start;
  inline-size: min(20rem, 82vw);
}

.slider__slide > * {
  block-size: 100%;
}

/* ---- Controls ---------------------------------------------------------- */
.slider__controls {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.slider__button {
  display: grid;
  place-items: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--control-line);
  color: var(--ink);
  transition:
    border-color var(--dur-fast) var(--ease-ui),
    background-color var(--dur-fast) var(--ease-ui),
    opacity var(--dur-fast) var(--ease-ui);
}

.slider__button:hover:not(:disabled) {
  background-color: var(--line);
  border-color: var(--ink);
}

.slider__button svg {
  inline-size: 1rem;
  block-size: 1rem;
}

.slider__dots {
  display: flex;
  gap: 0;
}

/* 24px control around an 8px mark, so the target is usable without the dot
   itself becoming a blob. */
.slider__dot {
  display: grid;
  place-items: center;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  border-radius: var(--radius-sm);
}

.slider__dot-mark {
  inline-size: 0.5rem;
  block-size: 0.5rem;
  border-radius: var(--radius-pill);
  background-color: var(--line-strong);
  transition:
    background-color var(--dur-fast) var(--ease-ui),
    transform var(--dur-fast) var(--ease-ui);
}

.slider__dot[data-active] .slider__dot-mark {
  background-color: var(--accent-strong);
  transform: scale(1.25);
}

/* When every slide already fits, the controls are not disabled chrome sitting
   there looking broken. They are removed.

   Attribute-only selector rather than `.slider[data-slider-static]`, because the
   root that carries the attribute is not always the element carrying the visual
   `.slider` class. */
[data-slider-static] .slider__controls {
  display: none;
}

/* ==========================================================================
   14. PAGE HERO
   --------------------------------------------------------------------------
   The compact counterpart to the homepage hero: same field treatment, no
   signature chart, no facts row. Every interior page uses it, which is why it
   lives here rather than in a page stylesheet.
   ========================================================================== */

.page-hero {
  position: relative;
  overflow: hidden;
  isolation: isolate;

  padding-block-start: calc(var(--header-h) + clamp(2.5rem, 6vh, 4rem));
  padding-block-end: clamp(2.5rem, 6vh, 4rem);
}

.page-hero__field {
  position: absolute;
  inset: 0;
  z-index: var(--z-behind);
  pointer-events: none;
}

.page-hero__inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-md);
}

.page-hero__title {
  font-size: var(--step-d1);
  line-height: var(--step-d1-lh);
  letter-spacing: var(--step-d1-ls);
  font-weight: var(--weight-bold);
  max-width: 18ch;
}

.page-hero__lead {
  font-size: var(--step-lead);
  line-height: var(--step-lead-lh);
  letter-spacing: var(--step-lead-ls);
  color: var(--muted);
  max-width: 58ch;
}

.page-hero__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

.page-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-block-start: var(--space-md);
}

@media (max-width: 26rem) {
  .page-hero__actions {
    inline-size: 100%;
  }

  .page-hero__actions .btn {
    flex: 1 1 100%;
  }
}

/* ==========================================================================
   15. BREADCRUMBS
   --------------------------------------------------------------------------
   An <ol>, because the trail is an ordered path. The separator is a pseudo-
   element rather than a character in the markup, so a screen reader reads
   "Home, Courses, AI Engineer" and not a string of slashes.
   ========================================================================== */

.crumbs {
  margin-block-end: var(--space-xs);
}

.crumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);

  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--muted);
}

.crumbs__item {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.crumbs__item + .crumbs__item::before {
  content: '/';
  color: var(--line-strong);
}

.crumbs__link {
  /* 24px minimum target, per WCAG 2.2 SC 2.5.8. */
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  transition: color var(--dur-fast) var(--ease-ui);
}

.crumbs__link:hover {
  color: var(--ink);
  text-decoration: underline;
}

.crumbs__item[aria-current='page'] {
  color: var(--ink);
}

/* ==========================================================================
   16. CLOSING CALL TO ACTION
   --------------------------------------------------------------------------
   The same closing band on the homepage, the catalogue and every track page.
   Started life in home.css; promoted here the moment a second page wanted it,
   rather than being copied into a second page stylesheet.
   ========================================================================== */

.closing {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.closing__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
  text-align: center;
  max-width: 44rem;
  margin-inline: auto;
}

.closing__title {
  font-size: var(--step-d2);
  line-height: var(--step-d2-lh);
  letter-spacing: var(--step-d2-ls);
  font-weight: var(--weight-bold);
}

.closing__lead {
  font-size: var(--step-lead);
  line-height: var(--step-lead-lh);
  letter-spacing: var(--step-lead-ls);
  color: var(--muted);
}

.closing__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm);
  margin-block-start: var(--space-xs);
}

@media (max-width: 26rem) {
  .closing__actions {
    inline-size: 100%;
  }

  .closing__actions .btn {
    flex: 1 1 100%;
  }
}

.closing__note {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.75rem;
}

/* ==========================================================================
   17. QUESTIONS
   --------------------------------------------------------------------------
   Native <details> and <summary>. Keyboard operable, announced correctly, and
   fully functional with scripting disabled, none of which a hand-built
   accordion manages without work.

   Started in home.css; promoted here when /faq/ wanted the same list.
   ========================================================================== */

.qa {
  display: flex;
  flex-direction: column;
  border-block-start: 1px solid var(--line);
}

.qa__item {
  border-block-end: 1px solid var(--line);
}

.qa__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);

  padding-block: var(--space-lg);
  cursor: pointer;

  font-size: var(--step-body);
  font-weight: var(--weight-semi);
  color: var(--ink);
  transition: color var(--dur-fast) var(--ease-ui);

  /* Removes the default disclosure triangle in every engine, since the chevron
     replaces it. */
  list-style: none;
}

.qa__summary::-webkit-details-marker {
  display: none;
}

.qa__summary:hover {
  color: var(--accent-strong);
}

.qa__summary:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.qa__icon {
  flex: none;
  color: var(--muted);
  transition: transform var(--dur-base) var(--ease-ui);
}

.qa__item[open] .qa__icon {
  transform: rotate(180deg);
}

.qa__body {
  padding-block-end: var(--space-lg);
  max-width: var(--width-prose);
  color: var(--muted);
  font-size: var(--step-small);
  line-height: 1.7;
}

/* Paragraphs inside an answer, where one is not enough. */
.qa__body > p + p {
  margin-block-start: var(--space-sm);
}

.qa__body a {
  color: var(--link);
  text-decoration: underline;
}

/* ==========================================================================
   18. NOTICE
   --------------------------------------------------------------------------
   Used for the honest states this site relies on: a track whose syllabus is
   not written, the student portal that is not open yet, a form that failed to
   send. Never a decorative callout.
   ========================================================================== */

.notice {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);

  padding: var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid var(--line-strong);
  background-color: var(--panel);
}

/* A 2px inline edge in the accent, which reads as a margin annotation. */
.notice--flagged {
  border-inline-start: 2px solid var(--accent);
}

.notice--error {
  border-inline-start: 2px solid var(--critical);
}

.notice__title {
  font-size: var(--step-body);
  font-weight: var(--weight-semi);
}

.notice__text {
  color: var(--muted);
  font-size: var(--step-small);
}
