/* learn.css - what is specific to the learn site, and nothing else.
 *
 * Same division of labour practice.css follows: the tokens, the Box treatment, the card,
 * button and control styling and the dark panels all arrive through app.css (which is
 * style.css plus the simulator's own additions), and this file adds only what a topic page
 * has and no other page here does - an article the app's cards sit inside, and the truth
 * table. It defines no tokens of its own; a second palette creeping back in is exactly
 * what the practice site's own check exists to prevent, and the learn harness asserts the
 * same thing about this file.
 */

/* ---- the article ----
   ONE COLUMN, and the prose and the cards share it. 1280px is the same cap `.grid` uses on
   every other page here, so a topic page is as wide as the app it embeds - which is what
   lets a waveform or a netlist have the pixels it wants without breaking out of anything.
   The prose was capped narrower than this for a while, at a 68ch reading measure; see the
   note below the heading rules for why that is gone. */
.learn-article {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 16px;
}
/* THE SAME GAP `.gh-sub + .grid` GIVES A PAGE OF CARDS, because an article is what sits there
   instead. It only shows on a topic whose first block is running text: a topic opening on an h2
   has 28px of its own, and margins collapse, so `logic-gates` does not move by a pixel -
   measured, not assumed. Without it a paragraph starts hard against the blurb under the crumb
   and the two read as one wrapped line. */
.gh-sub + .learn-article { margin-top: 24px; }

/* ---- THE HUB'S TWO TABLES ------------------------------------------------------------
   The learn hub lists the run of circuits in one Box and the three history topics in a second
   below it (see `history` in learn-manifest.js). Three rules, and all three are here rather than
   in practice.css because only this hub has two: that file owns `.gh-box` and everything in its
   header, and a practice hub that grew these classes would be carrying rules for a split it does
   not have.

   THE TITLE IS A HEADER ITEM, not a heading above the box, so it sits on the row that already
   carries the filter labels and the count - which is what keeps a named table the same height as
   the unnamed one it replaced. `--fg-default` against the header labels' `--fg-muted`, since the
   name of the table outranks the name of a filter on it. */
.gh-strand-title { font-weight: 600; color: var(--fg-default); white-space: nowrap; }
/* The second table's header has no chips, so there is room for a line saying what it is - and it
   needs saying, because "Evolution" alone does not tell a reader that nothing here is built. It
   shrinks before the count does and disappears rather than wrapping the header onto two rows on a
   phone, which is the one thing it must not cost. */
.gh-strand-note {
  color: var(--fg-muted);
  font-size: 12px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The gap between the two boxes. On the box rather than as a `+` rule, because the first table's
   own margin is not this file's business and the second may be hidden - a `+` selector would then
   leave the gap behind with nothing under it. */
#hubHistoryBox { margin-top: 16px; }
@media (max-width: 760px) {
  /* The note is the first thing to go: on a narrow header the title and the count are the two
     facts, and a truncated sentence between them reads as a rendering fault. */
  .gh-strand-note { display: none; }
}
/* THE MEASURE IS ON THE TEXT, NOT ON THE BLOCK, and the difference is the whole of this
   rule. Capping `.learn-prose` capped its HEADINGS too, so an h2's bottom rule stopped at
   the measure and read as a line someone had cut short - with the cards below it running
   the full column, which made the page look like a narrow article that had been pasted
   into a wide one. Now the section spans the column, so headings and their rules line up
   with the card edges, and only the running text is held to a width it can be read at. */
.learn-prose {
  max-width: none;
  margin: 0 0 18px;
}
/* NO MEASURE ON THE TEXT EITHER. A 68ch cap wrapped every paragraph at about half the
   column while the headings, their rules and the cards all ran its full width - which
   reads as hard line breaks in the source rather than as a deliberate column, and it was
   asked about as exactly that. So the prose now shares one width with everything else on
   the page: `.learn-article`'s 1280px is the only cap, and lines are as long as that
   allows. That is longer than a typographer would choose, and it is the point - one
   aligned column beats a narrow one with a void beside it. */
.learn-prose h2 {
  font-size: 20px;
  font-weight: 600;
  margin: 28px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-muted);
}
.learn-prose p { margin: 0 0 12px; line-height: 1.65; }
.learn-prose ul { margin: 0 0 12px 22px; }
.learn-prose li { margin: 0 0 6px; line-height: 1.6; }
.learn-prose code {
  font: 12px var(--font-mono);
  background: var(--canvas-subtle);
  border: 1px solid var(--border-muted);
  border-radius: 4px;
  padding: 1px 5px;
}
.learn-prose b { font-weight: 600; }

/* A step in a walkthrough, under an h2's section. Deliberately NOT another ruled heading: the
   h2's border is what separates one section of the page from the next, and repeating it here
   would make each step read as a section of its own. */
.learn-prose h3 {
  font-size: 15px;
  font-weight: 600;
  margin: 20px 0 8px;
}
.learn-prose ol { margin: 0 0 12px 22px; }

/* A quoted fragment of the design. It is a DARK panel from the `--panel-*` set, the same in both
   colour modes, because that is what every read-only code surface here already is - the editor
   above it, the console, the truth table - and a code block that followed the page's own light
   surface would be the only one on the site that did. `pre` is what makes the indentation the
   fragment's own rather than the markup's, so nothing may re-wrap it: it scrolls instead. */
.learn-prose pre.learn-code {
  margin: 0 0 12px;
  padding: 10px 14px;
  overflow-x: auto;
  background: var(--panel-bg);
  border-radius: var(--radius);
  font: 12px/1.6 var(--font-mono);
  color: var(--panel-fg);
}

/* A hole a card was moved into. The card keeps its own styling - it is the same element
   app.js wired - so all this has to do is give it the width it no longer gets from being a
   grid item, and space it from the prose around it. `min-width: 0` is in the shared block
   already and still applies, which is what lets a panel inside scroll rather than the card
   growing to its widest child. */
.learn-slot { margin: 0 0 24px; }
.learn-slot > .card { margin: 0; }

