@import url(https://fonts.googleapis.com/css?family=Urbanist:400,800,400italic);
@import url(https://fonts.googleapis.com/css?family=Arimo:400,800,400italic);
@import url(https://fonts.googleapis.com/css?family=Roboto+Mono:400,800,400italic);
@import url(https://fonts.googleapis.com/css?family=Goudy+Bookletter+1911:400,800,400italic);

:root {
  /* --- Theme tokens (override in theme files) --- */
  --accent: #009933;
  --accent-dim: #00993320;
  /* A light wash of the accent, for identity marks that label rather than
     call for attention. Derived, so a theme only ever sets --accent. */
  --accent-tint: color-mix(in srgb, var(--accent) 38%, #fff);
  --accent-dark: var(--accent);
  --heading-color: #738373;

  /* The one risk colour: .card.alert's rule, .admonition.warning's rule, icon
     and emphasis. A theme sets it when its accent would fight the default red
     -- two saturated hues ~30 deg apart at the same lightness vibrate against
     each other, which is what magenta + #d40000 did. Wash is derived, so a
     theme only ever sets --warning. */
  --warning: #d40000;
  --warning-wash: color-mix(in srgb, var(--warning) 6%, #fff);

  /* --- Question ------------------------------------------------------------
     The third semantic colour, for an admonition that asks rather than warns.
     It has to sit far from the accent AND far from --warning, or a deck that
     uses both grows two colours that read as the same alarm. Teal by default:
     hue 175, which is 156 deg from the default red and 156 from the HES-SO
     magenta, and cool where both of those are hot. Wash is derived, so a theme
     only ever sets --question. */
  --question: #0f766e;
  --question-wash: color-mix(in srgb, var(--question) 6%, #fff);

  /* --- Logo overlay --- */
  --logo-url: none;
  --logo-width: clamp(80px, 14vw, 160px);
  --logo-opacity: 0.5;
  --logo-offset-x: 24px;
  --logo-offset-y: 16px;
  --logo-aspect: 3 / 1;
}

@page {
  size: 1210px 681px;
  margin: 0;
}

@media print {
  .remark-slide-scaler {
    width: 100% !important;
    height: 100% !important;
    transform: scale(1) !important;
    top: 0 !important;
    left: 0 !important;
  }
}

.table-compact .compare { width:100%; border-collapse: collapse; font-size: 0.9em; }
.table-compact .compare th, .table-compact .compare td { padding: .45rem .6rem; border-bottom: 1px solid #e6e6e6; vertical-align: top; }
.table-compact .compare th { text-align: left; white-space: nowrap; }
.legend { margin-top: .5rem; font-size: .85em; color: #666; }

figcaption {
    text-align: left;
}
.frameContainer {
  overflow-x: hidden;
  overflow-y: scroll;
  height: 540px;
  width: 100%;
}

.blur {
    -webkit-filter: blur(5px) opacity(.3);
    -moz-filter: blur(10px) opacity(.3);
    -o-filter: blur(5px) opacity(.3);
    -ms-filter: blur(5px) opacity(.3);
    filter: blur(5px) opacity(.3);
}

html {
    height: 100%;
}

body {
    font-family: "Urbanist", "Inter", "Arimo", Helvetica, "Liberation Sans", sans-serif;
    height: 100%;
}

h1,
h2,
h3 {
    font-weight: 600;
    margin-bottom: 0;
}

.section-mark {
    text-align: center;
    position: absolute;
    top: 50%;
    width: 100%;
    -ms-transform: translateY(-50%);
    transform: translateY(-50%);
}

.left {
    width: 80%;
    text-align: left;
    margin-left: 10%;

}

.middle {
    margin: 0;
    position: absolute;
    top: 50%;
    width: 100%;
    -ms-transform: translateY(-50%);
    transform: translateY(-50%);
}

.remark-slide-content {
    height: 100%;
    padding: 0;
    font-size: 16pt;
}

.remark-slide-content h1 {
    font-size: 2em;
    color: var(--heading-color);
}

.remark-slide-content h2 {
    font-size: 1.5em;
    color: var(--heading-color);
}

.remark-slide-content h3 {
    font-size: 1.2em;
    color: var(--heading-color);
}

.footnote {
    position: absolute;
    bottom: 0.5em;
    font-size: 0.7em;
    left: 6em;
}

li p {
    line-height: 1.25em;
}

.remark-slide-content>p {
    margin-left: 60px;
}

.remark-slide-content>ul {
    margin-left: 60px;
}

.remark-slide-content>ul li {
    margin-left: 0.8em;
}

.no-bullet>ul {
    list-style-type: none;
    padding-left: 0;
}

.no-bullet>ul>li>ul {
    padding-left: 2.8em;
}

.red {
    color: #fa0000;
}

.blue {
    color: #0000fa;
}

a,
a>code {
    color: var(--accent-dark);
    text-decoration: none;
}

code {
    background: #e7e8e2;
    border-radius: 3px;
}

.remark-code,
.remark-inline-code {
    font-family: 'Roboto Mono';
}

.large .remark-code,
.large .remark-inline-code {
    font-size: 0.8em;
}

.remark-code-line-highlighted {
    background-color: #373832;
}

.pull-left {
    float: left;
    width: 45%;
}

.pull-right {
    float: right;
    width: 45%;
}

.pull-right~p {
    clear: both;
}

#slideshow .slide .content code {
    font-size: 0.8em;
}

#slideshow .slide .content pre code {
    font-size: 0.9em;
    padding: 15px;
}

.section-separator .middle {
    margin-left: 210px;
    width: 80%;
}

.fa-solid {
    color: var(--accent);
}

.fa-brands {
    color: var(--accent);
}

.fa-blank {
    color: #ffffff;
}

.fa-solid-dim {
    color: var(--accent-dim);
}

em {
    color: var(--accent);
}

strong {
    color: var(--accent);
}

a {
    color: var(--accent-dark);
    text-decoration: underline
}

.gray-text {
    color: #888;
}

/* ---------------------------------------------------------------------------
   .bar[D] -- a macron over its content, written in CSS rather than in math.

       .bar[**D**] averages your six **D**.

   The alternative was MathJax, and `$\bar{D}$` drags a serif glyph into a deck
   that has none: MathJax 2.7 ships no sans face to switch to (TeX, STIX-Web,
   Asana-Math, the two Gyres, Latin-Modern, Neo-Euler, all serif), `\text{}`
   resolves to the roman MathJax_Main, and `\mathsf{}` reaches variables but
   not operator names, digits or stretchy delimiters. A rule drawn here inherits
   the deck's own family, weight and colour instead, which is why `currentColor`
   rather than a literal: inside `.bar[**D**]` the letter is `strong` and
   therefore accent-coloured, and the rule follows it.

   remark renders a content class as a <span> unless its content contains
   block-level Markdown, so `.bar[D]` is inline, as it must be.

   A pseudo-element rather than `text-decoration: overline`, which sits at the
   font's overline metric -- a long way above the cap in Urbanist -- and cannot
   be tuned. `line-height: 1` is what makes the offset mean the same thing
   everywhere: it fixes the inline-block's height at 1em, so the rule does not
   drift when the surrounding leading changes.
   --------------------------------------------------------------------------- */
.bar {
    position: relative;
    display: inline-block;
    line-height: 1;
}

.bar::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 0.02em;
    border-top: 0.06em solid currentColor;
}

.gray-text em,
.gray-text strong {
    color: #888;
}

.dim strong {
    color: var(--accent-dim);
}

.dim em {
    color: var(--accent-dim);
}

.dimgray-text {
    color: #88888820;
}

.section-mark>h1 {
    font-size: 5.0em;
    color: var(--accent);
}

/* The running rail is chrome, not content.

   It used to be a `float: left` inside the content flow, which made every
   layout mode work around it: a float is ignored on a flex item, its painted
   height depended on the content box, and any slide whose content did not
   fill the slide left the rail (and the white behind it) stopping short.

   Pinned to the slide box instead, it is full height by construction, whatever
   the content does, and no layout mode has to know it exists. The gutter it
   used to reserve with `margin-right` is given back as padding on the slide,
   applied only where a rail is actually present. */
.perma-sidebar {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    background-color: var(--accent);
    color: #f4f4f4;
    width: 40px;
    padding: 0;
    margin: 0;
    text-align: center;
    z-index: 1;
}

.remark-slide-content:has(> .perma-sidebar) {
    padding-left: calc(40px + 2em);
}

/* That padding also moves the *static position* of absolutely positioned
   children, while `width: 100%` still resolves against the full padding box,
   so a full-width overlay would start at the gutter and hang off the right
   edge. Anchoring them explicitly restores exactly where they sat when the
   rail was a float. Overlays that already set `left` (.bottom-box,
   .slide-footer) are unaffected. */
.remark-slide-content:has(> .perma-sidebar) > .section-mark,
.remark-slide-content:has(> .perma-sidebar) > .middle {
    left: 0;
}

.perma-sidebar p {
    text-align: left;
    font-size: 80%;
    height: 35px;
    width: 670px;
    margin: 320px 0 0 -315px;
}

.perma-sidebar h2:last-of-type,
.perma-sidebar h3:last-child {
    color: #d2c295;
}

.svg-reportlet {
    width: 75%;
}

/* Slide-specific styling */
#slide-inverse .footnote {
    bottom: 12px;
    left: 20px;
}

#slide-how .slides {
    position: absolute;
    top: 151px;
    right: 140px;
}

#slide-how .slides h3 {
    margin-top: 0.2em;
}

#slide-how .slides .first,
#slide-how .slides .second {
    padding: 1px 20px;
    height: 90px;
    width: 120px;
    -moz-box-shadow: 0 0 10px #777;
    -webkit-box-shadow: 0 0 10px #777;
    box-shadow: 0 0 10px #777;
}

#slide-how .slides .first {
    background: #fff;
    position: absolute;
    top: 20%;
    left: 20%;
    z-index: 1;
}

#slide-how .slides .second {
    position: relative;
    background: #fff;
    z-index: 0;
}

