/* ============================================================
   /chat/<slug> — a student chat, drawn as the app.

   Loaded after walkthrough.css, which is here for the --wt-*
   ink tokens and the shared page furniture. This sheet declares
   no palette of its own: the four things — paper, a ruled edge
   and a hard offset cast, mono for anything that labels rather
   than speaks, ink plus one accent — are already stated on
   --wt-*, and restating them with different numbers is the one
   thing docs/WIREFRAME-LANGUAGE.md forbids.

   ---- The three rules this page was rebuilt around ----

   1. THE CONVERSATION IS THE PAGE. It is not inside a box with a
      scrollbar of its own. A screen is wider than it is tall, and
      a chat in a fixed-height window on a landscape screen is a
      letterbox: the first version put twenty-five messages behind
      a nested scroller and the whole conversation could be — was
      — scrolled straight past. You scroll down and you read it.

   2. NOTHING ABOVE IT THAT IS NOT CARRYING ITS WEIGHT. There used
      to be four stacked bars over the chat: a browser frame with
      three dots and a URL, an app bar, a topic-and-progress
      strip, and a row with the perspective switch and the spine.
      On a 900px-tall screen that is a fifth of the height gone
      before a word of the conversation. They are now one sticky
      bar and a fourteen-pixel spine, and that bar stays with the
      reader — which is the only thing that earns any height at
      all.

   3. MONO LABELS, BODY SPEAKS (§4). Everything a student wrote is
      in the body face at a size you can read for twenty minutes;
      everything that labels it — the name over a bubble, a
      button, a column head, the gap markers — is mono.

   The chat is ONE screen and the quiz and survey are TWO, side by
   side. A chat is prose and needs a measure; a quiz and a survey
   are the same questions answered separately by two people who
   could not see each other's answers, and reading across the row
   is the whole content of them. The split turns up where
   comparison is the point and gets out of the way where reading
   is.
   ============================================================ */

/* ============================================================
   0. The one token this sheet declares
   ============================================================ */
/* Text on an accent fill. The light accent is a deep violet and takes paper
   on it at 5.5:1; the dark accent is a pale one, and paper on that is 2.9:1 —
   white lettering on lilac. So the fill's foreground is a token of its own:
   paper in light, ink in dark. Declared for both themes rather than given a
   literal fallback, so a missing token shows as no colour rather than as one
   that happens to work in one of them (§1.2). */
:root { --cp-on-accent: #fffefb; }
html.dark-mode { --cp-on-accent: #15151e; }

/* ============================================================
   1. The rig
   ============================================================ */
/* The site's own column, and not a pixel more: starting at the same left edge
   as /news, /research, /faq and /topics is what makes a page look like part of
   this site. Clip on the main, so nothing in here can widen the document
   however far a stroke overshoots. */
.site-main { overflow-x: clip; }

.cp { position: relative; margin-top: 1.4rem; }

/* ============================================================
   2. What the page says before the drawing starts
   ============================================================ */
.page-content .cp-eyebrow {
  font-family: "Roboto Mono", monospace;
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-soft);
  margin: 0 0 0.4rem;
}
/* The statement the two of them were rated on is not printed here. Guide says
   it in the first message of every chat, in the product's own words, thirty
   seconds after the reader starts reading — a plate above the page saying it
   first is a restatement, not an introduction. It is still on the card at
   /chats, and on the survey screen where each student rates it again. */

/* One quiet line between the title and the conversation. Not a bordered
   aside, not a callout — the original thread pages set this as plain small
   text and were right to. */
.page-content .cp-notice {
  font-size: 0.78rem;
  line-height: 1.65;
  color: var(--text-soft);
  margin: 0 0 1.1rem;
}
.page-content .cp-notice b { font-weight: 600; color: var(--wt-ink-strong); }

/* ============================================================
   3. The three screens, and which one is up
   ============================================================ */
/* All three, always, in the order a student meets them. The page is one
   scroll: you read the conversation, you reach the end of it, and the quiz
   and the survey are simply what is next. Nothing has to be pressed to see
   any of it — which is also exactly what a reader with no JavaScript gets,
   so there is no second layout to keep in step. */
.cp-act { margin-bottom: 3.2rem; }
.cp-act:last-of-type { margin-bottom: 0; }

/* The three parts, named. A number and a title, the way the walkthroughs
   number their screens — it is a document with three sections in it, and the
   reader is walking down it rather than pressing between panes.
   No leading article: WALKTHROUGH-STYLE.md §4.2, where twelve titles lost
   theirs. "Chat transcript", not "The chat". */
.page-content h2.cp-acthead {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  font-family: "Roboto Mono", monospace;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--text-strong);
  margin: 2.6rem 0 0.7rem;
  padding-top: 1.4rem;
  border-top: 1.5px dashed var(--wt-faint);
  scroll-margin-top: calc(var(--banner-h) + 1rem);
}
.page-content h2.cp-acthead.is-first { margin-top: 0.4rem; padding-top: 0; border-top: 0; }
.page-content h2.cp-acthead b {
  font-family: "Roboto Mono", monospace;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--wt-accent);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   4. The one bar the chat keeps
   ============================================================ */
/* Sticky under the site banner, so the topic, whose screen you are on and how
   far through the conversation you are all stay with you. --banner-h is the
   chrome's own measure (64 / 48 / 76px) and is declared site-wide.
   It sticks only while the chat is on screen, because it is the chat's bar:
   sticky is bounded by its own containing block, and that is the section. */
/* It runs the whole width of the page, and that is the point of it: an app's
   top bar is fixed to the window it belongs to, and one that stops short of
   both edges reads as a plate floating over the page rather than as the chrome
   the conversation is being read under. So it breaks out of the site column —
   flush against the nav on one side and the window on the other, which is
   where the bar in the product sits.

   The breakout is derived rather than typed: .page-container is centred and
   symmetrically padded, so the gutter either side of the column is half the
   difference between the width the page has and the column it keeps. `100%`
   is the column (a percentage margin resolves against the containing block,
   not against this box), and --cp-page is the width the page has: the window,
   less the nav rail where the rail is laid out beside the page. Below 1280px
   the nav is a drawer *over* the page and the page has the whole window.

   Nothing else moves. The column is not negotiable — the title, the notice
   and every word of the conversation keep the left edge they share with
   /news, /research and /faq — and this is one bar stepping outside it, which
   is why the rule is written here and not on the container. .site-main's clip
   (§1) is what stops the step widening the document. */
