/* ============================================================
   Deriving Systems — "instrument panel"
   Palette, type and components are derived from the subject:
   measurement, orders of magnitude, and things that are read
   off a scale rather than looked up.

   ⚠ SIZING: mdBook sets the root font-size to 62.5%, so in this
   file **1rem = 10px**, NOT 16px. Every rem value below is
   therefore "the px number, divided by ten": 1.9rem is 19px,
   70rem is 700px. Getting this wrong once already shipped a
   400px-wide column of 12.5px text. When adding a size, write
   the px you want and move the decimal point.
   ============================================================ */

@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans+Condensed:wght@500;600;700&family=IBM+Plex+Serif:ital,wght@0,400;0,600;1,400&display=swap");

:root {
  --paper:     #f7f8fa;
  --ink:       #14181f;
  --graticule: #dde2ea;
  --signal:    #0b6e75;
  --flag:      #a81e4d;
  --muted:     #5c6675;

  --display: "IBM Plex Sans Condensed", ui-sans-serif, sans-serif;
  --body:    "IBM Plex Serif", Georgia, serif;
  --meter:   "IBM Plex Mono", ui-monospace, monospace;

  --measure: 70rem;   /* 700px reading column */
}

/* --- mdBook variable overrides ------------------------------
   Three selectors, and all three are needed. variables.css sets these
   same variables on `.light, html:not(.js)`, and a class outranks an
   element, so a plain `html {` here lost every one of them: the site ran
   on mdBook's light defaults and only looked designed because the rules
   further down this file also style those elements directly, by
   selector. Match the weight and the later file wins.

     html            the base case
     html.light      0,1,1 against variables.css's .light at 0,1,0
     html:not(.js)   ties variables.css, and custom.css loads after it

   Add a variable here and check it in the browser, not by eye:
   getComputedStyle(document.documentElement).getPropertyValue('--links')
   ------------------------------------------------------------ */
html,
html.light,
html:not(.js) {
  --bg: var(--paper);
  --fg: var(--ink);
  --sidebar-bg: #eef0f4;
  --sidebar-fg: var(--ink);
  --sidebar-non-existant: var(--muted);
  --sidebar-active: var(--flag);
  --sidebar-spacer: var(--graticule);
  --scrollbar: var(--muted);
  --icons: var(--muted);
  --icons-hover: var(--ink);
  --links: var(--signal);
  --inline-code-color: var(--flag);
  --theme-popup-bg: var(--paper);
  --theme-popup-border: var(--graticule);
  --quote-bg: #eef1f5;
  --quote-border: var(--signal);
  --table-border-color: var(--graticule);
  --table-header-bg: #eef0f4;
  --table-alternate-bg: #f2f4f7;
  --searchbar-border-color: var(--graticule);
  --searchbar-bg: #fff;
  --searchbar-fg: var(--ink);
  --searchresults-header-fg: var(--muted);
  --searchresults-border-color: var(--graticule);
  --search-mark-bg: #ffe08a;
}

/* --- Type --------------------------------------------------- */
.content {
  font-family: var(--body);
  font-size: 1.9rem;      /* 19px */
  line-height: 1.62;
}
.content main { max-width: var(--measure); }

.content h1, .content h2, .content h3, .content h4 {
  font-family: var(--display);
  font-weight: 600;
  letter-spacing: 0.005em;
  line-height: 1.15;
}
.content h1 { font-size: 4.2rem; margin-bottom: 0.15em; }   /* 42px */
.content h2 {
  font-size: 2.55rem;                                        /* 25.5px */
  margin-top: 2.2em;
  padding-top: 0.5em;
  border-top: 1px solid var(--graticule);
}
.content h3 { font-size: 1.95rem; margin-top: 2em; color: var(--muted); }

/* Chapter numbers read like channel labels on an instrument. */
.content h1 .header-num,
.chapter li.chapter-item strong {
  font-family: var(--meter);
  font-weight: 500;
  color: var(--flag);
}

.content code,
.content pre code { font-family: var(--meter); font-size: 0.86em; }