/* Two-column layout */
.left-column {
    width: 23%;
    height: 82%;
    float: left;
}

.left-column h2:last-of-type,
.left-column h3:last-child {
    color: #000;
}

.right-column {
    width: 65%;
    float: right;
    padding-top: 1em;
}

/* Two-column layout (40% left) */
.left-column2 {
    width: 35%;
    height: 85%;
    float: left;
}

.left-column2 h2:last-of-type,
.left-column2 h3:last-child {
    color: #000;
}

.right-column2 {
    width: 50%;
    float: right;
    padding-top: 1em;
    margin-right: 2.5em;
}

/* Two-column layout (60% left) */
.left-column3 {
    display: block;
    width: auto;
    height: 85%;
    margin: 0 32% 0 5%;
}

.left-column3 h2:last-of-type,
.left-column3 h3:last-child {
    color: #000;
}

.left-column3 li {
    margin-left: 25px;
}

.right-column3 {
    width: 30%;
    float: right;
    padding-top: 1em;
    padding-right: 1em;
}

/* Two-column layout (even split) */
.left-column-mid {
    width: 45%;
    float: left;
}

.right-column-mid {
    width: 45%;
    float: right;
}

/* Two-column layout (flipped) */
.left-column-inv {
    color: #777;
    width: 75%;
    height: 92%;
    float: left;
}