.cp-topbar {
  --cp-page: 100vw;
  position: sticky;
  top: var(--banner-h);
  z-index: 7;
  background: var(--wt-paper);
  /* No edge of its own. The bar's one line is drawn under the row of controls
     rather than under the whole bar — see .cp-topbar-in — and a box a window
     wide has no left or right edge to draw anyway. */
  border: 0;
  box-shadow: 0 2px 0 var(--wt-cast);
  margin-bottom: 1.2rem;
  margin-left: calc((100% - var(--cp-page)) / 2);
  margin-right: calc((100% - var(--cp-page)) / 2);
}
@media (min-width: 1280px) {
  .cp-topbar { --cp-page: calc(100vw - var(--sidebar-w)); }
}
/* The row of controls, and it carries the bar's one drawn line along its
   bottom — not the bar's own bottom edge, which is a spine lower down.

   That line is the same line as the rule under the nav's wordmark, and the
   two meet: the row takes --sn-mark-h, which is what site-nav.css measures
   that row at, so the app's chrome and the site's chrome are one edge across
   the whole window rather than two lines eight pixels apart. It is the
   difference between a bar the page has and a bar sitting on top of the page.

   Only where there is a rule to meet. Below 1280px the nav is a drawer and
   has left the page; folded, its wordmark row stacks the logo over the fold
   button and is half as tall again, and a bar that matched *that* would spend
   ninety pixels on one line of controls. Both fall back to the row's own
   height — 0.4rem of air over a 1.7rem control, which is what every control
   in here is set to (see .cp-swap). */
.cp-topbar-in {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.4rem 0.85rem;
  border-bottom: 1.5px solid var(--wt-ink);
  font-family: "Roboto Mono", monospace;
}
@media (min-width: 1280px) {
  html:not(.nav-fold) .cp-topbar-in { min-height: var(--sn-mark-h); }
  /* A drawn line is centred on the edge it was drawn along; a ruled one lives
     inside the box. Left equal, the two rules land three quarters of a pixel
     apart — invisible at 1x and a step at 2x. Half a rule off the row, and
     only where a stroke actually landed on it. */
  html.cp-ink:not(.nav-fold) .cp .cp-topbar-in.hi-box {
    min-height: calc(var(--sn-mark-h) - 0.75px);
  }
}
/* Below the sidebar's breakpoint the site's floating theme button leaves the
   bottom right corner and parks under the banner in the top right (site.css:
   42px, 0.8rem in from the edge) — which is now where the speed control ends.
   It was already grazing it when the bar stopped at the column's edge; a bar
   the width of the window puts the button straight over it. So the row keeps
   the corner clear: the button, the gap it sits in, and the same gap again on
   the other side of it. The spine below is not held off — it runs the full
   width, and a 14px strip under a floating button is still readable. */
@media (max-width: 1279px) {
  .cp-topbar-in { padding-right: calc(42px + 1.6rem); }
}
.cp-brand { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.cp-mark { width: 1.15rem; height: 1.15rem; flex: none; color: var(--wt-accent); }
.cp-mark svg { display: block; width: 100%; height: 100%; }
.page-content .cp-brand b {
  font-family: "Roboto Mono", monospace;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--wt-ink-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Whose screen you are on, and the way to the other one. The avatar is the
   product's own; the button around it is this page's.

   1.7rem, which is the height every other control in this bar is set to. It
   used to size itself — a 1.5rem avatar with padding and a border round it,
   which came out at 41px against the narration's 35 — and one control six
   pixels taller than the rest set the height of the row, and so of the bar.
   A bar is as tall as the tallest thing in it, so the way to shorten it
   without taking anything out is for the things in it to agree. */
.cp-swap {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: 1.7rem;
  border: 1.5px solid var(--wt-faint);
  border-radius: 999px;
  background: var(--wt-paper);
  padding: 0 0.6rem 0 0.15rem;
  font-family: "Roboto Mono", monospace;
  font-size: 0.7rem;
  color: var(--text-soft);
  cursor: pointer;
  transition: border-color 0.14s ease, color 0.14s ease;
}
/* The avatar inside it comes down with the pill. Scoped to .cp-swap-av: the
   same .cp-avatar is drawn in the quiz and survey screens' own app bars,
   where it is the product's size and nothing here is asking it to move. */
.cp-swap-av { width: 1.3rem; height: 1.3rem; font-size: 0.62rem; }
.cp-swap:hover { border-color: var(--wt-ink-strong); color: var(--wt-ink-strong); }
.cp-swap:focus-visible { outline: 2px solid var(--wt-accent); outline-offset: 2px; }
.page-content .cp-swap b {
  font-family: "Roboto Mono", monospace;
  font-weight: 700;
  color: var(--wt-ink-strong);
}
.cp-avatar {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  border: 1.5px solid var(--wt-faint);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Roboto Mono", monospace;
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--wt-ink-strong);
  background: var(--wt-accent-soft);
  flex: none;
}
.cp-glyph { font-size: 0.9em; line-height: 1; }

/* ---- the narration ----
   Every message in every thread has a recorded start time, so the transcript
   follows the audio rather than just playing beside it: the message being
   spoken is marked, and the page keeps up with it.

   Ruled, not drawn: a drawn box is a thing you read and a ruled one is a thing
   you press (§2), and a control whose outline is regenerated every time its
   fill moves is not a control anybody wants. */
.cp-audio {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-family: "Roboto Mono", monospace;
}
.cp-ap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.7rem;
  height: 1.7rem;
  padding: 0 0.35rem;
  border: 1.5px solid var(--wt-faint);
  border-radius: 4px;
  background: var(--wt-paper);
  font-family: "Roboto Mono", monospace;
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
  color: var(--wt-ink-strong);
  cursor: pointer;
  transition: border-color 0.14s ease, background-color 0.14s ease;
}
.cp-ap:hover { border-color: var(--wt-ink-strong); }
.cp-ap:focus-visible { outline: 2px solid var(--wt-accent); outline-offset: 2px; }
/* The one control here that starts something takes the accent fill. */
.cp-ap-play { border-color: var(--wt-accent); background: var(--wt-accent); }
.cp-ap-play:hover { border-color: var(--wt-accent); }
/* Drawn in CSS rather than set as a character: ▶ and ⏸ render as full-colour
   emoji on some platforms and as tofu on others, and neither is the ink. */
