/* ============================================================
   Polter Click — guide.html only.
   Everything shared (tokens, nav, buttons, .card-material,
   footer) lives in style.css; this sheet is the documentation
   layout on top of it: the two-column shell, the figures, the
   numbered steps and the callouts.
   ============================================================ */

/* ---------- shell ---------- */
.doc {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: 56px;
  max-width: 1180px;
  margin: 0 auto;
  padding: 44px 28px 120px;
}
@media (max-width: 960px) {
  .doc { grid-template-columns: minmax(0, 1fr); gap: 0; padding-top: 28px; }
}

/* ---------- table of contents ---------- */
.toc {
  position: sticky;
  top: 92px;
  align-self: start;
  max-height: calc(100vh - 120px);
  overflow-y: auto;
  padding-right: 6px;
  font-size: 0.88rem;
  scrollbar-width: thin;
}
.toc h2 {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 14px;
}
.toc ol {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: toc;
}
.toc > ol > li { counter-increment: toc; }
.toc > ol > li > a {
  display: block;
  padding: 6px 0 6px 26px;
  color: var(--ink-muted);
  border-left: 2px solid transparent;
  position: relative;
  line-height: 1.35;
}
.toc > ol > li > a::before {
  content: counter(toc, decimal-leading-zero);
  position: absolute;
  left: 8px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--ink-faint);
}
.toc > ol > li > a:hover { color: var(--ink); }
.toc > ol > li > a:is(.active, [aria-current="page"]) {
  color: var(--ghost-glow);
  border-left-color: var(--ghost);
}
.toc > ol > li > a:is(.active, [aria-current="page"])::before { color: var(--ghost-glow); }

/* Sections that live under another section — the Pages and Flow subtrees. They carry the same
   number their heading does (3.1, 4.1 …), built from the parent's counter, so the rail and
   the page agree. A subtree opens only while its parent or one of its children is the page
   being read; the rest of the time the rail is eight numbered lines. That was js/guide.js's job
   until the pages were split; each page now says which one it is, so it is CSS. Where :has is
   not supported nothing collapses, which is the harmless direction to fail in.
   The overview (guide.html) is no page of the outline, so both subtrees stay shut there too: it
   used to open them all, which was five extra lines with one subtree and read as a menu
   left open with two. Its own body already lists every page. */
@supports selector(:has(*)) {
  .toc > ol > li > .toc-nest { display: none; }
  .toc > ol > li:has([aria-current="page"]) > .toc-nest { display: block; }
}
.toc ol.toc-nest {
  list-style: none;
  margin: 0 0 4px;
  padding: 0 0 0 16px;
  counter-reset: sub;
}
.toc-nest > li { counter-increment: sub; }
.toc-nest > li > a {
  display: block;
  position: relative;
  padding: 5px 0 5px 36px;
  border-left: 2px solid transparent;
  color: var(--ink-muted);
  line-height: 1.35;
}
.toc-nest > li > a::before {
  content: counter(toc) "." counter(sub);
  position: absolute;
  left: 8px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--ink-faint);
}
.toc-nest > li > a:hover { color: var(--ink); }
.toc-nest > li > a.active {
  color: var(--ghost-glow);
  border-left-color: var(--ghost);
}
.toc-nest > li > a[aria-current="page"] { color: var(--ghost-glow); }
.toc-nest > li > a:is(.active, [aria-current="page"])::before { color: var(--ghost-glow); }
/* the top-level entry a nested section belongs to, while that section is open */
.toc > ol > li > a:is(.active-parent, :has(~ .toc-nest [aria-current="page"])) { color: var(--ink); }

/* ---------- search ----------
   The field sits over the contents; while it holds a query the results list stands in for
   the contents list (js/guide.js swaps the two). Hidden without JS: the markup ships with
   `hidden` and the script lifts it once the index exists. */