.right-column-inv {
    width: 20%;
    float: right;
    padding-top: 1em;
}

.caption {
    font-size: 0.7em;
}

.slide-slug {
    bottom: 12px;
    opacity: .5;
    position: absolute;
    left: 4em;
}

.small code {
    font-size: 9pt;
}

.tiny code {
    font-size: 8.5pt;
}

.small {
    font-size: 0.7em;
}

.larger {
    font-size: 1.2em;
}

.large {
    font-size: 1.6em;
}

.huge {
    font-size: 2.0em;
}

.larger code {
    font-size: 16pt;
}

.large code {
    font-size: 18pt;
}

.boxed-content {
    float: left;
    display: block;
    width: 89%;
    padding-right: 4%;
}

.boxed-bottom {
    clear: both;
    margin-top: 0px;
}

.bottom-box {
    position: absolute;
    bottom: 0;
    width: 89%;
    padding-left: 55px;
    padding-right: 4%;
    left: 40px;
    height: 15%;
}

/* Footer strip pinned to the bottom of a slide, left-aligned with
   .boxed-content and clearing the .perma-sidebar.

   Additive alternative to .bottom-box, which predates box-sizing: its
   width:89% excludes 55px + 4% of padding, so at 16:9 it runs ~11px past the
   right edge of the slide. .bottom-box is left exactly as it is, because the
   engine is shared with the chuv / nipreps / ohbm decks. */
.slide-footer {
    position: absolute;
    bottom: 0.7em;
    left: 40px;
    width: 89%;
    padding-left: 55px;
    padding-right: 4%;
    box-sizing: border-box;
}

/* ---------------------------------------------------------------------------
   Admonition: the consequence line, pinned under the content it qualifies.

   A card says what something *is*; an admonition says what happens when you
   get it wrong. It spans the row rather than joining it, carries no heading,
   and is the last thing read on the slide, so it is its own component and not
   a fifth .card.

     .admonition.warning[
     <i class="fa-solid fa-triangle-exclamation"></i> Miss either quiz, or ...
     ]

   Left edge is .cards.vcenter's, so the box lines up with the card row above
   it and with the h1. The right edge is .slide-footer's instead: a box that
   ran to the card row's right edge (55px) would pass under the slide counter
   pinned in the corner, and a bordered box crossing the number is a collision
   where a strip of running text was not. `.slide-footer` is left 40px + width
   89%, which is 11% - 40px in from the right; matching it leaves the counter
   the same corner it always had.
   --------------------------------------------------------------------------- */
.admonition {
    position: absolute;
    /* Higher than .slide-footer's 0.7em: a bordered box reads as an object
       sitting on the slide, and an object flush to the edge reads as having
       slipped off it. A strip of text has no edge to be flush with. */
    bottom: 1.6em;
    left: 88px;
    right: calc(11% - 40px);
    box-sizing: border-box;
    /* card vocabulary: same radius, hairline and shadow, so an admonition and
       a card read as one system. The 4px rule moves to the left edge, where a
       row of cards already spends its top edge. */
    background: #fff;
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-left: 4px solid var(--accent);
    border-radius: 10px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
    padding: 0.5em 0.85em;
    font-size: 0.9em;
    line-height: 1.35;
}

/* Markdown wraps the text in its own <p>; unmargined, the box hugs one line. */
.admonition p { margin: 0; }

/* The warning flavour borrows .card.alert's red, so "this one costs you"
   is the same colour across the deck whichever shape carries it. Bold and
   italic are accent-coloured by default (see the rules near the top), and
   magenta inside a red box reads as two warnings, not one. */
.admonition.warning {
    border-left-color: var(--warning);
    background: var(--warning-wash);
}

.admonition.warning .fa-solid,
.admonition.warning em,
.admonition.warning strong {
    color: var(--warning);
}

/* The question flavour asks instead of warning. Same shape, different token:
   a deck can then put a consequence on one slide and an open question on the
   next without the two reading as the same alarm. */
.admonition.question {
    border-left-color: var(--question);
    background: var(--question-wash);
}

.admonition.question .fa-solid,
.admonition.question em,
.admonition.question strong {
    color: var(--question);
}

/* Same trap as .slide-footer on a `class: vcenter` slide: pinned content would
   sit under the spread-out column. Join the column as its last row instead. */
.remark-slide-content.vcenter > .admonition {
    position: static;
    margin-top: auto;
    margin-bottom: 1.6em;   /* the gap its `bottom` gave it when pinned */
    /* width:100% of the content box is wider than the pinned version's
       right inset, which puts the box under the slide number in the corner.
       Stop where a .vcenter card row stops instead. */
    width: auto;
    margin-right: 55px;
}

/* A row block normally takes the height that is left and divides it among its
   rows. With an admonition below it that is the wrong instinct: the rows
   stretch into the admonition's space and push it off the bottom edge. Size to
   content instead and centre in what is left, and let the admonition keep the
   corner. Its own auto top margin then has nothing to do, so drop it, or the
   free space splits three ways and the box floats above the bottom. */
.remark-slide-content.vcenter:has(> .admonition) > .cards {
    flex: 0 1 auto;
    margin-top: auto;
    margin-bottom: auto;
}

