.katex-display, .box {
  /* background-color: var(--quote-bg); */
  /* border-radius: 4px; */
  /* padding: 0.75em 1em; */
  /* margin: 1em 0;
  overflow-x: auto; */
}

/* KaTeX's second vlist row only measures depth, but it otherwise sits above
   links in array cells and intercepts pointer events. */
.katex .vlist-t2 > .vlist-r:nth-child(2) {
  pointer-events: none;
}

/* .katex-display>.katex {
  text-align: left !important;
} */

#first-edition {
  margin-top: 0;
}

#of-tensor-programs {
  margin-top: -24px;
  margin-bottom: 0;
}

#of-tensor-programs+p {
  margin-top: 0;
}

#recommended-books,
#recommended-books-1,
#recommended-books-2,
#recommended-books-3,
#recommended-lectures,
#recommended-lectures-1,
#recommended-lectures-2 {
  margin-bottom: -16px;
}

#recommended-lectures,
#recommended-lectures-1,
#recommended-lectures-2 {
  margin-top: 0;
}

p:has(> em)+h5[id^="recommended-"] {
  margin-top: 0;
}

/* Standalone fully-italic paragraphs (e.g. "*Forward Pass*") sit tight.
   Keyed off the .em-only class set in custom.js, not `p:has(> em:only-child)`:
   `:only-child` ignores text nodes, so the latter also matched paragraphs with
   an inline *phrase* and surrounding text, wrongly collapsing their margins. */
p.em-only {
  margin: 0;
}

#mathematics-for-machine-learning,
#tensor-programming,
#tensor-interpretation-and-compilation {
  margin-bottom: -16px;
}

.full-bleed {
  width: 90vw;
  position: relative;
  left: 50%;
  margin-left: -45vw;
}

.full-bleed iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
  display: block;
}

.full-bleed-side {
  width: 90vw;
  position: relative;
  left: 50%;
  margin-left: -45vw;
  display: flex;
  gap: 1rem;
}

.full-bleed-side iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
  display: block;
}

/* A full-bleed video gallery: one player on stage down the left, the rest
   stacked in a rail down the right. Clicking a rail video swaps it onto the
   stage and the outgoing one drops into the rail. The swap is done purely by
   re-assigning grid tracks — the iframes never move in the DOM, because
   re-parenting an <iframe> reloads it and would restart whatever the reader had
   playing. `.is-active` comes from custom.js, as does `--rail-rows` (one row per
   video that is not on stage; CSS cannot count children).

   The row owns a track per player AND a track per caption, and the cells opt
   into them with `subgrid`. That shared track list is what lets the stage line
   up with the rail exactly: the stage player spans every track except the last
   caption's, so its top edge is the first thumbnail's top edge and its bottom
   edge is the last thumbnail's bottom edge, with the two captions left sitting
   side by side underneath. Cells with private row tracks cannot do this — their
   rows resolve independently and the stage can only be centered in the leftover
   space, which is what leaves a gap at each end.

   `minmax(0, …)` on the columns overrides the automatic minimum of an `fr`
   track. Without it a track refuses to shrink below its content's min-content
   width, and an aspect-ratio player in a definite-height area reports that as
   (height x 16/9) — enough to push the rail off the right edge of the screen. */
.video-row {
  width: 90vw;
  position: relative;
  left: 50%;
  margin-left: -45vw;
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
  grid-template-rows: repeat(var(--rail-rows, 3), auto auto);
  column-gap: 1rem;
  margin-top: 1.5rem;
  margin-bottom: 1.5rem;
}

/* Cells default into the rail, two tracks each, and only the picked one is
   hoisted out — so the rail keeps source order and auto-places top-to-bottom,
   never reshuffling as the reader clicks between videos. */
.video-cell {
  grid-column: 2;
  grid-row: span 2;
  display: grid;
  grid-template-rows: subgrid;
  min-width: 0;
}

.video-cell.is-active {
  grid-column: 1;
  grid-row: 1 / -1;
}

/* Counting the player's end line from the back (`-2`) is what makes one pair of
   rules serve both roles: a rail cell spans two tracks, so `1 / -2` is just its
   player track, while the stage spans all of them and the same span reaches the
   last player track — stopping short of the caption. */
.video-cell > iframe,
.video-cell > .video-cell-toggle {
  grid-column: 1;
  grid-row: 1 / -2;
}

.video-cell > p {
  grid-column: 1;
  grid-row: -2 / -1;
  align-self: start;
  margin: 0;
  /* Row gaps are 0 so that the player-to-caption step can be tighter than the
     gap between rail videos; the captions carry both spacings themselves. */
  padding: 0.5rem 0 1rem;
  font-size: 1.3rem;
  line-height: 1.35;
  text-align: center;
}

/* Rail players are sized by their column, and 16/9 sets the height — they are
   what gives the shared tracks their heights. The bare-iframe selector is the
   pre-JS fallback: until custom.js wraps them in cells the players are direct
   children, and they still paint at 16/9. */
.video-cell > iframe,
.video-row > iframe {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border: 0;
}

/* The stage fills its span instead of keeping 16/9, because its area is not
   16/9: it inherits the rail's height, and the rail carries three captions to
   the stage's one. Filling is what puts the edges where the request wants them;
   the small surplus becomes letterboxing inside the player, which sits on the
   player's own black background and is not visible in practice. */
.video-cell.is-active > iframe {
  height: 100%;
  aspect-ratio: auto;
}

/* The click target. It exists because a cross-origin <iframe> swallows every
   pointer event inside it — a click on the player itself never reaches the page
   — so "click a video" has to mean "click a transparent layer over it". Only
   rail videos carry one; on stage it is removed outright (below) so nothing
   sits between the reader and the controls of the video they came to watch. */
.video-cell-toggle {
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: #fff;
  font: inherit;
  cursor: pointer;
  transition: background 120ms ease-in;
}

.video-cell-toggle::after {
  content: "watch";
  font-size: 1.3rem;
  letter-spacing: 0.05em;
  padding: 0.35em 0.9em;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.72);
  opacity: 0;
  transition: opacity 120ms ease-in;
}

.video-cell-toggle:hover,
.video-cell-toggle:focus-visible {
  background: rgba(0, 0, 0, 0.18);
}

.video-cell-toggle:hover::after,
.video-cell-toggle:focus-visible::after {
  opacity: 1;
}

.video-cell.is-active > .video-cell-toggle {
  display: none;
}

/* One column has no room for a stage and a rail, and no margin to bleed into
   either, so the gallery collapses to a plain in-measure stack with the staged
   video on top. Cells take their two rows back from the parent — there is
   nothing left to line up with — and the `1 / -2` and `-2 / -1` placements above
   keep working unchanged against a two-track grid. Same breakpoint the sidenotes
   collapse at. */
@media (max-width: 1000px) {
  .video-row {
    width: 100%;
    left: auto;
    margin-left: 0;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
  }

  .video-cell,
  .video-cell.is-active {
    grid-column: 1;
    grid-row: auto;
    grid-template-rows: auto auto;
  }

  .video-cell.is-active {
    order: -1;
  }

  .video-cell.is-active > iframe {
    height: auto;
    aspect-ratio: 16 / 9;
  }
}

/* How far a margin note hangs out of the text column, as a share of the measure.
   Both gutters use it — the left one through `left`, the right one through a
   negative `margin-right` — so the two are mirror images and the page keeps one
   symmetric margin rather than two unrelated ones. With notes 35% wide it also
   fixes the gap between note and text at 2% on each side. */
:root {
  --margin-note-offset: 37%;
}

.sidenote {
  float: right;
  clear: right;
  margin-right: calc(-1 * var(--margin-note-offset));
  width: 35%;
  margin-top: 0.3rem;
  margin-bottom: 0;
  font-size: 1.2rem;
  line-height: 1.3;
}

/* The title page's two notes hang off lines only a few rows apart, so they
   float into the gutter back to back and read as one run-on block. Give them a
   blank line between. Front matter only: in the chapters the prose between two
   notes already spaces their floats apart. */
.sidenote.front-note {
  margin-bottom: 1rem;
}

/* A float whose parent is an *inline* box gets hit-tested as part of that
   inline, so the inline's own fragment ends up over everything preceding the
   float on that line. On the title page the "Jeffrey Zhang" link sits in the
   same `<em>` as the sidenote that follows it, so the `<em>` swallowed every
   click on it — the link was styled and hrefed, just unreachable. Positioned
   descendants paint after floats, so giving those links a layer of their own
   hands the clicks back. Only links *before* the note in the same inline are
   affected; ones after it, or outside the emphasis, always worked. */
:is(em, strong, small, b, i):has(.sidenote, .defnote, .lecnote, .cppnote, .gutter-figure) a {
  position: relative;
}

/* A blockquote insets its own content, so its notes push out by that inset
   again to land back on the shared gutter edge. */
blockquote .sidenote {
  margin-right: calc(-1 * var(--margin-note-offset) - 20px);
}

/* Pin each defnote to the line of its term instead of float-stacking.
   Absolute + auto top keeps the note's vertical static position (the line
   where the span sits in flow), while left/width place it in the margin.
   Requires a positioned containing block: see `.content main` below. */
/* Pinned the same way, and deliberately the same shape: a `.lecnote` is the
   lecture behind a term, hung in the same left gutter as the term itself.
   Authored as the defnote's SIBLING, never nested inside it — a nested span
   would land the refine ladder inside the note's first inner tag (mdbook-refine
   closes a defnote at the next `</span>`) and would fold the lecture title into
   the slugged anchor id in custom.js. Sharing the line is what connects the two:
   both take their static position from the same line of text, so the restack
   sweep below drops the lecture just under its label with nothing else to say
   so. That is also why one class covers both cases — a lecnote with no defnote
   beside it (the M.L.E. lecture in §1.2.4) simply stands on its own line. */
.defnote,
.lecnote {
  position: absolute;
  left: calc(-1 * var(--margin-note-offset));
  width: 35%;
  /* `--margin-note-raise` lifts one note off the line it would otherwise take,
     for when a tall item hangs lower than the things it sits beside. Set it
     per-note in the markdown (`style="--margin-note-raise: 48px"`); the restack
     sweep in custom.js carries it through, which a plain inline `margin-top`
     would not survive — the sweep clears that property on every pass. */
  margin-top: calc(0.3rem - var(--margin-note-raise, 0px));
  margin-bottom: 0;
  font-size: 1.2rem;
  line-height: 1.3;
  text-align: right;
  /* Muted gray via opacity so it tracks --fg in both light and dark themes. */
  opacity: 0.55;
}

blockquote .defnote,
blockquote .lecnote {
  left: calc(-1 * var(--margin-note-offset) - 20px);
}

/* == C++ notes ============================================================
   A remark that holds for the reference implementation and not for borscht's
   own — what `std::vector<T>` does here, what the chapter's code does instead.
   Same gutter, same shape and same 0.55 muting as a `.defnote`/`.lecnote`, and
   swept by the same restack pass in custom.js, so a C++ note cannot land on top
   of a definition. Authored the same way too: an inline span on the line whose
   vertical position the note should take, and a SIBLING of any defnote sharing
   that line rather than nested inside it.

       <span class="cppnote">*`std::vector<T>` copies on assignment ...*</span>

   The icon is the one thing done differently from a `.lecnote`, which pastes
   its Font Awesome `<svg>` into the markdown. A lecture's icon is furniture the
   author picks per note; a C++ note's icon *is* the note's type, so CSS supplies
   it and authoring stays a bare span.

   The logo rides as a `mask-image` painted with `background-color: currentColor`
   rather than as an `<img>`. That is what lets one file serve both themes: the
   mark takes the note's own color and muting, exactly like the `.fa-svg` lecture
   icon does, so ./assets/cpp.svg needs none of the `_dark` twin that every drawn
   `.png` figure there carries. Dropping the brand blue is the point and not a
   shortcut — a saturated hexagon would be the one loud thing in a margin built
   entirely out of muted gray. */
.cppnote {
  position: absolute;
  left: calc(-1 * var(--margin-note-offset));
  width: 35%;
  margin-top: calc(0.3rem - var(--margin-note-raise, 0px));
  margin-bottom: 0;
  font-size: 1.2rem;
  line-height: 1.3;
  text-align: right;
  opacity: 0.55;
}

