/* =========================================================================
   PROJECT DIRECTORY DRAWER
   Moved out of styles/main.css so meet-the-processor/ can load it on its own.

   That page runs its own token set and deliberately loads none of main.css:
   the WebGL scene owns the viewport and the site's layout rules would fight
   the canvas. The drawer is the one shared component it does want, so it now
   lives here and both sides link it. main.css @imports it, so every ordinary
   page keeps it without touching eighteen files.

   THE TOKENS BELOW ARE NOT DECLARED HERE. Every var() still resolves against
   the host page's :root, which is what keeps the drawer tracking the light and
   dark themes on the main site. meet-the-processor/style.css republishes the
   handful it does not already define, scoped to the drawer's own roots. If you
   add a var() to this file, add it there too.
   ========================================================================= */

/* ------------------------------------------------------------------ *
   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; }
/* Drawer links are navigation, so they take the sans the top bar's links took.
   Keeping them mono would have left the mobile menu speaking a different voice
   from the desktop bar it stands in for. */
.toc__menu a {
  display: block;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-display);
  font-size: 0.92rem;
  letter-spacing: 0;
  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: var(--fill); 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; }

/* One picture per difficulty, sitting immediately after that difficulty's
   lesson columns and picked up by the row's own gap, so it reads as the next
   thing along rather than something parked at the far edge.

   Capped in HEIGHT, and shorter than a full lesson column on purpose. Every
   sheet shares one grid cell in `.navsheets`, so all four stand as tall as the
   tallest thing in any of them, and a picture taller than the longest lesson
   list would deepen the bands that have no picture at all.

   Inert: no pointer events, no drag, no selection. It is there to be looked
   at, and the only things in this band that answer to a cursor are the
   lesson links. */
.navsheet__art {
  margin: 0;
  flex: 0 0 auto;
  align-self: flex-start;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;

  /* The picture is taller than the row it sits in, and overhangs into the
     sheet's own padding. A negative block margin is what makes that free:
     a flex item contributes its OUTER size to the line, so 14rem of picture
     less 33px of negative margin contributes 191px, exactly the tallest
     lesson column. The band therefore cannot deepen, which is the constraint
     this whole rule exists to satisfy. The two margins must keep summing to
     -33px; only their split may change.

     The split is what centers it. Measuring from `flex-start`, the margin box
     starts at the sheet's 88px top padding, so the picture's top edge lands at
     88 - 4 = 84px. The band is only VISIBLE from 65px, where the top bar's
     1px border ends, down to 327px, so 84 leaves 19px of white above the
     picture and 19px below it. Centering on the row instead put 7px above and
     32px below, because the row is not the thing the eye sees. */
  margin-top: -4px;
  margin-bottom: -29px;
}
.navsheet__art img {
  display: block;
  /* 14rem centered in the row spans 71.5px to 295.5px inside the sheet. The
     top bar reaches 65px and the sheet ends at 327px, so it clears both.
     Going much past this slides the picture under the bar. */
  max-height: 14rem;
  width: auto;
  height: auto;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hairline);
  -webkit-user-drag: none;
}

/* 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 {
  /* The Start Building purple, so the one color that means "this is the
     action" is the same in the bar, the hero and the directory. It was
     #4f1f9e, a deeper shade that belonged to no other control on the page.
     White on #6b2fc9 measures 7.3:1, so the label stays legible while lit. */
  background: var(--fill);
  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: color-mix(in srgb, var(--fill) 22%, transparent);
}
.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`. */
/* The Project Directory tab is a filled pill, not a text link.

   It is the one control in the bar that opens something rather than navigating
   somewhere, and it is the entry point to every lesson on the site, so it earns
   the weight. Same purple and same full radius as the primary button, one step
   smaller, so the bar and the hero read as the same system.

   It keeps its sliding underline OFF: an underline is how a TEXT link announces
   itself, and drawing one under a filled pill is two affordances doing one job.
   The open state is carried by the darker fill instead. */
.projtab {
  border: 0;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0;
  background: var(--fill);
  color: #fff;
  border-radius: 999px;
  /* 8px/16px around a 20px line box gives the reference's 36px pill exactly.
     The line-height matters as much as the padding here: at line-height 1 the
     same padding produced a 30px pill, because the text box collapsed to the
     font size instead of carrying its own leading. */
  padding: 0.5rem 1rem;
  line-height: 1.25rem;
  transition: background-color 0.18s var(--ease);
}
.projtab:hover,
.projtab[aria-expanded="true"] {
  background: var(--fill-hover);
  color: #fff;
}

/* 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: 901px) {
  .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: 900px) {
  .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: 901px) {
  .toc__menu .is-mobile-only { display: none; }
}
@media (max-width: 900px) {
  .toc__menu .is-desktop-only { display: none; }
}

/* Link decoration is stated here rather than left to the host page's reset.
   main.css clears it globally, so on the site this changes nothing; on
   meet-the-processor, which has no such reset, every row in the drawer and
   every link in a flyout sheet was arriving underlined. A shared component
   should not depend on what the page around it happens to have cleared. */
.toc__menu a,
.navsheet__links a { text-decoration: none; }