.content pre {
  background: #eef1f5;
  border: 1px solid var(--graticule);
  border-left: 3px solid var(--signal);
  border-radius: 0;
  line-height: 1.45;
}

/* Code sits on the same light grey as a blockquote, inline and in a
   block, and it is pinned HERE rather than left to highlight.js.

   highlight.js's stylesheet carries a background of its own on `.hljs`,
   and mdBook used to link three of those stylesheets at once, so
   whichever loaded last decided what colour the code on this site was.
   That is how every inline `code` span spent a while rendering as a
   solid black rectangle you could only read by selecting it: ayu won,
   and its near-black background sat under chrome.css's dark brown
   --inline-code-color. index.hbs now links the light theme only.

   Note the selectors are STRUCTURAL, not `.hljs`. That class is added at
   runtime by a script that does throw on some builds, and a background
   that depends on it disappears exactly when the highlighting does. An
   element's position in the tree is always there. */
.content :not(pre) > code,
.content pre > code {
  background: #eef1f5;
}
.content pre > code { color: var(--ink); }

/* The inline padding is set here for the same reason. chrome.css only
   pads inline code that carries `.hljs`, so without this the grey box
   fits tight against the glyphs on any page where the script did not
   run, and loose on the pages where it did. Square corners, because the
   code blocks and the rule boxes are square. */
.content :not(pre) > code {
  padding: 0.15em 0.35em;
  border-radius: 0;
}

.content table { font-size: 0.9em; }

.sidebar .chapter { font-family: var(--display); font-size: 1.5rem; }
.sidebar .part-title {
  font-family: var(--meter);
  font-size: 1.2rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ============================================================
   SIGNATURE: the magnitude ruler, and other generated charts.
   These are matplotlib SVGs (see pipeline/make_charts.py), not
   hand-positioned HTML/CSS — regenerate with
   `pipeline/.venv/bin/python pipeline/make_charts.py` after
   changing any numbers.
   ============================================================ */
.content img.ruler-chart,
.content img.chart {
  display: block;
  width: 100%;        /* SVG — scales losslessly, and every chart
                         then shares the text column's edges */
  max-width: 100%;
  height: auto;
  margin: 2.6rem auto;
}
.content img.ruler-chart {
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--graticule);
  padding: 1rem 0;
}

/* --- The rule box: one sentence you carry out of the room ---- */
.rule {
  font-family: var(--display);
  font-size: 2rem;            /* 20px */
  line-height: 1.4;
  margin: 2.8rem 0;
  padding: 1.5rem 1.7rem;
  background: #fff;
  border: 1px solid var(--ink);
  border-left: 5px solid var(--flag);
  scroll-margin-top: 4rem;
}
.rule .rule-id {
  display: block;
  font-family: var(--meter);
  font-size: 1.15rem;         /* 11.5px */
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--flag);
  margin-bottom: 0.6rem;
}

/* --- Margin notes (Crafting Interpreters' habit) -------------
   Always inline, never floated. A float-into-the-margin version
   with a hardcoded negative margin used to live here; it clipped
   off-screen at real-world viewport/zoom combinations we couldn't
   reproduce reliably enough to trust a numeric fix. Inline can't
   overflow the viewport because it never leaves normal document
   flow — worth the lost "margin note" flourish. -------------- */
.aside {
  font-family: var(--display);
  font-size: 1.65rem;         /* 16.5px */
  line-height: 1.5;
  color: var(--muted);
  border-left: 2px solid var(--graticule);
  padding-left: 1.4rem;
  margin: 2rem 0;
}

/* --- Challenges --------------------------------------------- */
.challenges { border-top: 2px solid var(--ink); margin-top: 4rem; padding-top: 0.4rem; }
.challenges h2 { border-top: none; margin-top: 0.8em; }
.challenges ol { padding-left: 1.8rem; }
.challenges li { margin-bottom: 1.1rem; }