.cp-ap-icon {
  display: block;
  width: 0;
  height: 0;
  margin-left: 2px;
  border-top: 0.34rem solid transparent;
  border-bottom: 0.34rem solid transparent;
  border-left: 0.52rem solid var(--cp-on-accent);
}
.cp-audio.is-playing .cp-ap-icon {
  width: 0.5rem;
  height: 0.62rem;
  margin-left: 0;
  border: 0;
  background: linear-gradient(
    to right,
    var(--cp-on-accent) 0 36%,
    transparent 36% 64%,
    var(--cp-on-accent) 64% 100%
  );
}
.cp-ap-time {
  font-size: 0.64rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.cp-ap-track {
  width: clamp(4rem, 12vw, 9rem);
  height: 1.7rem;
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}
.cp-ap-track::before {
  content: "";
  flex: 1;
  height: 3px;
  border-radius: 2px;
  background: var(--wt-faint);
}
.cp-ap-track { position: relative; }
.cp-ap-fill {
  position: absolute;
  left: 0;
  top: 50%;
  height: 3px;
  margin-top: -1.5px;
  width: var(--pct, 0%);
  border-radius: 2px;
  background: var(--wt-accent);
  pointer-events: none;
}
.cp-ap-track:focus-visible { outline: 2px solid var(--wt-accent); outline-offset: 2px; border-radius: 3px; }
.cp-ap-speed { min-width: 2.1rem; }

/* The message being spoken. An accent rule down its edge and the paper
   lifted — the same two marks Guide's own messages use, because this is the
   same idea: "this one, now". Nothing moves and nothing is redrawn. */
.cp-msg.is-speaking .cp-bubble {
  box-shadow: inset 3px 0 0 var(--wt-accent);
  padding-left: 1.15rem;
}
.cp[data-me="left"] .cp-msg[data-who="left"].is-speaking .cp-bubble,
.cp[data-me="right"] .cp-msg[data-who="right"].is-speaking .cp-bubble {
  box-shadow: inset -3px 0 0 var(--wt-accent);
  padding-left: 0.9rem;
  padding-right: 1.15rem;
}
/* Once the narration has been started, a message is a place in it. */
.cp[data-seekable] .cp-msg { cursor: pointer; }

/* ---- the spine, as the bar's own bottom edge ----
   Every message is a tick: the student who disagrees below the axis, the one
   who agrees above it, Guide as a diamond on the axis itself. Position
   carries who — colouring one student one way and the other another would be
   the mistake /topics avoids by drawing agreement and disagreement in the
   same ink. The accent is spent on where you are and nothing else. */
/* Under the drawn line, not over it. The spine used to hang the dashed edge
   at its top and leave the drawn one at the foot of the bar, which put the
   bar's hard edge six pixels below the rule the nav ends on — near enough to
   read as a miss rather than as a choice. Turned over, the drawn line is the
   one that runs across and the dashed one is where the bar gives out, which
   is also the right way round for what they are: a rule is chrome, and a
   dashed line is a map's own edge. */
.cp-spine {
  position: relative;
  height: 14px;
  border-bottom: 1.5px dashed var(--wt-faint);
  cursor: pointer;
  touch-action: none;
}
.cp-spine:focus-visible { outline: 2px solid var(--wt-accent); outline-offset: -2px; }
.cp-axis {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: var(--wt-faint);
  opacity: 0.7;
}
.cp-ticks { position: absolute; inset: 0; }
.cp-tick {
  position: absolute;
  width: 2px;
  margin-left: -1px;
  background: var(--wt-ink);
  border-radius: 1px;
  opacity: 0.45;
}
.cp-tick.is-right { bottom: 50%; height: 5px; }
.cp-tick.is-left { top: 50%; height: 5px; }
.cp-tick.is-guide {
  top: 50%;
  left: var(--x);
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
  background: var(--wt-ink-strong);
  border-radius: 0;
  transform: rotate(45deg);
  opacity: 0.8;
}
.cp-tick.is-seen { opacity: 1; }
/* How much of the conversation is on screen, and where. */
.cp-band {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--x, 0%);
  width: var(--w, 15%);
  min-width: 4px;
  background: var(--wt-accent-soft);
  box-shadow: inset 0 -1.5px 0 var(--wt-accent);
  pointer-events: none;
}
html:not(.cp-js) .cp-topbar { position: static; }
html:not(.cp-js) .cp-spine, html:not(.cp-js) .cp-swap { display: none; }

/* ============================================================
   5. The conversation
   ============================================================ */
.cp-thread {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  /* The conversation sits in the middle of the column rather than along its
     left edge. Left to fill the whole width, a run of messages from one
     person stacked up one side and left half the page empty, which reads as a
     layout fault rather than as a chat.
     The 8px floor is the room the strokes need: they overshoot their bubble
     by seven pixels, and with no padding at all the leftmost of them fell
     outside the column and was clipped. */
  padding: 0 max(8px, calc((100% - 58rem) / 2));
}

.cp-msg {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* A measure, not a proportion: about seventy characters of the body face,
     which is what a paragraph wants. */
  max-width: min(34rem, 88%);
  /* The sticky bar is a shade over 3.2rem tall; a message jumped to must not
     land under it. */
  scroll-margin-top: calc(var(--banner-h) + 4.5rem);
}
/* Guide is indented from the left and carries an accent bar down its edge,
   which is what the product does with it — it is neither of the two of them,
   and the shape says so before the name does. The bar is an inset shadow
   rather than a border, because a drawn box has its borders taken off. */
.cp-msg[data-who="guide"] {
  max-width: min(40rem, 96%);
  margin-left: clamp(0px, 4%, 2.5rem);
}
.cp-msg[data-who="guide"] .cp-bubble {
  box-shadow: inset 3px 0 0 var(--wt-accent);
  padding-left: 1.15rem;
}

/* Whose screen you are on decides which side a student's own messages sit.
   That single comparison is the whole of the split — and it is a class on the
   rig rather than two copies of the thread, so the conversation exists once. */
.cp[data-me="left"] .cp-msg[data-who="left"],
.cp[data-me="right"] .cp-msg[data-who="right"] { align-self: flex-end; align-items: flex-end; }
/* Your own name is not printed over your own messages, in the product or
   here. */
