/* =========================================================================
   Silicon From Scratch — main.css
   Phase 1: static foundation. Organized in sections so later phases
   (animations, extra components) can be added cleanly.

   CONTENTS
   1. Design tokens (CSS variables)
   2. Reset & base
   3. Typography
   4. Page background (engineering grid texture)
   5. Layout helpers (container, section, kicker)
   6. Buttons
   7. Top bar / nav
   8. Hero
   9. The idea
   10. Epigraph
   11. Learn ladder (circuit-node path)
   12. What you'll find
   13. Tools
   14. About
   15. Footer
   16. Motion (load fade-up) + prefers-reduced-motion
   17. Responsive
   ========================================================================= */

/* ------------------------------------------------------------------ *
   1. DESIGN TOKENS
 * ------------------------------------------------------------------ */
:root {
  /* Color */
  --bg:          #F7F8F3;   /* page background — matches the logo */
  --paper:       #FCFCF9;   /* cards / raised surfaces */
  --ink:         #23232A;   /* primary text */
  --soft-ink:    #56565F;   /* secondary text */
  --accent:      #6B2FC9;   /* purple, from the logo */
  --accent-deep: #4F1F9E;   /* hover */
  --hairline:    rgba(35, 35, 42, 0.12);
  --grid-line:   rgba(35, 35, 42, 0.06);

  /* Type families */
  --font-display: "Bricolage Grotesque", system-ui, sans-serif;
  --font-body:    "Newsreader", Georgia, serif;
  --font-mono:    "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;

  /* Spacing scale */
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2rem;
  --space-5: 3rem;
  --space-6: 5rem;
  --space-7: 8rem;

  /* Layout */
  --maxw:        88rem;   /* outer content width — wide, uses more of the screen */
  --maxw-prose:  44rem;   /* comfortable measure for body prose */
  --radius:      14px;
  --radius-sm:   10px;

  /* Effects */
  --shadow-soft: 0 1px 2px rgba(35, 35, 42, 0.04),
                 0 8px 24px rgba(35, 35, 42, 0.06);
  --shadow-lift: 0 2px 6px rgba(35, 35, 42, 0.06),
                 0 16px 40px rgba(35, 35, 42, 0.10);

  /* Motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ------------------------------------------------------------------ *
   2. RESET & BASE
 * ------------------------------------------------------------------ */
*,
*::before,
*::after { box-sizing: border-box; }

/* Fluid root font-size — the whole design is sized in rem (spacing, container
   widths, type), so scaling this one value scales the ENTIRE layout with the
   viewport: the same layout everywhere, just resized. It shrinks toward a
   readable floor on phones. NOTE: the ceiling is capped at 16px (the normal
   baseline) for now — we're deliberately NOT enlarging on big screens yet; raise
   this max later to turn large-screen scaling back on. See STYLE_GUIDE.md →
   "Responsive strategy". */
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  font-size: clamp(12px, 6.5px + 0.66vw, 16px);
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.25rem;            /* ~20px */
  line-height: 1.65;
  color: var(--ink);
  background-color: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-deep); }

/* Visible, on-brand focus ring for keyboard users */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Skip link for keyboard / screen-reader users */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: #fff;
  padding: 0.6rem 1rem;
  border-radius: 0 0 var(--radius-sm) 0;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  z-index: 100;
}
.skip-link:focus {
  left: 0;
  color: #fff;
}

/* ------------------------------------------------------------------ *
   3. TYPOGRAPHY
 * ------------------------------------------------------------------ */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0 0 var(--space-3);
}

h1 { font-size: clamp(2.4rem, 6vw, 4rem); }
h2 { font-size: clamp(1.9rem, 4vw, 2.8rem); }
h3 { font-size: clamp(1.15rem, 2vw, 1.4rem); letter-spacing: -0.01em; }

p { margin: 0 0 var(--space-3); }

/* Kicker: mono, uppercase, letter-spaced label above section headings */
.kicker {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--accent);
  margin: 0 0 var(--space-2);
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
/* Small tick mark before kicker text, echoing a circuit lead */
.kicker::before {
  content: "";
  width: 1.5rem;
  height: 1px;
  background: var(--accent);
  display: inline-block;
}

/* Standard section header: the "Your Path" treatment, made reusable. Apply
   alongside .kicker (so it keeps the mono uppercase + purple tick), bumping the
   size up and the text to soft-ink. This is THE section-header font style. */
.section-title {
  font-size: clamp(1.15rem, 1.6vw, 1.35rem);
  color: var(--soft-ink);
}

/* Superscripts and subscripts must not stretch the line they sit on. Left at
   their defaults, a line carrying e- or h+ gets extra leading and the
   paragraph's rhythm breaks against every other paragraph on the page. */
sup,
sub {
  font-size: 0.72em;
  line-height: 0;                /* the part that keeps the line box unchanged */
  position: relative;
  vertical-align: baseline;
}
sup { top: -0.45em; }
sub { bottom: -0.25em; }

/* Inline code / tool names */
code {
  font-family: var(--font-mono);
  font-size: 0.86em;
  background: rgba(107, 47, 201, 0.07);
  color: var(--accent-deep);
  padding: 0.12em 0.4em;
  border-radius: 5px;
  white-space: nowrap;
}

/* ------------------------------------------------------------------ *
   4. PAGE BACKGROUND — faint engineering-grid texture
 * ------------------------------------------------------------------ */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(to right,  var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: 48px 48px;
}

/* ------------------------------------------------------------------ *
   5. LAYOUT HELPERS
 * ------------------------------------------------------------------ */
.container {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--space-3);
}

.section {
  padding-block: var(--space-7);
  border-top: 1px solid var(--hairline);
}

.section__head { max-width: var(--maxw-prose); margin-bottom: var(--space-5); }
.section__note {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--soft-ink);
  margin-top: calc(var(--space-2) * -1 + 0.25rem);
}

.prose { max-width: var(--maxw-prose); }
.prose p { color: var(--soft-ink); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* ------------------------------------------------------------------ *
   6. BUTTONS
 * ------------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.03em;
  line-height: 1;
  padding: 0.9rem 1.4rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.18s var(--ease),
              box-shadow 0.18s var(--ease),
              background-color 0.18s var(--ease),
              border-color 0.18s var(--ease),
              color 0.18s var(--ease);
}

.btn--primary {
  background: var(--accent);
  color: #fff;
}
.btn--primary:hover {
  background: var(--accent-deep);
  color: #fff;
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
}

.btn--ghost {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--hairline);
}
.btn--ghost:hover {
  color: var(--accent-deep);
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: var(--shadow-soft);
}

/* ------------------------------------------------------------------ *
   7. TOP BAR / NAV
 * ------------------------------------------------------------------ */
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* Above the drawer (60) + backdrop (55) so the hamburger stays visible while
     the menu is open — it flips to an X and doubles as a tap-to-close. */
  z-index: 70;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.2) blur(8px);
  border-bottom: 1px solid var(--hairline);
  /* Auto-hiding bar (main.js): slides up out of view until summoned. */
  transition: transform 0.28s ease;
  will-change: transform;
}
.topbar.is-hidden {
  transform: translateY(-100%);
}
/* Three-column bar: wordmark hard left, nav centered in the viewport, the index +
   toggle group hard right. Spans the full width (overriding .container's max-width)
   so the wordmark hugs the left edge rather than the centered content column. */
.topbar__inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-3);
  height: 4rem;
  max-width: none;
  padding-inline: clamp(var(--space-3), 4vw, var(--space-5));
}
.topbar__inner .wordmark { justify-self: start; }
.topbar__inner .nav { justify-self: center; }
.topbar__inner .topbar__right { justify-self: end; }

.wordmark {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
}
.wordmark:hover { color: var(--ink); }
.wordmark__dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
/* Symbol mark stands in for the dot in the top bar. */
.wordmark__mark {
  width: 26px; height: 26px;
  object-fit: contain;
  flex: none;
}

.nav {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2.2rem);
}
.nav a {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--soft-ink);
  position: relative;
  padding: 0.25rem 0;
}
.nav a:hover { color: var(--accent-deep); }
/* Animated hairline underline on hover/focus */
.nav a::after {
  content: "";
  position: absolute;
  left: 0; bottom: -2px;
  width: 100%; height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.2s var(--ease);
}
.nav a:hover::after,
.nav a:focus-visible::after { transform: scaleX(1); }

/* GitHub mark, hard right in the top bar. Shown at every width. */
.ghlink {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  color: var(--soft-ink);
  transition: color 0.18s var(--ease);
}
.ghlink svg {
  width: 1.35rem;
  height: 1.35rem;
  fill: currentColor;
}
.ghlink:hover { color: var(--ink); }

/* Mobile menu toggle — hidden on desktop */
.nav-toggle {
  display: none;
  width: 2.6rem; height: 2.6rem;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  content: "";
  display: block;
  width: 18px; height: 2px;
  background: var(--ink);
  border-radius: 2px;
  transition: transform 0.2s var(--ease), opacity 0.2s var(--ease);
}
.nav-toggle__bars::before { transform: translateY(-6px); }
.nav-toggle__bars::after  { transform: translateY(4px); }

/* "Help" placeholder — looks like a nav item but isn't a link yet */
.nav__pending {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--soft-ink);
  opacity: 0.5;
  cursor: default;
  padding: 0.25rem 0;
}

/* Right-hand group in the bar: nav + section index + mobile toggle */
.topbar__right {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2.2rem);
}

/* ------------------------------------------------------------------ *
   PROJECT MENU DRAWER. `.toc` is just the empty host the panel starts in
   before JS moves it to <body>; the drawer is opened by the top bar's
   "Project Directory" nav item on desktop and the hamburger on mobile.
 * ------------------------------------------------------------------ */
.toc { display: none; }
/* Panel that slides in from the edge of the screen. It's moved to <body> in JS
   so its height is measured against the viewport, not the transformed top bar.
   Full height on mobile; on desktop it stops level with the band (see below). */
.toc__menu {
  position: fixed;
  top: 0;
  right: 0;
  height: 100vh;
  height: 100dvh;
  width: min(360px, 86vw);
  overflow: auto;
  margin: 0;
  /* clear the top bar at the top of the panel */
  padding: calc(4rem + var(--space-3)) var(--space-4) var(--space-4);
  list-style: none;
  background: var(--bg);
  border-left: 1px solid var(--hairline);
  box-shadow: var(--shadow-lift);
  visibility: hidden;
  transform: translateX(100%);
  transition: transform 0.34s var(--ease), visibility 0.34s var(--ease);
  z-index: 60;
}
.toc__menu.is-open {
  visibility: visible;
  transform: translateX(0);
}
/* Dim the page behind the open drawer. */
.toc-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.28s var(--ease), visibility 0.28s var(--ease);
  z-index: 55;
}
.toc-backdrop.is-open { opacity: 1; visibility: visible; }
.toc__menu li { margin: 0; }
.toc__menu a {
  display: block;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  line-height: 1.3;
  color: var(--soft-ink);
}
.toc__menu li:last-child a { border-bottom: 0; }
.toc__menu a:hover,
.toc__menu a:focus-visible { color: var(--accent-deep); }
.toc__empty {
  padding: 0.75rem 0;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--soft-ink);
}

/* ---- Project menu inside the drawer ---------------------------------------
   Three levels stepping from the drawer's own dark tone, through a gray, to a
   white leaf. Rows go edge-to-edge, so the panel drops its side padding. */
.toc__menu--proj {
  padding: calc(4rem + var(--space-2)) 0 var(--space-3);
}

.toc__menu .menu__sub { list-style: none; margin: 0; padding: 0; }

/* The toggle row: label on the left, flipping chevron on the right. */
.toc__menu .menu__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  border: 0;
  cursor: pointer;
  text-align: left;
  font-family: var(--font-display);
  letter-spacing: 0;
  transition: background-color 0.15s var(--ease), color 0.15s var(--ease);
}
.toc__menu .menu__label { display: block; }

/* The chevron: two strokes meeting at a point; flips up when its branch opens. */
.toc__menu .menu__chev {
  width: 0.95em;
  height: 0.95em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.25s var(--ease);
}
.toc__menu .menu__item.is-open > .menu__toggle .menu__chev { transform: rotate(180deg); }

/* Level 1 — difficulty: flat quick-link style (font-display, soft-gray text on
   the drawer's dark bg, hairline divider) so the outermost level matches the
   top-bar links. The inner two levels keep the stepped gray -> white treatment. */
.toc__menu .menu__item--l1 > .menu__toggle {
  background: transparent;
  color: var(--soft-ink);
  font-size: 0.95rem;
  font-weight: 400;
  padding: 0.7rem var(--space-4);
  border-bottom: 1px solid var(--hairline);
}
.toc__menu .menu__item--l1 > .menu__toggle:hover { color: var(--accent-deep); }