blockquote .cppnote {
  left: calc(-1 * var(--margin-note-offset) - 20px);
}

/* `.cppnote-item` is the mobile echo: under 1000px there is no gutter, and
   custom.js rebuilds these notes as list items at the foot of the chapter. The
   marker cannot ride along in the copied innerHTML the way a lecture's inline
   icon does, so the list item is given a class of its own and hangs the same
   mask here.

   `../../assets/` because a url() in a stylesheet resolves against the
   stylesheet, and an `additional-css` entry keeps its theme/css/ path in the
   built book while src/assets is copied to /assets at the root — the same two
   hops every `_dark` figure swap further down this file makes. */
.cppnote::before,
.cppnote-item::before {
  content: "";
  display: inline-block;
  width: 1.3em;
  height: 1.3em;
  /* Sits the mark on the cap height rather than the baseline, the same way
     `.defnote img, img.icon` seats an inline illustration. */
  vertical-align: -0.3em;
  margin-right: 0.4em;
  background-color: currentColor;
  -webkit-mask: url("../../assets/cpp.svg") center / contain no-repeat;
  mask: url("../../assets/cpp.svg") center / contain no-repeat;
}

/* A third-party embed hung in the left gutter — so far only the tweet behind
   the preface's research question. Written as a block `<div>` after the
   paragraph rather than as an inline span on a line: the widget replaces the
   authored `<blockquote>` with an iframe of its own, which cannot live inside
   an inline box. It takes `.defnote`'s absolute placement in the gutter but
   none of its prose treatment — a card is a picture, like a `.gutter-figure`,
   so it keeps full opacity instead of the 0.55 muting the note apparatus wears,
   and it hugs the gutter's right edge, against the measure, when the widget
   renders narrower than the 35% column. `!important` beats the inline styles
   the widget writes onto its own element. custom.js re-sweeps the stack once
   the iframe swaps in (see the ResizeObserver in the restack block); the card
   goes away with the rest of the gutter under 1000px, which is why the same
   quote also stands as an ordinary numbered sidenote in the prose. */
.defnote-embed {
  opacity: 1;
}

.defnote-embed .twitter-tweet,
.defnote-embed twitter-widget {
  margin-left: auto !important;
  margin-right: 0 !important;
}

/* An illustration set as an icon riding inline with the text rather than as a
   figure: images in a defnote (always icon-sized, the margin has no room for a
   figure) plus anything opting in with `class="icon"` in running prose. Height
   in `em` ties the icon to whatever font-size it lands in; `width: auto` plus a
   per-image aspect-ratio set alongside the src resolves the width before the
   file decodes, so the line never reflows around a zero-width box. Taller than
   the text so the illustration stays legible, with vertical-align pulling it
   back down to sit centered on the cap height rather than the baseline. In a
   defnote it inherits that note's 0.55 opacity, like the rest of the margin
   apparatus. */
.defnote img,
img.icon {
  display: inline-block;
  height: 1.6em;
  width: auto;
  vertical-align: -0.5em;
  /* No trailing margin: every icon so far leads an inline `code` span, and
     mdBook already pads those 0.3em on the left (`:not(pre) > .hljs`), which
     supplies the gap. Adding margin here doubled it. An icon placed before
     plain prose instead needs its own space in the markdown. */
  margin-right: 0;
}

/* Lecture-video markers. mdBook 0.5 bundles Font Awesome Free 6.2.0 and inlines
   it through the `{{fa}}` handlebars helper, but that helper only runs over the
   theme templates — chapter markdown is rendered to HTML before it ever reaches
   handlebars, so `{{fa}}` written in a `.md` comes out literally. The icon is
   therefore pasted as a raw `<svg>` at each video caption, wrapped in the same
   `.fa-svg` span mdBook's own chrome uses so its `width/height: 1em` and
   `fill: currentColor` (css/general.css) apply for free. currentColor is the
   point: unlike the `.png` icons this needs no `_dark` twin to survive ayu.
   `.video-icon` only adds what the caption context needs — a touch more size
   than the surrounding text and a real gap, since a caption is plain prose and
   not the inline `code` span that `img.icon` leans on for its spacing. */
.fa-svg.video-icon svg {
  width: 1.1em;
  height: 1.1em;
  vertical-align: -0.18em;
  margin-right: 0.4em;
  /* Matches the muted weight of the caption line itself rather than competing
     with the link text it introduces. */
  opacity: 0.75;
}

/* == Gutter figures ========================================================
   A picture hung out in a margin beside the passage it belongs to, rather than
   set as a figure inside the measure. Same gutters the notes use, so a figure
   shares their edge and the page keeps one margin: see the
   `--margin-note-offset` comment above.

   Left and right are built differently, and deliberately so — each side copies
   the note that already lives there, which is what buys correct collision
   behaviour for free. The left gutter is absolutely-positioned notes swept by
   custom.js, so a left figure is absolute and joins that sweep. The right
   gutter is floats stacked by `clear: right`, so a right figure is a float and
   stacks with the sidenotes on its own.

   Authored as inline spans on the line whose vertical position the figure
   should take — for a figure flanking a section title, the `<small>` table-of-
   contents line right under it. Deliberately NOT inside the heading itself:
   mdBook slugs heading text into the anchor id, and a heading is not a place to
   hide layout.

       <small>[...](#i-table-of-contents)</small><span class="gutter-figure
       gutter-figure-left"><img src="./assets/shannon.png" alt="..."><span
       class="gutter-figure-caption">[Claude Shannon](https://...)</span></span>

   Photographic cutouts on transparency need no `_dark` twin the way the drawn
   `.png` figures do — a cutout carries no background to clash with the theme. */
.gutter-figure {
  width: 35%;
  margin-top: calc(0.3rem - var(--margin-note-raise, 0px));
  margin-bottom: 0;
}

.gutter-figure-left {
  position: absolute;
  left: calc(-1 * var(--margin-note-offset));
}

.gutter-figure-right {
  float: right;
  clear: right;
  margin-right: calc(-1 * var(--margin-note-offset));
}

/* Beats the icon sizing in `.defnote img, img.icon` above, which exists for
   glyphs riding inline with text — a gutter figure is a picture, not an icon. */
.gutter-figure img {
  display: block;
  width: 100%;
  height: auto;
}

/* The credit under a gutter figure: a person's name linked to their Wikipedia
   page, a document's title linked to the copy the fan was photographed from.
   Same size, leading and 0.55 muting as the rest of the margin apparatus, so a
   caption reads as gutter furniture rather than as a second voice beside the
   prose. Alignment mirrors the gutter it sits in — right in the left gutter,
   left in the right one — which puts every caption against the measure, on the
   same edge the notes already hang from. */
.gutter-figure-caption {
  display: block;
  margin-top: 0.3rem;
  font-size: 1.2rem;
  line-height: 1.3;
  opacity: 0.55;
}

.gutter-figure-left .gutter-figure-caption {
  text-align: right;
}

/* Clickable defnotes: behave like header anchors (click updates URL hash). */
.defnote-link {
  cursor: pointer;
}

.sidenote-number {
  counter-increment: sidenote-counter;
}

.sidenote-number::after,
.sidenote::before {
  font-size: 1.2rem;
  position: relative;
  vertical-align: baseline;
}

.sidenote-number::after {
  content: counter(sidenote-counter);
  top: -0.5rem;
  left: 0.1rem;
  color: rgb(0, 150, 207)
}

.sidenote::before {
  content: counter(sidenote-counter) " ";
  top: -0.5rem;
  color: rgb(0, 150, 207)
}

/* Block-level float sidenotes: use data-n attribute so DOM order doesn't matter */
.sidenote-float::before,
.defnote-float::before {
  content: attr(data-n) " ";
  font-size: 1.2rem;
  position: relative;
  vertical-align: baseline;
  top: -0.5rem;
  color: rgb(0, 150, 207);
}

.sidenote-ref::after {
  content: attr(data-n);
  font-size: 1.2rem;
  position: relative;
  vertical-align: baseline;
  top: -0.5rem;
  left: 0.1rem;
  color: rgb(0, 150, 207);
}

.content {
  counter-reset: sidenote-counter;
}

/* Containing block for absolutely-positioned .defnote margin notes.
   `%` in .defnote (left/width) resolves against this text column, matching
   the geometry the old float used relative to the paragraph. */
.content main {
  position: relative;
}

/* --------------------------------------------------------------------------
   Jupyter notebook cells, emitted by preprocessors/mdbook-nb from committed,
   already-executed .ipynb files. Static In[]/Out[] blocks in the style of ISL:
   steel-blue prompts hanging in the left gutter, a cream input box (syntax
   highlighting still comes from mdBook's highlighter), warm-brown text output,
   and a pink box for tracebacks.
   -------------------------------------------------------------------------- */
:root {
  --nb-prompt: #2e6d8e;        /* In[]/Out[] labels */
  --nb-in-bg: #fdf6ec;         /* input code box */
  --nb-in-border: #e7dcc0;
  --nb-out-color: #9c4a1c;     /* text/plain + execute_result output */
  --nb-error-bg: #f7d7d9;      /* traceback box */
  --nb-error-color: #7b241c;
  --nb-stderr-bg: #fdeaea;     /* stderr stream */
  --nb-out-max-height: 22em;   /* tall outputs scroll instead of running on */
  --nb-hl-bg: rgba(184, 127, 0, 0.15);  /* `hl=` line band, okabe orange */
  /* Line height of the In[] code, pinned rather than left at `normal` because
     the `hl=` bands are positioned in multiples of it. mdbook-nb emits the band
     offsets as calc(var(--nb-code-lh) * 1em * n); change this and they follow.
     1.15 is what Source Code Pro's `normal` already came to at the code size, so
     pinning it left every existing cell where it was; raise it for airier code.
     --nb-hl-bleed is how far a band is widened past the text column so it
     reaches the edges of the box: mdBook's `pre > code` padding.

     Both have to be declared here, not down on the code element that uses them.
     mdbook-nb's inline `--nb-hl-size`/`--nb-hl-pos` reference them, and a custom
     property has its var()s substituted on the element it is *declared* on — the
     .nb-in-code div. Anything they name has to be visible there or the whole
     value goes invalid and the band swells to fill the box. */
  --nb-code-lh: 1.15;
  --nb-hl-bleed: 1rem;
}
html.coal, html.navy, html.ayu, html.green {
  --nb-prompt: #6bb3d6;
  --nb-in-bg: #1f2733;
  --nb-in-border: #33404f;
  --nb-out-color: #e0975a;
  --nb-error-bg: #3a1c1f;
  --nb-error-color: #e0a0a0;
  --nb-stderr-bg: #2c1618;
  --nb-hl-bg: rgba(230, 159, 0, 0.17);
}

.nb-cell {
  position: relative;
  margin: 0.9rem 0;
}

/* Prompts hang just left of the box (nearer than the wider .defnote margin,
   so the two never collide). */
.nb-prompt {
  position: absolute;
  left: -7rem;
  width: 6rem;
  text-align: right;
  padding-right: 0.55rem;
  font-family: var(--mono-font, "Source Code Pro", monospace);
  font-size: 0.82em;
  line-height: 1.5;
  color: var(--nb-prompt);
  white-space: nowrap;
  user-select: none;
}

/* Source notebook's filename, emitted once per `{{#nb}}` by mdbook-nb. Sits
   above the input box, aligned with it, quieter than the prompt beside it. */
.nb-file {
  font-family: var(--mono-font, "Source Code Pro", monospace);
  font-size: 0.78em;
  line-height: 1.5;
  color: var(--nb-prompt);
  opacity: 0.7;
  padding-bottom: 0.2rem;
  overflow-wrap: anywhere;
  user-select: none;
}

/* When `src-url` is configured the label is a link to the notebook in the repo.
   Keep it looking like the label — the underline on hover is the only tell. */
.nb-file a {
  color: inherit;
  text-decoration: none;
}

.nb-file a:hover {
  color: inherit;
  text-decoration: underline;
}

/* Manual counterpart to .nb-file, for hand-written fenced blocks that aren't
   notebook cells: put `<div class="code-file">path/to/file.rs</div>` (blank line
   after it, so pulldown-cmark still parses the fence as Markdown) above the
   fence and the block gets the same quiet filename header. Wrap the label in an
   <a> for the linked variant, exactly as `src-url` does for notebooks. */