.cp[data-me="left"] .cp-msg[data-who="left"] .cp-who,
.cp[data-me="right"] .cp-msg[data-who="right"] .cp-who { display: none; }
.cp[data-me="left"] .cp-msg[data-who="left"] .cp-bubble,
.cp[data-me="right"] .cp-msg[data-who="right"] .cp-bubble {
  background: var(--wt-accent-soft);
  color: var(--wt-ink-strong);
}

.page-content .cp-who {
  font-family: "Roboto Mono", monospace;
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  color: var(--text-soft);
  margin: 0 0 0.25rem 0.35rem;
}

/* A chat bubble is a thing somebody said, so it is set in the body face. */
.cp-bubble {
  position: relative;
  border: 1.5px solid var(--wt-faint);
  border-radius: 6px;
  padding: 0.7rem 0.9rem;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.95rem;
  line-height: 1.68;
  color: var(--wt-ink-strong);
  background: var(--wt-paper);
  overflow-wrap: anywhere;
}
.cp-msg[data-who="guide"] .cp-bubble { background: var(--wt-hatch); }
.page-content .cp-bubble p { font-family: inherit; font-size: 1em; line-height: 1.68; margin: 0 0 0.65em; }
.page-content .cp-bubble p:last-child { margin: 0; }
.page-content .cp-bubble em, .page-content .cp-bubble i { font-style: italic; }
/* The topic statement in Guide's opening message.
   The transcript pages set it centred and italic; the product does neither.
   Bold and left-aligned, like the rest of the message it is part of — a
   statement two people are about to argue about is the substance of that
   message, not a pull quote from it.
   `p.center` is the transcript's own class and it is a safe hook: there is
   exactly one of them in each of the sixteen chats, always in Guide's first
   message, always the statement. Everywhere else <em> stays italic, because
   everywhere else it is emphasis. */
.page-content .cp-bubble p.center { text-align: left; }
.page-content .cp-bubble p.center em { font-style: normal; font-weight: 600; }
.page-content .cp-bubble strong, .page-content .cp-bubble b { font-weight: 600; }
.page-content .cp-bubble ul { margin: 0.4em 0; padding-left: 1.2em; }
.page-content .cp-bubble li { font-family: inherit; font-size: 1em; line-height: 1.62; margin: 0 0 0.25em; }

/* A message the reader has not been given yet — the closing exchange, which
   arrives when they reach the end. */
.cp-msg.is-ahead, .cp-sep.is-ahead { display: none; }
.cp-msg.is-new { animation: cp-rise 0.36s cubic-bezier(0.22, 1, 0.36, 1) both; }
@keyframes cp-rise {
  from { opacity: 0; transform: translateY(0.6rem); }
  to { opacity: 1; transform: none; }
}

/* ---- how long the two of them left it ----
   A Sway chat is not a half-hour sitting; it runs over evenings, and the
   pauses are part of what it is. */
.cp-sep {
  align-self: stretch;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin: 0.4rem 0;
  font-family: "Roboto Mono", monospace;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.cp-sep::before, .cp-sep::after {
  content: "";
  flex: 1;
  height: 1.5px;
  background: var(--wt-faint);
  opacity: 0.55;
}

/* ---- the typing indicator ----
   It shows on the *other* student's screen: while one of them is typing, the
   other is watching three dots. It belongs to the closing exchange and to
   nothing else. */
.cp-typing { display: none; }
.cp-typing.is-on { display: flex; }
.cp[data-me="left"] .cp-typing[data-mine], .cp[data-me="right"] .cp-typing[data-mine] { display: none; }
.cp-typing .cp-bubble { display: flex; align-items: center; gap: 0.3rem; padding: 0.95rem 1rem; }
.cp-typing i {
  width: 0.35rem;
  height: 0.35rem;
  border-radius: 50%;
  background: var(--wt-ink);
  animation: cp-typedot 1.1s ease-in-out infinite;
}
.cp-typing i:nth-child(2) { animation-delay: 0.14s; }
.cp-typing i:nth-child(3) { animation-delay: 0.28s; }
@keyframes cp-typedot {
  0%, 65%, 100% { opacity: 0.28; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-0.2rem); }
}

/* ---- Guide, mid-sentence ----
   The product streams Guide's replies a word at a time, so the closing one
   does here. The caret says the sentence is not finished. */
.cp-caret {
  display: inline-block;
  width: 2px;
  height: 1em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: var(--wt-accent);
  animation: cp-blink 1.1s steps(1, end) infinite;
}
@keyframes cp-blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* ---- emoji reactions ----
   Six emoji, no more, and the badge sits over the bottom edge of the bubble
   on the far side from the sender. Drawn in rather than recorded — the
   transcripts do not carry reactions — which is what the caveat says. */
.cp-react {
  position: absolute;
  bottom: -0.7rem;
  right: 0.7rem;
  display: flex;
  align-items: center;
  padding: 0.1rem 0.35rem;
  border: 1.5px solid var(--wt-faint);
  border-radius: 999px;
  background: var(--wt-paper);
  font-size: 0.9rem;
  line-height: 1.3;
}
.cp[data-me="left"] .cp-msg[data-who="left"] .cp-react,
.cp[data-me="right"] .cp-msg[data-who="right"] .cp-react { right: auto; left: 0.7rem; }

/* ---- the message box ----
   The three controls the product has, at the end of the conversation where a
   student would find them. Nobody can type into it — this chat already
   happened — so it sits quiet. */
.cp-composer {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 1.2rem max(0px, calc((100% - 58rem) / 2)) 0;
  opacity: 0.5;
}
.cp-emoji { flex: none; font-size: 1.1rem; line-height: 1; color: var(--text-soft); }
.page-content .cp-say {
  flex: 1;
  min-width: 0;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--text-soft);
  margin: 0;
  padding: 0.45rem 1rem;
  border: 1.5px dashed var(--wt-faint);
  border-radius: 18px;
  background: var(--wt-paper);
  overflow: hidden;
  white-space: nowrap;
}
.cp-send {
  flex: none;
  border: 1.5px solid var(--wt-accent);
  border-radius: 4px;
  background: var(--wt-accent);
  color: var(--cp-on-accent);
  font-family: "Roboto Mono", monospace;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 0.55rem 1.1rem;
}

/* ============================================================
   6. The notice between the chat and the quiz
   ============================================================ */
/* The product ends a chat with a modal across the screen. Here it arrives when
   the reader reaches the end of the conversation and then stays put, as the
   join between the chat and the quiz — a sheet they had to dismiss before they
   could carry on scrolling would be the page fighting the one thing it asks
   them to do. */