/* Level 2 — project: a gray step toward the white leaf. */
.toc__menu .menu__item--l2 > .menu__toggle {
  background: #4a4a55;
  color: #f3f3f6;
  font-size: 0.92rem;
  padding: 0.68rem var(--space-4) 0.68rem calc(var(--space-4) + 0.9rem);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.toc__menu .menu__item--l2:first-child > .menu__toggle { border-top: 0; }
.toc__menu .menu__item--l2 > .menu__toggle:hover { background: #565662; }

/* Level 3 — the project's contents: white, with the dark-purple hover. */
.toc__menu .menu__leaf > a {
  display: block;
  background: #fff;
  color: #23232A;
  font-family: var(--font-display);
  font-size: 0.9rem;
  letter-spacing: 0;
  line-height: 1.3;
  padding: 0.6rem var(--space-4) 0.6rem calc(var(--space-4) + 1.8rem);
  border: 0;
  border-top: 1px solid rgba(35, 35, 42, 0.08);
}
.toc__menu .menu__leaf:first-child > a { border-top: 0; }
.toc__menu .menu__leaf > a:hover,
.toc__menu .menu__leaf > a:focus-visible { background: #4f1f9e; color: #fff; }

/* Collapsible panel: animate the grid row 0fr -> 1fr so it slides open without
   needing a fixed height; the single child clips its overflow. */
.toc__menu .menu__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.28s var(--ease);
}
.toc__menu .menu__item.is-open > .menu__panel { grid-template-rows: 1fr; }
.toc__menu .menu__panel > .menu__sub { overflow: hidden; min-height: 0; }

/* ---- Desktop flyout sheets ------------------------------------------------
   On wide screens the drawer stays a short list of difficulties; pressing one
   slides a white sheet out to its left, over the page, holding that
   difficulty's projects (bold headings) and their lessons. It is a horizontal
   band: it runs from the drawer's left edge across the rest of the viewport,
   only as tall as its contents, with the projects sitting side by side. The
   container's right edge is pinned to the drawer's left edge, so a sheet at
   translateX(100%) rests hidden underneath the drawer and travels leftward as
   it opens. */
.navsheets {
  position: fixed;
  top: 0;
  right: min(360px, 86vw);   /* == the drawer's width */
  left: 0;
  max-height: 100dvh;
  z-index: 58;               /* under the drawer (60), over the backdrop (55) */
  pointer-events: none;
  /* Every sheet is stacked in one grid cell, so the cell (and therefore each
     sheet) takes the height of the tallest one. All four then open to the same
     height instead of the band jumping as you move between difficulties. */
  display: grid;
}
.navsheet {
  grid-area: 1 / 1;
  max-height: 100dvh;
  overflow: auto;
  padding: calc(4rem + var(--space-3)) var(--space-5) var(--space-5);
  background: #fff;
  box-shadow: 0 12px 30px rgba(35, 35, 42, 0.22);
  /* The band unrolls leftward out from under the drawer: the clip starts fully
     inset from the left (nothing showing) and opens to the full width. The
     negative bottom/right insets let the drop shadow escape the clip. */
  visibility: hidden;
  clip-path: inset(0 -40px -40px 100%);
  transition: clip-path 0.36s var(--ease), visibility 0.36s var(--ease);
  pointer-events: auto;
}
.navsheet.is-open {
  visibility: visible;
  clip-path: inset(0 -40px -40px 0);
}

/* Swapping difficulties while the band is already out: the band itself doesn't
   move (no wipe either way) and only its contents animate in. */
.navsheet.is-swap { transition: none; }
.navsheet.is-swap.is-open .navsheet__group {
  animation: navsheet-swap 0.3s var(--ease) both;
}
/* Each column follows the one before it, so the change reads left to right. */
.navsheet.is-swap.is-open .navsheet__group:nth-child(2) { animation-delay: 0.04s; }
.navsheet.is-swap.is-open .navsheet__group:nth-child(3) { animation-delay: 0.08s; }
.navsheet.is-swap.is-open .navsheet__group:nth-child(4) { animation-delay: 0.12s; }
@keyframes navsheet-swap {
  from { opacity: 0; transform: translateY(0.4rem); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .navsheet.is-swap.is-open .navsheet__group { animation: none; }
}

/* Projects sit side by side across the band, each its own column of lessons.
   Columns size to their contents so no lesson name has to wrap. */
.navsheet__cols {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
}
.navsheet__group { flex: 0 0 auto; }

/* Project name: bold black. */
.navsheet__title {
  margin: 0 0 0.4rem;
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: #000;
  white-space: nowrap;
}

/* Lessons: plain black, with the drawer's purple as the hover highlight. */
.navsheet__links { list-style: none; margin: 0; padding: 0; }
.navsheet__links li { margin: 0; }
.navsheet__links a {
  display: block;
  padding: 0.42rem 0.55rem;
  margin-left: -0.55rem;
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-size: 0.9rem;
  font-weight: 400;
  line-height: 1.35;
  color: #000;
  white-space: nowrap;   /* every lesson name stays on one line */
  transition: background-color 0.15s var(--ease), color 0.15s var(--ease);
}
.navsheet__links a:hover,
.navsheet__links a:focus-visible {
  background: #4f1f9e;   /* same purple the drawer's white leaves use */
  color: #fff;
}

/* The difficulty rows themselves: taller than a plain drawer row, so the four
   of them carry the panel. Scoped to .menu__cat, leaving the mobile drawer's
   level-1 rows at their own height. */
.toc__menu .menu__cat > .menu__toggle {
  position: relative;
  padding-top: 1.15rem;
  padding-bottom: 1.15rem;
  font-size: 1rem;
}
.toc__menu .menu__cat.is-active > .menu__toggle {
  color: #fff;
  background: rgba(107, 47, 201, 0.22);
}
.toc__menu .menu__cat.is-active > .menu__toggle::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--accent);
}
/* This chevron points left, toward the sheet it pulls out. */
.toc__menu .menu__chev--flyout { transform: rotate(90deg); }

/* ---- "Project Directory" nav item + the mirrored (left-hand) drawer --------
   It sits in the nav row where "Home" used to and opens this same menu docked
   to the LEFT edge, with its sheets unrolling left-to-right. Everything below
   is the mirror of the right-hand arrangement above. */

/* A <button>, but indistinguishable from its sibling links: same type, color,
   padding, and sliding hairline underline as `.nav a`. */
.projtab {
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--soft-ink);
  position: relative;
  padding: 0.25rem 0;
}
.projtab:hover { color: var(--accent-deep); }
.projtab::after {
  content: "";
  position: absolute;
  left: 0; bottom: -2px;
  width: 100%; height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.2s var(--ease);
}
.projtab:hover::after,
.projtab:focus-visible::after,
/* The underline stays drawn while its menu is out. */
.projtab[aria-expanded="true"]::after { transform: scaleX(1); }
.projtab[aria-expanded="true"] { color: var(--accent-deep); }

/* Drawer mirrored to the left edge. It doesn't slide in from that edge, though:
   the desktop menu drops straight down out from behind the top bar, drawer and
   band moving as one piece. */
.toc__menu.is-left {
  right: auto;
  left: 0;
  border-left: 0;
  border-right: 1px solid var(--hairline);
  transform: translateY(-100%);
}
.toc__menu.is-left.is-open { transform: translateY(0); }
/* Its chevrons point right, toward the sheets that now open that way. */
.toc__menu.is-left .menu__chev--flyout { transform: rotate(-90deg); }
/* The active row's purple bar moves to the edge facing its sheet. */
.toc__menu.is-left .menu__cat.is-active > .menu__toggle::before {
  left: auto;
  right: 0;
}

/* Sheets mirrored: pinned to the drawer's right edge. They drop down in step
   with the drawer rather than wiping sideways, so the two read as one panel
   coming out from behind the bar. */
.navsheets.is-left { right: 0; left: min(360px, 86vw); }  /* == drawer width */
.navsheets.is-left .navsheet {
  box-shadow: 0 12px 30px rgba(35, 35, 42, 0.22);
  clip-path: none;
  transform: translateY(-100%);
  transition: transform 0.34s var(--ease), visibility 0.34s var(--ease);
}
.navsheets.is-left .navsheet.is-open { transform: translateY(0); }
/* Swapping difficulties is the exception: the band holds still and only its
   columns animate. Repeated here because the rule above out-specifies the
   plain .navsheet.is-swap one and would otherwise re-drop the band each time. */
.navsheets.is-left .navsheet.is-swap { transition: none; }

/* The drawer ends level with the bottom of the band instead of running to the
   foot of the screen. --navsheet-h is the band's measured height, published by
   main.js; it falls back to full height if that measurement never ran. */
@media (min-width: 769px) {
  .toc__menu {
    height: var(--navsheet-h, 100dvh);
    border-bottom: 1px solid var(--hairline);
    /* The four rows share whatever height is left below the top bar, in equal
       parts, so they fill the drawer exactly — snug top to bottom, never a
       scrollbar, whatever the band's height works out to. */
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* First row starts flush against the bar's bottom hairline, last row runs
       to the drawer's bottom edge — no dark gap at either end. */
    padding-top: calc(4rem + 1px);
    padding-bottom: 0;
  }
  .toc__menu .menu__cat { display: flex; flex: 1 1 0; }
  .toc__menu .menu__cat > .menu__toggle {
    flex: 1;
    padding-top: 0;
    padding-bottom: 0;
  }
}

/* Sheets and the tab are a desktop arrangement only. */
@media (max-width: 768px) {
  .navsheets { display: none; }
  .projtab { display: none; }
}

/* ---- Mobile drawer extras -------------------------------------------------
   On narrow screens the top-bar links collapse into this drawer. Two things
   only appear there: the quick links (Home / Meet the Processor / GitHub /
   About / Tools) and a collapsible "Project Directory" that nests the tree one
   level deeper. Both are hidden on desktop, where the ☰ drawer stays flat and
   the links ride in the top bar. */

/* Quick links: mirror the flat level-1 rows so they sit uniformly above the
   "Project Directory" toggle, in the drawer's display font. */
.toc__menu .menu__quick > a {
  display: block;
  width: 100%;
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--soft-ink);
  padding: 0.7rem var(--space-4);
  border-bottom: 1px solid var(--hairline);
}
.toc__menu .menu__quick > a:hover,
.toc__menu .menu__quick > a:focus-visible { color: var(--accent-deep); }

/* Level 3 — the mobile-only project level. With the extra "Project Directory"
   parent, two grays step down to the white leaf: the difficulty parent (l2)
   darker, this project child (l3) lighter. */