/* ---- figures ----
   A hand-authored diagram drawn by the netlist viewer's own code (learn.js's drawFigures), so
   the node and wire rules come from synth.css and nothing here paints a shape. What is added is
   only the FRAME: the surface it sits on, and the caption under it.

   `position: relative` on the box is what the absolutely-positioned `.pn-nodes > .rf-node`
   needs to be placed against - the netlist viewer gets that from #flowRoot's own rule, which a
   figure is deliberately not. Its HEIGHT is set inline by drawStatic from the content, so there
   is none here: a figure is as tall as the thing it illustrates.

   `overflow-x: auto` rather than a scale-to-fit, so a wide diagram scrolls at full size instead
   of being shrunk into illegibility on a phone - the same trade the emulator's TV makes. */
.learn-fig { margin: 0 0 24px; }
/* The drawing is a block of exactly its own width (drawStatic sets it), so `margin: 0 auto`
   is the whole of the centring - no per-figure x offset in the topic file, which would only be
   centred at one column width. Wider than the box, the auto margins collapse and it starts at
   the left, which is what makes `overflow-x: auto` below behave on a phone. */
.learn-fig-box > .pn-static { position: relative; margin: 0 auto; }
.learn-fig-box {
  /* A BACKSTOP for the wrapper's own `position: relative`, which drawStatic sets inline. If an
     overlay inside a figure ever has no positioned ancestor it does not degrade, it LEAVES - the
     captions positioned against the page instead of the figure, which is how they went missing
     when this property was dropped from here and the wrapper's came from a rule alone. */
  position: relative;
  overflow-x: auto;
  background: var(--canvas-subtle);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  padding: 12px;
}
/* A symbol's name, under the shape. Absolutely positioned from the node's own box - learn.js
   reads that from the exported nodeSize - so it is centred on the symbol it names whatever the
   gate's width is. `pointer-events: none` because a figure is not interactive and a label must
   not be the one thing on it that looks like it is. */
.learn-fig-label {
  position: absolute;
  font: 11px var(--font-sans);
  color: var(--fg-muted);
  text-align: center;
  pointer-events: none;
}

/* The measured line under a placement: what was placed and how big it is. Mono, because it is
   numbers and cell names rather than prose - the same reason the truth table is - and a shade dimmer
   than the caption above it, since the caption says why the figure is there and this says what it
   contains. */
/* The layer palette, INSIDE the view at its left, which is where a layout tool puts one. The box is a
   flex row for it: a reserved column can never be slid under by a wide placement, where an absolutely
   positioned overlay would be the moment a figure is wider than the space beside it.

   `align-items: flex-start` keeps the palette at the TOP of the box rather than centred against the
   drawing's height, so it reads as a list of layers and not as a caption. */
/* THE COLUMNS STRETCH, so the placement's column is as tall as the box and has room to fit into - with
   `flex-start` every column was its own content's height and the layout had nothing to fill. The
   palette keeps its own alignment: a column of badges stretched to the box would push `Unselect All` to
   the bottom, away from the list it belongs to. */
.learn-fig-box {
  display: flex;
  align-items: stretch;
  gap: 14px;
}
.learn-fig-layers { align-self: flex-start; }
.learn-fig-layers {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  flex: 0 0 auto;
}
/* The drawing target takes what is left of the box and keeps centring itself in it - `.pn-static`'s own
   auto margins do that, so this only has to give it the room. It exists as a separate element because
   drawStatic EMPTIES what it is handed, and the palette shares the box with it. `min-width: 0` because a
   flex item refuses to shrink below its content otherwise, the same automatic-minimum trap `.card`
   carries a floor for. */
.learn-fig-draw { flex: 1 1 auto; min-width: 0; }

/* THE DRAWING'S OWN TOGGLES - the cells' mask artwork against the abstract, and the wires - in a row
   above the picture rather than in the mask column beside it. Two kinds of control, so two places:
   the column is the stack, top of the wafer downwards, and this is about what is drawn at all.
   `.layout-btn` supplies the look, so this rule is only the row. */
.learn-fig-views { display: flex; gap: 4px; margin-bottom: 4px; }

/* THE MOORE CHART. Every colour is a token, and each series has ONE of them so the legend, the line
   and the dots cannot say different things: the prediction is muted and dashed because it is a model
   rather than a measurement, what shipped is the accent, and the node is amber on the right axis.
   Nothing here is a literal colour - the canvas-renderer rule this repo states, applied to SVG. */
.learn-widget-chart { padding: 0; }
/* The scale toggle's row: the placement figures' own text-button row (`.learn-fig-views` supplies the
   look), pushed to the right so it reads as a control ON the chart rather than a heading above it. It
   is a SIBLING of the plot, not inside it, because toggling redraws the plot - see learn.js. */
/* The row holds the plot's caption on the left and its control on the right, so `space-between` rather
   than the `flex-end` this had when the button was alone in it. */
.learn-chart-ctl { justify-content: space-between; align-items: center; margin-bottom: 2px; }
.learn-chart-title { font-size: 13px; font-weight: 600; color: var(--fg-default); }
.learn-chart-body { display: block; width: 100%; }
.learn-chart { display: block; width: 100%; height: auto; }
.learn-chart-grid { stroke: var(--border-muted); stroke-width: 1; }
.learn-chart-axis { stroke: var(--border-default); stroke-width: 1; }
.learn-chart-tick { fill: var(--fg-muted); font: 11px var(--font-mono); }
.learn-chart-tick-node { fill: var(--attention-fg); }
/* Dashed, and it is the one series that is not data. */
/* `fill: none` is load-bearing now that this is a POLYLINE rather than a line: an unfilled polyline
   defaults to a black fill, so the prediction would come out as a solid shape under its own dashes. */
.learn-chart-pred { stroke: var(--fg-muted); stroke-width: 1.5; stroke-dasharray: 6 4; fill: none; }
.learn-chart-real { stroke: var(--accent-fg); stroke-width: 2; fill: none; }
.learn-chart-node { stroke: var(--attention-fg); stroke-width: 2; fill: none; }
.learn-chart-dot { fill: var(--accent-fg); stroke: var(--canvas-default); stroke-width: 1; }
.learn-chart-dot-node { fill: var(--attention-fg); }
/* THE MARKED STEP is bigger AND ringed - two encodings of one bit, the rule the layer badges and the
   Scoreboard's flags are both held to, so the mark survives a greyscale screenshot. */
