/* ============================================================
   Slide deck — shared layout system.
   Source of truth for the system + markup recipes: ./deck-spec.md (this folder).
   Used by teaching/<course>/lectures/ AND talks/<topic>/ — site-wide, not
   teaching-only, which is why it lives under /assets/ rather than in a
   course folder.
   Linked from each deck page: <link rel="stylesheet" href="/agentic-design-workflows/assets/deck/deck.css">

   MODEL (summary — deck-spec.md has the full class table)
   - The stage is a 16:9 carousel of .slide elements.
   - Pick a family per slide: `single` (one image, whole) · `auto`/`hero-*`/`auto-rows`
     (JS sizes each figure to its image, so images read whole) · `sq`/`two-3-3`/`grid-4`
     (CSS imposes a fixed frame, image cover-crops into it) · `points-*` (text + image).
   - FIT is an ORTHOGONAL axis, set per slide or per cell:
       .fill  (default)  object-fit:cover  → image fills the cell, cropped.
       .fit              whole image shown, scaled to fit — uncropped.
   ============================================================ */

/* Deck palette roles. Values come from /assets/css/tokens.css; a page may override or extend. */
:root{ --deck-gutter:6px;
  --ink:var(--gray-900); --paper:#fff; --line:var(--gray-200); --mute:var(--gray-500); }

/* ── stage + carousel mechanics ── */
.deck{margin-top:10px;}
.deck-stage{position:relative;aspect-ratio:16/9;width:100%;container-type:inline-size;
  background:var(--paper);border:1px solid var(--line);overflow:hidden;}
.slide{position:absolute;inset:0;display:grid;place-items:center;
  opacity:0;visibility:hidden;transition:opacity .25s ease;}
.slide.on{opacity:1;visibility:visible;}
.slide iframe{width:100%;height:100%;border:0;grid-area:1/1/-1/-1;}
/* ── single: one image, whole + contained, centered, full stage ──
   The definite 1fr×1fr track is required so the img's max-height:100% binds; without it
   the grid row is content-sized (indefinite) and a tall image overflows and gets clipped.
   (Bare `.slide > img` with no `single` class still falls back to the contain rule below,
   but only `single` guarantees no clipping — always tag single-image slides `single`.) */
.slide.single{grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr);}
.slide.single>img,.slide>img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;display:block;}

/* ── grid cells ── */
.slide figure{margin:0;min-width:0;min-height:0;position:relative;overflow:hidden;
  display:flex;align-items:center;justify-content:center;background:var(--paper);}
.slide figure img{display:block;width:100%;height:100%;object-fit:cover;} /* default = fill (cover) */

/* ── bleed: one image, edge-to-edge, cover-cropped (stretch overrides .slide's place-items:center,
   so the lone figure fills the stage instead of shrink-wrapping its image). ── */
.slide.bleed{place-items:stretch;background:var(--paper);}

/* ── bleed/cover grid family (full-stage cells, image cover-cropped) ──
   Use when images are uniform enough that an edge-to-edge crop reads clean (≈16:9 sets, 2×2). */
.slide.two-3-3,.slide.grid-4{place-items:stretch;gap:var(--deck-gutter);background:var(--paper);}
.slide.two-3-3 {grid-template-columns:3fr 3fr;}                             /* two equal columns */
.slide.grid-4  {grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;}  /* 2×2 */

/* ── 12-col explicit-placement family (CSS, no JS) ──
   The page spine is a 12-column grid (the design standard — divides into halves/thirds/quarters).
   `sq` makes each figure a square frame (image cover-cropped); place each figure with a
   c{start}-{end} span (1-indexed, inclusive). Add new c-classes below as slides need them.
   Use this family when you want CONTROL (uniform framing, exact margins); use `auto` when you
   want the layout to follow the images. */
.slide.sq{display:grid;grid-template-columns:repeat(12,1fr);align-items:center;
  gap:var(--deck-gutter);background:var(--paper);}
/* width:100% forces the cell to fill its grid track even when the image is itself square
   (an exactly-square image would otherwise shrink-wrap the figure to its natural pixel size). */