.remark-slide-content.vcenter:has(> .cards) > .admonition {
    margin-top: 0;
}

/* A card block on a centred slide spans the slide the way .cards.vcenter does,
   rather than keeping the float's 89% + 4% gutter: otherwise its right edge
   stops short of the admonition's underneath it. .steps and .goals set the
   same inset themselves. */
.remark-slide-content.vcenter > .cards {
    width: auto;
    padding-right: 0;
    margin-right: 55px;
}

.distribute {
    display: flex;
    justify-content: space-between;
    flex-direction: column;
    height: 80%;
    width: 100%;
}

/* ---------------------------------------------------------------------------
   Vertically centred slide: `class: vcenter` in the slide's property block.

       ---
       class: vcenter

       # Schedule

       .boxed-content[ ... ]

   The heading keeps its place at the top and everything after it is centred in
   the height that is left. Put several blocks on the slide and they spread
   evenly down that height instead: the free space is shared equally between
   every auto margin, so one block centres and N blocks space out, from the
   same single rule.

   `.remark-slide-content` is a `table-cell` by default, which is how remark
   does its own vertical alignment; only opted-in slides change. The running
   rail needs no special handling here, because it is pinned to the slide
   rather than floated inside the content.

   Absolutely positioned children (`.remark-slide-number`, `.slide-footer`,
   `.bottom-box`) are not flex items, so they are unaffected and keep sitting
   wherever they were pinned.
   --------------------------------------------------------------------------- */
.remark-slide-content.vcenter {
    display: flex;
    flex-direction: column;
}

/* A flex box inside a `display: table` parent is wrapped in an anonymous
   table-cell, and `height: 100%` then resolves against that anonymous box.
   Chrome stretches it, Firefox does not -- so in Firefox the slide's white
   background stopped short of the bottom while the pinned rail carried on,
   leaving a grey band. Making the slide a block gives the column a definite
   height to resolve against, and both engines then agree. */
.remark-slide:has(> .remark-slide-content.vcenter) {
    display: block;
}

/* Headings hold the top; every other block shares the remaining height. */
.remark-slide-content.vcenter > :not(h1, h2, h3, .perma-sidebar) {
    margin-top: auto;
    margin-bottom: auto;
}

/* A pinned footer sits outside the flex flow, so spread content would run
   underneath it. On a centred slide it joins the column as the last row
   instead: still flush to the bottom, but now occupying height the content
   above has to respect. The slide's own padding supplies the indent that the
   absolute version got from `left` + `padding-left`. */
.remark-slide-content.vcenter > .slide-footer {
    position: static;
    margin-top: auto;
    margin-bottom: 0.7em;   /* the gap its `bottom` gave it when pinned */
    width: 100%;
    padding-left: 0;
}

/* ---------------------------------------------------------------------------
   Card decks: .cards + .card

   A row or grid of equal-weight boxes, for "four facts" / "four numbers" slides.
   Deliberately mirrors the visual vocabulary of .gr-group in ext/roulette.css
   (10px radius, hairline border, soft shadow) so cards authored in Markdown and
   groups drawn by the roulette read as one system.

     .cards.cols-4[
     .card[
     ### Heading
     body text
     ]
     .card[ ... ]
     ]

   remark emits a <div> for a content class whose bracketed body contains a
   newline, so the nesting above is valid block markup. Keep each .card[ on its
   own line or you will get a <span> and the grid will not apply.

   Arrangement, on .cards:
     (default)          auto-fit, min 220px, wraps as needed
     .cols-1 ... .cols-5 fixed column count
     .rows-2            two rows, columns derived from the card count
     .steps             one column, each card a row: .enum[] number then text
     .goals             one column of aims: .marker[] icon then claim and gloss
     .tight             half gap
     .fill              stretch to most of the slide height
   Emphasis, on .card:
     .wide              spans every column, for a full-width row
     .muted             greyed out (a section that is not this one)
     .accent            filled with the dim accent
   Hovering any card that is neither .muted nor .alert gives it the .accent
   fill for as long as the pointer is on it.
   Spans inside a card:
     .kicker            uppercase eyebrow
     .metric            the big number
     .metric-unit       smaller suffix on the big number
     .enum              the number in a .steps row
     .marker            the icon in a .goals row
     .foot              sinks to the bottom of the card
   --------------------------------------------------------------------------- */
.cards {
    /* box model deliberately identical to .boxed-content, so a .cards block
       clears the floated .perma-sidebar the same way every other slide does */
    float: left;
    width: 89%;
    padding-right: 4%;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 20px;
    align-items: stretch;
    margin-top: 0.7em;
}

.cards.cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.cards.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cards.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.cards.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.cards.cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }

.cards.rows-2 {
    grid-template-columns: none;
    grid-template-rows: 1fr 1fr;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
}

/* One column of cards that read as rows: a step number on the left, the step
   on the right. For an ordered sequence, where a grid of equal boxes would
   hide the order. The number goes in its own paragraph so that it becomes a
   flex item of the card and the text can wrap with a hanging indent:

     .cards.steps[
     .card[
     .enum[1]

     Draw one topic
     ]
     ]                                                                        */
.cards.steps {
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
}

.cards.steps .card {
    flex-direction: row;
    align-items: center;
    gap: 0.65em;
    padding-top: 0.4em;
    padding-bottom: 0.4em;
}

/* the number's paragraph: fixed width so every row's text starts on the same
   left edge, whatever the number's digit count */