.cp-notify {
  display: flex;
  justify-content: center;
  margin: 1.6rem 0 0;
}
.cp-notify[hidden] { display: none; }
.cp-modal {
  width: min(24rem, 100%);
  border: 1.5px solid var(--wt-ink);
  border-radius: 5px;
  background: var(--wt-paper);
  box-shadow: 4px 4px 0 var(--wt-cast);
}
.cp-notify.is-new .cp-modal { animation: cp-rise 0.34s cubic-bezier(0.22, 1, 0.36, 1) both; }
.cp-modal-head {
  padding: 0.55rem 0.8rem;
  border-bottom: 1.5px solid var(--wt-faint);
  background: var(--wt-hatch);
  font-family: "Roboto Mono", monospace;
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--wt-ink-strong);
}
.page-content .cp-modal-body p {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--wt-ink-strong);
  margin: 0;
  padding: 0.9rem 0.8rem;
}
.cp-modal-actions { display: flex; justify-content: flex-end; gap: 0.5rem; padding: 0 0.8rem 0.8rem; }
.cp-modal-btn {
  border: 1.5px solid var(--wt-faint);
  border-radius: 4px;
  background: var(--wt-paper);
  padding: 0.45rem 0.8rem;
  font-family: "Roboto Mono", monospace;
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--wt-ink-strong);
  cursor: pointer;
}
.cp-modal-btn.is-primary { border-color: var(--wt-accent); background: var(--wt-accent); color: var(--cp-on-accent); }
.cp-modal-btn:focus-visible { outline: 2px solid var(--wt-accent); outline-offset: 2px; }

.cp-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  border: 1.5px solid var(--wt-ink-strong);
  border-radius: 4px;
  background: var(--wt-paper);
  padding: 0.55em 1em;
  font-family: "Roboto Mono", monospace;
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--wt-ink-strong);
  box-shadow: 2px 2px 0 var(--wt-cast);
  cursor: pointer;
  transition: box-shadow 0.14s ease, transform 0.14s ease;
}
/* Hover deepens the cast; it does not recolour the edge. Pressed goes the
   other way. */
.cp-btn:hover { box-shadow: 3px 3px 0 var(--wt-cast); transform: translate(-1px, -1px); }
.cp-btn:active { box-shadow: 1px 1px 0 var(--wt-cast); transform: translate(1px, 1px); }
.cp-btn:focus-visible { outline: 2px solid var(--wt-accent); outline-offset: 2px; }
.cp-btn.is-go { border-color: var(--wt-accent); background: var(--wt-accent); color: var(--cp-on-accent); }

/* ============================================================
   7. Two screens, side by side
   ============================================================ */
.page-content .cp-actlead {
  font-size: 0.95rem;
  line-height: 1.7;
  color: var(--text-soft);
  margin: 0 0 1rem;
}
.cp-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.9rem, 1.8vw, 1.8rem);
  align-items: start;
}
.cp-half { min-width: 0; }
.cp-label { display: flex; align-items: baseline; gap: 0.55rem; margin: 0 0 0.45rem; }
.page-content .cp-label b {
  font-family: "Roboto Mono", monospace;
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--wt-ink-strong);
}
/* A dashed, unfilled, uppercase mono word for what a thing *is* — the .wp-tag
   shape, restated because this sheet does not load wire-page.css. */
.cp-side-tag {
  font-family: "Roboto Mono", monospace;
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-soft);
  border: 1.5px dashed var(--wt-faint);
  border-radius: 3px;
  padding: 0.1em 0.45em;
}

/* These two screens keep a frame, because there are two of them and the frame
   is what says they are separate screens. They have no height of their own
   either, and nothing to clip: the page scrolls, not the box. */
.cp-win {
  position: relative;
  border: 2px solid var(--wt-ink);
  border-radius: 6px;
  background: var(--wt-paper);
  box-shadow: 5px 5px 0 var(--wt-cast);
  color: var(--wt-ink);
}
.cp-app {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.45rem 0.75rem;
  border-bottom: 1.5px solid var(--wt-faint);
  background: var(--wt-hatch);
  font-family: "Roboto Mono", monospace;
}
.cp-topnav { margin-left: auto; display: flex; align-items: center; gap: 0.6rem; }
/* The theme control in the product's top bar is Material's brightness_auto —
   a capital A in a rounded square — not a moon. */
.cp-abadge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border: 1.5px solid var(--wt-faint);
  border-radius: 4px;
  font-size: 0.66rem;
  font-weight: 700;
  line-height: 1;
  color: var(--wt-ink-strong);
}

.cp-sheet { padding: 1.1rem 1.3rem 1.2rem; display: flex; flex-direction: column; }
.page-content .cp-h {
  font-family: "Roboto Mono", monospace;
  font-size: 0.86rem;
  font-weight: 700;
  line-height: 1.4;
  color: var(--wt-ink-strong);
  margin: 0 0 0.35rem;
}
.page-content .cp-p {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.86rem;
  line-height: 1.62;
  color: var(--wt-ink-strong);
  margin: 0 0 0.6rem;
}
.page-content .cp-p.is-soft { color: var(--text-soft); }
.page-content .cp-p b { font-weight: 600; }

/* ---- the understanding quiz ---- */
/* A column, so that the mark sits on the card's floor. Pairs of cards are
   given a common height (see alignRows in js/chat-player.js), and whichever
   of the two has less in it gets the slack — put below the mark it reads as a
   box that failed to fill, and put above it as a card with room in it. */
.cp-quiz {
  display: flex;
  flex-direction: column;
  border: 1.5px solid var(--wt-ink);
  border-radius: 5px;
  padding: 0.85rem 0.9rem;
  margin-bottom: 0.7rem;
}
.page-content .cp-quiz > b {
  display: block;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1.55;
  color: var(--wt-ink-strong);
  margin-bottom: 0.6rem;
}
.page-content ul.cp-answers { list-style: none; margin: 0; padding: 0; }
.page-content ul.cp-answers li {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  border-radius: 4px;
  background: var(--wt-hatch);
  padding: 0.5rem 0.6rem;
  margin: 0 0 0.3rem;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--wt-ink);
  transition: background-color 0.24s ease, color 0.24s ease;
}
.page-content ul.cp-answers li.is-on { background: var(--wt-accent-soft); color: var(--wt-ink-strong); }
.cp-radio {
  flex: none;
  display: inline-block;
  width: 0.85rem;
  height: 0.85rem;
  margin-top: 0.3rem;
  border-radius: 50%;
  border: 1.5px solid var(--wt-ink);
  transition: transform 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
}
.cp-answers li.is-on .cp-radio, .cp-radio.is-on {
  border-color: var(--wt-accent);
  background: var(--wt-accent);
  box-shadow: inset 0 0 0 2px var(--wt-paper);
}
.cp-radio.is-landing { transform: scale(1.4); }