.slide.sq figure{margin:0;width:100%;aspect-ratio:1;overflow:hidden;background:var(--paper);}
.slide.sq figure img{width:100%;height:100%;object-fit:cover;display:block;}
/* column-span utilities (1-indexed, inclusive over 12 cols) */
.slide .c1-4{grid-column:1/5;}  .slide .c5-8{grid-column:5/9;}  .slide .c9-12{grid-column:9/13;}
.slide .c1-6{grid-column:1/7;}  .slide .c7-12{grid-column:7/13;}
.slide .c2-5{grid-column:2/6;}  .slide .c8-11{grid-column:8/12;}
.slide .c2-6{grid-column:2/7;}  .slide .c7-11{grid-column:7/12;}
.slide .c3-6{grid-column:3/7;}  .slide .c7-10{grid-column:7/11;}

/* auto = justified "fit-to-box" row (PPT-style): images at equal height, widths follow
   their own aspect ratio, the whole group scaled to ~98% of width OR height (whichever binds
   first), centered. No crop, tops/bottoms aligned. JS (layoutAuto) sets each figure's px size
   from the image's natural aspect ratio; this CSS just provides the flex row + cell fill. */
.slide.auto{display:flex;align-items:center;justify-content:center;
  gap:var(--deck-gutter);background:var(--paper);}
.slide.auto figure{margin:0;flex:0 0 auto;overflow:hidden;background:var(--paper);}
.slide.auto figure img{width:100%;height:100%;object-fit:cover;display:block;}

/* hero + stack (image-aware, JS-sized like auto): a hero image that FILLS its frame (cover-cropped)
   beside a column of N (≥2) equal-height stacked images; the composite spans the FULL width and the
   90% height budget. hero-l = hero on the left, hero-r = hero on the right. First <figure> is the
   hero; every following <figure> joins the stack. JS (layoutAuto) sets the px sizes AND the grid
   placement (hero spans all rows; the stack fills the other column top→bottom) for any stack count. */
.slide.hero-l,.slide.hero-r{display:grid;grid-template-columns:auto auto;grid-auto-rows:auto;
  gap:var(--deck-gutter);justify-content:center;align-content:center;background:var(--paper);}
.slide.hero-l figure,.slide.hero-r figure{margin:0;overflow:hidden;background:var(--paper);}
/* cover, not contain: the stack figures are forced to equal width (the widest image's aspect), so a
   narrower one would letterbox under contain. The hero takes the leftover width at full height, so
   cover crops it to fill the frame. */
.slide.hero-l figure img,.slide.hero-r figure img{width:100%;height:100%;object-fit:cover;display:block;}
/* .fit modifier: the hero reads WHOLE (uncropped). JS sizes the hero figure to its own aspect
   (like a stack cell) instead of the leftover width, so contain shows it edge-to-edge with no crop.
   Use for portrait/vertical heroes where cover would chop top/bottom. Stack cells stay cover. */
.slide.hero-l.fit>figure:first-child img,.slide.hero-r.fit>figure:first-child img{object-fit:contain;}
/* hero + rows: the non-hero side is a multi-row gallery (wrap each row in <div class="row">).
   JS (layoutAuto) sizes the rows; this just makes each row a justified flex line beside the hero. */
.slide.hero-l>.row,.slide.hero-r>.row{display:flex;align-items:center;justify-content:center;
  gap:var(--deck-gutter);margin:0;}

/* auto-rows = multi-row justified gallery (image-aware): each .row is justified to the box
   width (images share that row's height, widths by aspect); rows stack and the whole block is
   scaled to fit 90% height. No crop. Wrap each row's figures in a <div class="row">. */
.slide.auto-rows{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--deck-gutter);background:var(--paper);}
.slide.auto-rows .row{display:flex;align-items:center;justify-content:center;gap:var(--deck-gutter);}
.slide.auto-rows figure{margin:0;flex:0 0 auto;overflow:hidden;background:var(--paper);}
.slide.auto-rows figure img{width:100%;height:100%;object-fit:cover;display:block;}

/* ── points + one image (a key-points panel beside a single image) ──
   A split slide: a short bulleted argument on one side, one image on the other.
   points-l = text LEFT / image RIGHT;  points-r = text RIGHT / image LEFT.
   Image is CONTAINED (whole, never cropped) so diagrams survive; keep bullets ≤4 short lines.
   Markup: <div class="slide points-l" data-cap="…">
             <div class="pts"><h3>optional</h3><ul><li>…</li></ul></div>
             <figure><img …></figure>
           </div>  (.pts first; image side follows from the class, not the DOM order) */