.toc__menu .menu__item--l3 > .menu__toggle {
  background: #70707d;
  color: #f6f6f8;
  font-size: 0.9rem;
  padding: 0.66rem var(--space-4) 0.66rem calc(var(--space-4) + 1.8rem);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.toc__menu .menu__item--l3 > .menu__toggle:hover { background: #7c7c89; }

/* White leaves sit one level deeper in the mobile tree, so indent them a step
   further than the desktop drawer's leaves. */
.toc__menu .is-mobile-only .menu__leaf > a {
  padding-left: calc(var(--space-4) + 2.7rem);
}

/* Reveal the matching arrangement per width. */
@media (min-width: 769px) {
  .toc__menu .is-mobile-only { display: none; }
}
@media (max-width: 768px) {
  .toc__menu .is-desktop-only { display: none; }
}

/* anchored sections clear the (overlay) top bar when jumped to */
main section[id] { scroll-margin-top: 5rem; }

/* ------------------------------------------------------------------ *
   8. HERO
 * ------------------------------------------------------------------ */
.hero {
  position: relative;
  padding-block: clamp(var(--space-4), 5.5vw, var(--space-6)) clamp(var(--space-5), 8vw, var(--space-7));
  text-align: center;
}
/* Scroll cue pinned to the foot of the first screen; the inline script on the
   home page fades it out (adds .is-hidden) once the reader starts scrolling. */
.hero__scroll {
  position: fixed;
  left: 50%;
  bottom: clamp(1.5rem, 4vh, 2.75rem);
  transform: translateX(-50%);
  z-index: 40;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  text-decoration: none;
  color: var(--soft-ink);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  transition: opacity 0.4s ease, color 0.2s ease;
}
.hero__scroll.is-hidden { opacity: 0; pointer-events: none; }
.hero__scroll:hover,
.hero__scroll:focus-visible { color: var(--accent); }
.hero__scroll-chev {
  width: 1.9rem;
  height: 1.9rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  animation: hero-scroll-bob 1.8s ease-in-out infinite;
}
@keyframes hero-scroll-bob {
  0%, 100% { transform: translateY(0); opacity: 0.65; }
  50% { transform: translateY(5px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero__scroll-chev { animation: none; }
}
.hero__logo {
  width: min(800px, 92%);
  /* Negative bottom margin trims the transparent padding baked into the PNG,
     pulling the headline (and the copy below it) up closer to the wordmark. */
  margin: 0 auto -4rem;
}
.hero h1 {
  max-width: 18ch;
  margin-inline: auto;
}
.hero h1 .accent { color: var(--accent); }
.hero__intro {
  max-width: 46ch;
  margin: 0 auto var(--space-4);
  font-size: 1.25rem;
  color: var(--soft-ink);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: center;
}

/* The build path: a vertical sequence of large line-art icons (left) paired with
   copy (right), joined by downward arrows. Each step fades in on scroll. */
.buildpath-section { padding-block: clamp(var(--space-5), 9vw, var(--space-7)); }
.buildpath {
  list-style: none;
  margin: 0 auto;
  max-width: 980px;
  padding: 0 var(--space-4);
  display: flex;
  flex-direction: column;
  gap: clamp(0.75rem, 2.5vw, 1.6rem);
  /* Node size — shared so arrows line up under icons. The lower floor lets the
     two serpentine lanes stay side-by-side on phones (each node ~half the
     column) instead of overflowing. */
  --bp-icon: clamp(112px, 20vw, 260px);
}
/* Positioned ancestor for the absolute .bp-trace overlay — needed at EVERY
   width now that the serpentine draws on phones too (desktop re-sets this in the
   2-column block below). */
.buildpath-layout { position: relative; }

/* ---- Build-path intro ("descending circuit rail") ----
   Below the desktop breakpoint this is just two intro lines stacked above the
   path. On wide screens (see the media query further down) it becomes a left
   column: a vertical accent trace that "draws" downward as the reader scrolls
   the path, with the two lines hung off it. It tells the reader what the site is
   without competing with the path — quieter weight/color, and it only occupies
   otherwise-empty left margin. */
/* Every line on the rail shares one type treatment. */
.buildpath-intro__lead,
.buildpath-intro__sub,
.buildpath-intro__note,
.buildpath-intro__code,
.buildpath-intro__end {
  margin: var(--space-4) auto 0;
  max-width: 980px;
  padding-inline: var(--space-4);
  font-family: var(--font-body);
  font-size: clamp(1.1rem, 1.35vw, 1.3rem);
  line-height: 1.62;
  letter-spacing: 0;
  color: var(--ink);
}
.buildpath-intro__lead { margin-top: 0; }   /* opener: flush to the top */
.buildpath-intro strong { color: var(--accent); font-weight: inherit; }
.buildpath-rail { display: none; }

/* Phones + tablets: drop the left explainer paragraphs entirely and let the
   serpentine path stand on its own, full-width. The desktop two-column layout
   (paragraphs on the left, path on the right) is gated to min-width: 1080px
   below; under that width only the path shows. */
@media (max-width: 1079px) {
  .buildpath-intro { display: none; }
}

@media (min-width: 1080px) {
  /* Two columns: the explainer text on the left, the serpentine path on the
     right. The path column is a flexible 1fr so its nodes can swing between a
     left lane and a right lane; the pair is capped and centered on the page.
     position:relative anchors the full-layout .bp-trace overlay. */
  .buildpath-layout {
    position: relative;
    display: grid;
    grid-template-columns: minmax(18rem, 30rem) minmax(0, 1fr);
    gap: clamp(2.5rem, 5vw, 5.5rem);
    max-width: min(1460px, 93vw);      /* widen on big screens, ease back on narrow ones */
    margin-inline: auto;
    padding-inline: var(--space-4);
    /* The serpentine's bulges reach further right than the text reaches left, so
       the centered grid reads right-biased by an amount that grows with the bulge
       size (which scales with width). Nudge left by a width-scaled amount so the
       path + paragraphs stay visually centered across desktop widths. */
    transform: translateX(clamp(-3.75rem, 6.5vw - 124px, -0.5rem));
  }
  .buildpath-main { min-width: 0; }
  /* Path sits flush inside its column (the grid handles centring). */
  .buildpath-main .buildpath { margin-inline: 0; max-width: none; padding-inline: 0; }
  /* Center the "YOUR PATH" eyebrow over the WHOLE layout (≈ the screen middle),
     not just the serpentine column: lift it out of the right column's flow and
     stretch it across the full layout width so its tick + text land dead center.
     The layout reserves top room for it. */
  .buildpath-layout { padding-top: clamp(2.4rem, 4vw, 3.4rem); }
  .buildpath-main .buildpath__title {
    position: absolute;
    top: -1.6rem;                    /* nudge the eyebrow up */
    left: var(--space-4);            /* align with the layout's content left edge */
    right: 0;
    z-index: 2;                       /* above the .bp-trace overlay */
    margin: 0;
    padding: 0;
    max-width: none;
    justify-content: flex-start;      /* left-justified, matching the Hands On eyebrow */
  }

  /* The explainer lines sit as a left column, spread down the full height so
     each lands beside the point on the path its branch taps into. The old
     vertical rail + node dots are gone — the .bp-trace branches connect them. */
  .buildpath-intro {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: flex-start;           /* paragraphs sit against the column's left edge */
    padding-block: 0.2rem 2rem;
    text-align: center;                /* centered within each block */
  }
  .buildpath-intro__lead,
  .buildpath-intro__sub,
  .buildpath-intro__note,
  .buildpath-intro__code,
  .buildpath-intro__end {
    position: relative;
    width: fit-content;                /* box hugs its text so branches meet the words */
    max-width: 42ch;                   /* wide enough that no line is left with 1-2 words */
    margin: 0;
    padding-inline: 0;
    text-wrap: balance;                /* even the line lengths — no short orphan lines */
  }
  .buildpath-intro__lead::before,
  .buildpath-intro__sub::before,
  .buildpath-intro__note::before,
  .buildpath-intro__code::before,
  .buildpath-intro__end::before { content: none; }   /* rail dots removed */
  .buildpath-rail { display: none; }                  /* vertical rail removed */
}
/* Section eyebrow atop the path — mimics "MEET THE PROCESSOR" on the Meet the
   Processor page. That label is a .kicker inside .journey__step, so it inherits
   the mono/uppercase/tracked kicker base but with the larger size and soft-ink
   color that .journey__step p applies. We reproduce those two overrides here. */
.buildpath__title {
  max-width: 980px;
  margin: -1.25rem auto var(--space-2);   /* nudged up a little */
  padding-inline: var(--space-4);   /* match the path's <ol> padding so the label's
                                        left edge lines up with the first icon */
  font-size: clamp(1.15rem, 1.6vw, 1.35rem);
  color: var(--soft-ink);
}
/* "Start Here" purple eyebrow + lead line atop the path (same .kicker treatment),
   aligned to the path's left edge. */
.buildpath__kicker {
  max-width: 980px;
  margin: 0 auto var(--space-4);
  padding-inline: 0;                 /* flush to the content-box left edge */
  font-size: 0.8rem;
}
/* Difficulty tier tags (Intermediate / Advanced) sit in the gap between two
   icons. Each carries the same downward arrow as the other transitions —
   centered under the icon column — with the tier label hung out into the left
   margin so it stays clear of the arrow and reaches toward the screen edge. */
.bp-tag {
  position: relative;
  align-self: flex-start;            /* shrink to the arrow; don't stretch wide */
}
.bp-tag__arrow {
  display: block;
  width: var(--bp-icon);             /* match the icon column so the ↓ centers under it */
  text-align: center;
  color: var(--accent);
  font-size: clamp(1.5rem, 3.5vw, 2.2rem);
  line-height: 1;
}
.bp-tag .kicker {
  position: absolute;
  top: 50%;
  left: calc(-1 * var(--space-4));   /* flush to the section's left edge */
  transform: translateY(-50%);       /* center on the arrow (no layout effect) */
  margin: 0;
  padding-inline: 0;
  font-size: 0.8rem;
  white-space: nowrap;
}
/* On wide desktops there's free margin to the left of the 980px path — hang the
   tier tags (and the "Beginner" lead) out into it. Only at the two-column
   breakpoint, though: below it the path goes full-width, so a hung tag would run
   off the left edge — keep them inline there. */
@media (min-width: 1080px) {
  .buildpath__kicker {
    position: relative;
    left: calc(-1 * clamp(2rem, (100vw - 980px) / 2 + 2rem, 13rem));
  }
  .bp-tag .kicker {
    left: calc(-1 * clamp(2rem, (100vw - 980px) / 2 + 2rem, 13rem));
  }
}
.bp-step {
  display: grid;
  grid-template-columns: var(--bp-icon) 1fr;
  gap: clamp(1.5rem, 5vw, 3.5rem);
  align-items: center;
}
.bp-frame {
  width: 100%;
  aspect-ratio: 100 / 64;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02);
  color: var(--ink);            /* fully opaque, crisp icon lines */
}
.bp-svg {
  width: 80%;
  height: 80%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.bp-svg .bp-txt {
  fill: currentColor;
  stroke: none;
  font-family: var(--font-mono);
  font-size: 13px;
  text-anchor: middle;
}
.bp-svg .bp-txt--sm { font-size: 11px; }
.bp-svg .bp-txt--xs { font-size: 9px; }
.bp-svg .bp-txt--xxs { font-size: 6.5px; }
.bp-svg .bp-lbl {
  fill: currentColor;
  stroke: none;
  font-family: var(--font-mono);
  font-size: 8px;
  text-anchor: middle;
}
.bp-svg .bp-stage {
  fill: currentColor;
  stroke: none;
  font-family: var(--font-mono);
  font-size: 6px;
  text-anchor: middle;
}
.bp-title {
  margin: 0 0 0.5rem;
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 3vw, 2rem);
  letter-spacing: -0.01em;
}
.bp-desc {
  margin: 0;
  max-width: 48ch;
  color: var(--soft-ink);
  font-size: 1.25rem;
  line-height: 1.6;
}
.bp-arrow {
  align-self: flex-start;            /* keep the glyph under the left icon column */
  margin: 0;
}
/* The ↓ glyph (mobile / no-desktop view) — styled exactly as the old .bp-arrow. */
.bp-arrow__glyph {
  display: block;
  width: var(--bp-icon);             /* match the icon column so the ↓ centers under it */
  text-align: center;
  color: var(--accent);
  font-size: clamp(1.5rem, 3.5vw, 2.2rem);
  line-height: 1;
}
/* The single serpentine "trace": one SVG overlaying the whole build-path layout.
   scroll.js measures the node icons + the explainer lines and draws the snaking
   line (through the nodes) plus the branches (out to the explainer text) into it.
   Desktop-only; below the breakpoint the ↓ glyphs handle the connections. */
.bp-trace {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;                        /* behind the nodes (which get z-index:1) */
  overflow: visible;
  pointer-events: none;
}
.bp-trace__snake,
.bp-trace__seg,
.bp-trace__branch {
  fill: none;
  stroke: var(--accent);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.bp-trace__snake,
.bp-trace__seg    { stroke-width: 2.5; }
.bp-trace__branch { stroke-width: 1.5; opacity: 0.55; }

/* ---- Serpentine path nodes + connectors (ALL widths) ------------------ *
   Placed AFTER the base .bp-step / .bp-frame rules so these win on source order.
   Each node is a half-width column hugging one lane, so its icon sits at 25%
   (left) or 75% (right) of the path column; the full-width connector SVGs curve
   between those two lane centers. This now applies at EVERY width — on phones
   the left explainer paragraphs are hidden and the serpentine spans full-width. */
  .buildpath {
    position: relative;
    display: block;
    margin: 0;
    max-width: none;
    /* Inset the two node lanes from the column edges so the serpentine's
       sideways bulges have room to swing without running off the screen. The
       inset shrinks on narrow screens so the two lanes stay usably wide. */
    padding: 0 clamp(16px, 6vw, 104px);
  }
  .bp-step {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    width: 50%;
    gap: 0.75rem;
  }
  .bp-step--left  { margin-right: auto; }   /* hug the left lane  -> icon center at 25% */
  .bp-step--right { margin-left: auto; }     /* hug the right lane -> icon center at 75% */
  /* Fixed node size (override the base 100%); opaque surface so the snaking
     trace passes cleanly BEHIND each node instead of through the icon. */
  .bp-frame { width: var(--bp-icon); background: var(--paper); }
  .bp-copy  { max-width: 32ch; }
  .bp-title { font-size: clamp(1.1rem, 2vw, 1.5rem); }
  .bp-desc  { font-size: 1rem; line-height: 1.5; max-width: 32ch; }   /* compact caption */
  /* The single snaking line + branches; nodes ride above it (z-index below). */
  .bp-trace { display: block; }
  .buildpath > li { position: relative; z-index: 1; }

  /* The arrows are now just vertical spacers between nodes; the connecting
     line is the single scroll-drawn .bp-trace SVG. Glyph stays hidden. */
  .bp-arrow { width: 100%; height: clamp(3rem, 7vw, 5rem); margin: 0; padding: 0; }
  .bp-arrow__glyph { display: none; }

/* ------------------------------------------------------------------ *
   8a. THE JOURNEY — one pinned image that cross-fades through three chip
   states (face -> dies -> internals) while the copy scrolls past and fades
   in chunk by chunk. Full-bleed so the chip hugs the screen edge.
 * ------------------------------------------------------------------ */
.journey { padding-block: var(--space-6); }
.journey__layout {
  display: flex;
  align-items: flex-start;
  /* push the image + copy pair toward the right (free space falls on the left) */
  justify-content: flex-end;
  gap: clamp(var(--space-4), 6vw, var(--space-7));
  padding-inline: clamp(var(--space-3), 5vw, var(--space-6));
}
/* Media column: pinned and centered in the viewport while the copy scrolls. */
.journey__media {
  flex: 0 0 auto;
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
}
/* Crop frame: square window zoomed past the gray border; the three substrate
   states are stacked so they line up pixel-for-pixel and can cross-fade. */
.journey__crop {
  position: relative;
  width: clamp(320px, 42vw, 560px);
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-soft);
}
/* The die map gets its own, larger frame centered over the smaller one and
   faded in. Its aspect ratio matches the image, so the whole map shows with no
   crop and no letterbox — just bigger, with its own (correspondingly larger)
   border. (It's positioned against the sticky media, which is already a
   containing block.) */
.journey__detail {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: clamp(380px, 50vw, 680px);   /* larger than the .journey__crop frame */
  aspect-ratio: 1400 / 1202;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-soft);
  opacity: 0;            /* faded in on scroll */
  will-change: opacity;
  pointer-events: none;
}
/* Zoom layer inside the die-map frame: fills it and holds the image + all the
   highlight rectangles. scroll.js scrubs scale()/x on this layer to focus on a
   single core, so the frame (.journey__detail) keeps its centring transform and
   stays pinned exactly in place. transform-origin = the focal core, so that
   point holds still while everything else expands outward (mirrors
   .overview-band__zoom). The frame's overflow:hidden crops the overshoot. */
.journey__zoom {
  position: absolute;
  inset: 0;
  transform-origin: 18% 28%;   /* center of the top-left Zen 5 core */
  /* No will-change/cached layer here: under the zoom the browser re-rasterises
     the highlight label text at the scaled size, so it stays crisp instead of
     being a magnified bitmap. */
}
/* Spotlight mask: a hole exactly over the upper-left Zen 5 core (same box as
   .journey__hl--core.is-left.is-row-1) with a huge box-shadow of the page bg
   filling everything else. Faded in at the end of the zoom (scroll.js) to crop
   the image to that one core; the frame's overflow:hidden clips the shadow. */
.journey__spotlight {
  position: absolute;
  left: 0.5%;
  top: 18.25%;
  width: 35%;
  height: 19.5%;
  box-shadow: 0 0 0 100vmax var(--bg);
  opacity: 0;                  /* faded in at the end of the core-focus zoom */
  pointer-events: none;
  will-change: opacity;
}
.journey__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.15);   /* zoom past the gray border so only the chip shows */
  /* Promote each state to its own compositor layer so the scrubbed opacity
     cross-fade is GPU-composited (no per-frame repaint of the large image). */
  will-change: opacity;
  backface-visibility: hidden;
}
.journey__img--ihs { transform: scale(1.16); }
/* The die map fills its own frame exactly (frame shares its aspect ratio), so
   the whole image shows with no crop. The .journey__detail wrapper carries the
   fade, so the image itself stays fully opaque. */
/* Nudge ONLY the die image down a hair (it rides inside .journey__zoom, the
   highlight rectangles are siblings) so the die features sit slightly lower —
   aligning them under the L2/L1/fetch rectangles without moving the rectangles.
   The 1px is in image space, so the zoom scales it up to a small on-screen shift. */
.journey__img--detail { transform: translateY(1px); }
/* Scroll-revealed highlight rectangle over the die map. Same translucent
   light-purple fill as the ALU overview band. Positioned in % of the detail
   frame (which shares the image's aspect ratio, so %s map straight onto the
   image). A perfect rectangle down the center spine: flush with the bottom edge
   and rising only to the visible cutoff near the top of the die. */
.journey__hl {
  position: absolute;
  background: rgba(205, 180, 242, 0.55);
  opacity: 0;            /* faded in on scroll */
  pointer-events: none;
  will-change: opacity;
}
.journey__hl--spine {
  left: 36%;
  width: 28%;            /* centered: 36% + 28% = 64% */
  bottom: 1.5%;          /* a tad further down toward the bottom edge */
  top: 18.25%;           /* top edge flush with the Zen 5 core's top edge */
}
/* The eight Zen 5 cores: two columns of four flanking the cache spine. Column
   class sets the horizontal band, row class the vertical band (image-space %s).
   A translucent teal sets them apart from the purple cache highlight. */
.journey__hl--core { background: rgba(86, 220, 210, 0.45); }
.journey__hl--core.is-left  { left: 0.5%;  width: 35%; }   /* outer (left) edge a fraction wider */
.journey__hl--core.is-right { left: 64.5%; width: 35%; }   /* outer (right) edge a fraction wider */
.journey__hl--core.is-row-1 { top: 18.25%; height: 19.5%; }
.journey__hl--core.is-row-2 { top: 38.25%; height: 19.5%; }
.journey__hl--core.is-row-3 { top: 58.25%; height: 19.5%; }
.journey__hl--core.is-row-4 { top: 78.25%; height: 19.5%; }

/* A shorter rectangle above the top-left core: same width + left edge, top just
   shy of the image's top edge. */
.journey__hl--north {
  left: 0.5%;            /* in line with the top-left core's (new) left edge */
  width: 34.2%;          /* a tiny tiny less on the right */
  top: 1.8%;             /* a tincy bit more on top */
  height: 10.9%;         /* a little less on the bottom */
  background: rgba(245, 190, 90, 0.5);   /* amber — distinct from cache + cores */
}
/* Duplicate placed directly to the right of the first. */
.journey__hl--north2 { left: 34.7%; width: 31.35%; }  /* ~1/6 less, then ~1/12 wider on the right side */

/* Test/Debug block: left edge flush with the right edge of the rightmost IFOP
   PHY (34.7% + 31.35% = 66.05%), top aligned with the IFOPs, extending right to
   just shy of the die's right edge. A distinct rose fill sets it apart. */
.journey__hl--testdebug {
  left: 66.05%;
  width: 32.95%;        /* reaches ~99% — just shy of the right edge */
  top: 1.8%;            /* top edge aligned with the IFOP PHY rectangles */
  height: 10.9%;        /* same height as the IFOP PHY rectangles */
  background: rgba(232, 120, 180, 0.5);   /* rose — distinct from amber/teal/purple */
}

/* Wide, skinny band filling the gap between the IFOP PHY / Test/Debug row and
   the cores+cache below. Spans from the leftmost IFOP's left edge (0.5%) to the
   Test/Debug's right edge (99%). A distinct blue fill. */