.cards.steps .card > *:has(> .enum) {
    flex: 0 0 1.5em;
    margin: 0;
}

.cards.steps .card > *:has(> .enum) + * { flex: 1 1 auto; margin: 0; }

.cards .card .enum {
    display: block;
    text-align: center;
    font-size: 1.9em;
    font-weight: 800;
    line-height: 1;
    color: var(--accent);
}

/* A .steps block on a vertically-centred slide takes the height that is left
   and shares it equally among its rows, so five steps read as five bands of
   the slide rather than a short stack floating in the middle. The slide's
   content box is a flex column there, so this is flex: 1 plus equal rows; no
   magic offsets, and it follows the title whatever height the title takes. */
.remark-slide-content.vcenter > .cards.steps {
    flex: 1 1 auto;
    /* a flex item will not shrink past its content without this */
    min-height: 0;
    /* span the content box rather than keep the float's 89% + 4% gutter, which
       leaves a stripe of empty slide. The right inset is the 55px .cards.vcenter
       uses, so a steps row ends where every other card row on the deck ends;
       the left runs flush with the h1. */
    width: auto;
    padding-right: 0;
    /* bottom margin clears the page number in the corner */
    margin: 0.8em 55px 1.9em 0;
    grid-auto-rows: 1fr;
}

/* In a row the corner is the right-hand end, not the top right: take the icon
   out of the absolute corner, push it past the text, and let the row centre
   it. The min-height reserve that keeps a grid card's heading clear of the
   icon is meaningless here, so drop it. */
.cards.steps .card > *:has(> .section-icon.corner) {
    position: static;
    margin-left: auto;
    flex: 0 0 auto;
}

.cards.steps .card > *:has(> .section-icon.corner) + * { min-height: 0; }

/* ---------------------------------------------------------------------------
   .cards.goals — one column of aims: an icon, a claim, and its gloss.

   The Objectives slide of four decks was the same thing built four times: a
   .boxed-content list, a circle-right glyph, .large on the claim, .gray-text on
   the qualifier, .indent to pull the qualifier under the claim, and a <br /> to
   fake the rhythm between items. Three of the four disagreed about the font
   sizes and two about the gloss syntax. This is that shape as a component, so a
   deck writes the words and nothing else.

     .cards.goals[
     .card[
     .marker[<i class="fa-solid fa-circle-right"></i>]

     Apply **DAGs** to represent a pipeline
     .gray-text[nesting and flattening the same computation]
     ]
     ]

   The icon sits in its own fixed-width flex column, so the gloss lines up under
   the claim without .indent's magic 43px. Claim and gloss have to stay in the
   SAME paragraph -- a third child of the card becomes a third flex item and
   lands beside the claim rather than under it -- so keep them on consecutive
   lines with no blank line between.

   Hovering a goal lights it like any other card; that rule is above and needs
   nothing here.                                                              */
.cards.goals { grid-template-columns: minmax(0, 1fr); gap: 20px; }

.cards.goals .card {
    flex-direction: row;
    align-items: center;
    gap: 0.5em;
    padding-top: 0.3em;
    padding-bottom: 0.3em;
    font-size: 1.18em;
}

/* the icon's paragraph: fixed width so every claim starts on the same edge */
.cards.goals .card > *:has(> .marker) { flex: 0 0 1.3em; margin: 0; }

/* This paragraph holds both the claim and its gloss, so its leading is how a
   long CLAIM wraps. Keep it comfortable and let the gloss close the gap from
   its own side; tightening this instead cramps any claim that runs to two
   lines, which is the trap the <br /> version fell into. */
.cards.goals .card > *:has(> .marker) + * {
    flex: 1 1 auto;
    margin: 0;
    line-height: 1.25;
}

.cards .card .marker {
    display: block;
    text-align: center;
    font-size: 1.15em;
    line-height: 1;
    color: var(--accent);
}

/* A block, not a second line after a <br />: as inline content its line box
   could never be shorter than the paragraph's strut, so the small gloss always
   sat a full claim-line away and shrinking it only widened the gap. As a block
   it owns its own leading and its own distance from the claim. */
.cards.goals .card .gray-text {
    display: block;
    font-size: 0.9em;
    line-height: 1.3;
    margin-top: -0.1em;
}

/* A lone goal row is a statement, not a list, and can afford the air: the
   component's padding is tuned for two-line rows stacked four to six deep. */
.cards.goals .card:only-child {
    padding-top: 0.75em;
    padding-bottom: 0.75em;
}

.cards.goals:has(> .card:only-child) { margin-top: 1.5em; }

/* Same deal as .steps on a vertically-centred slide: take the height that is
   left and share it equally among the goals, so six aims read as six bands. */
.remark-slide-content.vcenter > .cards.goals {
    flex: 1 1 auto;
    min-height: 0;
    width: auto;
    padding-right: 0;
    margin: 0.7em 55px 1.9em 0;
    grid-auto-rows: minmax(0, 1fr);
}

/* A card that takes the whole row: "three across, then one wide" from a single
   .cards.cols-3 block, rather than stacking a second .cards under the first.
   One grid means the rows share their column edges, and a step sequence can
   run through every card without crossing a block boundary. */
.cards .card.wide { grid-column: 1 / -1; }

.cards.tight { gap: 10px; }