.slide.points-l,.slide.points-r{grid-template-rows:minmax(0,1fr);align-items:stretch;
  gap:clamp(14px,3.2cqw,40px);padding:clamp(16px,4.3cqw,46px);background:var(--paper);}
.slide.points-l{grid-template-columns:5fr 7fr;}
.slide.points-r{grid-template-columns:7fr 5fr;}
.slide.points-l .pts{grid-column:1;}   .slide.points-l figure{grid-column:2;}
.slide.points-r .pts{grid-column:2;}   .slide.points-r figure{grid-column:1;}
/* both cells are pinned to the single explicit row. Required for points-r: the .pts comes first in
   the DOM but is placed in column 2, so auto-placement would push the later figure (column 1) into
   an implicit second row — collapsing the text row to 0 and spilling the copy off the top. */
.slide.points-l .pts,.slide.points-r .pts,
.slide.points-l figure,.slide.points-r figure{grid-row:1;}
.slide.points-l figure,.slide.points-r figure{margin:0;width:100%;height:100%;
  overflow:hidden;background:var(--paper);display:flex;align-items:center;justify-content:center;}
.slide.points-l figure img,.slide.points-r figure img{max-width:100%;max-height:100%;
  width:auto;height:auto;object-fit:contain;display:block;}
.pts{display:flex;flex-direction:column;justify-content:center;min-width:0;}
.pts h3{font-family:var(--font-sans);font-weight:700;letter-spacing:-.01em;
  font-size:clamp(15px,2.4cqw,24px);color:var(--gray-800);line-height:1.2;margin:0 0 .7em;}
.pts ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.6em;}
.pts li{font-family:var(--font-sans);font-size:clamp(12px,1.7cqw,17px);line-height:1.45;
  color:var(--gray-750);padding-left:1.15em;position:relative;}
.pts li::before{content:"+";position:absolute;left:0;top:0;line-height:1.45;
  font-family:var(--font-mono);font-weight:500;font-size:.95em;
  color:var(--accent);}

/* ── part TOC / agenda divider — list the parts, highlight the one being entered ──
   A recurring divider: one instance per part transition; the row for the part being entered gets
   `.active` (full opacity + accent), the rest dim to .3. Place the .toc inside the page's typeset
   `.embed` wrapper (same as other dividers — the fit script scales it to the stage); cqw tracks the
   stage width. Each row: a mono `.toc-n` "Part N" label + a `.toc-h` heading. Scales to any row count.
   Markup: <div class="slide" data-cap="…"><div class="embed"><div class="toc">
             <div class="toc-row active"><span class="toc-n">Part I</span><h2 class="toc-h">…</h2></div>
             <div class="toc-row"><span class="toc-n">Part II</span><h2 class="toc-h">…</h2></div>
           </div></div></div> */
.toc{width:min(820px,94%);display:grid;grid-template-columns:auto 1fr;
  row-gap:clamp(10px,2cqw,22px);column-gap:clamp(16px,3cqw,34px);text-align:left;
  /* vertical breathing room: a short TOC is centred with room to spare so this is invisible,
     but a long one (9 rows in what-is-research) would otherwise sit flush against the stage
     edges — the padding pushes it past the fit script's ceiling so it scales down slightly. */
  padding:1.6cqw 0;}
.toc .toc-row{display:grid;grid-template-columns:subgrid;grid-column:1/-1;align-items:baseline;
  padding-bottom:clamp(10px,1.8cqw,20px);border-bottom:1px solid var(--line);opacity:.3;transition:opacity .2s;}
.toc .toc-row:last-child{border-bottom:0;padding-bottom:0;}
.toc .toc-n{grid-column:1;grid-row:1;font-family:var(--font-mono);
  font-size:clamp(11px,1.3cqw,14px);letter-spacing:.2em;text-transform:uppercase;color:var(--mute);}
.toc .toc-h{grid-column:2;grid-row:1;margin:0;font-family:var(--font-sans);font-weight:700;
  color:var(--ink);font-size:clamp(16px,2.2cqw,36px);letter-spacing:-.01em;line-height:1.05;}