.learn-chart-dot.on { r: 6; stroke: var(--fg-default); stroke-width: 2; }
.learn-chart-cursor { stroke: var(--fg-muted); stroke-width: 1; stroke-dasharray: 3 3; }
.learn-chart-legend { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px;
                      font: 11px var(--font-sans); color: var(--fg-muted); }
.learn-chart-key { display: inline-flex; align-items: center; gap: 5px; }
.learn-chart-key-pred, .learn-chart-key-real, .learn-chart-key-node {
  width: 14px; height: 0; border-top-width: 2px; border-top-style: solid; display: inline-block;
}
.learn-chart-key-pred { border-top-color: var(--fg-muted); border-top-style: dashed; }
.learn-chart-key-real { border-top-color: var(--accent-fg); }
.learn-chart-key-node { border-top-color: var(--attention-fg); }
/* AND THEY CARRY WORDS, where every other `.layout-btn` in this repo carries a 13px glyph: the shared
   rule is a fixed 24 x 20 box built for an icon, so `Detail`, `Abstract` and `Routing` overflowed it
   and printed across each other. Only the two properties that make a fixed box a text box are
   overridden - the width and the padding - so the border, the radius, the colours and the `.active`
   accent all still come from the shared control and cannot drift from it.
   11px to match the layer badges beside them, and `line-height: 18px` for the reason `.btn` pins one:
   it is what stops a label from resizing the row. */
.learn-fig-views .layout-btn {
  width: auto;
  padding: 0 8px;
  font: 11px var(--font-sans);
  line-height: 18px;
  letter-spacing: 0.02em;
}

/* A badge in the layer's own colour: the fill, the text on it and the border are all set inline by
   learn.js from what the drawer reports, because they come from the artwork rather than from here.
   What this rule owns is the SHAPE - a small pill, monospace like the other layer names on the page,
   and wide enough that N-WELL and CONTACT read at the same width. */
.learn-fig-layers .learn-layer-btn {
  font: 11px var(--font-sans);
  line-height: 18px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 2em;
  cursor: pointer;
  text-align: center;
  letter-spacing: 0.02em;
}
.learn-fig-layers .learn-layer-btn:hover { filter: brightness(1.12); }
/* An OFF badge is an outline: the colour is still there to identify the layer, and the empty middle
   says the layer is not drawn. Two encodings of one bit, which is why the `.on` class exists as well
   as the inline colours - a greyscale screenshot still tells them apart. */
.learn-fig-layers .learn-layer-btn:not(.on) { background: transparent; }

/* SELECT ALL / UNSELECT ALL: the same pill geometry as a badge, so the column reads as one control
   rather than two, and deliberately NEUTRAL - a colour on these would claim to be a layer. They are
   actions, so nothing here has an on state to paint. */
.learn-fig-layers .learn-layer-all {
  font: 11px var(--font-sans);
  line-height: 18px;
  padding: 0 8px;
  border: 1px solid var(--border-default);
  border-radius: 2em;
  cursor: pointer;
  text-align: center;
  letter-spacing: 0.02em;
  background: transparent;
  color: var(--fg-muted);
}
.learn-fig-layers .learn-layer-all:hover {
  border-color: var(--accent-fg);
  color: var(--accent-fg);
}

/* One row under the figure: what was measured on the left, who to credit on the right. `baseline` so
   the mono numbers and the proportional credit sit on the same line rather than on the same box, and
   `wrap` so a long tally drops the credit below it on a phone instead of squeezing it. */
.learn-fig-foot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin-top: 6px;
}
/* BOXED AND UNDER THE DRAWING, which is what makes it read as that placement's caption rather than as
   a note about the whole figure - it sat at the bottom-left of a three-column box before, as far from
   the thing it measures as the layout allows. */
.learn-fig-stats {
  font: 11px var(--font-sans);
  color: var(--fg-muted);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  padding: 3px 10px;
  background: var(--canvas-default);
  white-space: nowrap;
}

/* The credit for the cell artwork, bottom right of the figure it belongs to. Right-aligned rather
   than centred like the line above it, because it is an attribution and not part of what the figure
   says - the reading eye should pass over it, and find it when it looks. */
.learn-fig-source {
  font-size: 11px;
  color: var(--fg-muted);
  margin-left: auto;      /* right end of the row, and still right when the row wraps */
}
.learn-fig-source a { color: var(--fg-muted); }
.learn-fig-source a:hover { color: var(--accent-fg); }

.learn-fig-caption {
  font-size: 12px;
  color: var(--fg-muted);
  margin-top: 8px;
  text-align: center;
}

/* ---- The cross section, beside the layout ----
   A THIRD flex child of the box, so the wafer sits next to the cell it is a cut through and the
   layer palette keeps its reserved column. Both drawing panels are `1 1 0` rather than `1 1 auto`,
   which is what makes them share the room EVENLY: with `auto` the basis is the content's own width
   and a 144-lambda dff_gate would take four fifths of the box from a section that wants half.
   `min-width: 0` for the automatic-minimum trap `.card` carries a floor for. */
.learn-fig-sec {
  flex: 1 1 0;
  min-width: 0;
  border-left: 1px solid var(--border-default);
  padding-left: 12px;
}
/* The middle column: the drawing, then the line that measures it. `align-items: center` is what
   centres the boxed caption under a drawing narrower than the column. */
.learn-fig-mid {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;      /* the drawing sits in the middle of the room it is given */
  gap: 8px;
}
.learn-fig-draw { flex: 0 0 auto; align-self: stretch; }
/* WHAT THE CUT IS, above the drawing: `cut at 10λ — through both gates`. Mono, because it is a
   coordinate and a fixed phrase rather than prose, and the same 11px as the measured line under the
   figure - the two are the same kind of statement about what is on screen. */
/* The header is two things on one line: WHICH drawing this is, and either the invitation to cut the
   cell or the confirmation that this is it. `space-between` rather than two centred lines, because
   the second is an aside and should not compete with the first. */
.learn-sec-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  font: 11px var(--font-sans);
  color: var(--fg-muted);
  margin-bottom: 6px;
}
.learn-sec-what { color: var(--fg-default); }
/* THE MODE CONTROL, top left of the section: `Ideal Pair`, which is a button while a cut is shown and
   a label while the ideal pair already is. Both wear the same pill so the header does not change shape
   between the two states - only what it does. */