.journey__hl--smu {
  left: 0.5%;
  width: 98.5%;         /* 0.5% -> 99% */
  top: 12.6%;           /* grown ~1/4 taller on top (was 13.5%) */
  height: 5.4%;         /* grown ~1/4 on each side (was 3.6%) */
  background: rgba(96, 165, 250, 0.5);   /* blue — distinct from the others */
}
/* Center the label vertically within the band; smaller so the long string
   doesn't overpower the thin rectangle. */
.journey__hl--smu .journey__hl-label {
  top: 50%;
  font-size: clamp(0.6rem, 0.78vw, 0.82rem);
}

/* "Going Deeper" sub-block overlays: two rectangles sitting on the right side
   of the upper-left (cropped) Zen 5 core. Image-space %s, so they ride the zoom
   with the core. Same translucent purple as the L3 cache (the .journey__hl
   default fill). Right edge just shy of the crop's right edge (35.5%); the two
   stacked with about a rectangle's height of gap between them. */
.journey__hl--deeper {
  left: 28%;
  width: 7.05%;         /* trimmed in from the left; right edge pulled back a sliver */
  height: 5%;           /* bottoms extended ~3/7 taller (3.5% -> 5%) */
}
.journey__hl--deeper-1 { top: 22.3125%; }  /* down a sliver (1/4 of prior nudge) */
/* Thin vertical riser above the upper L2 cache: left flush with it (28%), width
   ~1/4 of the L2 cache (7.05% / 4 ≈ 1.76%), bottom flush with the L2's top
   (22.3125%). Its top sits just shy of the CROPPED image's top edge — after the
   core-focus zoom that edge maps to ~18.34% image-space, so 18.6% sits a sliver
   below it. Same dark purple as the fill. */
.journey__hl--deeper-riser {
  left: 28%;
  width: 1.76%;
  top: 18.45%;          /* the smallest hair shy of the cropped top (~18.34%) */
  height: 3.8625%;      /* up to the upper L2 cache's top edge (22.3125%) */
  background: rgba(124, 58, 237, 0.5);    /* dark violet — matches the fill region */
}
.journey__hl--deeper-2 { top: 32.45%; }    /* down the sliverest of slivers */
/* Wide block to the LEFT of the bottom L2 cache, in a distinct teal. Bottom
   flush with that L2 cache (37.45%); height just shy of the L2's (5% -> 4.75%).
   A ~3/4-L2-width gap sits between them (0.75 × 7.05% ≈ 5.29%), so the block's
   right edge is at 28% − 5.29% = 22.71%; it extends left ~2 × the L2 width
   (14.1%), putting its left edge at ~8.61%. */
.journey__hl--deeper-west {
  left: 10.325%;        /* extended the tiniest hair more left */
  width: 12.275%;       /* right edge held at ~22.6% */
  top: 33.3%;           /* top shrunk down by the tiny bit (0.6%) */
  height: 4.15%;        /* bottom held flush at 37.45% */
  background: rgba(34, 211, 238, 0.5);    /* cyan — distinct from the purples/cores */
}
/* Small tab on top of the L1 Data cache, same teal, reading as a short
   extension. Width ≈ 3/5 of the L1's height (0.6 × 4.15% ≈ 2.49%); its height
   ≈ 1/4 of that width (≈ 0.62%); bottom flush with the L1's top (33.3%); placed
   just right of the L1 top surface's center (L1 center ≈ 16.46%). */
.journey__hl--deeper-l1tab {
  left: 15.915%;
  width: 2.19%;         /* right side pulled in another smidge */
  top: 32.48%;          /* bottom 33.3% − height 0.82% */
  height: 0.82%;        /* taller — grown upward, bottom stays flush */
  background: rgba(34, 211, 238, 0.5);    /* same cyan as the L1 Data cache */
}
/* Third rectangle nestled in the gap between the two. Right edge just shy of
   the others' (34.8% vs 35.05%); top/bottom just shy of touching deeper-1's
   bottom (27.31%) and deeper-2's top (32.45%). */
/* Fill region between the two L2 caches: left/width match the L2 caches (28% /
   7.05%), top flush with deeper-1's bottom (27.3125%) and bottom flush with
   deeper-2's top (32.45%). A deeper purple — same family as the L2s but clearly
   distinct. A clip-path cuts a rectangular hole exactly where the Tags block
   sits, so the fill wraps AROUND it instead of overlapping it. Hole coords are
   the Tags rect expressed as %s of this element's own box. */
.journey__hl--deeper-fill {
  left: 28%;
  width: 7.05%;
  top: 27.3125%;
  height: 5.1375%;      /* down to deeper-2's top edge (32.45%) */
  background: rgba(124, 58, 237, 0.5);    /* deeper violet — distinct from the light-lavender L2s */
  clip-path: polygon(
    0% 0%, 0% 100%, 100% 100%, 100% 0%, 0% 0%,
    35.74% 8.52%, 97.87% 8.52%, 97.87% 95.13%, 35.74% 95.13%, 35.74% 8.52%
  );
}
.journey__hl--deeper-3 {
  left: 30.52%;         /* widened left by half a sliver (0.2%) */
  width: 4.38%;         /* left +0.2%, right +0.1% — right edge now ~34.9% */
  top: 27.75%;          /* shortened top by half the sliver (0.2%) */
  height: 4.45%;        /* a hair more on the bottom — bottom ~32.2% */
}
/* Small cyan square near the top right of the cropped core, just left of the
   purple riser (left edge 28%). Sized a touch smaller on each side than the L2
   Cache Tags block (4.38% × 4.45% -> 3.8% × 3.9%). Top sits just below the
   cropped image's top edge (~18.34%); right edge a sliver shy of the riser. */
.journey__hl--deeper-ne {
  left: 23%;
  width: 3.3%;          /* right edge ~26.3% — pulled in a tiny bit more */
  top: 18.45%;          /* top flush with the riser's top */
  height: 2.65%;        /* bottom brought up a very tad more */
  background: rgba(34, 211, 238, 0.5);    /* same cyan as the L1 Data cache */
}
/* Instruction Fetch and Decode: to the LEFT of the L1i, right edge flush with
   the L1i's left edge (23%). ~2x the L1i width (3.3% -> 6.6%), ~1/5 taller
   (2.65% -> 3.18%), top flush with the L1i (18.45%). Distinct orange. */
.journey__hl--deeper-fetch {
  left: 17.15%;         /* left side shortened in a sliver more */
  width: 5.85%;         /* right edge still at 23% */
  top: 18.45%;          /* top flush with the L1i */
  height: 3.18%;        /* ~1/5 taller than the L1i */
  background: rgba(251, 146, 60, 0.5);    /* orange — distinct from cyan/purple/teal */
}
/* Small block hanging off the fetch block's bottom-left: top flush with the
   fetch's bottom (21.63%), left aligned with the fetch's left. Height ~1/3 of the
   fetch's height; width ~1/5 of the fetch's bottom width. Same orange. */
.journey__hl--deeper-fetch-sub {
  left: 17.15%;         /* aligned with the fetch block's left edge */
  width: 2.42%;         /* extended right a sliver */
  top: 21.47%;          /* tucked up into the fetch's bottom (21.63%) so the
                           junction reads as solidly flush, no hairline gap */
  height: 1.01%;        /* bottom stays at ~22.48% (1/5 trimmed off earlier) */
  background: rgba(251, 146, 60, 0.5);    /* same orange as the fetch block */
}
/* Thin extension off the fetch block's bottom-right: left edge at the fetch's
   right edge (23%), top flush with the L1i's bottom (21.1%), bottom flush with
   the fetch block's bottom (21.63%). Runs right to just shy of the dark purple
   riser (28%). Same orange as the fetch block. */
.journey__hl--deeper-fetch-ext {
  left: 23%;            /* fetch's right edge */
  width: 4.92%;         /* right edge ~27.92% — a sliver closer to the riser (28%) */
  top: 21.1%;           /* flush with the L1i's bottom */
  height: 0.53%;        /* down to the fetch block's bottom (21.63%) */
  background: rgba(251, 146, 60, 0.5);    /* same orange as the fetch block */
}
/* Thin vertical riser off the extension's right end, ~3 slivers wide, rising up
   to be flush with the L1i's top (18.45%). Its bottom meets the extension's TOP
   (21.1%) so the two read as one continuous extension, not an overlap. Orange. */
.journey__hl--deeper-fetch-up {
  left: 27.71%;         /* right edge (27.92%) aligned with the extension's */
  width: 0.21%;         /* ~3 slivers */
  top: 18.45%;          /* flush with the L1i's top */
  height: 2.65%;        /* down to the extension's top (21.1%) — no overlap */
  background: rgba(251, 146, 60, 0.5);    /* same orange as the fetch block */
}
/* Short horizontal piece off the TOP of the riser, running left to be flush with
   the L1i's right edge (26.3%). Same height as the first (lower) extension. */
.journey__hl--deeper-fetch-top {
  left: 26.3%;          /* flush with the L1 Instruction Cache's right edge */
  width: 1.41%;         /* right edge (27.71%) meets the riser's left edge */
  top: 18.45%;          /* flush with the riser's / L1i's top */
  height: 0.53%;        /* same as the lower extension */
  background: rgba(251, 146, 60, 0.5);    /* same orange as the fetch block */
}

/* Centered label sitting at the top of the highlight rectangle. Inherits the
   rectangle's scroll fade, so it appears with it. */
.journey__hl-label {
  position: absolute;
  top: 33.3%;            /* ~2/3 up the rectangle */
  transform: translateY(-50%);
  left: 0;
  right: 0;
  text-align: center;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: clamp(0.85rem, 1.1vw, 1.05rem);
  letter-spacing: 0.04em;
  color: #fdfdfb;
  text-shadow: 0 1px 6px rgba(20, 20, 26, 0.85);
  pointer-events: none;
}
/* Core labels: smaller and centered within their (smaller) rectangle. */
.journey__hl-label--core {
  top: 50%;
  font-size: clamp(0.6rem, 0.85vw, 0.78rem);
  line-height: 1.15;
}
/* Deeper sub-block labels: the rectangles are tiny, so shrink to fit. They
   scale with the zoom (the rectangles ride .journey__zoom), so this is the
   pre-zoom size. */
.journey__hl-label--deeper {
  top: 50%;
  font-size: clamp(0.26rem, 0.34vw, 0.34rem);
  letter-spacing: 0.02em;
  line-height: 1.1;
  white-space: nowrap;
}
/* Render the fraction as real "1/2" text, bumped up and letter-spaced so the
   digits sit a touch left/right of the slash instead of jammed against it. */
.journey__hl-label--deeper .frac {
  font-size: 1.05em;
  letter-spacing: 0.08em;
}
/* "L2 Cache Tags" sits in the narrower middle rectangle, so it needs a smaller
   font than the other two deeper labels to fit. */
.journey__hl-label--deeper-tags {
  font-size: clamp(0.2rem, 0.26vw, 0.26rem);
  line-height: 1.05;     /* tight stacking of the two lines */
}
/* "L1 Instruction cache" sits in the small teal square (deeper-ne). Two stacked
   lines, both centered; sized down so the longer top line fits inside the box. */
.journey__hl-label--deeper-l1i {
  font-size: clamp(0.115rem, 0.153vw, 0.153rem);
  line-height: 1.1;      /* tight stacking of the two lines */
}
/* "Instruction Fetch and Decode" sits in the wider orange block; a touch larger
   than the L1i label since it has more room. */
.journey__hl-label--deeper-fetch {
  font-size: clamp(0.17rem, 0.225vw, 0.225rem);
  line-height: 1.1;
}
/* "L2$ Control and Interconnect" runs vertically up the left strip of the fill
   frame (the band to the left of the Tags hole). Rotated 90° CCW so it reads
   bottom-to-top; centered in that strip. */
.journey__hl-label--ctrl {
  left: 17.9%;           /* center of the left strip (x 0–35.74% of the box) */
  right: auto;
  top: 50%;
  transform: translate(-50%, -50%) rotate(-90deg);
  white-space: nowrap;
  font-size: clamp(0.14rem, 0.19vw, 0.19rem);
  letter-spacing: 0.02em;
}
/* The dies/internals states start hidden and are revealed on scroll. */
.journey__img--dies,
.journey__img--gray { opacity: 0; }

/* Copy column: wide measure. Each chunk is tall enough to give the pinned
   media room to hold and to pace the line-by-line reveal, but the text itself
   is grouped tightly — the empty space sits between chunks, not between lines. */
/* Don't grow — stay at the 60rem measure — so the layout's justify-content
   can center the image + copy pair instead of the copy filling to the edge. */
/* Extra bottom runway keeps the sticky media pinned past the final copy so the
   die map holds in place while its highlight(s) reveal. */
.journey__copy { flex: 0 1 auto; min-width: 0; max-width: 60rem; padding-block: 12vh 30vh; }
.journey__step {
  min-height: 56vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.journey__step + .journey__step { margin-top: var(--space-4); }
.journey__step--gallery { min-height: 56vh; }   /* match the other steps' spacing */
/* Extra gap between "Under the covers" and "Where the beauty lies": pushes the
   gallery step and everything after it down together, so each highlight stays in
   sync with its own step's text — only the absolute scroll position shifts. */
.journey__step + .journey__step--gallery,
.journey__step + .journey__step--cache,
.journey__step + .journey__step--cores,
.journey__step + .journey__step--ifop,
.journey__step + .journey__step--testdebug,
.journey__step + .journey__step--smu { margin-top: var(--space-6); }
/* Start the "Going Deeper" chunk lower so it lands after the core-focus zoom. */
.journey__step + .journey__step--deeper { margin-top: 30vh; }
/* Push "A cache of one's own" further below "Inside a single core" so it lands
   well after the L2 rectangles have faded in. */
.journey__step + .journey__step--l2 { margin-top: 26vh; }
/* Push "Built for speed" well below "A cache of one's own" so the L1 rectangles
   have room to fade in after that section has been read. */
.journey__step + .journey__step--l1 { margin-top: 34vh; }
/* "Coming soon" — the final beat below "Inside a single core". */
.journey__soon {
  margin: var(--space-6) 0 0;
  text-align: center;
  font-family: var(--font-mono);
  font-size: clamp(1.3rem, 3vw, 1.9rem);
  letter-spacing: 0.08em;
  color: var(--soft-ink);
}
/* Lower the copy from "First stop" through the SMU section a little. Applied as
   a transform on the inner content (not the step box), so it's purely visual and
   the step elements keep their layout positions — the die-shot highlight fade-in
   timing, which is tied to those step elements, stays exactly the same. */
.journey__step--cache > *,
.journey__step--cores > *,
.journey__step--ifop > *,
.journey__step--testdebug > *,
.journey__step--smu > * {
  transform: translateY(2rem);
}
.journey__step .kicker { margin-bottom: var(--space-3); }
/* Each word is its own box so the line-by-line reveal can fade/translate it. */
.journey__step .jw { display: inline-block; }
.journey__step p {
  color: var(--soft-ink);
  font-size: clamp(1.15rem, 1.6vw, 1.35rem);
  line-height: 1.65;
}
/* Match the muted gray bold used in lesson .prose: strong inherits the
   paragraph's --soft-ink color with the browser's bold weight. */
.journey__step strong { color: var(--soft-ink); }
/* "Learn more about pipelining" link: pulled up snug under the paragraph it
   follows, with a normal gap restored before the next paragraph. Scoped under
   .journey__step so it beats .step__more's later margin-top. */
.journey__step .journey__more { margin-top: -1.4rem; margin-bottom: 2.3rem; }

/* ---- Cache-hierarchy comparison table -----------------------------------
   Lives in the L1 ("Small but speedy") step. In normal flow (and on mobile)
   it's a tidy block that collapses to cards; on desktop scroll.js relocates it
   into the sticky .journey__media so it pins below the raised die image. */
.cache-table {
  --cache-accent: #22d3ee;   /* cyan — matches the L1 cache rectangles on the die */
  margin: var(--space-4) 0 0;
  max-width: 42rem;
}
.cache-table table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
}
.cache-table__cap {
  caption-side: top;
  text-align: left;
  margin-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  line-height: 1.45;
  color: var(--soft-ink);
}
.cache-table th,
.cache-table td {
  text-align: left;
  padding: 0.55rem 0.7rem;
  border-bottom: 1px solid var(--hairline);
  font-size: 0.82rem;
  vertical-align: baseline;
}
/* Cyan column headers + underline match the L1 cache rectangles on the die. */
.cache-table thead th {
  color: var(--cache-accent);
  font-weight: 600;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-bottom: 1.5px solid var(--cache-accent);
}
.cache-table tbody th {
  color: var(--ink);
  font-weight: 600;
}
.cache-table td { color: var(--soft-ink); }
/* Group the two L1 rows: a cyan accent (matching the L1 die rectangles) down the
   left edge plus a faint tint, and no divider between them so they read as one. */