.toc .toc-row.active{opacity:1;}
.toc .toc-row.active .toc-n,.toc .toc-row.active .toc-h{color:var(--accent);}

/* split variant: the part list keeps to the left at a smaller size and the active part
   spells out its drawing types on the right. Add `toc--split` to the .toc and put a
   <ul class="toc-k"><li>…</li></ul> inside the `.active` row. */
.toc--split{width:92cqw;grid-template-columns:auto max-content minmax(0,1fr);
  column-gap:clamp(20px,4cqw,52px);}
.toc--split .toc-h{font-size:clamp(13px,1.6cqw,26px);}
.toc--split .toc-k{grid-column:3;grid-row:1;margin:0;padding:0;list-style:none;
  display:grid;row-gap:clamp(3px,.6cqw,7px);align-self:start;}
.toc--split .toc-k li{font-family:var(--font-sans);color:var(--ink);
  font-size:clamp(11px,1.25cqw,19px);line-height:1.25;}
.toc--split .toc-k li::before{content:"—";color:var(--accent);margin-right:.55em;}

/* optional caption chip, bottom-left of a cell (used sparingly) */
.slide figure figcaption{position:absolute;left:8px;bottom:7px;
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.04em;
  color:var(--gray-750);background:color-mix(in srgb, var(--gray-50) 82%, transparent);padding:2px 6px;border-radius:3px;}

/* ── typeset text cards — .txt-card / .stmt / .cmp ──
   Promoted out of laar64150's lectures (2026-09) once a third deck needed them; built from
   modeling-as-thinking's `m7-cols` tokens. All three live inside the page's `.embed` wrapper, which deck.js
   scales down to the stage — so author them WIDE: the script only ever scales down,
   never up, and a card authored narrow leaves wide margins on a fullscreen stage.
     .txt-card  heading + `+` bullets, one level of `–` sub-bullets. The workhorse.
     .stmt      one sentence carried large, centered, with an optional `.sub` line under it.
     .cmp       a compact comparison table — first column is the row label, the rest are cases.
   Markup: <div class="slide" data-cap="…"><div class="embed"><div class="txt-card">…</div></div></div> */
.txt-card{width:min(1180px,94%);display:flex;flex-direction:column;gap:1.1em;text-align:left;}
.txt-card h3{margin:0;font-family:var(--font-sans);font-weight:700;color:var(--ink);
  font-size:clamp(22px,3cqw,52px);letter-spacing:-.01em;line-height:1.1;}
.txt-card .lead{font-family:var(--font-sans);font-weight:600;color:var(--accent);
  font-size:clamp(15px,1.9cqw,28px);line-height:1.35;}
.txt-card ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.62em;}
.txt-card li{font-family:var(--font-sans);color:var(--mute);
  font-size:clamp(14px,1.95cqw,30px);line-height:1.45;padding-left:1.05em;position:relative;}
.txt-card li::before{content:"+";position:absolute;left:0;color:var(--accent);
  font-family:var(--font-mono);}
.txt-card li b{color:var(--ink);font-weight:600;}
.txt-card li .src{font-family:var(--font-mono);font-size:.8em;
  letter-spacing:.04em;color:var(--gray-400);}
.txt-card ul ul{margin:.5em 0 0;gap:.45em;}
.txt-card ul ul li{font-size:clamp(13px,1.65cqw,25px);}
.txt-card ul ul li::before{content:"\2013";}
.txt-card a{color:inherit;text-decoration:underline;text-underline-offset:2px;
  text-decoration-color:var(--gray-250);word-break:break-all;}

/* one sentence, carried large */
.stmt{width:min(1100px,94%);text-align:center;}
.stmt p{margin:0;font-family:var(--font-sans);font-weight:700;color:var(--ink);
  font-size:clamp(22px,3.1cqw,56px);line-height:1.18;letter-spacing:-.01em;text-wrap:balance;}
.stmt .sub{margin-top:.6em;font-weight:400;color:var(--mute);
  font-size:clamp(15px,1.9cqw,30px);line-height:1.45;}

/* comparison table: <h3> + <table>; thead labels the cases, each row's first cell is its label.
   Keep it to ~4 columns and ~6 rows — past that the fit script shrinks it below reading size. */