/* Whether the answer they picked was the right one. Sway marks it, so a
   wireframe that left it out would be a drawing of a screen that does not
   exist — but it is said in words, in the mono a label is set in, because
   this drawing has no success colour and no danger colour. */
/* Hidden, not absent. A card that grows by a line the moment it is marked
   changes height under the alignment that has already been measured — and
   the two columns are only worth putting side by side if question three is
   level with question three. Reserving the line also stops each card jumping
   as the answers land. */
.page-content .cp-verdict {
  visibility: hidden;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-family: "Roboto Mono", monospace;
  font-size: 0.6rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
  margin: auto 0 0;
  padding-top: 0.5rem;
}
.cp-quiz.is-marked .cp-verdict { visibility: visible; }
.cp-verdict b {
  border: 1.5px solid var(--wt-ink-strong);
  border-radius: 3px;
  padding: 0.05em 0.45em;
  font-weight: 700;
  color: var(--wt-ink-strong);
}
.cp-verdict.is-wrong b { border-style: dashed; border-color: var(--wt-faint); color: var(--text-soft); }

/* ---- the post-chat survey ---- */
/* The eight pixels are the room the strokes need: the outline overshoots the
   table by seven, and without somewhere to put it the drawn edge either lands
   inside the table — a line down the middle of the first column, a hair from
   the statements — or pushes the wrapper into overflow, which brings up a
   scrollbar, which changes this container's inline size, which resizes the
   column heads (they are set in cqi), which resizes the table, which rebuilds
   the strokes. That last one is a loop, and it hangs the tab. Padding breaks
   it at the source: the strokes live in the padding and never widen anything.
   The negative margin keeps the table where it was. */
.cp-matrixwrap {
  overflow-x: auto;
  container-type: inline-size;
  padding: 8px;
  margin: 0.2rem -8px 0.8rem;
}
.page-content table.cp-matrix {
  width: 100%;
  border-collapse: collapse;
  border: 1.5px solid var(--wt-ink);
  background: var(--wt-paper);
  box-shadow: none;
  border-radius: 0;
  margin: 0;
  font-family: "Roboto Mono", monospace;
  table-layout: fixed;
}
.page-content table.cp-matrix th,
.page-content table.cp-matrix td {
  border: 1.5px solid var(--wt-faint);
  padding: 0.45rem 0.25rem;
  text-align: center;
  line-height: 1.35;
  vertical-align: middle;
  font-weight: 400;
}
/* A head is hatched, never filled: a filled head reads as a selected row. */
.page-content table.cp-matrix th {
  background: var(--wt-hatch);
  color: var(--wt-ink-strong);
  font-weight: 700;
}
/* The statement column carries prose, and prose needs air on both sides of
   it. The radio columns stay tight — they hold a dot. */
.page-content table.cp-matrix td:first-child,
.page-content table.cp-matrix th:first-child {
  text-align: left;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--wt-ink-strong);
  width: 40%;
  padding: 0.5rem 0.7rem 0.5rem 0.75rem;
}
/* Columns that mean equal steps are drawn as equal steps — a scale whose
   columns size themselves to their labels draws a ruler with uneven marks on
   it. The head type is sized against the table rather than the window, so it
   holds at every width instead of only at one. */
.page-content table.cp-matrix th:not(:first-child) {
  padding-left: 0.15rem;
  padding-right: 0.15rem;
  font-size: min(0.62rem, 2.1cqi);
  letter-spacing: -0.01em;
}
.page-content table.cp-matrix.is-bare td:first-child,
.page-content table.cp-matrix.is-bare th:first-child { width: auto; text-align: center; }
.page-content table.cp-matrix.is-bare th { font-size: min(0.62rem, 2.1cqi); }

/* Where the student was before they started. A hollow ring the filled dot
   moves away from, so the row says what changed as well as what it ended at. */
.cp-was {
  display: inline-block;
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  border: 1.5px dashed var(--wt-faint);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.cp-was.is-on { opacity: 1; }

.cp-textarea {
  border: 1.5px solid var(--wt-ink);
  border-radius: 4px;
  padding: 0.6rem 0.7rem;
  min-height: 3.2rem;
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--wt-ink-strong);
  background: var(--wt-paper);
}
.cp-textarea.is-empty { color: var(--text-soft); border-style: dashed; border-color: var(--wt-faint); }

.cp-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
  margin-top: auto;
  padding-top: 0.9rem;
}
.cp-hint {
  font-family: "Roboto Mono", monospace;
  font-size: 0.66rem;
  line-height: 1.5;
  color: var(--text-soft);
}
.cp-next {
  border: 1.5px solid var(--wt-accent);
  border-radius: 4px;
  background: var(--wt-accent);
  color: var(--cp-on-accent);
  font-family: "Roboto Mono", monospace;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  padding: 0.5rem 1.2rem;
}

/* A student who never filled the survey in. Two of the sixteen chats have
   one, and the honest thing is to say so rather than to draw a filled-in page
   that nobody filled in. */
.cp-none {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  border: 1.5px dashed var(--wt-faint);
  border-radius: 5px;
  padding: 2.4rem 1.2rem;
}
.page-content .cp-none p {
  font-family: "IBM Plex Sans", sans-serif;
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--text-soft);
  max-width: 22rem;
  margin: 0;
}

/* ============================================================
   8. Where the two of them ended up
   ============================================================ */
/* The page's last word, and the only part of it that is not a drawing of a
   screen. Neither side of the axis is coloured and neither direction is the
   good one — colouring one of them would say that one of them is the failure,
   which is the opposite of what the product is for. */