.cache-table__l1 th,
.cache-table__l1 td { background: rgba(34, 211, 238, 0.1); }
.cache-table__l1 th:first-child {
  box-shadow: inset 3px 0 0 var(--cache-accent);
}
.cache-table__l1:not(.cache-table__l1--first) th,
.cache-table__l1:not(.cache-table__l1--first) td { border-top: 0; }
.cache-table__l1--first th,
.cache-table__l1--first td { border-bottom: 0; }

/* Pinned variant: when scroll.js moves the figure into the sticky media, it sits
   centered below the (raised) die image. --cache-table-top sets how far below the
   viewport center it rides; tweak alongside the JS raise amount. */
.journey__media .cache-table {
  --cache-table-top: calc(50% + 3.5rem);   /* how far below center it rides */
  position: absolute;
  left: 50%;
  top: var(--cache-table-top);
  transform: translateX(-50%);
  width: clamp(380px, 50vw, 680px);        /* match the die frame so edges line up */
  max-width: none;
  margin: 0;
  padding: 0.85rem 1.2rem 1rem;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-top: 2px solid var(--cache-accent);   /* cyan accent edge, matching the L1 die blocks */
  border-radius: var(--radius);            /* match the image frame's corners */
  box-shadow: var(--shadow-soft);
  opacity: 0;                 /* GSAP fades it in with the raise */
  z-index: 2;                 /* above the spotlight mask */
}
.journey__media .cache-table table { table-layout: fixed; }
/* Give the wider text columns room so nothing wraps at the pinned size. */
.journey__media .cache-table th:nth-child(1),
.journey__media .cache-table td:nth-child(1) { width: 15%; }
.journey__media .cache-table th:nth-child(2),
.journey__media .cache-table td:nth-child(2) { width: 13%; }
.journey__media .cache-table th:nth-child(3),
.journey__media .cache-table td:nth-child(3) { width: 34%; }
.journey__media .cache-table th:nth-child(4),
.journey__media .cache-table td:nth-child(4) { width: 38%; }
.journey__media .cache-table th,
.journey__media .cache-table td { padding: 0.46rem 0.65rem; font-size: 0.78rem; white-space: nowrap; }
.journey__media .cache-table thead th { font-size: 0.62rem; }
.journey__media .cache-table__cap { font-size: 0.7rem; margin-bottom: 0.7rem; }

/* Headings: the title and "Under the covers" share the same display size. */
.journey__title,
.journey__subhead { font-size: clamp(1.8rem, 4vw, 2.7rem); }
.journey__subhead { margin-bottom: var(--space-3); }

/* "Where the beauty lies": the other CCD die shots sit in a row beneath the
   copy and fade in on scroll. */
.journey__gallery {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;   /* center the pair under the paragraph */
  gap: var(--space-3);
  margin-top: var(--space-5);
}
.journey__accent {
  flex: 1 1 240px;
  min-width: 0;
  max-width: 360px;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--hairline);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
  opacity: 0;            /* faded in on scroll */
}

@media (max-width: 768px) {
  /* On phones, let the hero own the first screen: fill the viewport and center
     its content so the logo clears the fixed top bar (4rem) and the AMD chip in
     the journey section stays below the fold until the reader scrolls. */
  .hero {
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-block: calc(4rem + var(--space-4)) var(--space-5);
  }

  .journey__layout { flex-direction: column; }
  /* Drop the pin when stacked: image on top, copy below. */
  .journey__media { position: static; height: auto; }
  .journey__crop { width: min(360px, 100%); }
  .journey__step { min-height: 0; margin-top: var(--space-5); }
  .journey__step:first-child { margin-top: var(--space-3); }
  /* Without the pin, show the dies/internals states stacked below the face. */
  .journey__img--dies,
  .journey__img--gray { position: static; opacity: 1; }

  /* Cache table collapses to a stack of cards: hide the head row and turn each
     row into a labeled card (the data-label on each cell supplies the field
     name). The L1 pair keeps its cyan accent so the grouping still reads. */
  .cache-table table,
  .cache-table tbody,
  .cache-table tr,
  .cache-table th,
  .cache-table td { display: block; width: auto; }
  .cache-table thead {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  .cache-table tr {
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    background: var(--paper);
    padding: 0.6rem 0.9rem;
    margin-top: var(--space-2);
  }
  .cache-table tbody th {
    font-size: 1.05rem;
    padding: 0 0 0.4rem;
    margin-bottom: 0.4rem;
    border-bottom: 1px solid var(--hairline);
  }
  .cache-table td {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.3rem 0;
    border-bottom: 0;
    font-size: 0.9rem;
  }
  .cache-table td::before {
    content: attr(data-label);
    color: var(--ink);
    font-weight: 600;
    font-size: 0.66rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    align-self: center;
  }
  /* Re-apply the L1 grouping accent to the (now block) L1 cards. Each L1 is its
     own card; the shared cyan edge keeps them reading as a pair. */
  .cache-table__l1 th,
  .cache-table__l1 td { background: none; box-shadow: none; }
  .cache-table__l1 { box-shadow: inset 3px 0 0 var(--cache-accent); }
  .cache-table__l1--first th { border-bottom: 1px solid var(--hairline); }
}

/* Desktop only: let the hero own the entire first screen so none of the
   "Meet the Processor" section is visible on landing, while trimming the
   journey's top padding so its beginning sits a little higher once scrolling. */
@media (min-width: 769px) {
  .hero { min-height: 100vh; box-sizing: border-box; }
  .journey { padding-block-start: var(--space-4); }   /* was --space-6 — moved up */
  /* The section moved up 3rem (space-6 -> space-4). That's right for the opening
     chunk, but it pulled "Under the covers" up by the same amount, arriving too
     soon. Add that 3rem back as runway before the second chunk so it (and every
     chunk after) keeps its original timing while the opening stays higher. */
  .journey__copy > .journey__step:nth-child(2) {
    margin-top: calc(var(--space-4) + 3rem + 9vh);
  }
  /* Lower just the opening chunk so it sits vertically centered on the pinned
     IHS image. Transform only, so the later steps aren't pushed down. */
  .journey__copy > .journey__step:first-child {
    transform: translateY(9vh);
  }
  /* Runway that keeps the die map pinned through the focus-on-a-core zoom and
     the "Inside a single core" copy. The "Coming soon" beat sits low enough that
     the map unpins right around it, so the two scroll up together as the section
     ends (instead of the map holding pinned past it). */
  .journey__soon { margin-top: 22vh; }
  /* ~50vh of space after "Coming soon" so the map unpins exactly when that text
     reaches the viewport's vertical center — i.e. when it lines up top-to-bottom
     with the centered die image — and the two then scroll up together. */
  .journey__copy { padding-bottom: 50vh; }
  /* Pull the next section up into that empty runway tail (after the image has
     scrolled off) to close the gap a little, without changing the unpin point. */
  .learn { margin-top: -22vh; }
}

/* ------------------------------------------------------------------ *
   8b. THE CHIP — a CPU with a subtle, scroll-driven arc (Phase 3)
   The model lives in a normal-flow canvas inside .cpu-stage__viewport, so the
   page scrolls straight through (no pinning, no overlay). scripts/cpu-hero.js
   maps the section's progress through the viewport to a gentle zoom + a few
   degrees of rotation that peak when the section is centered. The static emblem
   is both the loading state and the no-WebGL / reduced-motion fallback.
 * ------------------------------------------------------------------ */
.cpu-stage {
  padding-block: var(--space-7);
  border-top: 1px solid var(--hairline);
}
.kicker--center { justify-content: center; }   /* center the kicker tick + text */
.cpu-stage h2 { margin-bottom: var(--space-2); }
.cpu-stage__lead {
  color: var(--soft-ink);
  font-size: 1.25rem;
  max-width: 42ch;
  margin: var(--space-2) 0 0;
}

/* Two columns: text on the left, the two rendering stages on the right. */
.cpu-stage__layout {
  position: relative;                  /* the canvas overlay positions against this */
  display: flex;
  align-items: center;
  gap: var(--space-5);
  text-align: left;
}
.cpu-stage__text {
  flex: 0 0 36%;
  max-width: 36%;
  position: relative;                  /* keep text in the same stacking context */
  z-index: 1;                          /* canvas (z-index 2) overlaps it */
}

/* Reserved-height box on the right. It holds nothing but the fallback emblem
   and reserves the stage height so the async model load causes no layout shift;
   the actual chips render in the shared canvas overlay above. */
.cpu-stage__stage {
  position: relative;
  flex: 1;
  min-width: 0;                        /* let flex children shrink below content */
  height: min(60vh, 520px);
}

/* One transparent canvas spanning the whole layout (text + stage). Both chips
   render into it, so there's no clip seam between them and they can overlap the
   text. pointer-events: none keeps the text beneath it selectable. */
.cpu-stage__canvas {
  position: absolute;
  inset: 0;
  z-index: 2;                          /* layer the chips above the text + emblem */
  width: 100% !important;
  height: 100% !important;
  pointer-events: none;
  opacity: 0;                          /* fades in once the model is ready */
  transition: opacity 0.6s ease;
  will-change: transform;
}
html.cpu-ready .cpu-stage__canvas { opacity: 1; }

/* Static fallback emblem — centered in the stage box. It is the loading state
   and the standin whenever the 3D can't run; hidden once the model is ready. */
.cpu-stage__fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--accent);
  opacity: 0.85;
}
.cpu-stage__fallback svg { width: min(40vmin, 220px); height: auto; }
html.cpu-ready .cpu-stage__fallback { display: none; }

@media (max-width: 768px) {
  .cpu-stage__lead { font-size: 1.05rem; }
  /* Stack: text above, stage below. `align-items: stretch` keeps both children
     full-width — without it the stage box (whose only content is the absolutely
     positioned canvas + emblem) would collapse to zero width, leaving the
     re-mounted canvas with nothing to render into. */
  .cpu-stage__layout { flex-direction: column; align-items: stretch; gap: var(--space-3); }
  .cpu-stage__text { flex-basis: auto; max-width: none; }
  /* `flex: none` is essential: the base rule's `flex: 1` becomes flex-basis:0%
     in a column and would override the height below, collapsing the box to 0px
     (canvas + emblem are absolute, so they add no intrinsic height) — which left
     the re-mounted canvas with zero height and nothing to render. The height is
     kept tight (wide, short band) so the side-by-side chips fill it with little
     blank space above/below; cpu-hero.js's MOBILE camDist is tuned to match. */
  .cpu-stage__stage { flex: none; height: min(40vh, 220px); }
}

/* ------------------------------------------------------------------ *
   9. THE IDEA
 * ------------------------------------------------------------------ */
.idea .prose p {
  font-size: 1.25rem;
  color: var(--ink);
}
.idea .prose p + p { color: var(--soft-ink); font-size: 1.25rem; }

/* ------------------------------------------------------------------ *
   10. EPIGRAPH — centered italic pull-quote
 * ------------------------------------------------------------------ */
.epigraph {
  padding-block: var(--space-6);
  border-top: 1px solid var(--hairline);
}
.epigraph blockquote {
  max-width: 38ch;
  margin: 0 auto;
  text-align: center;
  font-family: var(--font-body);
  font-style: italic;
  font-size: clamp(1.4rem, 3vw, 2rem);
  line-height: 1.4;
  color: var(--ink);
}
/* Small purple node marks framing the quote, echoing the circuit motif */
.epigraph blockquote::before,
.epigraph blockquote::after {
  content: "";
  display: block;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--accent);
  margin: 0 auto var(--space-3);
}
.epigraph blockquote::after { margin: var(--space-3) auto 0; }

/* ------------------------------------------------------------------ *
   11. LEARN LADDER — circuit-node path
 * ------------------------------------------------------------------ */
.ladder {
  position: relative;
  max-width: 50rem;
  margin-top: var(--space-5);
  padding-left: 2.5rem;
}
/* Vertical connector line */
.ladder::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 0.6rem;
  bottom: 0.6rem;
  width: 2px;
  background: linear-gradient(
    to bottom,
    transparent 0,
    var(--hairline) 1.5rem,
    var(--hairline) calc(100% - 1.5rem),
    transparent 100%);
}

.step {
  position: relative;
  padding: 0 0 var(--space-5) 0;
}
.step:last-child { padding-bottom: 0; }

/* The circuit node dot on the connector line */
.step::before {
  content: "";
  position: absolute;
  left: calc(-2.5rem + 1px);
  top: 0.4rem;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--paper);
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 4px var(--bg);
}
/* Filled node for the active/first step */
.step--active::before { background: var(--accent); }

.step__tag {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent);
  display: inline-block;
  margin-bottom: 0.4rem;
}
.step--planned .step__tag { color: var(--soft-ink); }
.step h3 { margin-bottom: 0.5rem; }
.step p { color: var(--soft-ink); margin: 0; max-width: 60ch; }

/* The active ALU step links to its deep-dive page. The heading reads as ink
   (not a colored link) so the ladder stays calm; it warms to accent on hover. */
.step__link { color: inherit; }
.step__link:hover { color: var(--accent-deep); }
.step__more {
  font-family: var(--font-mono);
  font-size: 0.9em;                /* a touch smaller than the body copy it follows */
  letter-spacing: 0.04em;
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: var(--space-2);
  margin-left: 0.5rem;             /* nudge it a little to the right */
}
.step__more span { transition: transform 0.18s var(--ease); }
.step__more:hover { color: var(--accent-deep); }
.step__more:hover span { transform: translateX(3px); }