.learn-sec-mode {
  font: 11px var(--font-sans);
  line-height: 20px;
  padding: 0 10px;
  border: 1px solid var(--accent-fg);
  border-radius: var(--radius);
  color: var(--accent-fg);
  background: var(--canvas-default);
  white-space: nowrap;
}
button.learn-sec-mode { cursor: pointer; }
button.learn-sec-mode:hover { background: var(--accent-subtle); }
/* The label form is the CURRENT state, so it is filled - the same "on" treatment the layer badges and
   the section switch use, rather than a second idiom for the same idea. */
span.learn-sec-mode { background: var(--accent-subtle); }
/* THE INVITATION, which is the mode control's own text on the ideal pair: `Click the layout to see the
   cut`. Not a button - the thing to press is the layout - so it wears the pill without the pointer, and
   it is muted rather than accent, since it is telling the reader what to do rather than offering to do
   it. That replaced a separate hint at the other end of the header, which said the same thing twice. */
.learn-sec-mode.learn-sec-invite {
  border-style: dashed;
  border-color: var(--border-default);
  color: var(--fg-muted);
  background: transparent;
}

/* ---- THE FIGURE SPEAKS IN THE PAGE'S VOICE ----
   Every piece of text in this figure is the page's sans now, not mono: the badges, the player, the
   measured line, the section's header and the labels inside the drawing. Mono in this repo means an
   EDITOR or a PANEL - the code block, the console, the log, the truth table - and a diagram's labels
   are none of those, so a figure written in it read as a different kind of object from the prose
   around it. The four mono rules left in this file are exactly those: inline code, the code block,
   the truth table and the log. */

/* The region captions, as ordinary page text above the drawing. Absolutely positioned at a percentage
   of the width, which is the drawing's own x expressed in the one unit that survives being scaled, and
   the SAME 11px mono as every other line in the figure - inside the SVG they were sized in user units
   and so came out at whatever the current scale made them. */
.learn-sec-regions {
  position: relative;
  height: 16px;
  font: 11px var(--font-sans);
  color: var(--fg-muted);
}
.learn-sec-region {
  position: absolute;
  transform: translateX(-50%);
  white-space: nowrap;
}

/* ---- The step panel, under the drawing ----
   The reference puts the step's title and a sentence about it beside the picture; here it goes under
   it, because the figure is already a row of three and a fourth column would leave nothing readable.
   `min-height` holds the room for two lines, so the figure does not jump as the animation steps
   through descriptions of different lengths. */
/* THREE LINES RESERVED, not two. Measured across the nine steps at a 553px panel: seven descriptions
   wrap to two lines and two - the gate oxide and the N-DIFF implant - to three, so the box grew 13px
   on exactly those and the drawings above it moved as the reader stepped. The cost is about 17px of
   whitespace under the short steps, which is the last thing in the column and so costs nothing to
   read; the benefit is that one more line can be added to any description without anything shifting. */
.learn-sec-step { margin-top: 8px; min-height: 6.4em; }
.learn-sec-step-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg-default);
  margin-bottom: 3px;
}
.learn-sec-step-desc {
  font-size: 12px;
  line-height: 1.45;
  color: var(--fg-muted);
}

/* The progress bar: a second encoding of the `3 / 8` beside it, which is the rule anything here
   carrying meaning in colour is held to. It takes the rest of the control row and has a floor, so it
   is still a bar when the row wraps on a phone. */
/* CAPPED, or it eats every pixel the row has left: the reference's bar sits in a ~300px side panel
   and reads as a gauge, where one stretched across 1200px reads as a rule under the controls. */
/* THE BAR IS THE STEPPER NOW, so it is a control rather than a readout: it takes a pointer, it takes
   focus, and it is tall enough to hit. 6px was a gauge; 10px with 4px of invisible padding either side
   is a target a finger can find without the row growing. */
/* WIDER, AND SEGMENTED. It was capped at 200px on the grounds that a bar stretched across the row
   reads as a rule under the controls - true of a continuous fill, and not of this: nine cells with gaps
   read as steps at any width, and at 200px they were 19px each, which is neither a target nor a
   picture. So it takes the room the row has left, with a floor that keeps it a bar when the row wraps
   on a phone. `background: none` because the segments carry the colour now - a track behind them would
   show through the gaps as a line joining what the gaps exist to separate. */
.learn-prog {
  flex: 1 1 320px;
  min-width: 120px;
  height: 12px;
  padding: 0;
  background: none;
  display: flex;
  gap: 3px;
  cursor: pointer;
  align-self: center;
}
.learn-prog-seg {
  flex: 1 1 0;
  border-radius: 2px;
  background: var(--border-muted);
  transition: background 0.2s ease;
}
.learn-prog-seg.on { background: var(--accent-fg); }
.learn-prog:hover .learn-prog-seg { background: var(--border-default); }
.learn-prog:hover .learn-prog-seg.on { background: var(--accent-fg); }
@media (prefers-reduced-motion: reduce) {
  .learn-prog-seg { transition: none; }
}
/* Focus is VISIBLE, because this is the only way to the steps without a pointer since Prev and Next
   were removed - an invisible focus ring on the one keyboard control would be worse than no keys. */
.learn-prog:focus-visible {
  outline: 2px solid var(--accent-fg);
  outline-offset: 2px;
}

/* A player button that cannot do anything says so, as the reference's do at the ends. */
.learn-fig-ctl button:disabled { opacity: 0.4; cursor: not-allowed; }

/* THE HOVER MARKER'S RULE MOVED TO pnr.css, because it reached one of the four pages that draw it.
   `drawHoverLine` is the shared renderer's, so both apps and the twenty topic figures emit the element -
   and only a topic page loaded a rule for it, so on `pnr.html` and `code2silicon.html` it was an
   unstyled `<line>`, i.e. invisible. `pnr.css` loads after this file on a topic page, so one rule there
   serves all four; a copy left here would be dead. */
.learn-sec-body > .pnr-sec-svg {
  display: block;
  width: 100%;
  height: auto;
}
/* `.pnr-mat-*` carry no styling: they exist ONLY so the process animation can hide a material it has
   not made yet, the way a layer class lets it hide a mask. The look comes from the `.pnr-sec-*` class
   on the same element, so these two concerns stay apart - one says what a shape IS, the other what
   turns it off. */