.cp-outcome {
  margin: 2rem 0 0;
  border: 1.5px solid var(--wt-ink);
  border-radius: 5px;
  background: var(--wt-paper);
  box-shadow: 3px 3px 0 var(--wt-cast);
  padding: 1.1rem 1.2rem 1.2rem;
  /* The scale's column heads have to be sized against the room a column
     actually has, and this box is the only thing that knows it — the window
     does not, because the name and shift columns take a fixed bite out of it
     first. See .cp-axis-head. Safe against the drawn outline: the ink puts
     `position: relative` on every .hi-box (section 10), so this box is
     already the containing block its own SVG resolves against. */
  container-type: inline-size;
}
.page-content .cp-outcome-title {
  font-family: "Roboto Mono", monospace;
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-soft);
  margin: 0 0 1rem;
}
/* One source of truth for the two columns that are not points: the head row,
   every student's row, and the arrow that has to line up with all of them all
   read the same two numbers. */
.cp-axis-head, .cp-arc {
  --cp-name-w: 8rem;
  --cp-shift-w: 4rem;
  --cp-gap: 0.3rem;
  display: grid;
  grid-template-columns: var(--cp-name-w) repeat(7, minmax(0, 1fr)) var(--cp-shift-w);
  gap: var(--cp-gap);
}
/* Seven labels over seven columns, and the longest word in them decides
   whether the row is a scale or a pile-up. "Moderately" is ten monospace
   characters, so it needs 6em to sit on one line; the seven columns share
   whatever the name and shift columns and the eight gaps leave of this box,
   so one column is (100cqi − name − shift − 8×gap) / 7 and the type has to
   stay under a seventh of that per em — 46 rather than 42 for a little air
   between neighbours.

   A flat size can only ever be right at one width, which is how the labels
   came to overlap: below 900px the size stopped at 0.52rem while the columns
   kept narrowing, and by a 390px phone "Moderately" was 50px of word in a
   26px column, lapping the labels either side of it. The cap is what the row
   was always set in, so this is a no-op wherever it already fitted. The same
   fix, and the same reasoning, as the student walkthrough's scale table
   (walkthrough-student.css, .wt2-matrix.is-scale). */
.cp-axis-head {
  align-items: end;
  font-family: "Roboto Mono", monospace;
  --cp-head-cap: 0.6rem;
  font-size: min(
    var(--cp-head-cap),
    (100cqi - var(--cp-name-w) - var(--cp-shift-w) - 8 * var(--cp-gap)) / 46
  );
  line-height: 1.3;
  color: var(--text-soft);
  text-align: center;
  margin-bottom: 0.5rem;
}
.cp-arc { position: relative; align-items: center; margin-bottom: 0.5rem; }
.page-content .cp-arc-name {
  font-family: "Roboto Mono", monospace;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--wt-ink-strong);
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.page-content .cp-arc-name span {
  display: block;
  font-size: 0.58rem;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.cp-slot { position: relative; height: 1.6rem; z-index: 1; }

/* ---- which way they moved ----
   An arrow from where they started to where they ended: one line across the
   ground they actually covered, with the head at the end they arrived at. It
   is drawn in ink rather than in the accent, because the accent is already
   carrying "after" on the filled dot and a second accent mark on the same row
   would be saying the same thing twice.

   Spans the seven point columns and positions itself inside them, so --a and
   --b are simply the centres of the two columns as percentages — no counting
   grid tracks, and no arithmetic that has to agree with the number of points
   on the scale. */
/* Out of the grid entirely. Placed as a grid item it took a cell of its own,
   and every dot after it shifted along a column — the arrow was right and the
   figure it was annotating was wrong. Absolutely positioned across exactly
   the seven point columns instead, so --a and --b stay simple percentages of
   the scale and nothing else on the row moves. */
.cp-move {
  position: absolute;
  top: 50%;
  left: calc(var(--cp-name-w) + var(--cp-gap));
  right: calc(var(--cp-shift-w) + var(--cp-gap));
  height: 1.5px;
  margin-top: -0.75px;
  pointer-events: none;
}
/* The shaft, stopping short of both dots. */
.cp-move::before {
  content: "";
  position: absolute;
  top: 0;
  left: calc(min(var(--a), var(--b)) + 0.34rem);
  right: calc(100% - max(var(--a), var(--b)) + 0.34rem);
  height: 1.5px;
  background: var(--wt-ink);
}
/* The head, its tip on the dot they arrived at. */
.cp-move::after {
  content: "";
  position: absolute;
  top: 50%;
  left: var(--b);
  width: 0;
  height: 0;
  border-top: 0.3rem solid transparent;
  border-bottom: 0.3rem solid transparent;
  border-left: 0.46rem solid var(--wt-ink);
  transform: translate(-0.8rem, -50%);
}
.cp-move.is-back::after {
  border-left: 0;
  border-right: 0.46rem solid var(--wt-ink);
  transform: translate(0.34rem, -50%);
}
.cp-pt {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0.72rem;
  height: 0.72rem;
  margin: -0.36rem 0 0 -0.36rem;
  border-radius: 50%;
}
.cp-pt.is-pre { border: 1.5px solid var(--wt-ink); }
.cp-pt.is-post { background: var(--wt-accent); }
/* One point standing for two readings, for a student who ended where they
   started. Half the ring they arrived with, half the fill they left with —
   before on the left and after on the right, the way the two of them are
   ordered everywhere else on this figure. */
.cp-pt.is-both {
  border: 1.5px solid var(--wt-ink);
  background: linear-gradient(to right, transparent 0 50%, var(--wt-accent) 50% 100%);
}
.page-content .cp-arc-shift {
  font-family: "Roboto Mono", monospace;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--wt-ink-strong);
  text-align: center;
  margin: 0;
  font-variant-numeric: tabular-nums;
}
.page-content .cp-arc-shift.is-still { font-weight: 400; color: var(--text-soft); }
.page-content .cp-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.4rem;
  margin: 0.9rem 0 0;
  padding-top: 0.8rem;
  border-top: 1.5px dashed var(--wt-faint);
  font-family: "Roboto Mono", monospace;
  font-size: 0.62rem;
  color: var(--text-soft);
}
.cp-legend span { display: inline-flex; align-items: center; gap: 0.45em; }
.cp-key { width: 0.72rem; height: 0.72rem; border-radius: 50%; flex: none; }
.cp-key.is-pre { border: 1.5px solid var(--wt-ink); }
.cp-key.is-post { background: var(--wt-accent); }
.cp-key.is-both {
  border: 1.5px solid var(--wt-ink);
  background: linear-gradient(to right, transparent 0 50%, var(--wt-accent) 50% 100%);
}

/* ============================================================
   9. Narrow
   ============================================================ */