/* ------------------------------------------------------------------ *
   12. WHAT YOU'LL FIND
 * ------------------------------------------------------------------ */
/* An editorial index rather than a 2x2 grid of cards: full-width rows split
   into a mono label column and the content, divided by hairlines. */
.findings {
  list-style: none;
  margin: var(--space-5) 0 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
}
.finding {
  display: grid;
  grid-template-columns: minmax(7rem, 13rem) 1fr;
  column-gap: clamp(var(--space-3), 5vw, var(--space-6));
  row-gap: 0.35rem;
  padding: var(--space-4) var(--space-2);
  border-bottom: 1px solid var(--hairline);
  transition: background 0.18s var(--ease);
}
.finding:hover { background: rgba(253, 253, 251, 0.035); }
.finding__num {
  grid-column: 1;
  grid-row: 1 / -1;            /* label spans the whole row, content beside it */
  align-self: start;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.1em;
  color: var(--accent);
  margin: 0;
}
.finding h3 { grid-column: 2; margin: 0 0 0.1rem; }
.finding p { grid-column: 2; color: var(--soft-ink); margin: 0; font-size: 1.0625rem; }

/* The interactive-tools call to action: a faint accent wash and a link heading
   that stays white (a "white heading") with an accent arrow that nudges. */
.finding--cta { background: rgba(183, 148, 246, 0.06); }
.finding--cta:hover { background: rgba(183, 148, 246, 0.11); }
.finding__link { color: var(--ink); text-decoration: none; }
.finding__link span {
  display: inline-block;
  margin-left: 0.35rem;
  color: var(--accent);
  transition: transform 0.18s var(--ease);
}
.finding__link:hover span { transform: translateX(4px); }

/* ------------------------------------------------------------------ *
   13. TOOLS
 * ------------------------------------------------------------------ */
.tools .prose p { font-size: 1.25rem; }
.tools__actions { margin-top: var(--space-4); }

/* ------------------------------------------------------------------ *
   14. ABOUT ME
 * ------------------------------------------------------------------ */
.aboutme__grid {
  display: grid;
  grid-template-columns: minmax(180px, 240px) 1fr;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}

/* Circular portrait with a faint accent ring + a circuit-node dot on the rim */
.aboutme__photo {
  position: relative;
  margin: 0;
  width: 100%;
}
.aboutme__photo img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  object-position: 50% 50%;   /* crop centered so the face sits upper-middle */
  border-radius: 50%;
  border: 1px solid var(--hairline);
  box-shadow: 0 0 0 7px var(--paper),
              0 0 0 8px rgba(107, 47, 201, 0.18),
              var(--shadow-soft);
}
.aboutme__photo::after {
  content: "";
  position: absolute;
  right: 8%;
  bottom: 11%;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px var(--bg);
}

.aboutme__body p { color: var(--soft-ink); font-size: 1.0625rem; }
.aboutme__body p:first-child {        /* the greeting reads as a lead-in */
  font-size: 1.18rem;
  color: var(--ink);
}
.about__actions {
  margin-top: var(--space-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

@media (max-width: 768px) {
  .aboutme__grid { grid-template-columns: 1fr; justify-items: center; text-align: left; }
  .aboutme__photo { max-width: 220px; margin-inline: auto; }
}

/* ------------------------------------------------------------------ *
   15. FOOTER
 * ------------------------------------------------------------------ */
.footer {
  border-top: 1px solid var(--hairline);
  padding-block: var(--space-5);
}
.footer__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.footer a {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--soft-ink);
}
.footer a:hover { color: var(--accent-deep); }

/* 3D-model attribution — small, muted line under the main footer row. */
.footer__credit {
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.03em;
  line-height: 1.7;
  color: var(--soft-ink);
  opacity: 0.85;
}
.footer__credit + .footer__credit {
  margin-top: 0;                       /* sit directly under the line above */
  line-height: 1.3;                    /* tighter leading between the two credit lines */
}
.footer__credit a {
  font-size: inherit;                  /* sit inline with the sentence, not the 0.8rem links */
  letter-spacing: inherit;
  text-decoration: underline;
}

/* ------------------------------------------------------------------ *
   16. MOTION — Phase 2: scroll-driven reveals (GSAP + ScrollTrigger)
   CSS here defines ONLY the pre-reveal hidden state, and ONLY when the
   `js` class is present on <html>. Without JavaScript the whole page is
   fully visible. GSAP animates elements into place (scripts/scroll.js).
 * ------------------------------------------------------------------ */

/* Reveal targets start hidden only when JS is active (prevents a flash,
   while staying fully visible if JS never runs). */
html.js .hero > *,
html.js .doc-hero__text > *,
html.js .doc-hero__art,
html.js .figure,
html.js .section__head > *,
html.js .idea .prose > *,
html.js .tools .prose > *,
html.js .journey__step > *,
html.js .journey__media,
html.js .buildpath__title,
html.js .buildpath__kicker,
html.js .buildpath-intro__lead,
html.js .buildpath-intro__sub,
html.js .buildpath-intro__note,
html.js .buildpath-intro__code,
html.js .buildpath-intro__end,
html.js .bp-tag,
html.js .bp-step,
html.js .epigraph blockquote,
html.js .ladder .step,
html.js .findings .finding,
html.js .handson-col > *,
html.js .handson-intro,
html.js .getstarted__lead,
html.js .getstarted__cta,
html.js .page-logic .prose > *,
html.js .page-logic .checklist > *,
html.js .page-logic .table-wrap {
  opacity: 0;
}
/* Figures nested inside a Hands On widget ride their card's own reveal, so they
   aren't hidden/animated separately (scroll.js skips them). */
html.js .handson-grid .figure { opacity: 1; }
/* (.aboutme content is revealed by a CSS animation on the About page itself, so
   it doesn't wait on the deferred GSAP CDN — see about/index.html.) */

/* Learn path: the connector line is "drawn" downward (scaleY from the top)
   and the node dots "pop" in — both driven by GSAP via CSS variables.
   Defaults (1) mean fully drawn, so without JS the path looks normal. */
.ladder::before {
  transform: scaleY(var(--line-progress, 1));
  transform-origin: top center;
}
.step::before {
  transform: scale(var(--node-scale, 1));
}
html.js .ladder { --line-progress: 0; }
html.js .ladder .step { --node-scale: 0; }
/* Build-path trace starts undrawn under JS; scroll.js draws it as the reader
   scrolls. Default (1) keeps it fully drawn with JS off. */
html.js .buildpath-intro { --rail-progress: 0; }

/* Reduced motion: never hide, never move — show the final state instantly.
   (scripts/scroll.js also disables all animation in this case.) */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  html.js .hero > *,
  html.js .doc-hero__text > *,
  html.js .doc-hero__art,
  html.js .figure,
  html.js .section__head > *,
  html.js .idea .prose > *,
  html.js .tools .prose > *,
  html.js .epigraph blockquote,
  html.js .ladder .step,
  html.js .buildpath-intro__lead,
  html.js .buildpath-intro__sub,
  html.js .buildpath-intro__note,
  html.js .buildpath-intro__code,
  html.js .buildpath-intro__end,
  html.js .findings .finding,
  html.js .handson-col > *,
  html.js .handson-intro,
  html.js .getstarted__lead,
  html.js .getstarted__cta,
  html.js .page-logic .prose > *,
  html.js .page-logic .checklist > *,
  html.js .page-logic .table-wrap,
  html.js .aboutme__photo,
  html.js .aboutme__body > * {
    opacity: 1 !important;
    transform: none !important;
  }
  /* Show the Learn path fully, undrawn-state disabled. */
  html.js .ladder { --line-progress: 1 !important; }
  html.js .ladder .step { --node-scale: 1 !important; }
  /* Build-path trace fully drawn, no scrub. */
  html.js .buildpath-intro { --rail-progress: 1 !important; }
}

/* ------------------------------------------------------------------ *
   17. RESPONSIVE
 * ------------------------------------------------------------------ */

/* Tablet and below: the hamburger replaces the inline links and opens the
   project drawer (the same panel the desktop nav item opens). */
@media (max-width: 768px) {
  .nav-toggle { display: inline-flex; }   /* hamburger appears */
  .nav { display: none; }                 /* inline links collapse into the drawer */
  /* With the center nav gone, drop back to a simple two-end layout so the toggle
     group doesn't slide into the middle grid track. */
  .topbar__inner { display: flex; justify-content: space-between; }

  /* Active hamburger becomes an X */
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars {
    background: transparent;
  }
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars::before {
    transform: translateY(0) rotate(45deg);
  }
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars::after {
    transform: translateY(-2px) rotate(-45deg);
  }

  /* Tighter vertical rhythm on smaller screens */
  :root { --space-7: 5rem; --space-6: 3.5rem; }

  .finding { grid-template-columns: 1fr; row-gap: 0.5rem; }
  .finding__num { grid-row: auto; }
}

/* Phones */
@media (max-width: 480px) {
  body { font-size: 1rem; }
  .ladder { padding-left: 2rem; }
  .step::before { left: calc(-2rem + 1px); }
  .hero__actions .btn { width: 100%; justify-content: center; }
}

/* ------------------------------------------------------------------ *
   18. DARK THEME (homepage) — a die-shot-inspired dark surface with a
   soft purple glow, echoing the ALU "On the die" band. Scoped to
   <body class="theme-dark"> so other pages stay on the light theme.
   It mostly works by re-pointing the design tokens; each section then
   gets its own gradient so the glow shifts color/position as you scroll
   (similar mood, different per section).
 * ------------------------------------------------------------------ */
.theme-dark {
  --bg:          #1b1b22;   /* die-shot base */
  --paper:       #24242e;   /* raised dark surface (cards, nodes) */
  --ink:         #fdfdfb;   /* primary text */
  --soft-ink:    rgba(253, 253, 251, 0.72);
  --accent:      #b794f6;   /* lighter purple reads on dark (matches the die kicker) */
  --accent-deep: #d4bbff;   /* hover — lighter still */
  --hairline:    rgba(253, 253, 251, 0.14);
  --grid-line:   rgba(253, 253, 251, 0.045);

  --shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.30),
                 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-lift: 0 2px 6px rgba(0, 0, 0, 0.35),
                 0 16px 40px rgba(0, 0, 0, 0.50);
}

/* Primary button: the accent is light on dark, so use dark text for contrast. */
.theme-dark .btn--primary { color: #15151b; }
.theme-dark .btn--primary:hover { color: #15151b; }

/* Inline code: a touch more presence on the dark surface. */
.theme-dark code {
  background: rgba(183, 148, 246, 0.16);
  color: #d9c6ff;
}
/* Control signals (operation, Ainvert, Binvert, sel, clk) in inline <code> wear
   the same light blue they have on the back of the Verilog cards (the control-chip
   blue), rather than the default code purple. */
code.ctrl-sig,
.theme-dark code.ctrl-sig {
  color: #4a90d9;
  background: rgba(74, 144, 217, 0.14);
}

/* Skip link stays visible when focused on dark. */
.theme-dark .skip-link { background: var(--accent); color: #15151b; }

/* One continuous surface: a single ambient glow fixed to the viewport, with the
   section dividers removed so the whole homepage reads as one big page. */
.theme-dark {
  background:
    radial-gradient(75% 55% at 50% 0%,
                    rgba(107, 47, 201, 0.30), transparent 60%),
    #1b1b22;
  background-attachment: fixed;
}
.theme-dark .section,
.theme-dark .cpu-stage,
.theme-dark .epigraph,
.theme-dark .footer { border-top: 0; }

/* Drop the engineering-grid texture on the dark theme — just the smooth glow. */
.theme-dark.theme-dark::before { display: none; }


/* ------------------------------------------------------------------ *
   HANDS ON — an intro line, then a two-up grid of interactive widgets,
   slightly staggered up/down for a livelier, non-grid feel. Built to hold
   more widgets over time (they flow left/right down the grid).
 * ------------------------------------------------------------------ */
/* Pull the Hands On section up (less top padding than a stock section). */
/* Extra breathing room above Hands On so it sits clear of the end of Your Path
   (roughly the title-to-first-card distance). */
.findings-section { padding-top: var(--space-5); margin-top: clamp(7rem, 14.5vw, 12rem); }
.handson-intro {
  max-width: 60rem;
  margin: 0 auto var(--space-5);
  text-align: center;
  /* Same text style as the "Start with the basics…" line in Your Path. */
  font-family: var(--font-body);
  font-size: clamp(1.1rem, 1.35vw, 1.3rem);
  line-height: 1.62;
  letter-spacing: 0;
  color: var(--ink);
}
/* Two masonry columns at EVERY width (they never collapse to one) — the layout
   just resizes with the fluid root font-size. Left: D-latch + datapath. Right:
   Check Yourself + waveform. */
.handson-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.25rem, 4vw, 3.75rem);
  align-items: start;
}
/* Widgets fill their column rather than their own centered max-widths. */
.handson-grid > * { width: 100%; }
/* In a half-width column the answer choices read better stacked. */
.handson-grid .quiz__options { grid-template-columns: 1fr; }
/* Each side is its own column that stacks its widgets independently (a masonry
   layout), so the two columns pack tightly no matter how tall the other is. */
.handson-col { display: flex; flex-direction: column; gap: clamp(1.75rem, 3vw, 2.75rem); min-width: 0; }
/* Title in the top-left of each Hands On widget card — the display face used by
   the section headings (e.g. "Check Yourself" on the Logic Gates page). */
.hoi-title {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
  font-size: clamp(1.15rem, 1.7vw, 1.4rem);
  text-align: center;
  margin: 0 0 var(--space-3);
}
.aluwave .hoi-title { margin-bottom: 0; }   /* the card's flex gap sets the spacing */
.handson-grid .quiz { max-width: none; margin-inline: 0; }
/* The gated D-latch card is kept smaller and centered within its cell. */
.handson-grid .gate-card--solo { max-width: 29rem; margin-inline: auto; }

/* ALU datapath explorer in the grid: the datapath + the control panel share ONE
   card, styled exactly like the Check Yourself card (paper bg, hairline border,
   soft shadow, rounded). The datapath keeps its own white plate inside so the
   schematic — drawn with dark ink/idle colors — stays legible on the dark card;
   the control panel sits straight on the card and reads in the dark theme. */
.handson-grid .alu-widget {
  display: block;
  margin-top: 0;
  /* Same card as .quiz. */
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: clamp(var(--space-4), 5vw, var(--space-5));
  box-shadow: var(--shadow-soft);
}
/* The datapath's white plate: light tokens so the schematic reads dark-on-white,
   flush to the top of the card and filling its full width. */
.handson-grid .alu-widget__stage {
  --paper:       #FCFCF9;
  --ink:         #23232A;
  --soft-ink:    #56565F;
  --accent:      #6B2FC9;
  --accent-deep: #4F1F9E;
  --hairline:    rgba(35, 35, 42, 0.12);
  --alu-active:   var(--accent);
  --alu-active-2: var(--accent-deep);
  --alu-invert:   var(--accent);
  --alu-idle-ink: var(--soft-ink);
  width: auto;
  margin: 0;
  justify-self: stretch;
  background: #FCFCF9;
  border: 1px solid rgba(35, 35, 42, 0.12);
  border-radius: var(--radius-sm);
  padding: 0.35rem;
}
.handson-grid #alu-diagram { width: 100%; height: auto; }   /* fill width; height follows 685:970 */
/* The control panel: transparent on the shared card, offset by a hairline. */
.handson-grid .alu-panel {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: var(--space-3) 0 0;
  margin-top: var(--space-3);
  border-top: 1px solid var(--hairline);
  gap: var(--space-3);
}
/* Trim the control panel down a notch to sit comfortably in the grid cell. */
.handson-grid .alu-controls__group { gap: 0.45rem; }
.handson-grid .alu-readout { padding-bottom: var(--space-2); }
.handson-grid .alu-readout__name { font-size: 1.7rem; }
.handson-grid .alu-preset { padding: 0.4rem 0.35rem; font-size: 0.7rem; }
.handson-grid .alu-bit { padding: 0.4rem 0.15rem; }
.handson-grid .alu-bit__val { font-size: 0.95rem; }

/* "See the code" card: a paper panel that links out to the GitHub source, with
   the GitHub mark on the left and a nudging arrow on the right. Mirrors the
   .code-card rules in styles/alu.css (this stylesheet serves the homepage). */
.code-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: clamp(var(--space-3), 3vw, var(--space-4));
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-soft);
  color: var(--ink);
  text-decoration: none;
  transition: transform 0.18s var(--ease), box-shadow 0.18s var(--ease),
              border-color 0.18s var(--ease);
}
.code-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
  border-color: var(--accent);
}
.code-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.code-card__logo { flex: none; width: 2.25rem; height: 2.25rem; fill: var(--ink); }
.code-card__text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.code-card__title {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
}
.code-card__sub { color: var(--soft-ink); font-size: 0.9rem; }
.code-card__arrow {
  margin-left: auto;
  flex: none;
  color: var(--accent);
  font-size: 1.2rem;
  transition: transform 0.18s var(--ease);
}
.code-card:hover .code-card__arrow { transform: translateX(3px); }