/* The layout only advertises the drag while there is a section for it to move, which is also the
   only time the handler does anything - a resize cursor over a figure that cannot be dragged is a
   control that does not exist. */
.learn-fig-cutting .learn-fig-draw svg { cursor: ew-resize; }

/* The effects canvas, exactly over the drawing it decorates. `pointer-events: none` because the SVG
   under it takes the clicks that choose a cut - a canvas on top would swallow them - and it is the one
   thing in this figure with no meaning of its own, so nothing may depend on hitting it. */
.learn-sec-body { position: relative; }
.learn-sec-body > canvas.pnr-fx {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
/* WHICH PHYSICAL PROCESS THIS STEP IS, under its description: the shapes say what a step leaves
   behind and this says what it does. A quiet pill rather than a coloured one - it names the step, and
   the step's own colours are in the drawing above it. */
.learn-fx-badge {
  display: inline-block;
  margin-top: 6px;
  padding: 2px 8px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  font-size: 11px;
  color: var(--fg-muted);
  background: var(--canvas-default);
}

/* ---- The figure's controls: the two switches, the cut stepper, the player ----
   One row under the box, which is where the process animation this was built from puts its own
   controls. `wrap` so a phone drops the player below the section switch rather than squeezing both,
   and `baseline` so the step counter sits on the same line as the buttons naming it. */
.learn-fig-ctl {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 8px;
  margin-top: 8px;
}
/* THE PLAYER'S BUTTONS ARE OUTLINED ACCENT PILLS, all one size, which is the refinement asked for:
   they were the palette's neutral `Select All` pill, so the row read as a strip of small grey labels
   rather than as controls. `min-width` is what makes Prev, Play, Next and Reset the same width
   whatever their text, so the row does not jump as `▶ Play` becomes `⏸ Pause`. */
.learn-fig-ctl .learn-layer-all {
  border-color: var(--accent-fg);
  color: var(--accent-fg);
  background: var(--canvas-default);
  border-radius: var(--radius);
  line-height: 26px;
  padding: 0 14px;
  min-width: 92px;
}
.learn-fig-ctl .learn-layer-all:hover { background: var(--accent-subtle); }
.learn-fig-ctl .learn-layer-all:disabled { border-color: var(--border-default); color: var(--fg-muted); }
.learn-fig-ctl .learn-layer-all:disabled:hover { background: var(--canvas-default); }
/* The switch wears the same pill as the actions beside it and differs only by STATE - filled when on,
   which is the site's existing active idiom (`.layout-btn.active`, `.hierarchy-node-row.active`, the
   hub's chips). It is not `.layout-btn`, which is a fixed 24x20 icon button that wrapped the label
   onto two lines. */
.learn-fig-ctl .learn-fig-switch {
  border-color: var(--accent-fg);
  color: var(--accent-fg);
  line-height: 26px;
  padding: 0 14px;
  min-width: 92px;
  border-radius: var(--radius);
}
.learn-fig-ctl .learn-fig-switch.on {
  border-color: var(--accent-fg);
  color: var(--accent-fg);
  background: var(--accent-subtle);
}
.learn-anim-step {
  font: 11px var(--font-sans);
  color: var(--fg-muted);
  margin-left: 4px;
}

/* On a phone the box is a column, so the section goes UNDER the layout rather than beside it - and
   its left border becomes a top one, or the divider would run along the wrong edge of a stacked
   pair. 760px is the breakpoint every app here already uses. */
@media (max-width: 760px) {
  .learn-fig-box { flex-direction: column; }
  .learn-fig-layers { flex-direction: row; flex-wrap: wrap; }
  .learn-fig-sec {
    border-left: 0;
    padding-left: 0;
    border-top: 1px solid var(--border-default);
    padding-top: 12px;
    width: 100%;
  }
}

/* ---- Synthesize, inside the netlist viewer ----
   learn.js moves the button into this row and puts the legend in it too. The button takes the
   left; the legend is a centred flex row of its own, so giving it `flex: 1` lets it keep that
   centring within whatever is left rather than being shoved along by the button. `wrap` is
   what lets the legend drop below the button on a phone instead of squeezing it, and the
   legend's own `margin-top` is dropped here because the footer now owns that gap. */
.learn-synth-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 12px;
}
.learn-synth-foot > .legend-row { flex: 1 1 auto; margin-top: 0; }

/* ---- prose components: a two-column split, an illustration, an aside ----
   These three are the learn site's own layout vocabulary for a topic that ARGUES something
   rather than running it - `lego-logic` is the first of them - and they are deliberately named
   for what they do rather than for that topic, since any later one wants the same three.

   They live here and not in a topic's own markup for the reason `.truth-wrap` does: a topic
   file carries prose, and prose that has to restate a border and a background every time it
   wants a figure is a second stylesheet written in HTML. */

/* Text beside a picture. `align-items: center` so a short paragraph sits against the middle of
   the illustration rather than at its top, and 1fr/1fr because the two halves are peers - the
   picture is the argument as much as the sentence is. It stacks at 760px, which is the one
   breakpoint every app here uses. */
/* TOP-ALIGNED, not centred. Two columns of prose are never the same height - the Kilby and Noyce
   paragraphs are 92px and 115px - and centring them against each other splits the difference, so the
   shorter column starts about 12px LOWER than the taller one. Measured: the two illustration panels are
   the same 198px, their tops were at 626 and 614, and nothing about the drawings was wrong. Aligning to
   the start puts both panels on one line, which is what a side-by-side comparison is for. */
.learn-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px 24px;
  align-items: start;
  margin: 0 0 18px;
}
/* `min-width: 0` for the automatic-minimum trap `.card` carries a floor for: `1fr` is
   `minmax(auto, 1fr)` and a grid item refuses to go below its own min-content width, so one
   unshrinkable descendant - a wide table, a drawing - would set the width of the whole page and
   the symptom would be the header bar reading narrower than the document. Measured here at 500px
   with nothing overflowing; the floor is what keeps that true of a narrower one. */
.learn-split > * { min-width: 0; }
/* The last paragraph in a column keeps no trailing gap: a 12px margin under the final line is space
   the column does not need, and with the columns top-aligned it shows up as an uneven bottom edge. */