.code-file {
  font-family: var(--mono-font, "Source Code Pro", monospace);
  font-size: 0.78em;
  line-height: 1.5;
  color: var(--nb-prompt);
  opacity: 0.7;
  padding-bottom: 0.2rem;
  overflow-wrap: anywhere;
  user-select: none;
}

.code-file a {
  color: inherit;
  text-decoration: none;
}

.code-file a:hover {
  color: inherit;
  text-decoration: underline;
}

/* Close the browser's default `pre` top margin so the label sits on the box the
   way .nb-file does. `+ pre` covers both the plain block and the `playground`
   one mdBook emits for runnable Rust. */
.code-file + pre {
  margin-top: 0;
}

.nb-in-code pre {
  background: var(--nb-in-bg);
  border: 1px solid var(--nb-in-border);
  border-radius: 4px;
}

.nb-in-code pre > code {
  line-height: var(--nb-code-lh);
}

/* `{{#nb file.ipynb:cell hl=3-5,8}}` — tint the named source lines so prose can
   point at them. mdbook-nb emits one --nb-hl-img layer per range, each sized to
   that range's line count and offset to its first line; here they just get
   plugged into the code element's background.

   Why a background and not <mark> around the lines: mdBook highlights code in
   the browser, and hljs.highlightBlock rebuilds each <code> from its
   textContent, so any markup baked in by a preprocessor is discarded on load. A
   background survives. The cost is that the bands are computed from
   --nb-code-lh above rather than measured, so the code's line-height has to
   stay pinned. Painted on <code>, not <pre>, because the active highlight.js
   stylesheet gives <code> its own opaque background — a band under that would
   be invisible — and because `1em` then means the code's font size. */
.nb-in-code[data-hl] pre > code {
  background-image: var(--nb-hl-img);
  background-size: var(--nb-hl-size);
  background-position: var(--nb-hl-pos);
  background-repeat: no-repeat;
  /* Offsets are measured from the top-left of the text; painting is allowed out
     to the padding box, which is what lets the bleed above show. */
  background-origin: content-box;
  background-clip: padding-box;
}

/* Output: no box, warm-brown monospace, aligned to the code column. */
.nb-out {
  margin: 0.25rem 0 0.1rem;
}
.nb-out-text,
.nb-out-html {
  font-family: var(--mono-font, "Source Code Pro", monospace);
  font-size: 0.9em;
  line-height: 1.4;
  color: var(--nb-out-color);
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  /* Sideways only. A vertical scroller here would be a wheel trap: the pointer
     rests over an output for most of the page, and the browser latches the
     gesture to the box under it, so the page stops moving with the reader's
     fingers. The height cap lives in .nb-clamped below, which hides the
     overflow instead of scrolling it. */
  overflow-x: auto;
  overflow-y: hidden;
}
/* Text output: don't wrap long lines (a one-line list/array), scroll sideways.
   HTML output (pandas tables etc.) keeps normal wrapping so cells lay out. */
.nb-out-text {
  white-space: pre;
}
.nb-out-stderr {
  background: var(--nb-stderr-bg);
  padding: 0.3em 0.5em;
  border-radius: 3px;
}
.nb-out-img img {
  max-width: 100%;
  height: auto;
}

/* Theme-swapped notebook images: a cell may carry a dark-styled variant in its
   output metadata (see preprocessors/mdbook-nb), emitted alongside the light
   one. Show light by default; swap to dark on the dark themes. Single-image
   outputs have no .nb-img-* class and are unaffected. */
.nb-out-img .nb-img-dark { display: none; }
html.coal .nb-out-img .nb-img-dark,
html.navy .nb-out-img .nb-img-dark,
html.ayu  .nb-out-img .nb-img-dark,
html.green .nb-out-img .nb-img-dark { display: block; }
html.coal .nb-out-img .nb-img-light,
html.navy .nb-out-img .nb-img-light,
html.ayu  .nb-out-img .nb-img-light,
html.green .nb-out-img .nb-img-light { display: none; }
.nb-out-html { color: inherit; }

.nb-error {
  background: var(--nb-error-bg);
  color: var(--nb-error-color);
  font-family: var(--mono-font, "Source Code Pro", monospace);
  font-size: 0.9em;
  padding: 0.3em 0.6em;
  border-radius: 3px;
  white-space: pre;
  overflow-x: auto;
  overflow-y: hidden;
}

/* The height cap on a tall output: clamp and offer a button, never a nested
   scrollbar (see the overflow note on .nb-out-text above). theme/js/custom.js
   measures every output and adds .nb-clamped to the .nb-out wrapper only where
   the cap actually bites, so a two-line print() is left alone instead of being
   quietly made unscrollable-past. Cells tagged `full-output` are skipped there,
   which is what keeps .nb-full's opt-out working with no rule of its own.

   The fade is a mask rather than an overlaid gradient: the clamped element is
   the <pre> itself, and a ::after on it would be text at the end of the output
   rather than a layer over it. A mask also fades .nb-out-stderr's and
   .nb-error's tinted backgrounds along with their text, which an overlay in one
   fixed color could not do across six themes. */
.nb-out.nb-clamped > .nb-out-text,
.nb-out.nb-clamped > .nb-out-html,
.nb-out.nb-clamped > .nb-error {
  max-height: var(--nb-out-max-height);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 2.5em), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 2.5em), transparent);
}

/* Quiet and monospaced, matching the In[]/Out[] prompts and the .nb-file label
   rather than reading as page furniture. */
.nb-out-more {
  display: block;
  margin-top: 0.2rem;
  padding: 0;
  border: none;
  background: none;
  font-family: var(--mono-font, "Source Code Pro", monospace);
  font-size: 0.78em;
  line-height: 1.5;
  color: var(--nb-prompt);
  opacity: 0.7;
  cursor: pointer;
  user-select: none;
}

.nb-out-more:hover {
  opacity: 1;
  text-decoration: underline;
}

/* `display: block` above outranks the UA sheet's `[hidden] { display: none }`,
   so the attribute needs saying again — custom.js hides the button rather than
   dropping it when a re-measure finds the output no longer overflows. */
.nb-out-more[hidden] {
  display: none;
}

/* Below the Tufte margin breakpoint the left gutter disappears: drop prompts
   inline above their box instead of letting them hang off-page. */
@media (max-width: 1000px) {
  .nb-prompt {
    position: static;
    display: block;
    width: auto;
    text-align: left;
    padding: 0 0 0.15rem 0;
  }
}

@media (max-width: 1000px) {
  .sidenote,
  .defnote,
  .lecnote,
  .cppnote,
  .gutter-figure {
    display: none;
  }

  .sidenote-number::after {
    content: counter(sidenote-counter);
    cursor: pointer;
    text-decoration: underline;
  }

  .mobile-only {
    display: block;
  }

  .mobile-sidenotes {
    margin-top: 2rem;
    border-top: 1px solid currentColor;
    opacity: 0.7;
  }

  .mobile-sidenotes h6 {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 0.5rem;
  }

  .mobile-sidenotes ol,
  .mobile-sidenotes ul {
    font-size: 1.2rem;
    line-height: 1.4;
    padding-left: 1.5rem;
    margin: 0;
  }

  /* The lecture list follows the sidenote list under its own heading rather
     than joining it: dropping unnumbered items into that <ol> would walk its
     numbering out of step with the .sidenote-number markers in the text. */
  .mobile-sidenotes h6 + ul {
    list-style: none;
    padding-left: 0;
  }

  /* Every list but the first is preceded by its own heading, whichever kind
     of list it is — sidenotes come as an <ol>, lectures and C++ notes as
     <ul>s. */
  .mobile-sidenotes :is(ol, ul) + h6 {
    margin-top: 1rem;
  }

  /* The margin's muting is the gutter's business: down here the note is body
     content in its own section, which already carries a 0.7 of its own. */
  .mobile-sidenotes .cppnote-item::before {
    opacity: 0.75;
  }

  .dual {
    grid-template-columns: 1fr;
  }
}

.toc ul,
.toc li {
  list-style: none;
}

.toc > ul {
  padding-left: 0;
}

.toc ul ul {
  padding-left: 2rem;
}

/* Hover-expanded contents. A third list level in the TOC is not part of the
   compact outline: it holds that section's subsections, revealed only while its
   entry is hovered (or focused into, for keyboard readers). The panel is taken
   out of flow and hung under the entry, so opening one never reflows the
   entries below it — the compact TOC keeps its shape and the expansion just
   floats over the top. The `li` is the hover target rather than the `<a>` so
   the pointer can travel into the panel without closing it (the panel is a DOM
   descendant, so :hover still holds), and the open delay keeps a mouse merely
   crossing the list from flickering panels open on the way past. Everything in
   this paragraph is the pointer half; the touch half is the `@media (hover:
   none)` block at the end of the section. */
.toc ul ul > li {
  position: relative;
}

/* The affordance: an entry that has an expansion says so. */
.toc ul ul > li:has(> ul) > a::after {
  content: " ▾";
  font-size: 0.8em;
  opacity: 0.4;
}

.toc ul ul > li:hover > a::after,
.toc ul ul > li:focus-within > a::after {
  opacity: 0.75;
}

.toc ul ul ul {
  position: absolute;
  top: 100%;
  /* Indented by the same 2rem step the level above it uses, so the panel reads
     as one rung deeper rather than as a floating box. */
  left: 2rem;
  z-index: 20;
  width: max-content;
  max-width: calc(100% - 2rem);
  margin: 0;
  padding: 1rem 1.6rem;
  background: var(--theme-popup-bg);
  border: 1px solid var(--theme-popup-border);
  border-radius: 4px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  font-size: 0.92em;
  line-height: 1.6;
  /* `visibility` (not `display`) so the panel is inert to the pointer and to
     tabbing while closed, but still transitionable. */
  opacity: 0;
  visibility: hidden;
  transition: opacity 100ms ease-out, visibility 0s linear 100ms;
}

.toc ul ul > li:hover > ul,
.toc ul ul > li:focus-within > ul {
  opacity: 1;
  visibility: visible;
  transition: opacity 120ms ease-in 140ms, visibility 0s linear 140ms;
}

/* The section's "In which ..." blurb, hoisted into the panel by custom.js from
   the blockquote under its heading. `max-width` is doing two jobs: it keeps the
   blurb to a readable measure, and — since a max-content sized box is clamped
   by its children's max-widths — it is also what stops one long sentence from
   stretching the whole panel to the full text column. */
.toc ul ul ul > .toc-blurb {
  max-width: 58ch;
  margin-bottom: 0.9rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--theme-popup-border);
  line-height: 1.5;
  text-wrap: pretty;
  opacity: 0.72;
}

/* The blurb arrives as the blockquote's own markup — a <p>, sometimes several —
   so its paragraph margins are re-set to the panel's own spacing rather than
   the body's. */
.toc ul ul ul > .toc-blurb > p {
  margin: 0;
}

.toc ul ul ul > .toc-blurb > p + p {
  margin-top: 0.6rem;
}

@media (prefers-reduced-motion: reduce) {
  .toc ul ul ul,
  .toc ul ul > li:hover > ul,
  .toc ul ul > li:focus-within > ul,
  .toc-toggle::after {
    transition: none;
  }
}

/* The toggle (built in custom.js) is the touch counterpart of hovering an
   entry, and only that: with a pointer the panel is already one hover away, so
   the control would be a second way to do a thing the reader can do. */
.toc-toggle {
  display: none;
}

/* Nothing to hover with a finger, so the panels are opened by tap instead. Two
   changes, and the second is the point: they lay out in flow rather than
   floating (a card pinned to a line of text is a pointer idiom — it assumes
   something is hovering to keep it alive), and they start closed, so the
   outline stays the short list it is meant to be until the reader opens one.
   `display` rather than the visibility/opacity pair used above, because iOS
   leaves :hover stuck on the last-tapped element and those rules would hold a
   panel open under it; `display: none` outranks them. */