.cmp{width:min(1280px,96%);display:flex;flex-direction:column;gap:.9em;text-align:left;}
.cmp h3{margin:0;font-family:var(--font-sans);font-weight:700;color:var(--ink);
  font-size:clamp(20px,2.7cqw,46px);letter-spacing:-.01em;line-height:1.1;}
.cmp table{border-collapse:collapse;width:100%;table-layout:fixed;}
.cmp th,.cmp td{text-align:left;vertical-align:top;padding:.5em .75em .5em 0;
  border-bottom:1px solid var(--line);font-family:var(--font-sans);color:var(--mute);
  font-size:clamp(12px,1.5cqw,23px);line-height:1.32;}
.cmp thead th{font-family:var(--font-mono);color:var(--accent);
  font-size:clamp(10px,1.05cqw,15px);letter-spacing:.13em;text-transform:uppercase;
  border-bottom:1px solid var(--ink);}
.cmp tbody th{font-weight:600;color:var(--ink);}
.cmp tbody tr:last-child th,.cmp tbody tr:last-child td{border-bottom:0;}
.cmp td b,.cmp td strong{color:var(--ink);font-weight:600;}
.cmp .note{font-family:var(--font-sans);color:var(--mute);line-height:1.4;
  font-size:clamp(12px,1.4cqw,21px);}

/* ── typeset wrapper: a card inside a carousel slide, centered and clipped; deck.js
   (fitEmbeds) scales the child to its slide so nothing scrolls. ── */
.slide .embed{width:100%;height:100%;display:flex;align-items:center;justify-content:center;
  overflow:hidden;padding:14px;box-sizing:border-box;}
.slide .embed>*{transform-origin:center center;}

/* ── title slide ── */
.slide--title{flex-direction:column;text-align:center;gap:14px;background:var(--ink);
  display:flex;align-items:center;justify-content:center;}
.slide--title .tt{font-family:var(--font-sans);font-weight:700;
  font-size:clamp(16px,2.6cqw,46px);color:var(--gray-50);letter-spacing:-.01em;line-height:1.1;}
.slide--title .ts{font-family:var(--font-mono);font-size:11px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--gray-400);}

/* ── placeholder slide (image still to source) ── */
.slide .ph{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:9px;width:calc(100% - 28px);height:calc(100% - 28px);
  border:1.5px dashed var(--gray-250);border-radius:8px;text-align:center;padding:18px;}
.slide .ph .ph-tag{font-family:var(--font-mono);font-size:9px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--gray-300);}
.slide .ph .ph-desc{font-family:var(--font-sans);font-size:13px;
  color:var(--gray-500);max-width:48ch;line-height:1.5;}

/* ── talk pages: the deck stands in for the project hero, full width at the top ── */
#site-main .deck{margin-top:0;margin-bottom:40px;}
/* site.css sets body line-height 1.7; the stage sets its own per element */
#site-main .deck-stage{line-height:1.35;}
/* bare deck: the slide images carry themselves, so no caption strip and no notes.
   The elements stay in the DOM because deck.js addresses them by id. */
.deck--bare .deck-stage{background:#111;}
.deck--bare .deck-meta,
.deck--bare #deck-notes-toggle,
.deck--bare #deck-present,
.deck--bare .deck-notes{display:none;}

/* ── control bar ── */
.deck-bar{display:flex;align-items:center;gap:14px;margin-top:12px;}
.deck-btn{appearance:none;border:1px solid var(--line);background:var(--paper);
  width:34px;height:34px;border-radius:8px;cursor:pointer;color:var(--gray-750);font-size:16px;
  line-height:1;display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  transition:border-color .12s,background .12s;}
.deck-btn:hover{border-color:var(--gray-300);background:#fff;}
.deck-btn:disabled{opacity:.32;cursor:default;}
.deck-count{font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;
  color:var(--mute);flex:0 0 auto;min-width:54px;text-align:center;
  display:flex;align-items:baseline;justify-content:center;}
#deck-jump{font:inherit;color:var(--gray-800);width:2.2em;text-align:right;padding:1px 3px;
  border:1px solid transparent;border-radius:4px;background:transparent;-moz-appearance:textfield;}
#deck-jump:hover{border-color:var(--line);}
#deck-jump:focus{outline:none;border-color:var(--gray-300);background:#fff;}
.deck:fullscreen #deck-jump{color:var(--gray-150);}
.deck:fullscreen #deck-jump:focus{background:#1c1c1c;border-color:var(--gray-700);}
.deck-meta{flex:1;min-width:0;}
.deck-cap{font-family:var(--font-sans);font-size:13.5px;color:var(--gray-800);
  font-weight:500;line-height:1.4;display:block;}
#deck-credit{font-family:var(--font-sans);font-size:10.5px;color:var(--gray-500);
  line-height:1.35;display:none;margin-top:2px;}