/* ------------------------------------------------------------------ *
   ALU WAVEFORM — a static timing diagram of the 1-bit ALU. A signal-name
   panel on the left, a 10 ns time scale on top, and the traces on the
   right, color-coded by role: data inputs (purple), the control line
   (datapath blue), the output (Meet-the-Processor amber). The ALU sketch
   sits on top. Everything rides white plates on the usual paper card.
 * ------------------------------------------------------------------ */
.aluwave {
  position: relative;          /* anchors the hover tooltip */
  margin: 0;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-soft);
  padding: clamp(var(--space-3), 4vw, var(--space-4));
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
/* The 1-bit ALU sketch on top, framed on its own white plate. */
.aluwave__art {
  background: #fff;
  border: 1px solid rgba(35, 35, 42, 0.12);
  border-radius: var(--radius-sm);
  padding: clamp(0.5rem, 2vw, 0.9rem);
  margin: 0;
  text-align: center;
}
.aluwave__art img {
  max-width: 100%;
  max-height: 15rem;
  width: auto;
  height: auto;
  display: inline-block;
  border-radius: 4px;
}
/* The scope: transparent on the card with a top hairline — the SAME treatment as
   the datapath's control panel (not a white plate). Its SVG is colored for the
   dark card. Scrolls horizontally on narrow screens rather than squishing. */