/* --- Footer -------------------------------------------------
   Rebuilt to match craftinginterpreters.com's geometry, which was
   measured rather than eyeballed:

     - the rule runs the full column width
     - the next-chapter link is absolutely positioned at the RIGHT
       end and pulled up by half its line-height, so it sits ON the
       rule rather than under it
     - it carries the page background, which knocks the rule out
       behind the text and produces the "line stops, label starts"
       effect
     - the byline sits centred below, smaller and quieter

   Lives in theme/index.hbs, a fork of mdBook 0.4.52's default
   template. See CLAUDE.md before upgrading mdBook.
   ------------------------------------------------------------ */
.content main {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - 10rem);
}
/* The gap above the rule is padding, not margin, so `margin-top:
   auto` cannot collapse it away once a page is long enough. */
.content main > .book-footer {
  margin-top: auto;
  margin-bottom: 4.8rem;
  padding-top: 4.8rem;
}
.book-footer .footer-rule {
  position: relative;
  border-top: 1px solid var(--graticule);
  padding-top: 4.8rem;              /* 48px, matching the reference */
}
.book-footer {
  font-family: var(--display);
  font-size: 1.5rem;                /* 15px */
  color: var(--muted);
  text-align: center;
}
.book-footer .next-chapter {
  position: absolute;
  right: 0;
  top: -1.2rem;                     /* half the line-height, to centre
                                       the label on the rule */
  line-height: 2.4rem;
  padding-left: 0.8rem;
  background: var(--paper);         /* knocks the rule out behind it */
  font-size: 1.7rem;                /* 17px */
  text-transform: uppercase;
  letter-spacing: 0.1rem;
  color: var(--signal);
  text-decoration: none;
  white-space: nowrap;
}
.book-footer .next-chapter:hover { text-decoration: underline; }
.book-footer .sep { padding: 0 0.4rem; color: var(--graticule); }
.book-footer .copyright { color: var(--signal); text-decoration: none; }
.book-footer .copyright:hover { text-decoration: underline; }

/* Absolute positioning would push a long chapter title off a narrow
   screen, so drop back to normal flow there. */
@media (max-width: 700px) {
  .book-footer .next-chapter {
    position: static;
    display: block;
    white-space: normal;
    padding-left: 0;
    margin-bottom: 1.4rem;
    background: none;
  }
  .book-footer .footer-rule { padding-top: 3rem; }
}

/* --- Quality floor ------------------------------------------ */
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--flag);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
@media (max-width: 700px) {
  .content { font-size: 1.75rem; }   /* 17.5px */
  .content h1 { font-size: 3.2rem; } /* 32px */
  .content h2 { font-size: 2.2rem; }
  .rule { font-size: 1.8rem; padding: 1.2rem 1.3rem; }
}

/* ============================================================
   BREATHING ROOM
   Typographic life, not decoration. Everything below is CSS only
   and needs no change to any chapter's markdown, so it cannot
   drift out of sync with the prose.
   NOTE the project-wide gotcha: 1rem = 10px here (root is 62.5%).
   ============================================================ */

/* --- Drop cap ------------------------------------------------
   On the first paragraph of the body, after the epigraph on a
   chapter or straight after the h1 on the front matter. Signals
   "a book starts here" before a single word is read. --------- */
.content blockquote + p::first-letter,
.content h1 + p::first-letter {
  float: left;
  font-family: var(--display);
  font-weight: 600;
  font-size: 6.4rem;          /* 64px, about three lines of body */
  line-height: 0.82;
  padding: 0.6rem 1.1rem 0 0;
  color: var(--flag);
}
/* A drop cap on a one-line paragraph looks like a mistake. */
.content blockquote + p:has(+ h2)::first-letter { float: none; font-size: inherit;
  color: inherit; padding: 0; line-height: inherit; }

/* --- Section ornament ----------------------------------------
   A division sign above every section break, matching the
   favicon. The book's recurring act is doing the division, so
   the mark that separates its sections may as well say so. ---- */