.learn-split > div > p:last-child,
.learn-split > div > .truth-wrap:last-child { margin-bottom: 0; }

/* A hand-drawn illustration, on the same surface a netlist figure gets (`.learn-fig-box`) so
   the two kinds of picture on this site read as one kind of thing. A COLUMN rather than that
   file's flex row, because there is no layer palette to reserve room for and nothing to sit
   beside: the drawing is centred and its caption goes under it.

   `min-height` is what keeps two illustrations side by side from being wildly different
   heights when one holds a single brick and the other a tower. */
.learn-illus {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 18px 14px;
  min-height: 180px;
  background: var(--canvas-subtle);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
}
/* Width from the markup, height derived - so a wide drawing shrinks to the column on a phone
   instead of overflowing it, and never grows past its own size on a desktop. */
.learn-illus svg { display: block; max-width: 100%; height: auto; }
.learn-illus-cap {
  font-size: 12px;
  color: var(--fg-muted);
  text-align: center;
}

/* An aside: the question the paragraph above it just raised, set apart so it reads as the
   turn in the argument rather than as one more paragraph. The accent bar and fill are the
   site's existing "this one is marked" idiom (`.layout-btn.active`, `.hierarchy-node-row.active`,
   the hub's chips) rather than a colour of its own, and the radius is square on the barred
   edge so the bar reads as an edge and not as a stripe inside a box. */
/* ---- a stepper widget in the prose ----
   The frame only: the DRAWING inside it is a `.learn-illus`, the controls under it are the layout
   player's own `.learn-fig-ctl` row, and both are reused rather than restyled - which is what makes a
   widget look like the rest of the page instead of like the page it was ported from. So these four
   rules are the head, the body and the two things the shared `.learn-illus` gets wrong when it is
   holding a step's picture rather than a standalone illustration: it must not stretch to 180px for a
   small chip drawing, and it must not centre a paragraph. */
.learn-widget { margin: 0 0 24px; }
.learn-widget-head {
  font-size: 15px;
  font-weight: 600;
  color: var(--fg-default);
  margin-bottom: 8px;
}
/* Shorter than a standalone illustration and never taller than its drawing: a step's picture is one
   chip, and a 180px floor would leave a band of empty canvas under every one of them. */
/* A ROW, not a column, because a step may put a drawing and one or more photographs beside each other
   - and `flex-wrap` is what makes that a stack on a phone with no second breakpoint. `align-items:
   flex-start` so a short drawing sits level with the top of a tall photograph rather than floating in
   the middle of it. */
.learn-widget-fig {
  min-height: 0;
  padding: 12px;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 14px;
}
.learn-widget-drawing { flex: 0 0 auto; }
/* SHRINKABLE, not fixed. `flex: 0 0 auto` is what made a row of four items throw the last one onto a
   second line for the sake of five pixels: measured, a drawing and three photographs came to 1227px of
   items and gaps against a 1222px content box, and none of them was allowed to give. With `0 1 auto`
   and `min-width: 0` they lose a few pixels each instead, and the wrap is left for a phone, where it is
   what you want. */
.learn-widget-shot { flex: 0 1 auto; min-width: 0; margin: 0; max-width: 100%; }
/* Capped in BOTH axes. The height cap is what lines several pictures up along their tops, and it used
   to be the only one - so a wide photograph took 337px of the row while a tall one took 154, and
   whether everything fitted depended on the aspect ratios of whatever pictures a step happened to
   carry. The width cap makes a step's row predictable: four items at 260 plus a 330 drawing and three
   gaps is 1042 of 1222, so a step can hold three photographs and still be one row.
   150px rather than 190 for the same reason a reader gave: at 190 these were simply too big beside
   156px of drawing. `--radius` and a border because a photograph on a panel wants an edge, the way
   every other box on this site has one. */
.learn-widget-shot img {
  display: block;
  max-height: 150px;
  max-width: min(260px, 100%);
  height: auto;
  width: auto;
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  background: var(--canvas-default);
}
/* WHOSE PICTURE IT IS, under it. None of these is this repo's, so the credit is part of the figure
   rather than a line at the bottom of the page nobody reads next to the thing it is about. */
.learn-widget-shot figcaption {
  font-size: 11px;
  color: var(--fg-muted);
  text-align: center;
  margin-top: 4px;
  /* Never wider than the picture's own cap, or a long credit sets the width of the figure and the row
     stops fitting for a reason that is not the picture. */
  max-width: 260px;
}
/* HALF AGAIN AS BIG as the drawings are authored. Every widget SVG here is written 220 units wide - the
   heights differ, because a chip package and a scale bar are different shapes - so scaling by WIDTH is
   what grows each one 1.5x while keeping its own aspect. A fixed HEIGHT would not: at 156px the short
   scale bars would come out 490px wide. `max-width` from `.learn-illus svg` still applies, so a narrow
   column shrinks it rather than overflowing. */
.learn-widget-fig svg { width: 330px; }
.learn-widget-body {
  font-size: 14px;
  line-height: 1.6;
  color: var(--fg-default);
  margin-top: 10px;
}
.learn-widget-body b { font-weight: 600; }
/* THE LABELLED FIELDS. `auto-fit` with a floor rather than a fixed column count, so three tiles sit in
   a row on a desktop and stack on a phone without a second breakpoint - and the floor is what stops a
   long value from squeezing its neighbours into two characters. Same surface and border as
   `.learn-fig-stats`, which is this site's existing small-number box, so a tile reads as one of those
   rather than as a new kind of panel. */
.learn-widget-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px;
  margin-top: 12px;
}
/* `--canvas-subtle`, where `.learn-fig-stats` uses `--canvas-default` - and the difference is what
   each one SITS ON rather than a second opinion about the same box. That line is inside a figure, whose
   frame is already subtle, so default reads as inset there. These tiles sit on the page, which IS
   default, so default would leave them as border-only outlines with no surface at all: measured in dark
   mode, tile #0d1117 on page #0d1117. Subtle gives them the raised box the labels want. */
.learn-widget-stat {
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  background: var(--canvas-subtle);
  padding: 8px 10px;
  min-width: 0;
}
/* The label is the quiet half and the value is the loud one, which is the whole point of a tile: the
   words say what the number is before the number has to be guessed at. */