@media (hover: none) {
  .toc ul ul ul {
    position: static;
    width: auto;
    max-width: none;
    padding: 0 0 0 2rem;
    background: none;
    border: 0;
    box-shadow: none;
    opacity: 1;
    visibility: visible;
    display: none;
  }

  /* Opened, the panel brings its blurb with it — which the old flattened
     fallback dropped, since it showed every panel at once and would have
     carried a paragraph for each. A panel opened by tap is one the reader
     asked for, and the blurb is most of the answer. */
  .toc ul ul > li[data-toc-open] > ul {
    display: block;
  }

  /* The ▾ moves off the link and onto the button, which is what a tap can
     actually land on without also following the link. */
  .toc ul ul > li:has(> ul) > a::after {
    content: none;
  }

  /* Sized to the glyph, not to the finger. A button box big enough to tap would
     be taller than the 2.3rem line the entries sit on, and would push apart only
     the rows that have one — an outline where the spacing tells you which
     sections happen to have subsections. The box stays inline-sized and the
     finger is served by the ::before below. */
  .toc-toggle {
    position: relative;
    display: inline-block;
    margin-left: 0.35em;
    padding: 0;
    vertical-align: baseline;
    background: none;
    border: 0;
    color: inherit;
    font-family: inherit;
    font-size: 0.85em;
    line-height: 1;
    opacity: 0.5;
    cursor: pointer;
  }

  .toc-toggle::after {
    content: "▾";
    transition: transform 120ms ease;
  }

  /* The actual tap target, out of flow so it costs the layout nothing. It grows
     mostly rightward, into the empty space past the end of the entry, because
     that is the one direction with nothing to steal a tap from: vertically it
     stops inside the line's own leading, and on the left it stops short of the
     link text. */
  .toc-toggle::before {
    content: "";
    position: absolute;
    inset: -0.7rem -3.2rem -0.7rem -0.3rem;
  }

  .toc ul ul > li[data-toc-open] > .toc-toggle {
    opacity: 0.85;
  }

  .toc ul ul > li[data-toc-open] > .toc-toggle::after {
    transform: rotate(180deg);
  }
}

p:has(> small > a[href="#table-of-contents"]) {
  margin-top: 0;
}

h2:has(+ p > small > a[href="#table-of-contents"]) {
  margin-bottom: 0;
}

p:has(+ ul) {
  margin-bottom: 0;
}

.mobile-only {
  display: none;
}

.dual {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  align-items: start;
  margin: 1em 0;
}

/* `min-width: 0` is what actually keeps the split down the middle: a grid item's
   automatic minimum size is its min-content width, so a cell holding a wide
   <pre> demands more than its 1fr share, stretches its track, and shoves the
   divider off-center (and the second column off-screen). Zeroing it lets the
   tracks stay equal and the code scroll inside its own box. */
.dual > * {
  margin: 0 !important;
  min-width: 0;
}

/* Variant of `.dual` for a primary/aside pair: the first cell keeps the full
   text measure and the second one sits out in the sidenote margin. The grid box
   itself stays the width of the text column (so `100%` is the measure), and the
   second track simply overhangs to the right — landing on the same edge as a
   `.sidenote`. The 1.5rem the grid gap eats is given back to the track, so the
   pair spans 100% + `--margin-note-offset` and ends on the gutter edge. */
.dual-margin {
  grid-template-columns: 100% calc(var(--margin-note-offset) - 1.5rem);
}

/* The margin cell's prompt would hang 7rem left of its box, i.e. on top of the
   first cell's code. Drop it inline above the box instead. Inline-block (rather
   than the breakpoint's `display: block`) puts it on the same line as the
   filename label, so the header stays one line tall and the two cells' code
   boxes still start level. */
.dual-margin > *:nth-child(2) .nb-file,
.dual-margin > *:nth-child(2) .nb-prompt {
  position: static;
  display: inline-block;
  width: auto;
  text-align: left;
  padding: 0 0 0.2rem 0;
}

.dual-margin > *:nth-child(2) .nb-file {
  padding-right: 0.6em;
}

/* Below the sidenote breakpoint there is no margin to sit in — stack instead.
   Declared here rather than in the breakpoint block above so it wins on order
   against the `.dual-margin` rule. */
@media (max-width: 1000px) {
  .dual-margin {
    grid-template-columns: 1fr;
  }
}

/* Prose/code columns want their tops flush (hence `align-items: start` above),
   but paired figures rarely have matching aspect ratios — center them so the
   shorter one doesn't hang off the top of the row. */
.dual > img {
  align-self: center;
  justify-self: center;
}

/* Opt-in yinit drop cap (gwern-style). Wrap a paragraph in a `.dropcap` div in
   the Markdown — keep blank lines inside so mdBook renders the contents:

       <div class="dropcap">

       Opening paragraph text...

       </div>

   Only the FIRST paragraph's FIRST letter is styled; the letter must be an
   uppercase A-Z (the only glyphs yinit provides). `::first-letter` honors just
   font/float/margin/color/line-height, which is all we need here.

   Sized as a plain float rather than with `initial-letter`, gwern's technique.
   The two engines that implement initial-letter disagree badly on how big an
   N-line cap is: for a 4-line cap Blink scales the glyph so ~0.77em of it spans
   the four lines, WebKit so 1.2em does (it goes off ascent+descent, and yinit
   caps fill their em box). Same declaration, and Safari/iPadOS drew the cap
   ~40% smaller than Chrome. An em font-size is the one sizing every engine
   agrees on: measured across Blink and WebKit the glyph below lands within 2%.

   The two numbers are a pair, tuned against the old Chrome rendering:
   7.6em reproduces its glyph height (125px ink), and 0.74 line-height makes the
   float box 5.62em, just under the 4 body lines (4 x 1.45em = 5.8em) it has to
   clear, so exactly four lines wrap around the cap. Retune both together if the
   body line-height ever moves off 1.45. */
.dropcap > p:first-of-type::first-letter {
  font-family: "Yinit", serif;
  font-weight: normal;
  font-style: normal;
  color: var(--fg);
  float: left;
  font-size: 7.6em;
  line-height: 0.74;
  /* No negative left margin: em here resolves against the cap's own 7.6em, so
     even -0.04em pulls the glyph ~0.3em of body text out past the column edge
     and the yinit flourishes bleed into the left gutter. Zero flushes the
     glyph's left side bearing with the text column. */
  margin: 0 0.12em 0 0;
}

/* gwern's signature lead-in: the first rendered line set in small caps. Because
   it's ::first-line (not a fixed word count), it reflows with the column and
   breaks at the wrap, exactly like gwern's. The yinit ::first-letter above wins
   for the initial glyph, so only the trailing text on line 1 is affected. */
.dropcap > p:first-of-type::first-line {
  font-variant-caps: small-caps;
  font-variant: small-caps;
  letter-spacing: 0.02em;
}

/* Headings h1-h4 in small caps, echoing the dropcap lead-in above. Scoped to
   `main` so the sidebar/menu chrome is untouched, and it rides on the header
   anchor mdBook wraps every heading title in (font-variant inherits).
   New Computer Modern carries a real `smcp` feature (CMU did not, so these used
   to be synthesized from scaled capitals) — the 0.02em tracking is the usual
   small-caps courtesy. Inline code and KaTeX opt back out: identifiers are
   case-bearing, and `borscht.forward()` in small caps reads as a typo. */
main h1,
main h2,
main h3,
main h4 {
  font-variant-caps: small-caps;
  font-variant: small-caps;
  letter-spacing: 0.02em;
}

main h1 code, main h2 code, main h3 code, main h4 code,
main h1 .katex, main h2 .katex, main h3 .katex, main h4 .katex {
  font-variant-caps: normal;
  font-variant: normal;
  letter-spacing: normal;
}

/* Acronyms set like the headings above. The heading rule only lowers *lowercase*
   letters into small caps, so an already-uppercase acronym would come through
   unchanged; `all-small-caps` adds `c2sc` on top of `smcp` (NCM ships both) so
   capitals shrink too, and the same 0.02em tracking keeps the color even.
   Usage: <span class="smallcaps">HTDP</span>. */
.smallcaps {
  font-variant-caps: all-small-caps;
  letter-spacing: 0.02em;
}

/* Theme-dependent owl illustration. The default <img src> points at owl.png
   (used by the light/rust themes); these override the rendered image per theme
   via `content`. Paths are relative to this CSS file, which mdBook emits at
   theme/css/custom-*.css, so assets/ is two levels up. */
html.coal img.owl {
  content: url("../../assets/owl2.png");
}

html.navy img.owl,
html.ayu img.owl,
html.green img.owl {
  content: url("../../assets/owl3.png");
}

html.navy img.owl-forest,
html.ayu img.owl-forest,
html.green img.owl-forest,
html.coal img.owl-forest {
  content: url("../../assets/owl_forest2.png");
}

/* Theme-dependent probability-space diagram, mirroring the owl swap above. */
html.coal img.probspace,
html.navy img.probspace,
html.ayu img.probspace,
html.green img.probspace {
  content: url("../../assets/probspace_dark.png");
}

html.coal img.vector,
html.navy img.vector,
html.ayu img.vector,
html.green img.vector {
  content: url("../../assets/vector_dark.png");
}

html.coal img.tensors,
html.navy img.tensors,
html.ayu img.tensors,
html.green img.tensors {
  content: url("../../assets/tensors_dark.png");
}

html.coal img.randvar,
html.navy img.randvar,
html.ayu img.randvar,
html.green img.randvar {
  content: url("../../assets/randvar_dark.png");
}

html.coal img.randproc,
html.navy img.randproc,
html.ayu img.randproc,
html.green img.randproc {
  content: url("../../assets/randproc_dark.png");
}

html.coal img.chain,
html.navy img.chain,
html.ayu img.chain,
html.green img.chain {
  content: url("../../assets/chain_dark.png");
}

html.coal img.conditional,
html.navy img.conditional,
html.ayu img.conditional,
html.green img.conditional {
  content: url("../../assets/conditional_dark.png");
}

html.coal img.jointbars,
html.navy img.jointbars,
html.ayu img.jointbars,
html.green img.jointbars {
  content: url("../../assets/jointbars_dark.png");
}

html.coal img.jointmatrix,
html.navy img.jointmatrix,
html.ayu img.jointmatrix,
html.green img.jointmatrix {
  content: url("../../assets/jointmatrix_dark.png");
}

html.coal img.stochastic_matrix,
html.navy img.stochastic_matrix,
html.ayu img.stochastic_matrix,
html.green img.stochastic_matrix {
  content: url("../../assets/stochastic_matrix_dark.png");
}

html.coal img.jointmatrixmarginal,
html.navy img.jointmatrixmarginal,
html.ayu img.jointmatrixmarginal,
html.green img.jointmatrixmarginal {
  content: url("../../assets/jointmatrixmarginal_dark.png");
}

html.coal img.broadcast,
html.navy img.broadcast,
html.ayu img.broadcast,
html.green img.broadcast {
  content: url("../../assets/broadcast_dark.png");
}

html.coal img.neuron,
html.navy img.neuron,
html.ayu img.neuron,
html.green img.neuron {
  content: url("../../assets/neuron_dark.png");
}

html.coal img.multiclass_logisticregression,
html.navy img.multiclass_logisticregression,
html.ayu img.multiclass_logisticregression,
html.green img.multiclass_logisticregression {
  content: url("../../assets/multiclass_logisticregression_dark.png");
}

html.coal img.multiclass_logisticregression_circuit,
html.navy img.multiclass_logisticregression_circuit,
html.ayu img.multiclass_logisticregression_circuit,
html.green img.multiclass_logisticregression_circuit {
  content: url("../../assets/multiclass_logisticregression_circuit_dark.png");
}

html.coal img.ffn,
html.navy img.ffn,
html.ayu img.ffn,
html.green img.ffn {
  content: url("../../assets/ffn_dark.png");
}

html.coal img.transformer,
html.navy img.transformer,
html.ayu img.transformer,
html.green img.transformer {
  content: url("../../assets/transformer_dark.png");
}

html.coal img.cpugpu,
html.navy img.cpugpu,
html.ayu img.cpugpu,
html.green img.cpugpu {
  content: url("../../assets/cpugpu_dark.svg");
}

html.coal img.ffn_circuit,
html.navy img.ffn_circuit,
html.ayu img.ffn_circuit,
html.green img.ffn_circuit {
  content: url("../../assets/ffn_circuit_dark.png");
}