.content h2::before {
  content: "÷";
  display: block;
  font-family: var(--display);
  font-size: 2.2rem;          /* 22px */
  font-weight: 400;
  line-height: 1;
  color: var(--graticule);
  margin-bottom: 0.65em;
  margin-top: -0.2em;
}
/* Challenges and the design note already carry a heavy rule. */
.challenges h2::before { content: none; }

/* --- Pull quote ----------------------------------------------
   For the four-word landings the voice is built around: "It is
   not a mistake." Breaks the column and gives the eye somewhere
   to rest between two dense pages. Opt in with <p class="pull">.
   ------------------------------------------------------------ */
.content .pull {
  font-family: var(--display);
  font-size: 2.9rem;          /* 29px */
  font-weight: 600;
  line-height: 1.22;
  color: var(--ink);
  margin: 3.2rem 0;
  padding-left: 1.8rem;
  border-left: 4px solid var(--flag);
  max-width: 52rem;           /* 520px, deliberately short of the column */
}

/* --- Aside marker --------------------------------------------
   The float experiment stays retired (see the note above), but a
   marker still reads as "this is a margin note" rather than "this
   is more paragraph". ---------------------------------------- */
.aside::before {
  content: "";
  display: block;
  width: 2.4rem;
  height: 2px;
  background: var(--signal);
  margin-bottom: 0.9rem;
  opacity: 0.55;
}

/* --- Chapter illustration ------------------------------------
   One spot drawing per chapter, sitting between the epigraph and
   the first paragraph. Deliberately narrower than the reading
   column: a picture that runs full measure reads as a diagram,
   and these are not diagrams. -------------------------------- */
.content .chapter-illustration {
  display: block;
  width: 100%;
  max-width: 54rem;           /* 540px inside a 700px column */
  height: auto;
  margin: 2.6rem auto 3.4rem;
  border: none;
}

/* The drop cap targeted `blockquote + p`, and the illustration now
   sits between those two. Add the new adjacency rather than moving
   the picture, because after the epigraph is where it belongs. */
.content .chapter-illustration + p::first-letter {
  float: left;
  font-family: var(--display);
  font-weight: 600;
  font-size: 6.4rem;
  line-height: 0.82;
  padding: 0.6rem 1.1rem 0 0;
  color: var(--flag);
}

/* The chapter body is wrapped in a block container by theme/index.hbs,
   because `main` is a flex container and FLOAT IS IGNORED ON FLEX ITEMS.
   `main` has to stay flex: the footer's `margin-top: auto` is what pins
   it to the bottom of short pages. So the content gets its own normal
   block formatting context to float inside. */
.content main > .chapter-body {
  min-width: 0;               /* flex items refuse to shrink without this */
}

/* --- Marginalia ----------------------------------------------
   A wry line in the margin, next to the paragraph it is teasing.

   READ THIS BEFORE EDITING. There was an earlier attempt at margin
   notes here that had to be reverted: `.aside` floated right with a
   hardcoded negative margin above a `@media (min-width: 1500px)`
   query, and it clipped off-screen at real viewport and zoom
   combinations.

   The reason it failed is worth writing down, because the obvious fix
   (a bigger number) would have failed too. A viewport media query
   measures the WINDOW. mdBook's sidebar eats roughly 300px of that
   window when it is open, and the note has no way to know. Tune the
   breakpoint with the sidebar closed and it overflows with the sidebar
   open; tune it for open and the margin never appears when closed.

   A container query measures the container, which is the thing that
   actually has or has not got room. That is why this version is safe
   where the last one was not. Do not replace it with a media query.

   `.aside` is untouched and stays inline: it carries real argument and
   belongs in the reading path. Only `.quip` goes to the margin, and
   nothing in it is load-bearing. -------------------------------- */
.content { container-type: inline-size; }

.quip {
  font-family: var(--display);
  font-size: 1.45rem;         /* 14.5px */
  line-height: 1.45;
  color: var(--muted);
  border-left: 2px solid var(--flag);
  padding-left: 1.2rem;
  margin: 2rem 0;
}