/* Cards in a row are always equal height: `align-items: stretch` sizes every
   card to the tallest one's content, so .foot lands on a shared baseline
   without anyone declaring a height. Two ways to place that row:

   .fill      force the row to most of the slide, whatever the content.
              Dashboard look; cards will have empty lower halves if the
              content is short.
   .vcenter   let the row take exactly the height its tallest card needs,
              and centre it in the slide. Usually what you want.

   A .steps block on a `class: vcenter` slide fills the remaining height and
   divides it equally among its rows, without either of those two.
   To reveal its rows one at a time, mark the cards .step-1 ... .step-N and put
   `class: stepwise` on the slide; ext/stepwise-svg.js does the rest, on one
   slide, so the counter does not move. */
.cards.fill { min-height: 60%; }

.cards.vcenter {
    float: none;
    position: absolute;
    top: 53%;
    transform: translateY(-50%);
    /* span the slide explicitly rather than inherit the float's width, so the
       row lines up with the h1 on the left and the slide edge on the right.
       left clears the .perma-sidebar. */
    left: 88px;
    right: 55px;
    width: auto;
    padding-right: 0;
    box-sizing: border-box;
    margin-top: 0;
}

.cards .card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: #fff;
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-top: 4px solid var(--accent);
    border-radius: 10px;
    /* named so the corner icon can sit on the content box rather than on the
       padding box, which is where top/right:0 would put it */
    --card-pad-y: 0.7em;
    --card-pad-x: 0.85em;
    padding: var(--card-pad-y) var(--card-pad-x) 0.8em;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
    line-height: 1.35;
}

/* A row wears its accent on the left, not on top.

   The 4px rule is the card's marker, and on a grid card the top is the short
   edge the eye lands on first. Turn the card into a row -- .steps, .goals and
   any .cols-1 are all one column of wide cards -- and the top becomes the long
   edge, so the rule reads as a divider between rows rather than as a marker on
   each of them. On the left it is the short edge again, and it matches the
   .admonition, which has worn its rule there all along for the same reason. */
.cards.cols-1 .card,
.cards.steps .card,
.cards.goals .card {
    border-top: 1px solid rgba(0, 0, 0, 0.08);
    border-left: 4px solid var(--accent);
}

/* .muted and .alert colour whichever edge the card is actually wearing.
   Without this a muted row keeps a bright accent rule down its side, which is
   the same mistake the greying rules below exist to avoid. */
.cards.cols-1 .card.muted,
.cards.steps .card.muted,
.cards.goals .card.muted {
    border-top-color: rgba(0, 0, 0, 0.08);
    border-left-color: #ccc;
}

.cards.cols-1 .card.alert,
.cards.steps .card.alert,
.cards.goals .card.alert {
    border-top-color: rgba(0, 0, 0, 0.08);
    border-left-color: var(--warning);
}

.cards .card > :first-child { margin-top: 0; }
.cards .card > :last-child { margin-bottom: 0; }

.cards .card h3 {
    font-size: 1.2em;
    color: var(--accent);
    margin: 0 0 0.35em 0;
}

.cards .card p { margin: 0.3em 0; }
.cards .card ul { margin: 0.3em 0; padding-left: 1.1em; }
.cards .card li { margin: 0.18em 0; }

.cards .card .kicker {
    display: block;
    font-size: 0.83em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #999;
    margin-bottom: 0.15em;
}

.cards .card .foot {
    display: block;
    padding-top: 0.5em;
    font-size: 0.85em;
    color: #888;
}

/* Markdown wraps a trailing .foot[] in its own <p>, so the flex item is the
   paragraph, not the span: the auto margin has to go on the paragraph or the
   footer will not sink. */
.cards .card > *:has(> .foot) { margin-top: auto; }

.cards .card .metric {
    display: block;
    text-align: center;
    font-size: 4.2em;
    font-weight: 800;
    line-height: 0.95;
    color: var(--accent);
}

.cards .card .metric-unit {
    font-size: 0.4em;
    font-weight: 600;
}

/* Greying a card means greying everything in it that is accent-coloured.
   `strong` and `em` are accent by default (see the rules near the top), so
   without this a muted card keeps bright pink bold text, which reads as the
   opposite of muted. Same shape as the .gray-text em/strong pair. */