@media (max-width: 900px) {
  .cp-pair { grid-template-columns: minmax(0, 1fr); gap: 1.6rem; }
  .cp-axis-head, .cp-arc { --cp-name-w: 5rem; --cp-shift-w: 2.8rem; }
  .cp-axis-head { --cp-head-cap: 0.52rem; }
  .page-content .cp-arc-name { font-size: 0.7rem; }
  .cp-msg { max-width: 92%; }
  .cp-swap-txt { display: none; }
  .cp-topbar-in { gap: 0.6rem; }
}
@media (max-width: 620px) {
  .cp-bubble { font-size: 0.92rem; padding: 0.6rem 0.75rem; }
  .cp-outcome { padding: 0.9rem; }
  .cp-axis-head, .cp-arc { --cp-name-w: 3.8rem; --cp-shift-w: 2.2rem; }
  /* Below this the seven columns are about 26px each, and a two-word label
     that fits one is four pixels of type — smaller than the fit term will
     ever be worth reading. So the scale stops labelling every point and
     labels itself the way an axis does: at its two ends and its middle, with
     the five points between them read off the spacing. The three that stay
     are placed by hand across the tracks they anchor, because a hidden grid
     item does not hold its column — the next one slides into it. */
  .cp-axis-head > .cp-ax { display: none; }
  .cp-axis-head > .cp-ax-lo,
  .cp-axis-head > .cp-ax-mid,
  .cp-axis-head > .cp-ax-hi { display: block; }
  .cp-axis-head > .cp-ax-lo { grid-column: 2 / 5; text-align: left; }
  .cp-axis-head > .cp-ax-mid { grid-column: 5 / 6; }
  /* Held off the shift heading beside it. The left end has an empty cell for
     a neighbour and needs no such inset; this one would otherwise read as
     "agree shift" on a 320px screen, where the column gap is under 5px. */
  .cp-axis-head > .cp-ax-hi { grid-column: 6 / 9; text-align: right; padding-right: 0.7em; }
  /* Three short labels with three columns each to sit in: the fit term was
     sized for seven and has nothing left to say here. */
  .cp-axis-head { font-size: 0.56rem; }
  .cp-strip-prog span:first-child { display: none; }
  .cp-prog-track { width: 3rem; }
}

/* ============================================================
   10. Ink
   ============================================================ */
/* js/hand-ink.js replaces a CSS border with generated strokes; this is the
   half of the arrangement that belongs to the page, restated against this
   sheet's own switch class and scoped to this page's own container — the same
   way next-paths.css and gallery-wire.css each carry their own copy, so that
   a page can be drawn without loading a stylesheet it has no other use for.

   html.cp-ink is set by the script, and is what takes the CSS border off. A
   reader with no JavaScript keeps the ruled box, which is the same design a
   shade quieter.

   The reveal exists but is almost never played: js/chat-player.js freezes
   every box as it is made, so they arrive drawn rather than drawing
   themselves. A box that was on the page when the page was has never come
   into being as far as the reader is concerned. The three things that play it
   are a message in the closing exchange, and the quiz and survey screens. */
html.cp-ink .cp .hi-box {
  position: relative;
  border-color: transparent;
  /* Squared: the generated outline has square corners, and a rounded fill
     under it shows through where the strokes cross. */
  border-radius: 0;
}
/* The rule above sets `position: relative`, so that the absolutely positioned
   stroke layer resolves against the box it was drawn for. That is right for
   every box on this page except the one that has to stay with the reader:
   sticky is a positioned containing block too, and it is what the bar is for.
   Without this the bar silently scrolls away, which is the whole thing it
   exists not to do.

   The ink is on .cp-topbar-in now, one box in, so nothing is putting `relative`
   on the bar today. The guard stays because the day something draws a line on
   the bar again is the day it stops sticking, with nothing on screen to say
   why. */
html.cp-ink .cp .cp-topbar.hi-box { position: sticky; border-radius: 0; }
.cp .hi-ink {
  position: absolute;
  left: -7px;
  top: -7px;
  overflow: visible;
  pointer-events: none;
  fill: none;
  z-index: 3;
}
.cp .hi-ink.hi-inset { left: 0; top: 0; }
.cp .hi-ink path {
  stroke: var(--wt-ink);
  stroke-width: 1.15;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
/* The second pass over an edge is lighter, as a real second pass is. */
.cp .hi-ink path.hi-p2 { stroke-width: 0.85; stroke-opacity: 0.5; }
.cp .hi-faint path { stroke: var(--wt-faint); stroke-width: 1; }
.cp .hi-accent path { stroke: var(--wt-accent); stroke-width: 1.2; }
.cp .hi-dash path { stroke-dasharray: 4 3.5; }

/* pathLength="1" normalises every stroke, so one dash length covers any edge
   and a long line takes no longer to draw than a short one. */
@keyframes cp-draw { to { stroke-dashoffset: 0; } }
@keyframes cp-inkin { to { opacity: 1; } }
html.cp-ink .cp .hi-ink:not(.hi-dash) path { stroke-dasharray: 1; stroke-dashoffset: 1; }
html.cp-ink .cp .hi-on > .hi-ink:not(.hi-dash) path {
  animation: cp-draw 0.34s cubic-bezier(0.4, 0.05, 0.25, 1) forwards;
  animation-delay: calc(min(var(--b, 0), 14) * 0.03s + var(--i, 0) * 0.03s);
}
/* A dashed line cannot be revealed by its dash pattern — that pattern is what
   the dashes are made of — so it comes in edge by edge on the same beat. */
html.cp-ink .cp .hi-dash path { opacity: 0; }
html.cp-ink .cp .hi-on > .hi-dash path {
  animation: cp-inkin 0.28s ease forwards;
  animation-delay: calc(min(var(--b, 0), 14) * 0.03s + var(--i, 0) * 0.03s);
}

/* ============================================================
   11. Reduced motion
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html.cp-ink .cp .hi-ink:not(.hi-dash) path { stroke-dashoffset: 0; animation: none; }
  html.cp-ink .cp .hi-dash path { opacity: 1; animation: none; }
  .cp-typing i, .cp-caret { animation: none; }
  .cp-typing i { opacity: 0.55; }
  .cp-caret { opacity: 1; }
  .cp-msg.is-new, .cp-notify.is-new .cp-modal { animation: none; }
  .cp-answers li, .cp-radio, .cp-was { transition: none; }
}