#deck-credit:not(:empty){display:block;}
.deck:fullscreen #deck-credit{color:var(--gray-500);}
#deck-credit a,.deck-scap .deck-credit a{color:inherit;text-decoration:underline;text-underline-offset:2px;}
#deck-credit a:hover,.deck-scap .deck-credit a:hover{color:var(--accent);}
.deck-hint{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;
  color:var(--mute);margin-top:6px;}
.deck-fs{margin-left:auto;flex:0 0 auto;font-size:14px;}

/* ── phone scroll view (deck.js adds .deck--scroll below 560px, outside fullscreen) ──
   Borrowed from reveal.js's scroll view: on a phone the carousel becomes one long column,
   every slide stacked in order at full width. Picture and video slides keep a fixed 16:9
   box; typeset slides grow to fit their copy (deck.js sizes .embed slides to their scaled
   card), so nothing scrolls inside a slide. Captions sit under each slide; the control bar
   and notes ride in a dock pinned to the bottom of the screen while the deck is in view.
   Fullscreen drops back to the carousel. */
.deck--scroll .deck-stage{aspect-ratio:auto;border:0;background:none;overflow:visible;
  padding-bottom:84px;}                                /* clears the dock over the last slide */
.deck--scroll .slide{position:relative;inset:auto;width:100%;box-sizing:border-box;
  opacity:1;visibility:visible;transition:none;
  aspect-ratio:16/9;overflow:hidden;border:1px solid var(--line);scroll-margin-top:64px;}
.deck--scroll .slide:has(.pts){aspect-ratio:3/4;}      /* copy stacked over a picture */
/* typeset slides with no picture, video or scaled card: let the copy set the height */
.deck--scroll .slide:not(:has(img,iframe,video,canvas,.embed)){overflow:visible;}
.deck-scap{display:none;}
.deck--scroll .deck-scap{display:block;margin:7px 1px 22px;}
.deck-scap .deck-cap{font-size:13px;}
.deck-scap .deck-credit{display:block;font-family:var(--font-sans);font-size:10.5px;
  color:var(--gray-500);line-height:1.35;margin-top:2px;}
.deck--scroll .deck-thumbs,
.deck--scroll #deck-thumbs-toggle,
.deck--scroll #deck-present,
.deck--scroll .deck-meta,
.deck--scroll .deck-hint{display:none;}
/* the dock: bar + notes, floating over the page while the deck is in view */
.deck-dock{position:fixed;left:12px;right:12px;bottom:calc(12px + env(safe-area-inset-bottom));
  z-index:150;max-width:520px;margin:0 auto;padding:8px;box-sizing:border-box;
  background:color-mix(in srgb, var(--paper) 94%, transparent);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  border:1px solid var(--line);border-radius:14px;box-shadow:0 6px 24px rgba(0,0,0,.12);
  opacity:0;visibility:hidden;transform:translateY(12px);
  transition:opacity .2s ease,transform .2s ease,visibility 0s linear .2s;}
.deck-dock.show{opacity:1;visibility:visible;transform:none;transition-delay:0s;}
/* progress hairline along the dock's top edge */
.deck-dock::before{content:"";position:absolute;left:14px;top:-1px;height:2px;border-radius:2px;
  width:calc((100% - 28px) * var(--deck-p, 0));background:var(--accent);transition:width .2s ease;}
.deck-dock .deck-bar{margin:0;flex-wrap:nowrap;gap:6px;}
.deck-dock .deck-count{flex:1 1 auto;}
.deck-dock .deck-notes{margin:0 0 8px;max-height:42vh;overflow-y:auto;overscroll-behavior:contain;
  max-width:none;border-radius:10px;}