.toc-search {
  position: relative;
  margin: 0 0 16px;
}
.toc-search input {
  width: 100%;
  padding: 8px 34px 8px 12px;
  font: inherit;
  font-size: 0.88rem;
  line-height: 1.35;
  color: var(--ink);
  background: var(--void-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}
.toc-search input::placeholder { color: var(--ink-faint); }
.toc-search input:focus { border-color: var(--ghost); box-shadow: 0 0 0 3px rgba(61, 123, 255, 0.18); }
.toc-search input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
/* the "/" hint: a shortcut, shown until the field is in use */
.toc-search kbd {
  position: absolute;
  right: 9px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.72rem;
  padding: 1px 6px;
  color: var(--ink-faint);
  pointer-events: none;
}
.toc-search input:focus ~ kbd, .toc-search.has-query kbd { display: none; }

.toc ol.toc-results {
  columns: 1;
  counter-reset: none;
}
.toc > ol.toc-results > li { counter-increment: none; margin: 0 0 4px; }
.toc > ol.toc-results > li > a {
  display: block;
  padding: 7px 10px;
  border-radius: 8px;
  border-left: 0;
  color: var(--ink-muted);
  line-height: 1.4;
}
.toc > ol.toc-results > li > a::before { content: none; }
.toc-results a:hover, .toc-results li.is-active a { background: var(--panel); color: var(--ink); }
.toc-results .res-where {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 2px;
}
.toc-results .res-text {
  display: block;
  font-size: 0.84rem;
}
.toc-results mark {
  background: rgba(143, 177, 255, 0.22);
  color: var(--ink);
  border-radius: 3px;
  padding: 0 1px;
}
.toc-results .res-empty {
  padding: 7px 10px;
  font-size: 0.84rem;
  color: var(--ink-faint);
}
/* the block a result opened, lit for a moment — a class the script removes, so it works with
   animations off too */
.doc-body .search-hit {
  box-shadow: 0 0 0 4px rgba(61, 123, 255, 0.35);
  border-radius: 6px;
  transition: box-shadow 0.6s;
}

/* The headings inside the section being read. js/guide.js builds these from the section's
   own h3s, so a new section gets its sub-navigation without a second list to maintain —
   and only the open section shows one, or thirteen sections' worth would fill the rail. */
.toc-sub {
  list-style: none;
  margin: 2px 0 10px;
  padding: 0 0 0 27px;
}
.toc-sub li { margin: 0; }
.toc-sub a {
  display: block;
  padding: 4px 0 4px 12px;
  border-left: 1px solid var(--border);
  color: var(--ink-faint);
  font-size: 0.84rem;
  line-height: 1.35;
}
.toc-sub a:hover { color: var(--ink); }
.toc-sub a.active {
  color: var(--ghost-glow);
  border-left-color: var(--ghost);
}
.toc .toc-foot {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
}
@media (max-width: 960px) {
  .toc {
    position: static;
    max-height: none;
    margin-bottom: 40px;
    padding: 18px 20px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
  }
  .toc ol { columns: 2; column-gap: 24px; }
  .toc .toc-foot { margin-top: 16px; padding-top: 14px; }
  @media (max-width: 560px) {
    /* one column on a phone, so tighten the rows — otherwise the contents
       alone are a whole screen before the guide starts */
    .toc ol { columns: 1; font-size: 0.85rem; }
    .toc > ol > li > a { padding: 3px 0 3px 24px; }
  }
}

/* ---------- page head ---------- */
.doc-head { margin-bottom: 8px; }
.doc-head h1 {
  font-size: clamp(2rem, 4vw, 2.9rem);
  line-height: 1.1;
  margin-bottom: 16px;
}
.doc-head .lede {
  font-size: 1.08rem;
  color: var(--ink-muted);
  max-width: 62ch;
  margin: 0;
}

/* ---------- sections ---------- */
.doc-body > section {
  padding-top: 66px;
  scroll-margin-top: 88px;
}
/* one section per page now: the top of the page needs no gap above the heading */
.doc-body > section:first-child { padding-top: 0; }
/* A section's own heading is an h1 on its own page (and still an h2 where one page
   carries several — the overview). One rule, both spellings. */
.doc-body > section > :is(h1, h2) {
  font-size: clamp(1.4rem, 2.4vw, 1.85rem);
  margin-bottom: 6px;
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.doc-body > section > :is(h1, h2) .n {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--ghost);
  letter-spacing: 0.08em;
}
.doc-body h3 {
  font-size: 1.06rem;
  margin: 34px 0 10px;
}
/* Objects, areas and colours are sub-sections of the waiting section now, so their own
   headings sit a level deeper. Quieter than an h3 and clearly under it. */
.doc-body h4 {
  margin: 22px 0 8px;
  font-size: 0.98rem;
  color: var(--ink);
}
/* deep-link targets inside a page land under the sticky nav otherwise */
.doc-body h4[id], .faq details[id] { scroll-margin-top: 88px; }

.doc-body p {
  color: var(--ink-muted);
  margin: 0 0 14px;
  max-width: 70ch;
}
.doc-body p.tight { margin-bottom: 6px; }
.doc-body strong { color: var(--ink); font-weight: 600; }
.doc-body a:not(.btn, .doc-step) {
  color: var(--ghost-glow);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(143, 177, 255, 0.4);
}
.doc-body a:not(.btn, .doc-step):hover { text-decoration-color: var(--ghost-glow); }

.doc-body ul, .doc-body ol.plain {
  color: var(--ink-muted);
  margin: 0 0 16px;
  padding-left: 20px;
  max-width: 70ch;
}
.doc-body li { margin-bottom: 7px; }
.doc-body li::marker { color: var(--ink-faint); }

/* ---------- an interactive screenshot (the Pages section) ----------
   The shot is clean; each control on it is a <button class="hotspot"> placed in percent of the
   image, so it stays on its control at any width. video/shots/pages.mjs measures and writes those
   buttons between the page's <!-- hotspots:… --> comments. The descriptions are an ordinary list
   under the figure — that is what a reader without JS, and the guide's search, get; js/hotspots.js
   hides it and shows one entry at a time in a card beside the control that was clicked. */
.hotspots { margin: 22px 0 10px; }
.hotspots-stage { position: relative; }
.hotspots-stage img {
  display: block;
  width: 100%;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}
.hotspot {
  position: absolute;
  display: block;
  padding: 0;
  margin: 0;
  border: 1.5px solid rgba(249, 226, 175, .75);
  border-radius: 7px;
  background: rgba(249, 226, 175, .07);
  cursor: pointer;
  animation: hotspot-glow 2.4s ease-in-out infinite;
}
.hotspot:hover,
.hotspot:focus-visible,
.hotspot[aria-expanded="true"] {
  outline: none;
  border-color: #f9e2af;
  background: rgba(249, 226, 175, .16);
  box-shadow: 0 0 0 3px rgba(249, 226, 175, .35), 0 0 18px rgba(249, 226, 175, .45);
  animation: none;
  z-index: 2;
}
@keyframes hotspot-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(249, 226, 175, 0); }
  50%      { box-shadow: 0 0 0 3px rgba(249, 226, 175, .22), 0 0 14px rgba(249, 226, 175, .35); }
}