.cards .card.muted { border-top-color: #ccc; }
.cards .card.muted h3,
.cards .card.muted .metric,
.cards .card.muted .enum,
.cards .card.muted em,
.cards .card.muted strong { color: #999; }

/* The section icon is a tint of the accent rather than the accent itself, so
   it needs the tint of the grey, not the grey. Same 38% mix as --accent-tint. */
.cards .card.muted .section-icon {
    color: color-mix(in srgb, #999 38%, #fff);
}

.cards .card.accent { background: var(--accent-dim); }

/* Hover lights a card the way .accent does, so a pointer crossing a row shows
   which one it is on. .muted and .alert keep their own colour, which is the
   message on those cards; a card not yet revealed by ext/stepwise-svg.js is
   `visibility: hidden` and takes no pointer events, so it cannot light up.

   Deliberately not wrapped in @media (hover: hover). That guard is the textbook
   answer for touch devices, but it also makes the rule invisible to headless
   Chrome, which reports `hover: none`, so it could never be tested here. A
   stranded highlight after a tap costs nothing on a slide and clears on the
   next one. */
.cards .card:not(.muted):not(.alert) {
    transition: background-color 120ms ease-out;
}

.cards .card:not(.muted):not(.alert):hover {
    background: var(--accent-dim);
}

.cards .card:not(.muted):not(.alert):hover .section-icon.corner {
    color: color-mix(in srgb, var(--accent) 25%, #fff);
}

/* Section identity icon. One icon per course section, reused wherever that
   section is named, so the four sections stay recognisable from deck to deck.
   Tinted rather than full accent: it labels, it does not call for attention.
   The nested <i> needs `inherit` because base.css colours .fa-solid directly. */
.section-icon {
    display: block;
    font-size: 2.4em;
    line-height: 1;
    margin-bottom: 0.3em;
    color: var(--accent-tint);
}

.section-icon .fa-solid,
.section-icon .fa-regular,
.section-icon .fa-brands { color: inherit; }

/* On a .accent card the tint sits on a wash of the same hue and goes muddy;
   take it a step darker so it still reads. */
.cards .card.accent .section-icon {
    color: color-mix(in srgb, var(--accent) 55%, #fff);
}

.cards .card .section-icon.corner {
    color: color-mix(in srgb, var(--accent) 15%, #fff);
}
.cards .card.accent .section-icon.corner {
    color: color-mix(in srgb, var(--accent) 25%, #fff);
}

/* Same icon, pushed to the top-right so the card's own content leads (a big
   .metric, say).

   Out of flow, so the kicker keeps exactly the top-left position it would have
   if there were no icon at all. What must not slide underneath is the heading,
   and the heading follows the kicker, so the clearance is bought by giving the
   kicker's own line the icon's height as a floor. The icon's height and that
   floor both come from --corner-icon and both resolve against the card's font
   size, so they track each other instead of drifting apart.

   Markdown wraps the icon in its own <p>; that <p> is the flex item, so the
   positioning goes on the wrapper, not on the span. */
.cards .card {
    --corner-icon: 2.4em;
}

.cards .card > *:has(> .section-icon.corner) {
    position: absolute;
    top: var(--card-pad-y);
    right: var(--card-pad-x);
    margin: 0;
}

/* Whatever follows the icon (the kicker, in practice) reserves its height. */
.cards .card > *:has(> .section-icon.corner) + * {
    min-height: var(--corner-icon);
}

.section-icon.corner {
    margin: 0;
}

.section-icon.centered {
    text-align: center;
    margin: 10px auto;
    font-size: 5.0em;
}

/* Hard-stop card: the one column on a slide that means "do not continue".
   Switching the token has to reach everything accent-coloured inside the card,
   not just the heading: `strong` and `em` are accent by default (see the rules
   near the top), and magenta bold inside a rust card reads as two messages
   instead of one. Same shape as the .muted rules above. */
.cards .card.alert { border-top-color: var(--warning); }
.cards .card.alert h3,
.cards .card.alert .metric,
.cards .card.alert .enum,
.cards .card.alert em,
.cards .card.alert strong { color: var(--warning); }

/* The icon is a tint of the card's colour, so on an alert card it is a tint of
   the warning, at the same strengths the accent tints use. */
.cards .card.alert .section-icon {
    color: color-mix(in srgb, var(--warning) 38%, #fff);
}
.cards .card.alert .section-icon.corner {
    color: color-mix(in srgb, var(--warning) 15%, #fff);
}

/* Shadows cost toner and add nothing on paper. */
@media print {
    .cards .card,
    .admonition { box-shadow: none; }
}

.cut-right {
    margin-right: 100px;
}

.align-right {
    text-align: right;
}

.rotate {
    -webkit-transform: rotate(-90deg);
    -moz-transform: rotate(-90deg);
    -o-transform: rotate(-90deg);
    -ms-transform: rotate(-90deg);
    transform: rotate(-90deg);
}

.hidden-text {
    color: transparent;
    user-select: none;
}

.pad-left {
    padding-left: 2.0em;
}

.install-cmd {
    top: 20px;
    position: absolute;
    right: 4em;
}

figure {
    display: block;
    margin-left: auto;
    margin-right: auto;
    font-size: 8pt;
}

figcaption {
    text-align: right;
    font-size: 1.2em;
}

.program-table {
    padding: 5px 0 0 50px;
}

.program-table th {
    visibility: hidden;
}

.program-table td:nth-child(2) {
    font-size: 1.2em;
    border-bottom: 1px solid #ddd;
    padding: 5px 0 5px 20px;
}

.program-table td:nth-child(1) {
    font-size: 0.9em;
    color: #999;
    padding-right: 20px;
}

/* One equation, given room to be the point of the slide. A hairline rule and
   generous padding: anything heavier competes with the maths it is framing. */
.formula {
    display: inline-block;
    border: 1px solid var(--accent);
    border-radius: 6px;
    padding: 0.3em 1.5em;
    background: #fff;
    font-size: 1.45em;   /* MathJax scales with the box, so size it here */
}

/* MathJax's display wrapper carries a 1em margin of its own, which doubles up
   with the box padding. Class name differs by output jax, so reset both. */
.formula .MathJax_Display,
.formula .MJXc-display { margin: 0; }

/* ---------------------------------------------------------------------------
   Banded plan table: a whole-course schedule, one row per week, grouped into
   sections that span their own weeks.

   Authored as a raw <table> rather than a Markdown table, because Markdown has
   no rowspan and the grouping is the whole point. Cell classes:

     td.sec    the section name, rowspan-ed over its weeks
     td.w      week number
     td.d      a date
     .mark     something that needs the eye (a swap, a graded session)
     tr.band   shade a section's rows to separate it from its neighbours
     tr.muted  a stretch that is not one of the graded sections
   --------------------------------------------------------------------------- */
.plan-table {
    /* Mirror the gutter the rail leaves on the left, so the table sits with
       equal air on both sides instead of hugging the rail. */
    width: 100%;
    padding-right: 2em;
    box-sizing: border-box;
}

.plan-table table {
    border-collapse: collapse;
    width: 100%;
    font-size: 0.84em;
    line-height: 1.3;
    /* Fixed layout so the two topic columns stay equal instead of being sized
       by whichever week happens to have the longest title. Widths are taken
       from the header row. */
    table-layout: fixed;
}

.plan-table thead th:nth-child(1) { width: 15%; }   /* section  */
.plan-table thead th:nth-child(2) { width: 4%;  }   /* week     */
.plan-table thead th:nth-child(3),
.plan-table thead th:nth-child(5) { width: 8%;  }   /* dates    */
.plan-table thead th:nth-child(4),
.plan-table thead th:nth-child(6) { width: 32.5%; } /* topics   */

.plan-table thead th {
    background: var(--accent);
    color: #fff;
    font-weight: 700;
    text-align: left;
    padding: 0.55em 0.8em;
    white-space: nowrap;
}

.plan-table td {
    padding: 0.4em 0.8em;
    border-bottom: 1px solid #ededed;
    vertical-align: middle;
}

.plan-table tbody tr:last-child td { border-bottom: none; }

.plan-table td.sec {
    font-weight: 700;
    color: var(--accent);
    white-space: nowrap;
    border-right: 1px solid #ededed;
}

.plan-table td.w { text-align: center; color: #aaa; }
.plan-table td.d { text-align: center; white-space: nowrap; color: #777; }

.plan-table .mark {
    display: block;        /* the annotation gets its own line under the topic */
    color: var(--accent);
    font-weight: 700;
    white-space: nowrap;   /* an annotation should not break mid-phrase */
}

.plan-table .mark i {
    margin-right: 0.3em;
    font-size: 0.92em;
}

/* A wash, not a colour: the bands only need to say "same section", and
   --accent-dim is strong enough to compete with the text on top of it. */
.plan-table tr.band td { background: color-mix(in srgb, var(--accent) 6%, #fff); }

.plan-table tr.muted td,
.plan-table tr.muted td.sec { color: #aaa; }

/* A plain readable table for authored Markdown. Unlike .program-table (a
   two-column time/content schedule, whose header is deliberately hidden) this
   makes no assumption about what the columns mean and keeps the header row. */
.data-table table {
    border-collapse: collapse;
    margin: 0.6em 0;
}

.data-table th {
    color: var(--accent);
    font-weight: 800;
    text-align: center;
    padding: 0.35em 0.9em;
    border-bottom: 2px solid var(--accent);
}

.data-table td {
    text-align: center;
    padding: 0.35em 0.9em;
    border-bottom: 1px solid #e6e6e6;
}

.data-table th:first-child,
.data-table td:first-child {
    text-align: left;
    white-space: nowrap;
}

.data-table tr:last-child td { border-bottom: none; }

/* text-align:center centres inline content, not a block-level <table>. */
.data-table.center table { margin-left: auto; margin-right: auto; }

.people-table td:nth-child(1) {
    padding-right: 20px;
}

.people-table img {
    object-fit: cover;
    width: 200px;
    height: 200px;
    filter: grayscale(1);
}

.indent {
    padding-left: 43px;
    display: block;
}

.many-people-table td:nth-child(1) {
    padding-right: 20px;
}

.many-people-table td:nth-child(1) img {
    object-fit: cover;
    width: 80px;
    height: 80px;
}

.top-right {
    font-size: 0.8em;
    position: absolute;
    width: 100px;
    text-align: center;
    padding: 0;
    margin: 0;
    top: 0;
    right: 5%;
}

.pad-top li {
    padding-top: 1.1em;
}

div.vertical-center {
    width: 85%;
    margin: -100px 20px 40px 100px;
    position: absolute;
    text-align: center;
    top: 50%;
}

div.white-bg {
    background: rgba(255, 255, 255, 0.6);
}

blockquote {
  background: #f9f9f9;
  border-left: 10px solid #ccc;
  margin: 20px 0 0 20px;
  padding: 1em 10px 0.5em 30px;
  font-size: 1.5em;
  font-style: italic;
  font-family: "Goudy Bookletter 1911";
  quotes: "\201C""\201D""\2018""\2019";
}

blockquote:before {
  color: #ccc;
  content: open-quote;
  font-size: 4em;
  line-height: 0.1em;
  margin-right: 0.25em;
  vertical-align: -0.4em;
}
blockquote p {
  display: inline;
}

.remark-slide-content.cover .remark-slide-number { display: none !important; }
.remark-slide-content.no-counter .remark-slide-number { display: none !important; }

.clear { clear: both; }
.border { border: 1px solid red; }

/* Anchor overlay to each slide's content box */
.remark-slide-content { position: relative; }

/* Paint the logo as an overlay */
.remark-slide-content::after {
  content: "";
  position: absolute;
  top: var(--logo-offset-y);
  right: var(--logo-offset-x);
  width: var(--logo-width);
  aspect-ratio: var(--logo-aspect);
  background-image: var(--logo-url);
  background-repeat: no-repeat;
  background-size: contain;
  background-position: top right;
  opacity: var(--logo-opacity);
  pointer-events: none;
  z-index: 20;
  mix-blend-mode: multiply;
}

/* Hide on print */
@media print {
  .remark-slide-content::after { display: none; }
}

/* Per-slide opt-out: add `class: no-logo` in the slide header */
.remark-slide-content.no-logo::after { display: none !important; }

/* Optional: smaller/lighter variant per slide with `class: small-logo` */
.remark-slide-content.small-logo {
  --logo-width: 90px;
  --logo-opacity: 0.18;
}