/* text beside an image stacks: a 5-of-12 column is ~140px at phone width */
@media (max-width:560px){
  .slide.points-l,.slide.points-r{grid-template-columns:minmax(0,1fr);
    grid-template-rows:auto minmax(0,1fr);gap:clamp(8px,2.5cqw,14px);padding:14px;}
  .slide.points-l .pts,.slide.points-r .pts{grid-column:1;grid-row:1;}
  .slide.points-l figure,.slide.points-r figure{grid-column:1;grid-row:2;}
  /* split TOC: the active part's drawing types drop under its heading */
  .toc--split{grid-template-columns:auto minmax(0,1fr);}
  .toc--split .toc-k{grid-column:2;grid-row:2;margin-top:.45em;}
}

/* ── narrow screens: caption drops to its own full-width line below the controls ──
   Phone-sized controls and reading-sized notes: on a phone this page doubles as the
   speaker-note view, held at arm's length while presenting, so hits are thumb-sized
   (44px) and the notes are set for glancing rather than for reading a web page. */
@media (max-width:560px){
  .deck-bar{flex-wrap:wrap;gap:6px;}
  .deck-meta{order:1;flex-basis:100%;margin-top:8px;min-width:0;}
  .deck-cap{font-size:13px;}
  .deck-btn{width:44px;height:44px;font-size:19px;border-radius:10px;}
  .deck-btn svg{width:19px;height:19px;}
  /* the count's flex-grow does the right-pushing here; margin-left:auto would force a wrap */
  .deck-fs{font-size:17px;margin-left:0;}
  .deck-btn--text{width:auto;padding:0 14px;font-size:12px;}
  /* five 44px hits + the counter must clear one row at 320px; keep the counter from growing */
  .deck-count{font-size:15px;flex:0 0 auto;min-width:56px;}
}