html.coal img.ffn_matmul,
html.navy img.ffn_matmul,
html.ayu img.ffn_matmul,
html.green img.ffn_matmul {
  content: url("../../assets/ffn_matmul_dark.png");
}

html.coal img.neuron_linreg,
html.navy img.neuron_linreg,
html.ayu img.neuron_linreg,
html.green img.neuron_linreg {
  content: url("../../assets/neuron_linreg_dark.png");
}

html.coal img.discriminant_decisionbad,
html.navy img.discriminant_decisionbad,
html.ayu img.discriminant_decisionbad,
html.green img.discriminant_decisionbad {
  content: url("../../assets/discriminant_decisionbad_dark.png");
}

html.coal img.discriminant_decision,
html.navy img.discriminant_decision,
html.ayu img.discriminant_decision,
html.green img.discriminant_decision {
  content: url("../../assets/discriminant_decision_dark.png");
}

html.coal img.discriminant_weight_bad,
html.navy img.discriminant_weight_bad,
html.ayu img.discriminant_weight_bad,
html.green img.discriminant_weight_bad {
  content: url("../../assets/discriminant_weight_bad_dark.png");
}

html.coal img.discriminant_weight,
html.navy img.discriminant_weight,
html.ayu img.discriminant_weight,
html.green img.discriminant_weight {
  content: url("../../assets/discriminant_weight_dark.png");
}

html.coal img.semanticgap,
html.navy img.semanticgap,
html.ayu img.semanticgap,
html.green img.semanticgap {
  content: url("../../assets/semanticgap_dark.png");
}

html.coal img.venn,
html.navy img.venn,
html.ayu img.venn,
html.green img.venn {
  content: url("../../assets/venn_dark.png");
}

html.coal img.venn_disjoint,
html.navy img.venn_disjoint,
html.ayu img.venn_disjoint,
html.green img.venn_disjoint {
  content: url("../../assets/venn_disjoint_dark.png");
}

/* Prose color-keys that name a part of a figure ("the orange output ..."), in
   the same Okabe-Ito hues the figures are drawn with. The dark values are the
   palette verbatim, matching the strokes in the *_dark.png variants. On white,
   though, only the blue is legible as text — the palette is tuned for fills,
   and orange/green/purple land at 2.3:1, 3.4:1 and 3.1:1 — so the light values
   are those three darkened (0.8x, 0.85x, 0.8x) to a uniform ~4.3:1. KaTeX
   inherits `color`, so inline math is keyed along with the prose around it. */
:root {
  --okabe-orange: #b87f00;
  --okabe-blue: #0072b2;
  --okabe-green: #008662;
  --okabe-purple: #a36186;
}
html.coal, html.navy, html.ayu, html.green {
  --okabe-orange: #e69f00;
  --okabe-blue: #56b4e9;
  --okabe-green: #35c79a;
  --okabe-purple: #e4a0c8;
}

/* Color key off (the menu-bar droplet, wired in custom.js): remapping the
   variables to `inherit` is enough to neutralize every keyed span at once —
   prose and KaTeX alike — because nothing sets a literal hex. Figures keep
   their colors; they're PNGs, and the prose still names the colors, so the
   toggle is about de-noising the running text, not hiding the key entirely. */
html[data-colorkey="off"] {
  --okabe-orange: inherit;
  --okabe-blue: inherit;
  --okabe-green: inherit;
  --okabe-purple: inherit;
}

.okabe-orange {
  color: var(--okabe-orange);
}

.okabe-blue {
  color: var(--okabe-blue);
}

.okabe-green {
  color: var(--okabe-green);
}

.okabe-purple {
  color: var(--okabe-purple);
}

/* Custom [!QUESTION] admonition, populated by custom.js (mdBook's built-in
   alert renderer only recognizes NOTE/TIP/IMPORTANT/WARNING/CAUTION). Reuses
   mdBook's own `.blockquote-tag`/`.blockquote-tag-title` layout, with its own
   accent color so "pause and think" prompts are visually distinguishable from
   [!WARNING]'s. Burnt-orange, matching .lemma's accent (#c9791f) above. */
:root {
  --blockquote-question-color: #c9791f;
}
html.coal, html.navy, html.ayu, html.green {
  --blockquote-question-color: #e0a458;
}

.blockquote-tag-question {
  border-inline-start-color: var(--blockquote-question-color);
}

.blockquote-tag-question .blockquote-tag-title {
  color: var(--blockquote-question-color);
}

/* URL-as-text links (wikipedia / SEP / nlab references, where the visible link
   text IS the full URL) render in New Computer Modern Mono — the Latin Modern
   Mono / Computer Modern typewriter face that arxiv.org/pdf/2311.04329 uses for
   URLs. Code blocks stay on --mono-font (Source Code Pro), so URLs remain
   distinct. Tagged by custom.js; scoped so it never touches code blocks or
   code-identifier links like `nanochat`/`numpy`. `.url-mono code` wins over
   `code` on specificity. */
a.url-mono,
a.url-mono code {
  font-family: "New Computer Modern Mono", monospace;
  font-weight: 400;
  font-style: normal; /* -> NewCMMono10-Regular, upright like arxiv's Latin
                          Modern Mono; `normal` overrides the italic inherited
                          from the sidenotes' *…* wrapper. */
}

/* Module/atom names — Linear, Embed, Conv2D — in New Computer Modern Sans, the
   sans companion of the body roman. This is the `\textsf{}` those names are set
   in in the papers they come from (Large et al. 2024): a family swap inside one
   superfamily, so the name reads as an identifier without --mono-font's
   typewriter color, and without spending `code`, which the book reserves for
   things a reader could actually type. The four NCM Sans cuts are already
   loaded by theme/fonts/fonts.css — this is the first rule to use them.

   Usage: <span class="mod">Linear</span>. */
.mod {
  font-family: "New Computer Modern Sans", sans-serif;
  font-size: 0.97em;  /* NCM Sans runs optically larger than the roman at an
                         equal em (x-height 444/1000 vs 431, caps 694 vs 683),
                         so 0.97 sits its x-height on the body's. */
  font-style: normal; /* NCM Sans is slanted, not truly italic; keep the name
                         upright inside emphasized runs and sidenotes. */
}

/* The `green` theme deliberately puts prose, chrome, and code in one typewriter
   face (see html.green below). A sans identifier would be the one thing
   breaking that, so there it follows the theme's face instead. */
html.green .mod {
  font-family: var(--mono-font);
  font-size: 1em;
}

/* Definition/theorem "snackbars" in the style of arxiv.org/pdf/2311.04329:
   a titled header bar, a lightly tinted body, and a thick accent rule on the
   left. Usage — leave a blank line after the opening tag so mdBook renders the
   markdown inside:

     <div class="definition" data-title="Definition 2.2.2: Probability measure">

     A **probability measure** $\mathbb{P}$ over ... such that
     1. $\mathbb{P}(\Omega) = 1$,
     2. ...

     </div>

   Use class "theorem", "lemma", or "example" for the accent-color variants. */
.definition,
.theorem,
.lemma,
.example {
  --snack-accent: #1c4a54;   /* left rule (and title, unless --snack-title-color) */
  --snack-title-bg: #cbdde3; /* header bar */
  --snack-body-bg: #ebf3f6;  /* body */
  border-left: 5px solid var(--snack-accent);
  border-radius: 3px;
  margin: 1.5em 0;
  background: var(--snack-body-bg);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  overflow: hidden;
}

/* Red, matching the [!CAUTION] admonition's brick-red (--blockquote-caution-color
   in the light theme): dusty-rose header, pale-pink body, near-black title. */