/* the card a click opens, positioned by the script against the stage */
.hotspot-card {
  position: absolute;
  z-index: 3;
  width: min(340px, calc(100% - 16px));
  padding: 14px 16px 15px;
  border-radius: var(--radius-sm);
  background: var(--panel);
  border: 1px solid rgba(249, 226, 175, .55);
  box-shadow: 0 14px 40px rgba(0, 0, 0, .55);
  color: var(--ink-muted);
  font-size: .94rem;
  line-height: 1.55;
}
.hotspot-card b:first-child { display: block; margin-bottom: 4px; color: #f9e2af; font-family: var(--font-display); font-size: 1.02rem; }
.hotspot-card b { color: var(--ink); }
.hotspot-card a { color: var(--ghost-glow); }
.hotspot-card[hidden] { display: none; }

/* Without the script the buttons would glow and do nothing, so they show only once it runs; with
   it the list is the cards' source, not something to read twice. */
.hotspots:not(.hotspots-on) .hotspot,
.hotspots:not(.hotspots-on) .hotspot-hint { display: none; }
.hotspots-on + .hotspot-list { display: none; }
.hotspot-list b:first-child { color: var(--ink); }

/* ---------- inline vocabulary ---------- */
kbd {
  font-family: var(--font-mono);
  font-size: 0.82em;
  padding: 2px 7px;
  border-radius: 5px;
  background: var(--panel-2);
  border: 1px solid var(--border-soft);
  border-bottom-width: 2px;
  color: var(--ink);
  white-space: nowrap;
}
code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  color: var(--ghost-glow);
  background: rgba(61, 123, 255, 0.09);
  border-radius: 4px;
  padding: 1px 5px;
}
.doc-body .node {
  font-family: var(--font-mono);
  font-size: 0.85em;
  color: var(--ink);
  border: 1px solid var(--border-soft);
  border-radius: 6px;
  padding: 1px 7px;
  white-space: nowrap;
}