/* ── fullscreen present mode ── */
.deck:fullscreen{background:#0e0e0e;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:12px;width:100vw;height:100vh;}
.deck:fullscreen .deck-stage{aspect-ratio:16/9;height:auto;
  width:min(100vw, calc((100vh - 76px) * 16 / 9));
  max-height:calc(100vh - 76px);border:0;border-radius:0;}
.deck:fullscreen .deck-hint{display:none;}
/* the stage is a block inside the full-width .deck-row wrapper, so centre it there;
   present mode drops the thumbnail strip */
.deck:fullscreen .deck-row{display:flex;justify-content:center;transform:none;}
.deck:fullscreen .deck-thumbs,
.deck:fullscreen #deck-thumbs-toggle{display:none;}
.deck:fullscreen .deck-bar{width:min(100vw, calc((100vh - 76px) * 16 / 9));
  padding:0 4px;margin-top:0;}
.deck:fullscreen .deck-btn{background:#1c1c1c;border-color:#3a3a3a;color:var(--gray-150);}
.deck:fullscreen .deck-btn:hover{background:#262626;border-color:var(--gray-700);}
.deck:fullscreen .deck-count{color:var(--gray-400);}
.deck:fullscreen .deck-cap{color:var(--gray-150);}

/* ── text buttons + notes disclosure ── */
.deck-btn--text{width:auto;padding:0 12px;font-family:var(--font-mono);
  font-size:10px;letter-spacing:.08em;text-transform:uppercase;}
.deck-notes{margin-top:9px;padding:11px 14px;border:1px solid var(--line);border-radius:8px;
  background:var(--paper);font-family:var(--font-sans);font-size:13px;line-height:1.55;
  color:var(--gray-750);max-width:82ch;}
.deck-notes[hidden]{display:none;}
.deck-notes .nn-empty{color:var(--mute);font-style:italic;}
.deck-notes ul{margin:0;padding-left:1.1em;list-style:disc;}
.deck-notes ul ul{margin:2px 0;list-style:circle;}
.deck-notes li{margin:0 0 4px;}
.deck-notes li:last-child{margin-bottom:0;}
.deck-notes b{font-weight:600;color:var(--gray-800);}
/* phone: notes set for glancing while presenting (must follow the base rules above) */
@media (max-width:560px){
  .deck-notes{margin-top:12px;padding:14px 16px;font-size:17px;line-height:1.62;}
  .deck-notes ul{padding-left:1.15em;}
  .deck-notes li{margin:0 0 10px;}
}
/* never project notes / second-window controls */
.deck:fullscreen .deck-notes,
.deck:fullscreen #deck-notes-toggle,
.deck:fullscreen #deck-present{display:none;}

/* -- thumbnail navigator (slide sorter) -------------------------------------
   A vertical, scrollable strip of live slide thumbnails that opens OUTSIDE the
   stage, in the page margin to its right: the stage keeps its full size and the
   slide is never covered. JS wraps the stage in .deck-row (the positioning box),
   sizes the strip to the margin that is actually free, and nudges the row left
   only when the right margin alone is too narrow. Toggled by the grid button in
   the control bar (and by `t`). Thumbs are cloned slide DOM scaled with a
   transform, so they stay in sync with the real layout -- no exported images. */
.deck-row{position:relative;width:100%;transition:transform .22s ease;}
.deck-thumbs{--thumbW:232px;
  position:absolute;top:0;left:100%;height:100%;width:var(--thumbW);
  margin-left:10px;box-sizing:border-box;overflow:hidden;z-index:5;
  background:var(--paper);border:1px solid var(--line);border-radius:3px;
  opacity:0;visibility:hidden;transform:translateX(-14px);
  transition:opacity .18s ease,transform .22s ease,visibility 0s linear .22s;
  display:none;}                                       /* .open while shown or fading (deck.js) */
.deck-thumbs.open{display:block;}
.deck-thumbs.on{opacity:1;visibility:visible;transform:none;transition-delay:0s;}
.deck-thumbs-inner{position:absolute;inset:0;display:flex;flex-direction:column;}
.deck-thumbs-head{flex:0 0 auto;display:flex;align-items:center;gap:8px;
  padding:9px 10px;border-bottom:1px solid var(--line);}
.deck-thumbs-title{font-family:var(--font-mono);font-size:9.5px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--mute);}
.deck-thumbs-close{margin-left:auto;appearance:none;border:0;background:transparent;
  color:var(--gray-750);font-size:15px;line-height:1;cursor:pointer;padding:3px 5px;border-radius:5px;}
.deck-thumbs-close:hover{background:rgba(0,0,0,.06);}
.deck-thumbs-list{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding:8px;display:flex;flex-direction:column;gap:8px;}
.deck-thumb{flex:0 0 auto;display:flex;gap:7px;align-items:flex-start;
  background:none;border:0;padding:0;cursor:pointer;text-align:left;font:inherit;}
.deck-thumb-n{font-family:var(--font-mono);font-size:9px;color:var(--mute);
  width:1.9em;flex:0 0 auto;padding-top:3px;text-align:right;}
.deck-thumb-frame{flex:1;min-width:0;aspect-ratio:16/9;position:relative;overflow:hidden;
  background:var(--paper);border:1px solid var(--line);border-radius:3px;
  transition:border-color .12s,box-shadow .12s;}
.deck-thumb:hover .deck-thumb-frame{border-color:var(--gray-300);}
.deck-thumb.active .deck-thumb-n{color:var(--accent);}
.deck-thumb.active .deck-thumb-frame{border-color:var(--accent);
  box-shadow:0 0 0 1.5px var(--accent);}
/* the cloned slide is rendered at stage size, then scaled down into the frame */
.deck-thumb-scale{position:absolute;top:0;left:0;transform-origin:0 0;
  container-type:inline-size;pointer-events:none;}
.deck-thumb-scale .slide{position:absolute;inset:0;opacity:1;visibility:visible;transition:none;}
.deck-thumb-vid{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-family:var(--font-mono);font-size:11px;color:var(--accent);}
/* fullscreen: dark to match the stage */
.deck:fullscreen .deck-thumbs{background:#121212;border-color:var(--gray-800);}
.deck:fullscreen .deck-thumbs-head{border-bottom-color:#2a2a2a;}
.deck:fullscreen .deck-thumbs-close{color:var(--gray-150);}
.deck:fullscreen .deck-thumbs-close:hover{background:rgba(255,255,255,.09);}
.deck:fullscreen .deck-thumb-frame{border-color:#3a3a3a;}