.theorem { --snack-accent: #b52731; --snack-title-color: #64151b; --snack-title-bg: #e1a9ad; --snack-body-bg: #f4dfe0; }
/* Orange, sampled from circle A in the venn-diagram assets: burnt-orange
   rule, tan header, pale peach body, near-black title. */
.lemma   { --snack-accent: #c9791f; --snack-title-color: #4a2c0c; --snack-title-bg: #e9c9a5; --snack-body-bg: #f7ebdd; }
/* Amber/yellow, matching the original Lemma screenshot: gold rule,
   warm-yellow header, cream body, near-black title. */
.example { --snack-accent: #c8912f; --snack-title-color: #3d2f14; --snack-title-bg: #f4dfa6; --snack-body-bg: #fdf8ec; }

.definition[data-title]::before,
.theorem[data-title]::before,
.lemma[data-title]::before,
.example[data-title]::before {
  content: attr(data-title);
  display: block;
  background: var(--snack-title-bg);
  color: var(--snack-title-color, var(--snack-accent));
  font-weight: 700;
  padding: 0.5em 1em;
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}

.definition > :not(pre),
.theorem > :not(pre),
.lemma > :not(pre),
.example > :not(pre) {
  padding-left: 1em;
  padding-right: 1em;
}

/* ol/ul need more left padding than prose: the marker gutter (numerals,
   bullets) lives inside that padding-box, and 1em is too narrow — wide
   markers like "III." get clipped against the border, reading as "I.".
   Keep the hanging-indent look but give the gutter enough room. */
.definition > ol,
.theorem > ol,
.lemma > ol,
.example > ol,
.definition > ul,
.theorem > ul,
.lemma > ul,
.example > ul {
  padding-left: 2.5em;
}

.definition > :first-child:not([data-title]),
.definition > *:nth-child(1) { margin-top: 0.75em; }
.definition > :last-child,
.theorem > :last-child,
.lemma > :last-child,
.example > :last-child { margin-bottom: 0.75em; }

/* Dark themes: dim the tints so they read as panels, not glare. */
html.coal .definition, html.navy .definition, html.ayu .definition, html.green .definition {
  --snack-accent: #7fb3bf; --snack-title-bg: #22333a; --snack-body-bg: #1a262c;
}
html.coal .theorem, html.navy .theorem, html.ayu .theorem, html.green .theorem {
  --snack-accent: #e2707e; --snack-title-color: #f6d3d7; --snack-title-bg: #3a171b; --snack-body-bg: #2a1014;
}
html.coal .lemma, html.navy .lemma, html.ayu .lemma, html.green .lemma {
  --snack-accent: #e0975a; --snack-title-color: #f0d3b4; --snack-title-bg: #3a2814; --snack-body-bg: #2c1d10;
}
html.coal .example, html.navy .example, html.ayu .example, html.green .example {
  --snack-accent: #d0a94e; --snack-title-color: #e8d9b0; --snack-title-bg: #322c1c; --snack-body-bg: #262117;
}
html.coal :is(.definition,.theorem,.lemma,.example)[data-title]::before,
html.navy :is(.definition,.theorem,.lemma,.example)[data-title]::before,
html.ayu  :is(.definition,.theorem,.lemma,.example)[data-title]::before,
html.green :is(.definition,.theorem,.lemma,.example)[data-title]::before {
  border-bottom-color: rgba(255, 255, 255, 0.08);
}

/* Sidenotes inside a snackbar are relocated by custom.js into this footer and
   rendered as footnotes at the BOTTOM of the box, not in the page margin (the
   box's overflow:hidden would clip a margin float anyway). The inline
   superscript marker stays put; only the note text moves here. */
.definition .box-notes,
.theorem .box-notes,
.lemma .box-notes,
.example .box-notes {
  margin-top: 1em;
  padding-top: 0.5em;
  border-top: 1px solid rgba(0, 0, 0, 0.18);
  font-size: 0.82em;
  line-height: 1.4;
}
.box-notes .sidenote {
  float: none;
  clear: none;
  display: block; /* also overrides the <=1000px `display:none` for margin notes */
  position: relative; /* containing block for the absolutely-placed number */
  width: auto;
  margin: 0 0 0.35em 0;
  padding-left: 1.5em; /* gutter holding the number; content hangs past it */
  font-size: inherit;
}
.box-notes .sidenote:last-child { margin-bottom: 0; }
/* Footnote number: use the frozen data-n (the live CSS counter is wrong once the
   note is moved out of document order). Positioned absolutely at the top-left so
   it doesn't occupy its own line — the reference list flows up beside it. */
.box-notes .sidenote::before {
  content: attr(data-n);
  position: absolute;
  top: 0;
  left: 0;
  font-size: 0.75em;
  color: inherit;
}
html.coal .box-notes, html.navy .box-notes, html.ayu .box-notes, html.green .box-notes {
  border-top-color: rgba(255, 255, 255, 0.18);
}


/* == Refinement ladders =====================================================
   A concept the book defines more than once — vaguely first, then precisely —
   carries a ladder of dots: one per definition, the current one filled, the
   others linking to their rung. Injected by preprocessors/mdbook-refine from
   `data-refine="<key>"`; see that file for the authoring syntax.

   Two forms. `.refine-ladder-box` is a strip inside a definition/theorem box,
   reading as a second header bar under the title, with worded steps. Inside a
   margin `.defnote` the label has no room for words, so `.refine-ladder-note`
   is dots alone, hung under the term. */
.refine-ladder {
  display: flex;
  align-items: center;
  gap: 0.6em;
  font-size: 0.78em;
  letter-spacing: 0.04em;
  text-transform: lowercase;
}

.refine-dots {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
}

/* Dots are drawn, never written: custom.js slugs a defnote's textContent into
   its anchor id, so any text here would rename existing links. */
.refine-dot {
  display: inline-block;
  width: 0.62em;
  height: 0.62em;
  border: 1px solid currentColor;
  border-radius: 50%;
  opacity: 0.55;
  transition: opacity 0.12s ease, transform 0.12s ease;
}
a.refine-dot:hover { opacity: 0.9; transform: scale(1.25); }
.refine-dot-current { background: currentColor; opacity: 1; }

/* Steps read as directions, not links: no underline, arrows on the outside. */
.refine-step { text-decoration: none; opacity: 0.8; }
.refine-step:hover { opacity: 1; text-decoration: underline; }
.refine-prev::before { content: "\2190\00a0"; }  /* <- */
.refine-next::after  { content: "\00a0\2192"; }  /* -> */
/* Ends of the ladder: the direction still shows, greyed, so the reader can see
   they are standing on the vaguest (or the most precise) statement. */
.refine-step-off { opacity: 0.25; }

/* Box form. The `> ` selectors match the specificity of the .definition rules
   above (padding, first-child margin) so these win by source order. */
.definition > .refine-ladder-box,
.theorem > .refine-ladder-box,
.lemma > .refine-ladder-box,
.example > .refine-ladder-box {
  justify-content: flex-end;
  margin: 0;
  padding: 0.3em 1em;
  background: var(--snack-title-bg);
  color: var(--snack-title-color, var(--snack-accent));
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  /* Sits under the ::before title bar; a hairline separates the two. */
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.06);
}
/* Restore the gap the title bar normally leaves before the body: the ladder
   displaced the first child that `*:nth-child(1)` was targeting. */
.definition > .refine-ladder-box + *,
.theorem > .refine-ladder-box + *,
.lemma > .refine-ladder-box + *,
.example > .refine-ladder-box + * { margin-top: 0.75em; }
html.coal :is(.definition,.theorem,.lemma,.example) > .refine-ladder-box,
html.navy :is(.definition,.theorem,.lemma,.example) > .refine-ladder-box,
html.ayu  :is(.definition,.theorem,.lemma,.example) > .refine-ladder-box,
html.green :is(.definition,.theorem,.lemma,.example) > .refine-ladder-box {
  border-bottom-color: rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

/* Margin-note form: its own line under the term, right-aligned like the label. */
.refine-ladder-note {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.25em;
}

/* Landing marker: stepping to another rung scrolls to a ladder that is a few
   pixels tall, so tint the definition it belongs to instead. */
@keyframes refine-arrive {
  from { background-color: rgba(28, 74, 84, 0.22); }
  to   { background-color: transparent; }
}
:is(.definition, .theorem, .lemma, .example):has(.refine-ladder:target),
.defnote:has(.refine-ladder:target) {
  animation: refine-arrive 1.6s ease-out;
}
/* A margin note is dimmed to 0.55 in the gutter; bring the one just stepped to
   up to full strength so it is findable. */
.defnote:has(.refine-ladder:target) { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  :is(.definition, .theorem, .lemma, .example):has(.refine-ladder:target),
  .defnote:has(.refine-ladder:target) { animation: none; }
}


/* == Source logos as reference-list bullets =================================
   Each logo is defined once as a data URI (self-contained). A .ref-list renders
   references as a list whose BULLET is the source logo, picked from each link's
   destination and hung into the indent (wrapped URLs align cleanly). PCM has no
   domain/favicon, so it's matched by its book DOI (9781400830398) and drawn as a
   masked nautilus shell that tints with the text color. */
:root {
  --logo-mathworld:  url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAKSElEQVRoge2Ye3SV1ZnGf8/+vnNObifQhLsXUEAuRlAQULxSrVWpraVaLg5YdDnVDg5xwJnWUU/jtFW0rZVI27S6ABGF4HJaqjhddNphqrNgIVC5VcDBCwEkICQhyck5Od9+54+D7VqdtZJwaf/K8++3vr2fZ7/vu/f7PtCNbnSjG904Deivs6yJ1Lf//9pVVQbYmdzpzAlIpUKSw/uT0EiwCeAGI4pPfPXAYYydEG3gSOY9qmY3nIltT19AqraEXlyEMQVnNwODMRxSK2YZZIWgYsDyh6/jwAYi/0uCaA1bWw7ws6+3/+0F1NTEyJWNw9vdyK4HBiA5zNJgn2BsAw5hjEKqQMqAlQBtQBysDdNOzF7FZ1ZQOWsf6KTT69QEPPVCMYUFszA/F2kgqBlsN9AT2VIi2wbmwF2F03TQe+CHgAYgWjEa8tHRoBOC3sSiR5kzfRM6ORHBSZN/traEwM0HHsKpJ95Wgd2P3Ot4q8CxDC+Hs6FIYxBDwI6DCzBiSKUnUqoYqRGUxGwIuIvZsG0X4yvqWLeuyyJOTsBTLxQTuPlI9yKLYbxN2s+hoL0BC64BTQUNRtYbXBOwAaceoAymvUgR0AAW4lQKVgzWgtSOOA9pLAVl7zOhYm9XRYRdJl9TE6O9YBZmc4DNQD3iYwo1mihxF6IAqR7YQktTNekLWiitj5FgKDA3Xxf+O8hdj7d6As0Ey2FKIwaAHcR0Li54jL4j6oDtXaHV9QjcdOdlYN/D0YZy/4wPypG7FafRwBvkoicwDuM0hUTRemL1CeLhfDxXg5ZgVorcLUCId/+OiwaB2wQWgvYjZcE2gS7HcFz7d2/xmxXZzmi5LpFP1Zbg7W7Mzsb7H2OBIcZh1gN8NVsbaqiccYjWaA3YBrxVEyv6IaaQ0N3Lf+tp2tvuBfskf2x+GriBGCOROw8YgrQBp3LgKNJXiLdO6gq1rgnorQvBX4+cR3wAegBsM7ADcwPofyB/m7XtPo73byMbAroQYyPf2LGHVV+NKCo6CyRyeoSg/VE8yzB2Yf4+zN7Csw1vyxEFQD9CdycLfpHsjFrnKZRKhRT1mgO6FofHNAyxEd/+BN4dwGkmxX3eYdzII5QP/xzOPYTZK4g/AHdwU69yrvvKYULNxutjCo8t5b676rlhSgan80gXvEisvQ7xBZRbggVxxERgIPHcb1nzyocd0es8Asnh/YHJiF3APrAMGRYxd2YT7dl1eHsb7GHKh03D6R6MF8nqcTJ8n9D9K9JAEu5pjFuR305bUSHgkB1FKibMxHDBFsRxfHALogw4jijENJWamtjpCUgwGjEIyOGtCLkQ8/n/HpzVQmA14NtxwQKwd/HZZcybmmbe1DT33b6OKHoM6SCQQdyFiz1H9coHCWNXIEsS11lYNCD/drhKjL4YPweaEeNJJ8tOQ4AJGINZDLM+GIvAZ0i4yX86mUw8By7IF6guxbvLSS0u+HQBAgLMJ8H+HmMOso1gY8AewHQDsiXI/RKny0AQ+WdQtBR0CNGbwJ3VEcOOayCFo6jPbGA00jtY9gm8+yPSdNrD/Xz5a4dR5h8wyomoRGrBuVkUBaOYPLWewVceo7z0S0AZzdHLzJvxPuMq1tPa89eUuP8B9cPnniTyPyXLSwQ6G2elZIPfEdg1oMEYr/PGK3tPMQKAKEYCaTsZ6sm534PfSBA+RLZtBmgizp6lctp2/MeLwR4BVwL2NMP6fhPZbLA32RxrAqCqylM1s4koewTzjVj4DpV37GH+9H1YtAk0nFjmHKT+QIgn3hG9TgRcGGIWIoTZefhsD+ZNTePdz/Dsx+xJjJ1YsBWAuXMz3D9tM22ZeZhewHQbqALcBVytS3h8+WdI1cbzqRkkwJVQkPtzy2DhbtBQXOJboN5InfYKnQjYkUPK4i2H2SCKkl+ktjagoKEB+V04jgAjMH87zywr/dNvD85qwUW/AeowViIVg/8XSoJFlNu3qa79OgSTgUIybgQLl49n0aopBP4LiEGYNWN+LYbhrcOeqCu90GFEGvgvsNupD3biSuqQuxHju8hyoPsIYmP5/ssLmT99Dxj4leOR4jS0fo+EayAqKKcgGkYQjEGMRYzC6I9zj2MujY+OAvVgh5BbgtkMoA3zracuoKrKWLhiByIAdmF8hHIponALWCPZ479ifY9GLs/8kXjsYQqChVSvWIJb8RZenwe/hoe/dpD8SNkCfASs5Zk1CdS0AFkTuXAZufY2grbj+JwjXro6v5+GYdZE6Oo7othZERs51iNlkSaCW4VYg/gy0k6a6ppY9dWIf5q5GRfdjeclpC+B+ynSWCJ2k1oc5y8Hp+yREKezMdtB5W35An5gdgO+MAkIbzdjNgr4kFxu/+kIgMb0XmAjMAlxObloNWbHMCZTPvJ2frCyEIBv3HGMT3q/RLr9mxhpIIn0LXoV/YiFtTfz1IsDSaXyEVdwDtAPac+fN0o5CoKLcBqEYzaiJ+JXHH2vuSN6XRspF66cg2MBsDf/mNnVmG3B6TZMq2nJLiX93gGqqjw/qR1Jzr+I8RhmrThNBncZWAtYHcZ2HKWYbiUX3Y1I49z5GOMIdCNGT2RJTE1EuZuovGNPR9S6Ng9MvvUQFlyNbBTSpcC7NGefJB6+CVxBIphCcZ8kN0xrBn8PzmVo0bPMn7aTG6/6HdbzNZTbhtGEdC5iKuJDnIbidAnQD0eENAp4H6kv0mK2N61m02u+I2pdm8i2thygoserSCNOiB5Dj8RE3mn8Dy7q/Rg+cw1Os3HRnYiziaJ/Y9Or+dDPnZsB6oA6zNaxaMVnwU3ElCJru3Bxj2v2KLgWc9NBF4N9RDaq7Yrd0rUIbHrN8/lb9uHC0UAFUhIYS59EIVFuO0d3bSZXuJbC+JWgC5Drz8ARPbluSoZJUzJ88RpjwgRjazoOmWmINNsanufxWa2Ubmunv5UQC+aCrkCAZwHH+q1l3dIOTx9OylYxUf3yWAieRYwA+0+MZnBZ8L8G9y74R5GtxqwRufEY54PagcNIBzFrQ0zH7HUi/zZh2APvz0eagHQdWAzP86itivvv/KQrrE7OFzIT1SuuxLlq4ByM3wN/AMbkbw1roT36R47t/l9KRyZQtidBbCBOw4AhwAwgA3YQ1AQ6CnYpsgvzzoUtJ/KPUDnjUFcpnZytUlUF4yvqKCh7H7mL828DvXCsxewSTGU4N46C8h7gPc4dIwz2E9duPEOBwUS6n0T4PK3ReuJ2LjAJ5PEsxvuqkyEPp+rMpVKOviNGEnEPTlMwPoNZDum3ePsIEcdRiFGYtwvNg64CvYvYjrc+SMOBgWAf4O05lHmlq2lz+gI+xYJfJIm3TiIezMTrc8jHT5i3jRgfA4YYCRRh1o5cEZxozox9SMvJ5mpp7L+Lqkm5U6FwZuz1Bc8lKS4djdl0xHhEb4wkECKBYeQ90EbwH4JeI5d7g53NH5yOM33mBHyKmpoY6WQZgTsLowxPnBDwZphvRYlDpMP9tG5poaqq0yuyG93oRje68VfH/wGdlFfhAo71zAAAAABJRU5ErkJggg==");
  --logo-wikipedia:  url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAZlBMVEX///8AAAAzMzMwMDAtLS0eHh4oKCj6+voYGBgaGhrR0dFbW1s9PT3z8/NPT09WVlYQEBDo6OhjY2Pf3995eXnExMRtbW1FRUWXl5doaGi+vr6zs7OdnZ3Z2dkjIyOQkJCEhISmpqYcB05TAAABn0lEQVRYhe2UyWKEIAyG2XFDUdxm3Mb3f8mCgJ0e6mBPPfBd/AUTEpIIQCQSiUT+Kz1kQ5WXhYRqhHjQisJlg6zMhxRmoxbVIGH9uwOuNkkIpqMAvO4pIWwy6sHY+jyWGMGduA5ioog0VjYEl4dQCbGn1klycTzwZt7BItGqjOjgblcU3D7agyxBVFn5IKw1z3xwe3P6If6DArOX85UiqUNeYGffedEG2JtbkNzKhhhfee52OqhCHAiEV5dzl2IiMpi5nbwMsQdgY3hwIeh0xsoHoHwqn6glou5TnQ5OFu8Y8TAHppKVVbzApHerYp0D7XXqSLrr2tcznQmG1NBSnpUUuivcjYbV8DyX2OP2BGF8qAUuVyY/4bqJj3MFeyFEJyP7/NrmJy3DyDxnaaQpv5DPOw4UPSrJ4WSKmmo50zv2uukIeejKF8BPNQ2uoeVJUaqENP20UJRknQyvoWVFbBsLH03/uFFDy7wiLO0U6b4iYXP4Tr2evzY9Ur6179Ay3856pGjgHL6TQT9FeqSK0Dl8p/n+AU/7H+wjkUjkJl+4BBLrIwrZsAAAAABJRU5ErkJggg==");
  --logo-grokipedia: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAYFBMVEUNDQ0AAAD8/Pz///8HBwfp6enLy8v4+PgYGBgvLy+8vLzT09PHx8fOzs6EhISrq6vg4OBBQUGcnJw5OTlXV1eNjY0mJiajo6N7e3va2tqWlpYfHx+2trZISEh1dXVsbGwzpoV0AAACBUlEQVRYhe2W2ZaDIAxAGxapWnG32vX//3LYbEcLwfacmafmTZJcTCAJu91XvoILLOQD76ssk3yqpjwp+1v6FoQBjAe6FFL09VYIQEOUh/LiRVaWWS7Ul/6klZBxAoORGPtJPhLQHoVa0otxAECubSnvANhzEUpL6GMASM1e9LKKlsHdrJ8iAGhpyM4QaIMDYG/3L31mOgq/5pdNZeP3W4HQoaH+jc1UHQD0lB4wAAzWPwkYMaC0QAGZBXQhIzjQCQHMPxDIgLaQiFKpjxYQTjQbTiMG4BZwD9swrJggNf6EPm/we6ICNADxdu+YARcbQf4xILcA9KqgAB47hBhA2BxG6g0BuEOIVfzfA45rADNtkTkJD4k5B+uKZ8O1TtNhb+WctnXdegm2mZCXglWNaC2VH1BYwFoL48QptfGZzk4m/0m7xq1qYa1Qkkrh/MshmIObA1w9eoBubhbhYsfPEfB2Z0xyrJqAG2WGAUb3C2fvVDClggJ2QAJXaTOgd4fl60lbAI+K9o2fbYDWZcEzGTYBsDw6AHaMxuxkgxDDy33cBpgTSdV0WJo6QLzlQkfcnaufl16XA8cm/4Kg+ruxpdWpc4+sWmZu7aXUPMJgyOj8sBO84uLRCAq5bWypPWVClk1EFI3+n61jT7e+dmwuSZEnyaWR3e6D9/Lirf3hwP3KV/5JfgDkFRJivFlZxgAAAABJRU5ErkJggg==");
  --logo-pcm:        url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAilBMVEX////yjTDzkB3yiyzzmk3++fPzjRD97NzzigD2sWzxhhf74tPxhx/5z67xhRLzjhf2tYD1oEP4v4n0lif859b5zKLzlUD+8+ryiyb4w5n4xJLykTT4yaTyhgD1qGj1rXL617v3u4373cX1pE/0n1b606z0nDj1qVr0o173un785M761rTxfwD2rWJzOlF8AAACBklEQVRYhe2XXXejIBCGESJalmC6GoMxnyabtun2//+9ZYYkp3vOINr20veKo8PDzDAwytikH9SvHlWrAYDaqLB0spvHIDOR9EgIrXfVNwAgq/Z9XiBAB2Q93dpDBLCZk2r2rbEYiWn6ACJJg6+zo0aEWnwRwFi6UOCkCvkQBTB2wFSo7ssAVglHEC1t9QBksiz5EzxaFiWIl6d1diMo2KpjBJAXhfQAN/LKt09+2TkQDFkOnwCc3wH8oZx7J84uCE3mkQTcHABCwXHdq4IsDAT8uTyjLrl0BPlyt0sMdSoIwEPZCwST4/5ttIthMxLA2Ie8P4SNsPvRgJUDFCW8TqEUzqMB7OQymWMaa2dYjwdADN8CXJwHEkNoneFsNKBzD4sLjFbuVIvXsYCMQwrWMIRKstSlQADSzKtaSyhGn4IG6mA+DLDc5jfhaViiXQ2VmA0EfDpMPPdRdXAWqE2IAOT2zZu1IlDJdAgS5WI4Xb3V0WVAWPJKIgDd22/U+v1+gxzwRiIdiBWSn6+hu1BVNAzQwL0uNLUFQwDVDNZPzIGeHzvO72eD/dFQNRQCdIubXmutcbpQofVJwMZYL+Gbs9CzQPwhgP7vG0OLcGuOAYTVqm36Gx8F+Gu8VLtr+r9vaED6UGxyADBOE2ACTAAv/OVBgOuI24/xgKxygkF6dYOeq2vSj+sfqlooEnLksIYAAAAASUVORK5CYII=");
}
.ref-list { display: block; margin: 0; padding-left: 0; } /* indent comes from .sidenote's gutter */
.ref-item { display: block; padding-left: 1.5em; margin: 0.2em 0; line-height: 1.35; }
/* Whole reference list in New Computer Modern Mono Regular, upright — matching
   arxiv's Latin Modern Mono URL style (deterministic; also covers the non-URL
   PCM citation, keeping the list uniform). */
.ref-item a { font-family: "New Computer Modern Mono", monospace; font-weight: 400; font-style: normal; }
.ref-item a[href*="mathworld.wolfram.com"]::before,
.ref-item a[href*="wikipedia.org"]::before,
.ref-item a[href*="grokipedia.com"]::before,
.ref-item a[href*="9781400830398"]::before {
  content: "";
  display: inline-block;
  width: 1em; height: 1em;
  margin-left: -1.5em;   /* hang into the .ref-item indent, acting as the bullet */
  margin-right: 0.5em;
  vertical-align: -0.15em;
  background: center/contain no-repeat;
}
.ref-item a[href*="mathworld.wolfram.com"]::before { background-image: var(--logo-mathworld); }
.ref-item a[href*="wikipedia.org"]::before        { background-image: var(--logo-wikipedia); }
.ref-item a[href*="grokipedia.com"]::before       { background-image: var(--logo-grokipedia); }
.ref-item a[href*="9781400830398"]::before        { background-image: var(--logo-pcm); }
/* Dark themes: the black Wikipedia W would vanish — flip it to white. */
html.coal .ref-item a[href*="wikipedia.org"]::before,
html.navy .ref-item a[href*="wikipedia.org"]::before,
html.ayu  .ref-item a[href*="wikipedia.org"]::before,
html.green .ref-item a[href*="wikipedia.org"]::before { filter: invert(1) brightness(1.7); }

/* --------------------------------------------------------------------------
   Hover previews for numpy documentation links (behavior in theme/js/custom.js).
   A small floating card showing the fetched function signature + summary. Reuses
   the notebook palette (cream in light, dark in the dark themes) so it reads as
   part of the same surface family; the signature line borrows the In[]/Out[]
   prompt color and the book's --mono-font.
   -------------------------------------------------------------------------- */
.doc-preview {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 1000;
  max-width: min(420px, calc(100vw - 24px));
  padding: 0.7rem 0.8rem;
  background: var(--nb-in-bg);
  color: var(--fg);
  border: 1px solid var(--nb-in-border);
  border-radius: 6px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
  font-size: 1.35rem;
  line-height: 1.4;
  opacity: 0;
  transform: translateY(-2px);
  pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.doc-preview.visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.doc-preview-sig {
  font-family: var(--mono-font, "Source Code Pro", monospace);
  font-size: 1.2rem;
  color: var(--nb-prompt);
  white-space: pre-wrap;
  word-break: break-word;
  margin-bottom: 0.4rem;
}
.doc-preview-summary {
  margin: 0;
}
.doc-preview-loading {
  color: var(--nb-prompt);
  font-style: italic;
}
.doc-preview-src {
  margin-top: 0.5rem;
  font-size: 1.1rem;
  font-family: var(--mono-font, "Source Code Pro", monospace);
  opacity: 0.65;
}


/* == `green` theme ==========================================================
   A sixth theme alongside mdBook's light / rust / coal / navy / ayu, styled
   after Modal's GPU Glossary (https://modal.com/gpu-glossary): one bright
   phosphor green on a near-black green ground, everything else a transparency
   of that same green. The hexes below are lifted from the glossary's own
   inline styles — #7FEE64 on #0D180A, with the 80% / 60% / 20% / 5% tints it
   uses for secondary text, disabled text, hover, and surfaces.

   Wiring: book.js builds its theme list from the buttons inside
   #mdbook-theme-list, so the only other change needed is the <li> in
   theme/index.hbs. There is no theme/css/variables.css in this repo — mdBook
   ships its own, and this file is loaded after it, so `html.green` (which
   outranks the built-in `.ayu`-style class selectors) is where the theme
   lives. That also means every rule here must be written `html.green`, not
   `.green`: the dark-theme groups further up this file are `html.coal,
   html.navy, html.ayu, html.green`, and a bare `.green` would lose to them.

   The green theme is a *dark* theme as far as the rest of this file is
   concerned — it is listed in each of those groups, so it inherits the dark
   figure swaps and dark component palettes, and then re-tints the ones that
   would otherwise read as blue/amber panels floating on green.
   -------------------------------------------------------------------------- */
html.green {
  --g-bg: #0d180a;
  --g-fg: #7fee64;
  --g-fg-80: rgba(127, 238, 100, 0.8);
  --g-fg-60: rgba(127, 238, 100, 0.6);
  --g-fg-40: rgba(127, 238, 100, 0.4);
  --g-fg-20: rgba(127, 238, 100, 0.2);
  --g-fg-10: rgba(127, 238, 100, 0.1);
  --g-fg-05: rgba(127, 238, 100, 0.05);
  /* The two non-green accents Modal's own palette allows itself. */
  --g-pale: #c8f9b6;
  --g-amber: #ffab5e;

  /* --- mdBook's theme variables (same set as its .ayu block) ------------- */
  --bg: var(--g-bg);
  --fg: var(--g-fg);

  --sidebar-bg: var(--g-bg);
  --sidebar-fg: var(--g-fg-80);
  --sidebar-non-existant: var(--g-fg-40);
  --sidebar-active: var(--g-fg);
  --sidebar-spacer: var(--g-fg-20);

  --scrollbar: var(--g-fg-40);

  --icons: var(--g-fg-60);
  --icons-hover: var(--g-fg);

  --links: var(--g-pale);

  --inline-code-color: var(--g-amber);

  --theme-popup-bg: var(--g-bg);
  --theme-popup-border: var(--g-fg);
  --theme-hover: var(--g-fg-20);

  --quote-bg: var(--g-fg-05);
  --quote-border: var(--g-fg-40);

  --warning-border: var(--g-amber);

  --table-border-color: var(--g-fg-40);
  --table-header-bg: var(--g-fg-20);
  --table-alternate-bg: var(--g-fg-05);

  --searchbar-border-color: var(--g-fg-60);
  --searchbar-bg: var(--g-bg);
  --searchbar-fg: var(--g-fg);
  --searchbar-shadow-color: var(--g-fg);
  --searchresults-header-fg: var(--g-fg-60);
  --searchresults-border-color: var(--g-fg-40);
  --searchresults-li-bg: var(--g-fg-05);
  --search-mark-bg: rgba(127, 238, 100, 0.35);

  --color-scheme: dark;

  /* The clipboard icon is recolored below by swapping the SVG's fill rather
     than by an invert()/hue-rotate() stack, so these are inert. */
  --copy-button-filter: none;
  --copy-button-filter-hover: none;

  --footnote-highlight: var(--g-fg-60);

  --overlay-bg: rgba(13, 24, 10, 0.6);

  --blockquote-note-color: var(--g-pale);
  --blockquote-tip-color: var(--g-fg);
  --blockquote-important-color: #ff8de6;
  --blockquote-warning-color: var(--g-amber);
  --blockquote-caution-color: #f87171;
  --blockquote-question-color: var(--g-amber);

  --sidebar-header-border-color: var(--g-fg-40);

  /* Notebook cells (the coal/navy/ayu block above is blue-and-amber). */
  --nb-prompt: var(--g-fg-60);
  --nb-in-bg: var(--g-fg-05);
  --nb-in-border: var(--g-fg-20);
  --nb-out-color: var(--g-amber);
  --nb-error-bg: #2a1113;
  --nb-error-color: #f0a0a0;
  --nb-stderr-bg: #241012;
  --nb-hl-bg: rgba(127, 238, 100, 0.14);
}

html.green ::selection {
  background-color: rgba(127, 238, 100, 0.2);
}

/* The glossary is drawn with 1px hairlines and hard corners throughout — no
   rounding anywhere — so flatten the surfaces mdBook rounds by default. */
html.green :is(pre,
               code,
               kbd,
               .theme-popup,
               #searchbar,
               .searchresults-outer,
               .definition,
               .theorem,
               .lemma,
               .example,
               .nb-in-code,
               .nb-cell pre,
               .doc-preview,
               .quiz) {
  border-radius: 0;
}

/* Menu bar and sidebar are separated from the text column by hairlines, and
   the sidebar's own edge is the glossary's dashed rule: a 1px column of 6-on,
   6-off green, painted as a repeating gradient rather than `border-style:
   dashed` so the dash length doesn't drift between browsers. */
html.green #mdbook-menu-bar {
  border-bottom: 1px solid var(--g-fg-20);
}

html.green .sidebar {
  background-image: linear-gradient(to bottom, var(--g-fg-40) 50%, transparent 50%);
  background-size: 1px 12px;
  background-position: right top;
  background-repeat: repeat-y;
}

/* The active chapter is a filled chip — green ground, background-colored text —
   the way the glossary marks the term you are reading. */
html.green .chapter-link-wrapper:has(> a.active) {
  background-color: var(--g-fg);
}
html.green .chapter li a.active {
  color: var(--g-bg);
}

/* Everything in one typewriter face, the way the glossary sets its whole page
   in degular-mono — here it is VS Code's default editor font, Menlo, with the
   rest of that stack behind it for platforms that don't ship it. Prose,
   chrome, and code all share the face, so the theme reads as one terminal
   document. Redefining --mono-font rather than only `body` is what carries it
   into code blocks and the `.nb-*` cells, which set that variable directly.

   Math is unaffected: KaTeX renders from its own KaTeX_* faces and never
   inherits `font-family` from the page. */
html.green {
  --mono-font: Menlo, Monaco, "Courier New", monospace;
}

html.green body {
  font-family: var(--mono-font);
}

/* Clipboard button: mdBook's octicon has no `fill`, so it paints black and the
   theme recolors it with a filter. Restating the icon with the fill baked in
   is exact where a filter chain is an approximation. Path data is mdBook's
   (octicons v2.0.0, MIT). */
html.green pre > .buttons button.clip-button::before {
  content: url('data:image/svg+xml,<svg width="21" height="20" viewBox="0 0 24 25" \
xmlns="http://www.w3.org/2000/svg" fill="%237fee64" aria-label="Copy to clipboard">\
<path d="M18 20h2v3c0 1-1 2-2 2H2c-.998 0-2-1-2-2V5c0-.911.755-1.667 1.667-1.667h5A3.323 3.323 0 \
0110 0a3.323 3.323 0 013.333 3.333h5C19.245 3.333 20 4.09 20 5v8.333h-2V9H2v14h16v-3zM3 \
7h14c0-.911-.793-1.667-1.75-1.667H13.5c-.957 0-1.75-.755-1.75-1.666C11.75 2.755 10.957 2 10 \
2s-1.75.755-1.75 1.667c0 .911-.793 1.666-1.75 1.666H4.75C3.793 5.333 3 6.09 3 7z"/>\
<path d="M4 19h6v2H4zM12 11H4v2h8zM4 17h4v-2H4zM15 15v-3l-4.5 4.5L15 21v-3l8.027-.032L23 15z"/>\
</svg>');
  opacity: 0.6;
}
html.green pre > .buttons button.clip-button:hover::before {
  opacity: 1;
}

/* Snackbars: the shared dark palette tints these blue / red / amber, which
   fights the monochrome. Here they are all the same green panel, told apart by
   the accent stripe alone (pale green, green, amber, and the one non-green the
   glossary uses for its "deprecated" chips). */
html.green :is(.definition, .theorem, .lemma, .example) {
  --snack-title-bg: var(--g-fg-10);
  --snack-body-bg: var(--g-fg-05);
  --snack-title-color: var(--g-pale);
}
html.green .definition { --snack-accent: var(--g-pale); }
html.green .theorem    { --snack-accent: var(--g-fg); }
html.green .lemma      { --snack-accent: var(--g-amber); }
html.green .example    { --snack-accent: #ff8de6; }
html.green :is(.definition, .theorem, .lemma, .example)[data-title]::before {
  border-bottom-color: var(--g-fg-20);
}
html.green .box-notes {
  border-top-color: var(--g-fg-20);
}
html.green :is(.definition, .theorem, .lemma, .example) > .refine-ladder-box {
  border-bottom-color: var(--g-fg-20);
  box-shadow: inset 0 1px 0 var(--g-fg-10);
}

/* Syntax highlighting. mdBook only swaps in tomorrow-night / ayu-highlight for
   the themes it knows are dark (coal, navy, ayu); everything else gets the
   light highlight.css, so `green` would otherwise render code as dark text on
   a #f6f7f6 slab. Rather than teach book.js about a sixth theme, override the
   token colors here — they need to be green-keyed anyway. Selector list
   mirrors highlight.css's groups. */
html.green .hljs {
  background: var(--g-fg-05);
  color: var(--g-fg);
}
html.green :is(.hljs-comment, .hljs-quote) {
  color: var(--g-fg-40);
  font-style: italic;
}
html.green :is(.hljs-variable,
               .hljs-template-variable,
               .hljs-attribute,
               .hljs-attr,
               .hljs-tag,
               .hljs-name,
               .hljs-regexp,
               .hljs-link,
               .hljs-selector-id,
               .hljs-selector-class) {
  color: var(--g-pale);
}
html.green :is(.hljs-number,
               .hljs-meta,
               .hljs-built_in,
               .hljs-builtin-name,
               .hljs-literal,
               .hljs-type,
               .hljs-params) {
  color: #decb6c;
}
html.green :is(.hljs-string, .hljs-symbol, .hljs-bullet) {
  color: var(--g-amber);
}
html.green :is(.hljs-title, .hljs-section) {
  color: #e9ffe2;
}
html.green :is(.hljs-keyword, .hljs-selector-tag) {
  color: var(--g-pale);
  font-weight: bold;
}
html.green .hljs-addition {
  color: var(--g-fg);
  background-color: rgba(127, 238, 100, 0.12);
}
html.green .hljs-deletion {
  color: #f87171;
  background-color: rgba(248, 113, 113, 0.12);
}

/* Links, the glossary's way: no underline and no color shift — the link is
   marked by a translucent green chip behind the words (its #7FEE6433, i.e.
   --g-fg-20). The negative inline margin cancels the padding so adding the
   chip doesn't move the surrounding text, and box-decoration-break keeps the
   chip on both halves of a link that wraps across lines.

   Scoped to `.content main` — the prev/next chevrons live outside <main>, so
   they are excluded automatically — and then opted back out for the links
   that are chrome rather than prose: heading self-links, the notebook's
   filename label, and the bibliography's URL-as-text entries, which carry
   their own styling. */
html.green .content main a {
  color: inherit;
  text-decoration: none;
  background-color: var(--g-fg-20);
  padding-inline: 0.25em;
  margin-inline: -0.25em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-color 0.12s ease;
}

html.green .content main a:hover {
  background-color: var(--g-fg-40);
}

html.green .content main :is(a.header, .nb-file a, .ref-item a, .refine-ladder a) {
  background-color: transparent;
  padding-inline: 0;
  margin-inline: 0;
}
html.green .content main :is(a.header, .nb-file a, .ref-item a):hover {
  color: inherit;
  text-decoration: underline;
}

/* Sidebar rows take the same treatment: a chip on hover, the solid green
   chip when active (set further up). */
html.green .chapter-link-wrapper:hover {
  background-color: var(--g-fg-20);
}

/* --- Cursors --------------------------------------------------------------
   The glossary swaps the pointer for terminal-drawn glyphs: a crosshair over
   the page, a blocky hand over anything clickable, an I-beam while dragging a
   selection. These are our own drawings in that idiom — dark-green fill,
   bright-green hairline — not Modal's asset files.

   Each url() carries its hotspot (the two numbers after it) and a keyword
   fallback for browsers that reject the image. `#` has to be written %23
   inside a data: URI or it starts a fragment; angle brackets are fine raw
   (mdBook's own clip-button icon is written the same way). */
html.green body {
  cursor: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" \
viewBox="0 0 24 24"><path d="M11 4 H13 V11 H20 V13 H13 V20 H11 V13 H4 V11 H11 Z" \
fill="%232e5524" stroke="%237fee64" stroke-width="1"/></svg>') 12 12, crosshair;
}

html.green :is(a, button, summary, label[for], .chapter-fold-toggle, .icon-button, .clip-button) {
  cursor: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="18" height="22" \
viewBox="0 0 18 22"><g fill="%232e5524" stroke="%237fee64" stroke-width="1">\
<rect x="3.5" y="1.5" width="3" height="9"/><rect x="6.5" y="6.5" width="3" height="4"/>\
<rect x="9.5" y="6.5" width="3" height="4"/><rect x="12.5" y="6.5" width="3" height="4"/>\
<rect x="3.5" y="10.5" width="12" height="8"/></g></svg>') 5 2, pointer;
}

html.green body:active {
  cursor: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="24" \
viewBox="0 0 12 24"><g fill="%232e5524" stroke="%237fee64" stroke-width="1">\
<rect x="4.5" y="2.5" width="3" height="19"/><rect x="1.5" y="2.5" width="9" height="3"/>\
<rect x="1.5" y="18.5" width="9" height="3"/></g></svg>') 6 12, text;
}

/* Text inputs keep a real I-beam rather than the crosshair. */
html.green :is(input, textarea) {
  cursor: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="24" \
viewBox="0 0 12 24"><g fill="%232e5524" stroke="%237fee64" stroke-width="1">\
<rect x="4.5" y="2.5" width="3" height="19"/><rect x="1.5" y="2.5" width="9" height="3"/>\
<rect x="1.5" y="18.5" width="9" height="3"/></g></svg>') 6 12, text;
}