/* ---------- figures ---------- */
figure {
  margin: 22px 0 26px;
}
figure img {
  width: 100%;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-soft);
  background: var(--void-2);
}
figure.inset img {
  max-width: 620px;
}
figcaption {
  margin-top: 10px;
  font-size: 0.85rem;
  color: var(--ink-faint);
  line-height: 1.5;
  max-width: 70ch;
}
figcaption b {
  color: var(--ink-muted);
  font-weight: 500;
}

/* ---------- numbered walkthrough ---------- */
.steps {
  list-style: none;
  counter-reset: step;
  margin: 26px 0 8px;
  padding: 0;
}
.steps > li {
  counter-increment: step;
  position: relative;
  padding: 0 0 30px 52px;
  margin: 0;
  border-left: 1px solid var(--border);
}
.steps > li:last-child { border-left-color: transparent; padding-bottom: 6px; }
.steps > li::before {
  content: counter(step);
  position: absolute;
  left: -16px;
  top: -2px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--panel-2);
  border: 1px solid var(--border-soft);
  color: var(--ghost-glow);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  display: flex;
  align-items: center;
  justify-content: center;
}
.steps > li h3 {
  margin: 3px 0 8px;
  font-size: 1.02rem;
}
.steps > li p:last-child { margin-bottom: 0; }
.steps figure { margin-bottom: 4px; }
.steps .model { margin: 18px 0 6px; }

/* A crop lifted out of a screenshot is only as big as the pixels it was cut from —
   `figure img { width: 100% }` would stretch a 710px crop across the column and mush it.
   Cap it at its own width instead, and let it sit narrower than the text. */
.crop img { width: auto; max-width: 100%; }
/* A crop taken at 2x carries twice the pixels it should occupy. `width: auto` discards the
   half-size `width` attribute, so such an image pins its CSS size inline: max-width: <half>. */

/* ---------- two crops at one magnification ----------
   A 1800px screenshot of the mask editor renders the object about fourteen pixels wide in
   this column, and the mask, its outline and the cutout are all invisible at that size.
   The wide shot stays for context; this is where the detail actually gets seen. */