/* The threshold is derived, not guessed, because guessing is what broke
   this the first time. `main` is 700px and CENTRED, so the free space to
   its right is only half the container's surplus:

       room to the right of main = (C - 700) / 2
       the note needs            = 32 gutter + 220 width = 252
       so                          (C - 700) / 2 >= 252
       therefore                   C >= 1204 px

   I first wrote 990px here, by adding 700 + 32 + 220 + slack and
   forgetting the centring halves the surplus. That would have clipped by
   214px: the identical failure to the one this component was rewritten
   to avoid, reached by a different route. Do the division.

   FLOAT, NOT ABSOLUTE. The first version of this used `position:
   absolute; left: 100%` with no `top`, expecting it to fall back to the
   static position beside its paragraph. It stuck to the top of the page
   instead, and the reason is a genuine spec rule rather than a mistake:
   `main` is `display: flex`, and the static position of an
   absolutely-positioned child of a FLEX container is the container's
   content-box origin, not where the element would have sat in flow.
   A float has no such special case. It aligns with the content that
   follows it, which is the entire point of a margin note. */
@container (min-width: 124rem) {
  .quip {
    float: right;
    clear: right;
    width: 22rem;             /* 220px */
    margin: 0.4rem 0 1.2rem 3.2rem;
    margin-right: -25.2rem;   /* -(220 width + 32 gutter), out into the margin */
    border-left: none;
    padding-left: 0;
  }
  .quip::before {
    content: "";
    display: block;
    width: 2.2rem;
    height: 2px;
    background: var(--flag);
    opacity: 0.5;
    margin-bottom: 0.8rem;
  }
}

/* --- Landing call to action ----------------------------------
   src/index.md only. The landing page deliberately offers one
   door, so it has to look like a door: set as a bold markdown
   link it rendered as one more sentence in a column of
   sentences, and the second line collapsed onto the first
   because a markdown soft break is a space. */
.content .start {
  margin: 3.4rem 0;
  padding: 1.6rem 1.9rem;
  background: #fff;
  border: 1px solid var(--graticule);
  border-left: 5px solid var(--flag);
}
.content .start p {
  margin: 0;
  font-family: var(--display);
  font-size: 1.6rem;          /* 16px, the subtitle line */
  line-height: 1.5;
  color: var(--muted);
}
.content .start a {
  font-size: 2.2rem;          /* 22px */
  font-weight: 600;
  text-decoration: none;
}
.content .start a:hover { text-decoration: underline; }

/* --- The course ladder ---------------------------------------
   src/course.md only. Twenty stages, each a heading plus two
   labelled lines, generated by pipeline/make_course.py. Both
   classes below are emitted by that script, so changing a name
   here means changing it there too. */

/* The same five pips ./vc guide prints in the terminal. Muted and
   small: it is a hint about a stage, not a rating anyone should
   shop by. */
.stage-diff {
  font-family: var(--meter);
  font-size: 1.3rem;          /* 13px */
  font-weight: normal;
  color: var(--muted);
  letter-spacing: 0.1em;
  white-space: nowrap;
}

/* Two rows of label + value. Grid rather than a table because the
   values wrap to several lines and the labels must stay put. */
.stage-meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.5rem 1.2rem;
  font-family: var(--display);
  font-size: 1.55rem;         /* 15.5px */
  line-height: 1.45;
  margin: 1.4rem 0 2.6rem;
  padding-left: 1.4rem;
  border-left: 2px solid var(--graticule);
  color: var(--muted);
}
.stage-meta .k {
  font-family: var(--meter);
  font-size: 1.1rem;          /* 11px */
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--flag);
  padding-top: 0.35rem;       /* optical baseline against the larger value */
}
.stage-meta code { font-size: 0.92em; }

/* The grid's two columns collapse on a phone: an 11px label above
   its value reads fine, side by side at 320px does not. */
@media (max-width: 700px) {
  .stage-meta { grid-template-columns: 1fr; gap: 0.2rem; }
  .stage-meta .k { padding-top: 0.6rem; }
}