.aluwave__scope {
  background: transparent;
  border: 0;
  border-top: 1px solid var(--hairline);
  padding: var(--space-3) 0 0;
  overflow-x: auto;
  /* Let a finger scroll the page vertically over the widget, but claim
     horizontal drags for the scrubber (aluwave.js) instead of panning. */
  touch-action: pan-y;
}
.awv-svg {
  display: block;
  width: 100%;
  min-width: 32rem;              /* keep it legible; the panel scrolls if narrower */
  height: auto;
  font-family: var(--font-mono);
}
/* Panel/header dividers, per-row separators, + step gridlines (light on dark). */
.awv-div    { stroke: var(--hairline); stroke-width: 1; }
.awv-rowsep { stroke: rgba(253, 253, 251, 0.06); stroke-width: 1; }
.awv-grid   { stroke: rgba(253, 253, 251, 0.06); stroke-width: 1; }
/* Header labels ("Signal", "Time (ns)"). */
.awv-hdr  { font-size: 10px; font-weight: 600; letter-spacing: 0.06em; fill: var(--soft-ink); }
/* Time-scale numbers — the display face reads friendlier than the mono. */
.awv-time { font-family: var(--font-display); font-size: 11px; fill: var(--soft-ink); }
/* Signal names in the left panel, colored by role (dark-card readable). */
.awv-name { font-size: 11px; font-weight: 500; }
.awv-name--in   { fill: #b794f6; }   /* inputs — light purple (the dark-theme accent) */
.awv-name--ctrl { fill: #60a5fa; }   /* control — light blue */
.awv-name--out  { fill: #d4bbff; }   /* output — the flip card's output value number */
.awv-name--muted { font-size: 9px; fill: rgba(253, 253, 251, 0.42); }   /* a hidden signal's strip label */
/* Traces. */
.awv-trace { fill: none; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.awv-trace--in   { stroke: #b794f6; }
.awv-trace--ctrl { stroke: #60a5fa; }
.awv-trace--out  { stroke: #d4bbff; stroke-width: 3; }
/* Colorless show/hide control: an eye (open = shown, slashed = hidden). */
.awv-toggle { cursor: pointer; }
.awv-toggle:focus { outline: none; }
.awv-eye__lid { fill: none; stroke: var(--soft-ink); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.awv-eye__pupil { fill: var(--soft-ink); }
.awv-eye__slash { stroke: var(--soft-ink); stroke-width: 1.4; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.awv-toggle:hover .awv-eye__lid,
.awv-toggle:hover .awv-eye__slash { stroke: var(--ink); }
.awv-toggle:hover .awv-eye__pupil { fill: var(--ink); }
.awv-toggle--off .awv-eye__lid { stroke: rgba(253, 253, 251, 0.4); }
.awv-toggle--off .awv-eye__pupil { fill: rgba(253, 253, 251, 0.4); }
.awv-toggle:focus-visible .awv-eye__lid { stroke: var(--accent); }
/* Travelling bead — EXACTLY the datapath treatment: a fat, round-capped dash
   that rides the trace like a bulge, color set per signal + a soft glow.
   Hidden at rest; the JS animation reveals it. */
.awv-bead {
  fill: none;
  stroke: currentColor;
  stroke-width: 6.5;
  stroke-linecap: round;
  stroke-dasharray: 12 100000;
  opacity: 0;
  pointer-events: none;
  filter: drop-shadow(0 0 3px currentColor);
}

/* Hover scrubber: a vertical guide through the cursor + a value tooltip. */
.awv-cursor {
  stroke: #f5c542;                     /* warm terminal-yellow */
  stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
  opacity: 0;
  pointer-events: none;
}
.awv-cursor.is-on { opacity: 0.92; }
.awv-tip {
  position: absolute;
  left: 0; top: 0;
  z-index: 5;
  pointer-events: none;
  min-width: 6rem;
  background: rgba(18, 18, 24, 0.96);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.55rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  line-height: 1.5;
  box-shadow: var(--shadow-soft);
  opacity: 0;
  transition: opacity 0.08s var(--ease);
}
.awv-tip.is-on { opacity: 1; }
.awv-tip__t { color: var(--soft-ink); font-size: 0.6rem; margin-bottom: 0.2rem; }
.awv-tip__row { display: flex; justify-content: space-between; gap: 1rem; font-weight: 600; }

/* ALU-slice waveform extra (slice-wave.js): the 2-bit Operation bus rendered as a
   ribbon with its binary value written in. Reuses the .awv-* palette above. */
.awv-busval { font-family: var(--font-mono); font-size: 9px; font-weight: 600; fill: #60a5fa; }
/* Zoom the datapath sketch to fill more of its plate without changing the plate's
   footprint — the plate clips the scaled overflow (mostly the drawing's margins). */
.slicewave .aluwave__art { overflow: hidden; }
.slicewave .aluwave__art img { transform: scale(1.3); }

/* In the right column it's capped and centered. */
.handson-grid .aluwave { max-width: 46rem; margin-inline: auto; width: 100%; }
/* Push the waveform (and the "See the code" card trailing it) down a little so
   the pair sits higher than dead-center against the tall datapath — roughly half
   the offset that would center it. The push grows with width (the datapath gets
   taller), so it still tracks the viewport. */
.handson-grid .aluwave { margin-top: clamp(0.15rem, 9vw - 110px, 1.5rem); }

/* Phones + smaller devices: the placement script (index.html) relocates the
   waveform to be a DIRECT child of the grid, where these rules make it span the
   whole row and open up wide — the timing diagram reads poorly squeezed into a
   half-column. Above the breakpoint it lives back in the right column (the two
   rules just above apply instead). Breakpoint lives in the mover script. */
.handson-grid > .aluwave {
  grid-column: 1 / -1;
  max-width: none;
  margin-inline: 0;
  margin-top: clamp(1.5rem, 5vw, 3rem);
}

/* Phones + smaller devices (matches the waveform mover's 820px breakpoint):
   stack the Hands On widgets in one column so the Check Yourself card spans the
   full screen width instead of being squeezed into a half-column, and let the
   waveform scale down to fit — drop its legibility floor so the timing diagram
   shrinks to the viewport rather than forcing a horizontal scrollbar. */
@media (max-width: 820px) {
  .handson-grid { grid-template-columns: 1fr; }
  .aluwave__scope { overflow-x: visible; }
  .awv-svg { min-width: 0; }
}

/* ------------------------------------------------------------------ *
   HOW TO GET STARTED — intro copy + a "Open the Project Directory"
   button with a nudge alongside it.
 * ------------------------------------------------------------------ */
.getstarted { max-width: 62rem; margin-inline: auto; }
/* The main line — same treatment as the Hands On intro paragraph. */
.getstarted__lead {
  max-width: 60rem;
  margin: 0 auto var(--space-5);
  text-align: center;
  font-family: var(--font-body);
  font-size: clamp(1.1rem, 1.35vw, 1.3rem);
  line-height: 1.62;
  letter-spacing: 0;
  color: var(--ink);
}
.getstarted__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(0.75rem, 2vw, 1.25rem);
}


/* ------------------------------------------------------------------ *
   CHECK-YOURSELF QUIZ — a multiple-choice widget (mirrors the ALU /
   Logic Gates quiz format): a diagram, answer buttons that turn green
   (correct) or red (wrong) on click, and a status line.
 * ------------------------------------------------------------------ */
.quiz {
  max-width: 52rem;
  margin-inline: auto;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: clamp(var(--space-4), 5vw, var(--space-5));
  box-shadow: var(--shadow-soft);
}
.quiz__q {
  font-family: var(--font-body);
  font-size: clamp(1.2rem, 2.2vw, 1.5rem);
  font-weight: 400;
  font-style: italic;
  text-align: center;
  margin: 0 0 var(--space-3);
}
.quiz__figure { max-width: 48rem; margin: 0 auto var(--space-4); }
/* Round the diagram image to match the site's other images (paper-card plates). */
.quiz__figure img { display: block; width: 100%; height: auto; border-radius: var(--radius-sm); }
.quiz__options {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2);
  counter-reset: quizopt;
}
.quiz__opt {
  counter-increment: quizopt;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  text-align: left;
  font-family: var(--font-display);
  font-size: 1.05rem;
  padding: 0.9rem 1.1rem;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background-color 0.15s var(--ease),
              border-color 0.15s var(--ease), color 0.15s var(--ease);
}
/* Numbered prefix before each choice: 1) 2) 3) 4) */
.quiz__opt::before {
  content: counter(quizopt) ")";
  flex: none;
  color: var(--accent);
  font-family: var(--font-mono);
  font-weight: 600;
}
.quiz__opt:hover { border-color: var(--accent); }
.quiz__opt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Correct pick — light green fill. */
.quiz__opt.is-correct { background: #86efac; border-color: #86efac; color: #052e16; }
/* Wrong pick — reddish fill, kept filled after the click. */
.quiz__opt.is-wrong { background: #e57373; border-color: #e57373; color: #3d0a0a; }
/* Once solved, lock the buttons so the answered state stays put. */
.quiz.is-solved .quiz__opt { pointer-events: none; }

.quiz__feedback {
  min-height: 1.5em;
  margin: var(--space-3) 0 0;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 500;
}
.quiz__feedback.is-correct { color: #4ade80; }
.quiz__feedback.is-wrong { color: #f87171; }

/* Revealed only after the correct answer is chosen (toggled by the class). */
.quiz__next {
  display: none;
  margin-top: var(--space-4);
  justify-content: center;
}
.quiz__next.is-shown { display: flex; }

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

/* ------------------------------------------------------------------ *
   FLIP CARD + VERILOG EDITOR — a card whose front sketch flips to an
   editable Verilog playground on the back. Mirrors the Logic Gates /
   1-bit ALU flip cards (styles/alu.css); duplicated here because the
   homepage loads only main.css. Used by the Hands On gated-D-latch card.
 * ------------------------------------------------------------------ */
.gate-card { position: relative; perspective: 1200px; }
/* A standalone flip card (not in a grid) — capped + centered. */
.gate-card--solo { max-width: 30rem; margin-inline: auto; }
.gate-card__inner {
  display: grid;                    /* stack both faces → identical footprint */
  transform-style: preserve-3d;
  transition: transform 0.6s var(--ease);
}
.gate-card.is-flipped .gate-card__inner { transform: rotateY(180deg); }
.gate-card__face {
  grid-area: 1 / 1;                 /* overlap the faces in one cell */
  position: relative;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
.gate-card:not(.is-flipped) .gate-card__face--back { pointer-events: none; }
.gate-card.is-flipped .gate-card__face--front { pointer-events: none; }

/* Front: the clickable sketch on a paper card (frame = the site's .figure look:
   paper bg, hairline border, soft shadow), enlarging slightly on hover/focus. */
.gate-card__face--front {
  cursor: pointer;
  margin: 0;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-soft);
  padding: clamp(var(--space-2), 3vw, var(--space-4));
  padding-bottom: 2.1rem;           /* room for the flip arrow */
  transition: transform 0.2s var(--ease);
  text-align: center;
}
.gate-card:not(.is-flipped) .gate-card__face--front:hover,
.gate-card__face--front:focus-visible { transform: scale(1.045); }
.gate-card__face--front:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
/* Caption sits below the image, split off by a faint top rule (matches .figure). */
.gate-card__face--front figcaption {
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px solid var(--hairline);
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--soft-ink);
  text-align: center;
}

/* Mono eyebrow naming the part at the top of the caption. */
.gate-name {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent);
  margin-bottom: 0.35rem;
}

/* Two stacked front sketches on a fixed-height white tile that cross-fade on a
   timer (fixed height so the card never resizes as the image swaps). */
.latch-swap {
  position: relative;
  display: block;
  width: 100%;
  height: clamp(12rem, 42vw, 18rem);
  margin-bottom: var(--space-2);
  background: #fff;
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.latch-swap__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.8s var(--ease);
}
.latch-swap__img.is-active { opacity: 1; }

/* Back: a column that fills the card — editor on top, controls below. Only the
   return arrow flips back (wired in gate-card.js); the frame is inert. */
.gate-card__face--back {
  transform: rotateY(180deg);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-soft);
  padding: clamp(var(--space-2), 2.5vw, var(--space-3));
  transition: transform 0.2s var(--ease);
}
/* Hovering the return arrow bulges the whole card a touch — the mirror of the
   cue the front gives before its flip — rather than recoloring the arrow. */
.gate-card.is-flipped .gate-card__face--back:has(.gate-card__return:hover) {
  transform: rotateY(180deg) scale(1.045);
}

/* A miniature VS Code editor filling the card. */
.code-editor {
  margin: 0;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border: 1px solid #333;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #1e1e1e;
  font-family: Consolas, "Courier New", ui-monospace, monospace;
  text-align: left;
}
.code-editor__bar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.35rem 0.3rem 0.6rem;
  background: #252526;
  border-bottom: 1px solid #333;
  color: #cccccc;
  font-size: 0.62rem;
  letter-spacing: 0.02em;
}
/* The </> mark — literal text so it reads exactly as typed, three distinct
   characters with a little tracking between them. */
.code-editor__glyph {
  flex: none;
  color: #569cd6;
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.12em;
}
/* The </> glyph + "Verilog HDL" label sit on the LEFT; `margin-right: auto` on the
   label pushes the Reset button over to the right. */
.code-editor__lang { margin-right: auto; }
.code-editor__reset {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.12rem 0.4rem;
  border: 1px solid #3a3a3a;
  border-radius: 4px;
  background: none;
  color: #cccccc;
  font-family: inherit;
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
}
.code-editor__reset:hover { background: #37373d; color: #fff; border-color: #4a4a4a; }
.code-editor__reset svg {
  width: 0.8rem;
  height: 0.8rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Copy action in the titlebar — a Windows-Explorer-style two-sheets glyph plus a
   "Copy" label (injected by main.js) that copies the block's exact code. Shares
   the Reset button's chrome (icon + uppercase text). Sits at the far right, or to
   the left of Reset when a block has one. Kept in sync with styles/alu.css. */
.code-editor__copy {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.12rem 0.4rem;
  border: 1px solid #3a3a3a;
  border-radius: 4px;
  background: none;
  color: #cccccc;
  font-family: inherit;
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
}
.code-editor__copy:hover { background: #37373d; color: #fff; border-color: #4a4a4a; }
.code-editor__copy svg {
  width: 0.8rem;
  height: 0.8rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.code-editor__copy .code-editor__copy-ico--done,
.code-editor__copy .code-editor__copy-txt--done { display: none; color: #6ec06e; }
.code-editor__copy.is-copied { border-color: #3a6a3a; color: #6ec06e; }
.code-editor__copy.is-copied .code-editor__copy-ico--copy,
.code-editor__copy.is-copied .code-editor__copy-txt--copy { display: none; }
.code-editor__copy.is-copied .code-editor__copy-ico--done { display: inline; }
.code-editor__copy.is-copied .code-editor__copy-txt--done { display: inline; }
.code-editor__body { position: relative; flex: 1; min-height: 5.5rem; overflow: hidden; }
.code-editor__hl,
.code-editor__ta {
  margin: 0;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  padding: 0.55rem 0.7rem;
  font-family: inherit;
  font-size: 0.85rem;
  line-height: 1.55;
  letter-spacing: 0;
  tab-size: 2;
  white-space: pre;
}
.code-editor__hl {
  position: absolute;
  inset: 0;
  overflow: hidden;
  color: #d4d4d4;
  background: #1e1e1e;
  pointer-events: none;
}
.code-editor__hl code { font: inherit; background: none; color: inherit; padding: 0; white-space: inherit; }
.code-editor__ta {
  position: absolute;
  inset: 0;
  border: 0;
  resize: none;
  overflow: auto;
  color: transparent;
  background: transparent;
  caret-color: #d4d4d4;
}
.code-editor__ta:focus { outline: none; }
.code-editor__ta::selection { background: rgba(38, 79, 120, 0.75); color: transparent; }
/* VS Code Dark+ Verilog token colors. */
.tok-kw { color: #569cd6; }
.tok-cm { color: #6a9955; }
.tok-op { color: #b794f6; }              /* operators: = & | ^ ~ ? + - == && … */
.tok-num { color: #b5cea8; }             /* numeric literals */
.tok-type { color: #4ec9b0; }            /* module names — declared or instantiated */
.tok-inst { color: #dcdcaa; }            /* instance names in a module instantiation */
.tok-paren { color: #ffd700; }           /* parentheses */
/* Syntax error — red wavy underline under the offending code (VS Code style). */
.tok-err {
  text-decoration: underline wavy #f14c4c;
  text-decoration-skip-ink: none;
  text-underline-offset: 2px;
}

/* Bottom row of the back: the live output box + the return button bottom-right. */
.gate-card__backrow { display: flex; align-items: stretch; gap: 0.5rem; }
.gate-card__backrow .gate-sim { flex: 1; min-width: 0; }

/* Output box: a toggle chip per input wire + the live value of the output. */
.gate-sim {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.02);
  font-family: var(--font-mono);
}
.gate-sim__inputs { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.gate-sim__chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.12rem 0.45rem;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--paper);
  color: var(--soft-ink);
  font: inherit;
  font-size: 0.68rem;
  cursor: pointer;
}
/* Data inputs (d) keep the purple accent; the control signal (clk) wears the
   light blue used for the hand-drawn example signals on the diagrams. */
.gate-sim__chip b { color: var(--accent); font-weight: 600; }
.gate-sim__chip[data-val="1"] {
  border-color: var(--accent);
  color: var(--accent-deep);
  background: rgba(107, 47, 201, 0.06);
}
.gate-sim__chip--control b { color: #4a90d9; }
.gate-sim__chip--control[data-val="1"] {
  border-color: #4a90d9;
  color: #2f6fb0;
  background: rgba(74, 144, 217, 0.08);
}
.gate-sim__out { font-size: 0.82rem; color: var(--soft-ink); white-space: nowrap; }
/* When a card shows more than one output (e.g. a full adder's sum + cout), the
   readouts sit together as one group on the right of the sim bar. */
.gate-sim__outs { display: flex; align-items: center; gap: 0.15rem 0.85rem; flex-wrap: wrap; }
.gate-sim__val { font-weight: 600; font-size: 1rem; color: var(--accent-deep); }
.gate-sim__val[data-bad="1"] { color: var(--soft-ink); }
/* The instruction-memory sim is short, so its return arrow was stubby next to the
   taller data-memory one. Match the data-memory arrow's height for consistency. */
.mem-card--imem .gate-card__return { min-height: 7.5rem; }
/* The taller arrow steals height from the code editor; grow the card a touch so
   the Verilog block fits without a scrollbar. */
.mem-card--imem .code-editor { min-height: 26rem; }

.gate-card__return {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.3rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--accent);
  cursor: pointer;
}
/* The arrow doesn't recolor on hover — hovering it grows the whole card
   instead (see .gate-card__face--back:has(.gate-card__return:hover) above). */
.gate-card__return svg {
  width: 1.15rem;
  height: 1.15rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Flip arrow on the front — bottom-right square box. */
.gate-card__flip {
  position: absolute;
  right: 0.7rem;
  bottom: 0.55rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--accent);
  pointer-events: none;
}
.gate-card__flip svg {
  width: 1.2rem;
  height: 1.2rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
@media (prefers-reduced-motion: reduce) {
  .gate-card__inner,
  .gate-card__face--front,
  .gate-card__face--back,
  .latch-swap__img { transition: none; }
}

/* ==========================================================================
   GLOSSARY TERM POPUP — a word in the prose that opens its definition centered
   on a blurred backdrop (the "doped" aside on Transistor Basics; the behavior
   lives in scripts/main.js section 4).
   ========================================================================== */

/* The word itself: purple with a dashed underline and a small question mark,
   so it reads as something to press rather than a link out of the page. The
   rule is drawn with text-decoration rather than a border, so its distance
   from the letters can be tuned: a border would sit at the bottom of the
   inline box, well below the baseline. */
.glossary-term {
  font: inherit;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color 0.15s var(--ease);
}
.glossary-term::after {
  content: "?";
  /* inline-block, not inline: a parent's underline propagates through inline
     children and `text-decoration: none` cannot cancel it, but it is never
     drawn through an atomic inline box. This keeps the rule under the word. */
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.62em;
  vertical-align: super;
  line-height: 0;
  margin-left: 0.15em;
  opacity: 0.85;
}
/* Hover brightens the word only. No highlight block behind it. */
.glossary-term:hover,
.glossary-term:focus-visible { color: var(--ink); }

/* Same blurred backdrop as an enlarged figure, so the two read as one idea. */
.glossary-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vmin;
  background: rgba(12, 12, 18, 0.55);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  opacity: 0;
  transition: opacity 0.22s ease;
}
.glossary-overlay.is-open { opacity: 1; }

.glossary-card {
  /* Wide enough for the three-lattice doping sketch to stay legible. */
  width: min(68rem, 100%);
  max-height: 86vh;
  overflow: auto;
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5);
  padding: clamp(var(--space-3), 3vw, var(--space-4));
  transform: scale(0.94);
  transition: transform 0.22s ease;
}
.glossary-overlay.is-open .glossary-card { transform: scale(1); }

/* Title hard left, close button hard right. */
.glossary-card__head {
  display: flex;
  /* Centred so the X sits on the same line as the middle of the title. */
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.glossary-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--accent);
}
.glossary-card__close {
  flex: none;
  width: 2.75rem;
  height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--soft-ink);
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.glossary-card__close svg {
  width: 1.5rem;
  height: 1.5rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}
.glossary-card__close:hover {
  color: var(--ink);
  border-color: var(--accent);
}

.glossary-card__body p { margin: 0; }
.glossary-card__body p + p { margin-top: var(--space-3); }

/* A diagram inside a definition: full width of the card, on its own white tile
   so a sketch drawn on white does not float on the dark card. */
.glossary-fig {
  margin: var(--space-3) 0;
  padding: var(--space-2);
  background: #fff;
  border-radius: var(--radius-sm);
}
.glossary-fig img {
  display: block;
  width: 100%;
  height: auto;
}

/* ==========================================================================
   ENLARGE — static figure images pop on hover and open enlarged on a blurred
   backdrop when clicked (see scripts/enlarge.js, which tags .enlargeable and
   builds the .enlarge-overlay). The whole card scales, not just the image.
   ========================================================================== */
.enlargeable {
  cursor: pointer;
  transition: transform .18s ease, box-shadow .18s ease;
  transform-origin: center;
}
.enlargeable:hover,
.enlargeable:focus-visible {
  transform: scale(1.03);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .45);
}
.enlargeable:focus-visible { outline: none; }

.enlarge-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vmin;
  background: rgba(12, 12, 18, 0.55);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  opacity: 0;
  transition: opacity .22s ease;
  cursor: pointer;
}
.enlarge-overlay.is-open { opacity: 1; }

/* The cloned card, enlarged and centered — keeps its own background, border and
   radius, but scaled up and free of its in-page layout limits. */
.enlarge-overlay .figure,
.enlarge-overlay .doc-hero__art,
.enlarge-overlay .flow-figure,
.enlarge-overlay .aboutme__photo {
  margin: 0;
  width: auto;
  max-width: min(92vw, 1000px);
  box-shadow: 0 30px 80px rgba(0, 0, 0, .5);
  transform: scale(.94);
  transition: transform .22s ease;
}
.enlarge-overlay.is-open .figure,
.enlarge-overlay.is-open .doc-hero__art,
.enlarge-overlay.is-open .flow-figure,
.enlarge-overlay.is-open .aboutme__photo { transform: scale(1); }
.enlarge-overlay .figure img,
.enlarge-overlay .doc-hero__art img,
.enlarge-overlay .flow-figure img,
.enlarge-overlay .aboutme__photo img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 78vh;
}

/* flow-figure has no in-page card; give it the standard card look when enlarged
   so it matches the other lightbox images (paper background, border, radius). */
.enlarge-overlay .flow-figure {
  background: var(--paper);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: clamp(var(--space-2), 3vw, var(--space-4));
}
.enlarge-overlay .flow-figure img { border-radius: var(--radius-sm); }

/* Opt-in: figures marked .enlarge-lg open larger in the lightbox. The
   !important overrides any in-page inline max-width the clone carries over. */
.enlarge-overlay .enlarge-lg { max-width: min(94vw, 820px) !important; }
.enlarge-overlay .enlarge-lg img { max-height: 86vh; }

/* Opt-in: .enlarge-xl fills the window. For a tall or square image the height
   cap, not the width, is what limits it, so this trims the card's padding a
   little and hands that space to the image — but only to --space-3, so the
   paper frame around the image stays as visible as on every other figure. */
.enlarge-overlay .enlarge-xl {
  max-width: 96vw !important;
  padding: var(--space-3) !important;
}
.enlarge-overlay .enlarge-xl img { max-height: 92vh; }

/* Wide images (e.g. the full waveform) fill nearly the whole viewport width
   when enlarged, since the default 1000px cap leaves them small. */
/* width/float/margin !important neutralise any inline float-layout sizing the
   clone carries over (e.g. the forwarding diagram floated at width: 40rem), so a
   wide figure expands to the full 96vw when enlarged regardless of its in-page size. */
.enlarge-overlay .enlarge-wide {
  max-width: 96vw !important;
  width: auto !important;
  float: none !important;
  margin: 0 !important;
}
.enlarge-overlay .enlarge-wide img { max-width: 100%; max-height: 90vh; }

/* A touch smaller than enlarge-wide (used by the pipeline timing diagram). */
.enlarge-overlay .enlarge-wide--sm { max-width: min(92vw, 1250px) !important; }
.enlarge-overlay .enlarge-wide--sm img { max-width: 100%; max-height: 86vh; }

/* Laundry figure: opens larger than default but not full-bleed. width/height:auto
   !important neutralise the inline flush-layout sizing (height + object-fit) the
   clone carries over, so it shows at natural size within the caps. */
.enlarge-overlay .enlarge-laundry { max-width: min(88vw, 1000px) !important; height: auto !important; }
.enlarge-overlay .enlarge-laundry img { width: auto !important; height: auto !important; max-width: 100%; max-height: 78vh; }

@media (prefers-reduced-motion: reduce) {
  .enlargeable,
  .enlarge-overlay,
  .enlarge-overlay .figure,
  .enlarge-overlay .doc-hero__art,
  .enlarge-overlay .flow-figure,
  .enlarge-overlay .aboutme__photo { transition: none; }
  .enlargeable:hover, .enlargeable:focus-visible { transform: none; }
}