.zoom-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin: 18px 0 26px;
}
.zoom-pair > div { margin: 0; }
.zoom-pair img { border-radius: var(--radius-sm); }
.zoom-pair span {
  display: block;
  margin-top: 9px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ghost);
}
.zoom-pair figcaption { grid-column: 1 / -1; margin-top: 2px; }
@media (max-width: 700px) {
  .zoom-pair { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- one section at a time ----------
   `paned` is added by js/guide.js, never written here: with the script off nothing is
   hidden and the guide stays one readable document. */
body.paned .doc-body > section { display: none; }
body.paned .doc-body > section.is-active { display: block; }

/* A section with h3s is split into one page per h3; its lead — the h2 and whatever comes
   before the first heading — stays on all of them. */
body.paned .subpane { display: none; }
body.paned .subpane.is-active { display: block; }
.subpane > h3:first-child { margin-top: 26px; }

.pane-nav {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  margin-top: 44px;
  padding-top: 22px;
  border-top: 1px solid var(--border);
}
.pane-nav a {
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-width: 46%;
  font-size: 0.98rem;
}
.pane-nav a span {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.pane-next { margin-left: auto; text-align: right; }

/* ---------- callout ---------- */
.note {
  border-left: 2px solid var(--ghost);
  background: rgba(61, 123, 255, 0.06);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: 14px 18px;
  margin: 20px 0;
  max-width: 72ch;
}
.note p { margin: 0; color: var(--ink-muted); font-size: 0.94rem; }
.note p + p { margin-top: 8px; }
.note .tag {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ghost-glow);
  margin-bottom: 6px;
}
.note.warn { border-left-color: #e0a35a; background: rgba(224, 163, 90, 0.07); }
.note.warn .tag { color: #e5b479; }

/* ---------- the model diagram ---------- */
.model {
  padding: 26px 26px 22px;
  margin: 24px 0 28px;
}
.model svg { width: 100%; height: auto; }

/* ---------- one block per kind ----------
   Prose, not cards: the kinds grid said the same thing twice when a paragraph followed
   it. The rule and the glyph are what keep five paragraphs from reading as one wall —
   the glyphs are the app's own, so a kind looks the same here as in the palette. */
.types { margin: 22px 0 26px; }
.type {
  padding-left: 18px;
  border-left: 2px solid var(--border);
  margin-bottom: 20px;
}
.type:last-child { margin-bottom: 0; }
.type h3 {
  margin: 0 0 6px;
  font-size: 1.02rem;
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.type h3 .g {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  color: var(--ghost-glow);
}
.type p { margin: 0; }

/* ---------- node reference grid ---------- */
.kinds {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin: 20px 0 26px;
}
@media (max-width: 700px) { .kinds { grid-template-columns: minmax(0, 1fr); } }
.kind {
  padding: 14px 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--panel);
}
.kind b {
  display: block;
  color: var(--ink);
  font-size: 0.95rem;
  margin-bottom: 3px;
}
.kind span {
  color: var(--ink-muted);
  font-size: 0.88rem;
  line-height: 1.45;
}
.kind i {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ghost);
  display: block;
  margin-bottom: 8px;
}

/* ---------- closing band ---------- */
.doc-cta {
  margin-top: 76px;
  padding: 40px 34px;
  text-align: center;
  background: radial-gradient(120% 140% at 50% -10%, rgba(61, 123, 255, 0.16), transparent 60%), var(--panel-2);
}
.doc-cta h2 { font-size: 1.5rem; margin-bottom: 10px; }
.doc-cta p { color: var(--ink-muted); margin: 0 auto 22px; max-width: 46ch; }

/* ---------- motion inside those diagrams ----------
   Every moving part sits on top of a drawing that already reads correctly standing
   still: the pulses are fully transparent at rest and the wires are plain dashed
   lines. That is what makes the blanket prefers-reduced-motion rule in style.css safe
   here — it flattens the animation and leaves an intact diagram behind. */
.fx { --cycle: 5s; }
.fx .fx-wire { stroke-dasharray: 5 5; animation: fx-flow 1.2s linear infinite; }
@keyframes fx-flow { to { stroke-dashoffset: -10; } }

/* one node lighting up as the flow reaches it — --d is its place in the chain */
.fx .fx-pulse {
  opacity: 0;
  animation: fx-pulse var(--cycle) ease-in-out infinite;
  animation-delay: var(--d, 0s);
}
@keyframes fx-pulse {
  0%, 3% { opacity: 0; }
  9%     { opacity: 1; }
  27%    { opacity: 0; }
  100%   { opacity: 0; }
}

/* A wait has no length of its own — it sits there until the thing happens, and drawing it
   as a bar swept at a constant speed says "this took two seconds", which is a lie. So a
   waiting node HOLDS lit and breathes instead, and --d places that window in the cycle. */
.fx .fx-wait {
  opacity: 0;
  animation: fx-wait var(--cycle) linear infinite;
  animation-delay: var(--d, 0s);
}
@keyframes fx-wait {
  0%        { opacity: 0; }
  2%        { opacity: 1; }
  9%        { opacity: .32; }
  16%       { opacity: 1; }
  23%       { opacity: .32; }
  30%       { opacity: 1; }
  34%       { opacity: .45; }
  36%       { opacity: 1; }
  38%, 100% { opacity: 0; }
}

/* The three dots inside a waiting node. They run their own short lap and are gated by the
   fx-wait wrapper around them, so they are only ever seen while that node is waiting —
   which is also why the two animations can be nested instead of fighting over one
   animation property. */
.fx .fx-dots {
  opacity: .22;
  animation: fx-dots 1.2s linear infinite;
  animation-delay: var(--dd, 0s);
}
@keyframes fx-dots {
  0%, 55%, 100% { opacity: .22; }
  18%           { opacity: 1; }
}

/* The colour diagram's scan line: down the bar over the first 60% of the lap, gone while
   the fullest row is picked, then back to the top. At rest it sits on the first row, which
   is a true picture of where a scan starts. */
.fx .fx-sweep { animation: fx-sweep var(--cycle) linear infinite; }
@keyframes fx-sweep {
  0%        { transform: translateY(0); opacity: 1; }
  60%       { transform: translateY(42px); opacity: 1; }
  64%, 100% { transform: translateY(42px); opacity: 0; }
}

/* The clip that uncovers each row's count as the line reaches it: the same 42 units on the
   same schedule, only it starts pulled up out of the bar and ends where it sits at rest —
   so with animations off the diagram shows every count, not none of them. */
.fx .fx-reveal { animation: fx-reveal var(--cycle) linear infinite; }
@keyframes fx-reveal {
  0%        { transform: translateY(-42px); }
  60%, 100% { transform: translateY(0); }
}

/* One short flash: a press landing, a key going out, the loop coming round. */
.fx .fx-flash {
  opacity: 0;
  animation: fx-flash var(--cycle) linear infinite;
  animation-delay: var(--d, 0s);
}
@keyframes fx-flash {
  0%       { opacity: 0; }
  1%, 7%   { opacity: 1; }
  9%, 100% { opacity: 0; }
}

/* a stage holding the floor for its half of the cycle, rather than blinking once */
.fx .fx-hold {
  opacity: 0;
  animation: fx-hold var(--cycle) linear infinite;
  animation-delay: var(--d, 0s);
}
@keyframes fx-hold {
  0%        { opacity: 0; }
  2%, 42%   { opacity: 1; }
  46%, 100% { opacity: 0; }
}

/* the sweep inside the capture track — 136 = track width 170 less the sweep's own 34 */
.fx .fx-scan { animation: fx-scan var(--cycle) linear infinite; }
@keyframes fx-scan {
  from { transform: translateX(0); }
  to   { transform: translateX(136px); }
}

/* An 880-unit diagram squeezed into a phone column renders its labels at about 4px.
   Give it a floor and let the card scroll sideways instead — legible beats complete.
   This covers the model diagram in section 02 as well, which had the same problem. */
@media (max-width: 700px) {
  .model { overflow-x: auto; padding: 20px 16px 16px; }
  .model svg { min-width: 720px; }
}

/* The running line — where a flow is in its lane right now. #f38ba8 is the one tone that
   is not a node colour, so it always reads as "position in time" rather than as a step. */
.fx .fx-bar {
  transform: scaleX(0);
  transform-box: fill-box;
  transform-origin: left center;
  animation: fx-bar var(--cycle) linear infinite;
  animation-delay: var(--d, 0s);
}
@keyframes fx-bar {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
.fx .fx-head {
  opacity: 0;
  animation: fx-head var(--cycle) linear infinite;
  animation-delay: var(--d, 0s);
}
@keyframes fx-head {
  0%   { opacity: 1; transform: translateX(0); }
  100% { opacity: 1; transform: translateX(var(--travel, 0px)); }
}

/* Flows belong to a stage, so their running lines only move while that stage is the live
   one. The gate reuses fx-hold and carries the diagram's own --cycle; the bars inside it
   override --cycle with their own, much shorter, lap time. */
.fx .fx-gate {
  opacity: 0;
  animation: fx-hold var(--cycle) linear infinite;
  animation-delay: var(--d, 0s);
}

/* ---------- the four words ---------- */
.terms { margin: 24px 0 28px; }
.term + .term {
  margin-top: 30px;
  padding-top: 30px;
  border-top: 1px solid var(--border);
}
.term > h3:first-child {
  margin: 0 0 10px;
  font-size: 1.24rem;
  color: var(--ghost-glow);
}
.term .model { margin: 20px 0 6px; }
.term > p:last-child { margin-bottom: 0; }

/* ---------- guide.html: the thirteen pages, listed ---------- */
.guide-index .index-list {
  list-style: none;
  margin: 26px 0 0;
  padding: 0;
  counter-reset: none;
}
.index-list > li {
  padding: 18px 0;
  border-top: 1px solid var(--border);
}
.index-list > li:last-child { border-bottom: 1px solid var(--border); }
.index-list a {
  display: block;
  font-family: var(--font-display);
  font-size: 1.12rem;
  color: var(--ink);
}
.index-list a .n {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--ghost);
  letter-spacing: 0.08em;
  margin-right: 10px;
}
.index-list a:hover { color: var(--ghost-glow); }
.index-list p {
  margin: 6px 0 0;
  color: var(--ink-muted);
  font-size: 0.93rem;
  max-width: 68ch;
}

/* ---------- the page before and the page after ---------- */
.doc-steps {
  display: flex;
  gap: 16px;
  margin: 72px 0 0;
  padding-top: 26px;
  border-top: 1px solid var(--border);
}
.doc-step {
  flex: 1 1 0;
  padding: 14px 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--ink);
  font-size: 0.95rem;
}
.doc-step span {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 4px;
}
.doc-next { text-align: right; }
.doc-step:hover { border-color: var(--border-soft); color: var(--ghost-glow); }
@media (max-width: 620px) {
  .doc-steps { flex-direction: column; }
  .doc-next { text-align: left; }
}

/* the contents column's own title links back to the overview */
.toc > h2 a { color: inherit; }
.toc > h2 a:hover { color: var(--ghost-glow); }