.learn-widget-stat-label {
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-muted);
  margin-bottom: 3px;
}
.learn-widget-stat-value {
  font: 14px/1.35 var(--font-mono);
  color: var(--fg-default);
  overflow-wrap: anywhere;
}
/* The axis under a timeline's bar: the two ends of the range, so `4 / 7` has something to be four of. */
.learn-widget-ends {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 11px;
  color: var(--fg-muted);
  margin-top: 4px;
}
/* The muted half of a step: the numbers it is really there to show, one per line so two steps can be
   compared by eye down the page rather than across a sentence. */
.learn-widget-body .learn-widget-facts { color: var(--fg-muted); margin-top: 6px; }

.learn-note {
  border-left: 3px solid var(--accent-fg);
  background: var(--accent-subtle);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 12px 16px;
  margin: 0 0 18px;
  line-height: 1.65;
}
.learn-note b, .learn-note strong { font-weight: 600; }

/* ---- LEGO bricks ----
   The artwork for the `lego-logic` topic: two shapes defined once in that topic's first block
   and placed with `<use>`. Everything here is a TOKEN, and that is the whole reason the shapes
   carry classes instead of fills - LEGO's own red, blue, yellow and green are four literal
   colours this file has no budget for (its own harness pins the count, and the only literals
   it allows are the four cross-section materials, which are the product rather than chrome).
   Painted from tokens the bricks follow the reader's colour mode like everything else here.

   `currentColor` is what makes one definition four bricks: the colour is set on the `<use>`
   element by one of the four classes below and inherits into the referenced shapes, so there
   is no copy of the geometry per colour. */
.lego-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.lego-body,
.lego-stud {
  fill: currentColor;
  /* The outline is the PANEL's own colour, so two bricks that touch read as two bricks - the
     same trick the cross section's mask shapes use against the same surface. */
  stroke: var(--canvas-subtle);
  stroke-width: 1.2;
}
/* The lit strip inside the body's top edge, which is what makes a rounded rectangle read as a
   brick rather than as a label. `--fg-on-emphasis` because it is the one token that is #ffffff
   in BOTH modes - it is defined for text on a coloured fill, which is exactly the surface this
   sits on. `--canvas-default` is the trap here: white in light mode too, so it looks identical
   until dark mode turns the highlight into a near-black groove. */
.lego-top { fill: var(--fg-on-emphasis); opacity: 0.24; }
.lego-red { color: var(--danger-fg); }
.lego-blue { color: var(--accent-fg); }
.lego-yellow { color: var(--attention-fg); }
.lego-green { color: var(--success-fg); }

/* A tower of 1x1s wobbles, because the sentence beside it says it does. `transform-box:
   fill-box` is load-bearing: without it `transform-origin: bottom center` resolves against the
   SVG's viewport rather than the group's own box, so the tower swings about a point off to one
   side. And a page that moves for a reader who asked it not to is what the query below is for -
   the tower simply stands still, which costs the illustration nothing. */
@keyframes lego-sway {
  0%, 100% { transform: rotate(0deg); }
  25% { transform: rotate(2.4deg); }
  75% { transform: rotate(-2.4deg); }
}
.lego-tower {
  transform-box: fill-box;
  transform-origin: bottom center;
  animation: lego-sway 2.6s infinite ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
  .lego-tower { animation: none; }
}

/* ---- the quiz ----
   The one interactive thing on a page you read, and the only one that produces a result - the
   Learn hub's badge for this topic is its score. It is a PANEL on the same surface a figure and
   an illustration get, so the three read as one kind of object; what is added is the question
   rows, the options and the two verdict states.

   Everything is a token, and the two states are the site's existing success/danger pair rather
   than a green and a red of their own. */
.learn-quiz {
  background: var(--canvas-subtle);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  padding: 18px 20px;
  margin: 0 0 24px;
}
/* One question, and the gap between two of them is what separates them - a rule per question
   would make each read as a section of the page. */
.learn-quiz-q + .learn-quiz-q { margin-top: 22px; }
.learn-quiz-ask { line-height: 1.6; margin-bottom: 10px; }
.learn-quiz-ask b { font-weight: 600; }
/* A question may quote a signal or an expression, and it wears the prose's own inline chip - the
   quiz is running text with buttons under it, not a panel with a language of its own. */
.learn-quiz-text code {
  font: 12px var(--font-mono);
  background: var(--canvas-default);
  border: 1px solid var(--border-muted);
  border-radius: 4px;
  padding: 1px 5px;
}
/* The options wrap rather than scroll: they are sentences of very different lengths (`0` beside
   `Every possible logic function can be built from just that gate`), so a row that refused to
   wrap would either clip the long one or force the page wider - the automatic-minimum trap this
   file already carries a floor for. */
.learn-quiz-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* An option is a BUTTON on the page's own surface, sized like the prose it follows rather than
   like a toolbar control: this is something to read and choose, not a tool. `text-align: left`
   because a long option wraps to two lines and centred text then reads as a label. */
.learn-quiz-opt {
  font: 14px var(--font-sans);
  line-height: 1.45;
  text-align: left;
  padding: 8px 14px;
  color: var(--fg-default);
  background: var(--canvas-default);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  cursor: pointer;
}
.learn-quiz-opt:hover { border-color: var(--accent-fg); }
/* THE TWO STATES. Right is the success pair, wrong the danger pair - and the border carries the
   colour as well as the fill, so the state survives a fill that is nearly the page in dark mode.
   A right answer LOCKS its question, so its button stops offering a hover and a pointer. */
.learn-quiz-opt.right {
  background: var(--success-subtle);
  border-color: var(--success-fg);
  color: var(--fg-default);
  cursor: default;
}
.learn-quiz-opt.right:hover { border-color: var(--success-fg); }
.learn-quiz-opt.wrong {
  background: var(--danger-subtle);
  border-color: var(--danger-fg);
}
/* The line under the options. `min-height` holds its room from the start, so answering does not
   push the next question down the page - the same reason the process player's step panel reserves
   its lines. The glyph is part of the text, which is what makes the state readable without the
   colour. */
.learn-quiz-verdict {
  min-height: 1.5em;
  margin-top: 8px;
  font-size: 13px;
  color: var(--fg-muted);
}
.learn-quiz-verdict.right { color: var(--success-fg); font-weight: 600; }
.learn-quiz-verdict.wrong { color: var(--danger-fg); }
/* THE WAY BACK TO THE SECTION, inside the line that says the answer was wrong. `color: inherit`
   rather than the site's link blue: the line is one sentence and a blue phrase in the middle of a
   red one reads as two messages, so the underline is what says it is a link and the hover moves it
   to the accent colour like every other link here. */
.learn-quiz-verdict a {
  color: inherit;
  text-decoration: underline;
}
.learn-quiz-verdict a:hover { color: var(--accent-fg); }
/* The score, which is also what the hub is told. Ruled off, because it is a statement about the
   panel above it rather than one more question. */
.learn-quiz-score {
  margin-top: 20px;
  padding-top: 12px;
  border-top: 1px solid var(--border-muted);
  font: 12px var(--font-sans);
  color: var(--fg-muted);
}
.learn-quiz-score.all { color: var(--success-fg); font-weight: 600; }

/* ---- the truth table ----
   A dark panel, like every other read-only output here, because that is what it is: the
   values are the run's, not the page's. Sized in `ch` so the columns line up on their own
   without a fixed width per column, and `overflow-x: auto` because a design with many
   outputs will run wider than the column on a phone. */
.truth-wrap {
  overflow-x: auto;
  background: var(--panel-bg);
  border-radius: var(--radius);
  padding: 10px 0;
}
/* A topic's prose may state a table of its own - the eight gates on `logic-gates`, one column
   per gate - and it is the same panel, so it reuses these rules rather than growing a second
   set of them. All it needs is the gap a paragraph has: inside the card the note below it owns
   that space, in running text nothing does, and the panel would touch the line after it. */
.learn-prose .truth-wrap { margin: 0 0 12px; }
table.truth-table {
  border-collapse: collapse;
  font: 12px/1.7 var(--font-mono);
  color: var(--panel-fg);
  margin: 0 auto;
  /* THE SIZE IS A TOPIC'S TO SET, from `truthTable.scale` in topics/<slug>.js - learn.js puts it
     on the article as `--truth-scale` and every table under it follows, the card's and the
     prose's alike. The declaration sits AFTER the shorthand because the shorthand would
     otherwise reset it, and only font-size is restated so the weight, the style and the family
     it resets stay reset. `1.7` is unitless, so the line-height scales with the text on its own.

     THE FALLBACK IS THE SITE DEFAULT, and it is 1.2 rather than 1: a truth table is the thing
     these pages are asking the reader to read, and at the panel's 12px - a size chosen for a
     console and a listing - it was too small for that. So the default is 120% of it, 14.4px, and
     a topic's `scale` is stated against the same 12px base: 1 is what every table was before this
     existed, 1.2 is what it is now, and anything up to 2.5 is a topic saying its tables want more
     room than that. One number in one place, so "the default size" is this fallback and nothing
     else needs to know it. */
  font-size: calc(12px * var(--truth-scale, 1.2));
}
table.truth-table th,
table.truth-table td {
  /* The cells breathe with the type rather than staying at a fixed 2px/10px, or a table asked to
     be half again as big comes out cramped instead of bigger. `min-width` is already in `ch`, so
     it follows the font size with no arithmetic. */
  padding: calc(2px * var(--truth-scale, 1.2)) calc(10px * var(--truth-scale, 1.2));
  text-align: center;
  min-width: 3ch;
}
table.truth-table thead th {
  color: var(--panel-dim);
  font-weight: 600;
  border-bottom: 1px solid var(--panel-line);
  padding-bottom: calc(5px * var(--truth-scale, 1.2));
}
/* The inputs are the question and the outputs are the answer, so the two halves are told
   apart rather than run together: the inputs are dimmer, and one empty column rules
   between them. A border on the cells themselves would draw a line through the header. */
table.truth-table .in { color: var(--panel-blue); }
table.truth-table th.in { color: var(--panel-blue); }
table.truth-table .sep {
  border-left: 1px solid var(--panel-line);
  padding: 0;
  min-width: 0;
  width: 1px;
}
/* A 1 is what the reader is looking for, so it is the one that is lit; a 0 is legible but
   recedes. Two encodings of the same bit would be better still, but the glyph IS the value
   here - unlike the Scoreboard's flags, where the letter had to carry it too. */
table.truth-table td.one { color: var(--panel-green); font-weight: 600; }
table.truth-table td.zero { color: var(--panel-dim); }
.truth-note {
  font-size: 11px;
  color: var(--fg-muted);
  margin-top: 8px;
}

/* ---- the log dialog ----
   The panel is the exercise sheet's own `.ex-backdrop`/`.ex-sheet` from practice.css, which
   a topic page loads - one dialog idiom on the site rather than a second. What is added here
   is only what a LOG needs that a description does not: the body is a dark console panel,
   monospace and pre-wrap so the synthesis report's padded columns line up and long lines
   wrap rather than being clipped, and it scrolls at a height that leaves the dialog on
   screen. `.console-box` is on the element too, so it inherits the console's own colours -
   this rule only overrides what the sheet's body would otherwise impose on it. */
#learnLogBody {
  max-height: 60vh;
  overflow: auto;
  font: 12px/1.6 var(--font-mono);
  white-space: pre-wrap;
  word-break: break-word;
}

/* ---- the hub ----
   The rows, chips and Box treatment are all practice.css's, which this page loads for
   exactly that reason. What differs is one word in the count and the kicker, so there is
   almost nothing here. */
.learn-kicker { font-size: 12px; color: var(--fg-muted); margin-bottom: 4px; }

@media (max-width: 760px) {
  /* The measure is a maximum, not a minimum: on a phone the column is the viewport, and
     the prose padding matches the shared block's card padding so the two line up. */
  /* Nothing to relax: the prose is uncapped at every width now, and the article's own
     padding is what narrows it here. */
  .learn-article { padding: 0 12px; }
  /* One column, and the ORDER is the markup's - which is why a split puts the illustration
     first in some places and second in others: stacked, each picture ends up where the prose
     around it reads best rather than always above or always below. */
  .learn-split { grid-template-columns: 1fr; }
}
