/**
 * Player shell styling — instrument-panel identity.
 *
 * Every color here is a `var(--wa-*)` token from `brand/tokens.css` — that
 * file is the ONLY place in the repo these hex values exist as CSS;
 * `test/brand-identity/no-color-literals.test.js` scans this file (and the
 * rest of `demo/`, plus `src/player-core/`) and fails the build if a
 * literal hex or a raw color function ever creeps back in. Low-opacity
 * fills use `color-mix(in srgb, var(--wa-x) N%, transparent)` rather than
 * an 8-digit hex, per that same requirement.
 *
 * Accent discipline (revised — see DESIGN.md's "Colors" section): `--wa-now`
 * marks playback position (the active karaoke word, the pointer highlight,
 * the scrubber's thumb) AND carries the one primary action per screen as a
 * confident solid fill — `.transport-toggle`, `.prompt-submit` — plus a
 * lighter tint on `.pc-followup` and the current `.history-item`'s left
 * edge. Everything else (secondary transport buttons, quiz choices, focus
 * rings) stays neutral so the accent still reads as "the thing to look at
 * or press," not decoration; quiz correct/incorrect keep borrowing
 * `--wa-diff-add`/`--wa-alert` instead (panel status lights, nominal/fault).
 *
 * Typography is all `var(--wa-font-*)`. Revised — see brand/tokens.css's own
 * Typography comment: headings, the slide counter, the clock, and every
 * other piece of UI chrome now read off the same system sans stack as body
 * copy. Monospace (`--wa-font-mono`) is scoped to real code and diff panes
 * only. No brand webfont is loaded — see brand/tokens.css's own header for
 * why that is the deliberate choice, not a placeholder.
 */

@import "../brand/tokens.css";

:root {
  color-scheme: dark;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html,
body {
  margin: 0;
  color: var(--wa-ink);
  font-size: 16px;
  line-height: 1.55;
  font-family: var(--wa-font-body);
  -webkit-font-smoothing: antialiased;
}
/* Page backdrop: a soft horizon wash behind the header, fading into the flat
   panel stage within one viewport — see brand/tokens.css's own comment on
   the --wa-atmosphere-* pairs for why this is the one deliberate exception
   to "panel tone only." Three offset radial layers (not a single gradient)
   so the blend reads as overlapping horizon bands, not a flat sunset; the
   linear-gradient layer on top is what actually fades it to --wa-stage so
   slide content never sits on a colored ground.

   The two hues are `--backdrop-a`/`-b` custom properties, not the
   atmosphere tokens directly — `body[data-backdrop="..."]` below repoints
   them per theme so this one gradient recipe serves every variant.
   `demo/live-transport.js`'s `classifyBackdropTheme()` picks the theme from
   the explainer's own title/prompt keywords and demo.js/live.js stamp it
   onto `data-backdrop`; "network" (both a named theme and the fallback) is
   the default here so the backdrop still looks intentional before any
   explainer has loaded. */
body {
  --backdrop-a: var(--wa-atmosphere-teal);
  --backdrop-b: var(--wa-atmosphere-navy);
  background-image:
    radial-gradient(140% 60% at 15% -20%, color-mix(in srgb, var(--backdrop-a) 65%, transparent) 0%, transparent 62%),
    radial-gradient(120% 70% at 85% -10%, color-mix(in srgb, var(--backdrop-b) 70%, transparent) 0%, transparent 58%),
    radial-gradient(160% 50% at 50% 8%, color-mix(in srgb, var(--backdrop-b) 45%, transparent) 0%, transparent 70%),
    linear-gradient(to bottom, transparent 0%, var(--wa-stage) 60%);
  background-repeat: no-repeat;
  background-size: 100% 28rem;
  background-position: top center;
  background-color: var(--wa-stage);
  transition: background-image 320ms var(--ease-out);
}
body[data-backdrop="ai"] { --backdrop-a: var(--wa-atmosphere-violet); --backdrop-b: var(--wa-atmosphere-indigo); }
body[data-backdrop="security"] { --backdrop-a: var(--wa-atmosphere-crimson); --backdrop-b: var(--wa-atmosphere-maroon); }
body[data-backdrop="data"] { --backdrop-a: var(--wa-atmosphere-emerald); --backdrop-b: var(--wa-atmosphere-teal-deep); }
body[data-backdrop="systems"] { --backdrop-a: var(--wa-atmosphere-rust); --backdrop-b: var(--wa-atmosphere-slate); }
body[data-backdrop="network"] { --backdrop-a: var(--wa-atmosphere-teal); --backdrop-b: var(--wa-atmosphere-navy); }
@media (prefers-reduced-motion: reduce) {
  body { transition-duration: 0s; }
}

.app {
  max-width: 60rem;
  margin: 0 auto;
  padding: 2rem 1.5rem 4rem;
  display: grid;
  gap: 1.25rem;
}

/* --- Header: mark + wordmark + slide counter, on --wa-stage ------------- */

/* Scoped to `.app`'s direct child, NOT the bare `.pc-header` class: dom.js
   stamps every `<header>` vnode `pc-header` too, so an unscoped rule here
   leaked this 3-column row layout into every slide's own title+text header
   as well — title in column 1, text squeezed into column 2 and vertically
   centered against it, which is what turned a title-card's subtitle into a
   narrow one-word-per-line column on the right instead of stacking under
   the title. The per-slide `.pc-header` overrides below (title-card, quiz)
   only reset `display`/spacing, not `grid-template-columns`, precisely
   because this rule was never supposed to reach them in the first place. */
.app > .pc-header {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--wa-line);
}
/* Echoes the current explainer's own title-card heading (set in live.js
   once the first slide mounts) — the wide gap between the wordmark and the
   slide counter was the single most visually prominent empty region on a
   session screen and did nothing. Empty-mode has nothing to echo yet, so
   it's naturally blank there rather than hidden outright. */
.pc-header-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
  font-family: var(--wa-font-heading);
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--wa-ink-faint);
}
.pc-brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--wa-ink); /* currentColor source for the inline mark/wordmark SVGs */
  font: inherit;
  cursor: pointer;
}
.pc-brand-mark { width: 1.5rem; height: 1.5rem; flex: none; }
.pc-brand-wordmark { height: 0.85rem; width: auto; }
.pc-slide-counter {
  color: var(--wa-ink-faint);
  font-family: var(--wa-font-heading-light);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
/* --- Live shell (data-mode). demo/index.html has no data-mode and keeps
   the 60rem sample-player column above. --- */

.app[data-mode] {
  max-width: none;
  margin: 0;
  min-height: 100dvh;
  gap: 0.75rem;
}
.app[data-mode="empty"] {
  padding: 1.25rem 1.5rem 3rem;
  grid-template-rows: auto auto 1fr;
}
.app[data-mode="empty"] .stage-area,
.app[data-mode="empty"] .controls,
.app[data-mode="empty"] .followups-area,
.app[data-mode="empty"] .transcript-area {
  display: none;
}
.app[data-mode="empty"] .prompt-section { order: 2; }
.app[data-mode="empty"] .history-area { order: 3; }
.app[data-mode="session"] {
  height: 100dvh;
  padding: 0.4rem 0.75rem 0.5rem;
  overflow: hidden;
  grid-template-rows: auto minmax(0, 1fr) auto auto auto;
  gap: 0.55rem;
}
/* The docked composer is a distinct functional zone from transport/
   follow-ups above it — DESIGN.md calls for rhythm "loose between stage and
   chrome," but chrome-to-chrome spacing here was as tight as inside a single
   control, so the whole bottom third read as one undifferentiated stack. A
   hairline separates it instead of relying on gap alone. */
.app[data-mode="session"] .prompt-section {
  padding-top: 0.55rem;
  border-top: 1px solid var(--wa-line);
}
.app[data-mode="session"] .pc-header {
  padding-bottom: 0.5rem;
}
.app[data-mode="session"] .stage-area { order: 2; }
.app[data-mode="session"] .controls { order: 3; }
.app[data-mode="session"] .followups-area { order: 4; }
.app[data-mode="session"] .prompt-section { order: 5; }
.app[data-mode="session"] .history-area { display: none; }

/* --- GitHub browse view -------------------------------------------------
   A third top-level view beside the composer and the stage. It owns the
   viewport the way session mode does -- the panes scroll internally, the
   page itself does not -- and it keeps the docked composer so a question is
   always one click away from a repository. */
.app[data-mode="github"] {
  height: 100dvh;
  padding: 0.4rem 0.75rem 0.5rem;
  overflow: hidden;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 0.55rem;
}
.app[data-mode="github"] .stage-area,
.app[data-mode="github"] .controls,
.app[data-mode="github"] .followups-area,
.app[data-mode="github"] .transcript-area,
.app[data-mode="github"] .history-area,
.app[data-mode="github"] .empty-hero,
.app[data-mode="github"] .starter-prompts {
  display: none;
}
.app[data-mode="github"] .github-area { order: 2; min-height: 0; }
.app[data-mode="github"] .prompt-section {
  order: 3;
  padding-top: 0.55rem;
  border-top: 1px solid var(--wa-line);
}
.app[data-mode="github"] .pc-header { padding-bottom: 0.5rem; }
.app:not([data-mode="github"]) .github-area { display: none; }

/* The header toggle reads as pressed while browsing, the same quiet
   treatment other toggles get -- never the playback amber. */
#github-toggle[aria-pressed="true"] {
  background: var(--wa-stage-raised);
  border-color: var(--wa-line);
  color: var(--wa-ink);
}
.app[data-mode="session"] .stage-area {
  min-height: 0;
  container-type: size;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
/* `is-cursor-idle` is toggled from live.js after the mouse sits still on
   the stage for a moment -- the stage is a narrated visual, not a control
   surface, so a stationary arrow left over it is just clutter. */
.app[data-mode="session"] .stage-area.is-cursor-idle {
  cursor: none;
}
.app[data-mode="session"] .stage-frame {
  min-height: 0;
  width: 100%;
  flex: 1 1 auto;
}
.app[data-mode="session"] .stage-area:has(.pc-animation) .stage-frame {
  flex: 0 1 auto;
  width: min(100%, calc((100cqh - 2.25rem) * 16 / 9));
  max-height: calc(100cqh - 2.25rem);
  aspect-ratio: 16 / 9;
}
.app[data-mode="session"] .stage {
  min-height: 0;
  height: 100%;
  padding: 0;
  /* `background-color`, not the `background` shorthand: the shorthand
     resets every other background sub-property to its initial value,
     which silently wiped out the title-card vignette's `background-image`
     below (that rule's higher specificity wins the property, but only for
     properties this rule actually still sets). */
  background-color: var(--wa-stage);
  border: 0;
}
.app[data-mode="session"] .stage > .pc-slide {
  height: 100%;
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}
/* A text-only slide (no `type`, no `layout` -- render.js emits header-only,
   no second child at all) has nothing to put in that second `1fr` row, so
   the header row (`auto`) stays pinned at the top with a huge empty void
   below it -- title small and stranded, most of the screen dead black.
   One row instead, centering the header as a single block, matching how
   this same slide shape already centers itself outside session mode (see
   the .stage:has(...) title-card rule further down). */
.app[data-mode="session"] .stage > .pc-slide:not([data-item-type]):not([data-layout]) {
  grid-template-rows: 1fr;
  place-items: center;
}
.app[data-mode="session"] .stage > .pc-slide[data-item-type="animation"] {
  display: grid;
  place-items: center;
  grid-template-rows: minmax(0, 1fr);
}
.app[data-mode="session"] .pc-animation {
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  min-height: 0;
}
/* Same overflow trap as .pc-animation above: `.pc-image`'s own
   `aspect-ratio` (base rule) plus a CSS Grid item's default `min-height:
   auto` (its intrinsic/aspect-ratio-derived size, not the `minmax(0, 1fr)`
   track's own 0 minimum) lets the placeholder/photo push taller than the
   `.pc-slide` row actually has room for -- in session mode, where the whole
   app shell is a fixed `100dvh` grid with `overflow: hidden`, that pushed
   the transport controls (including the scrubber) below the visible
   viewport instead of just clipping the image. Capping height here, not
   width, keeps the image at its full slide width and only trims how tall
   it's allowed to get.  */
.app[data-mode="session"] .pc-image {
  min-height: 0;
  max-height: 100%;
  /* The base rule's `max-width: 40rem` was sized for the 60rem `.app`
     column outside session mode -- session mode drops that column cap
     entirely (see `.app[data-mode]` above), so on a wide screen the photo
     stayed pinned to 40rem/640px while most of the stage sat empty, the
     same problem `.pc-diagram` had. `92cqw` lets it grow with the actual
     stage width instead. */
  max-width: min(100%, 92cqw);
}
.app[data-mode="session"] .pc-image-media {
  max-width: 100%;
  /* Cap against the stage-area, not the window: a viewport-relative
     height routinely overflowed the rounded stage and covered karaoke.
     Was `min(62cqh, 26rem)` -- the 26rem/416px ceiling was reached long
     before 62cqh on most screens, so the photo never grew past that no
     matter how much taller the stage had room for. Raised alongside
     `.pc-image`'s own width cap above. */
  max-height: min(78cqh, 46rem);
}
.app[data-mode="session"] .pc-diagram {
  align-self: center;
  justify-self: center;
  /* Was a flat `min(100%, 52rem)`: that fixed 52rem ceiling applied no
     matter how wide the session shell actually was (session mode removes
     `.app`'s 60rem cap entirely -- see `.app[data-mode]` above), so a wide,
     node-heavy flowchart got squeezed into the same ~832px box as a simple
     three-node one, shrinking every node/label in it to the point of being
     unreadable while most of the stage sat empty on either side. `92cqw`
     lets the box grow with the actual stage width instead of a value
     unrelated to it.

     `max-width` has to be raised here too, and this is the bug that made
     that earlier fix invisible: the base `.pc-diagram` rule near the end of
     this file still carries `max-width: min(100%, 52rem)` as its safety net
     for unbounded host pages. This override only replaced `width`, so every
     session diagram was still clamped to that same ~832px — a wide
     flowchart sat in a small frame in the middle of an otherwise empty
     stage no matter how much room the stage had. */
  width: min(100%, 92cqw);
  max-width: min(100%, 92cqw);
  /* Was `min(68cqh, 100%)`: a top-down flowchart is inherently taller than
     it is wide, and `.pc-diagram svg` below fills width first -- so the
     SAME available-width box makes a TD diagram want a lot more height than
     a normal 16:9 visual ever needs, and the previous cap was tight enough
     that `preserveAspectRatio` was shrinking the whole diagram down to fit
     the height, leaving wide empty margins either side even though the
     frame was already using its full width. Raised so a tall diagram
     actually gets to use the vertical room `.stage-area`'s query container
     has, instead of being squeezed narrower than its own frame. */
  max-height: min(88cqh, 100%);
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}
.app[data-mode="session"] .pc-diagram svg {
  /* `width: auto` kept the SVG at whatever small intrinsic pixel size the
     Mermaid renderer gave it (its own `width`/`height` attributes), instead
     of scaling up to use the much larger `.pc-diagram` frame around it --
     a five-node flowchart could render at a few hundred px in a frame with
     room for twice that. `width: 100%` (the standard responsive-SVG
     pattern) fills the frame instead; the SVG's own `viewBox` +
     `preserveAspectRatio` (default `xMidYMid meet`) still scale the actual
     diagram content proportionally within that box, so `max-height`
     clamping a very tall diagram's computed height letterboxes it rather
     than distorting it. Raised alongside `.pc-diagram`'s own cap above, for
     the same reason -- a tight height cap was the actual bottleneck once
     width was already filled, not the width rule itself. */
  max-width: 100%;
  max-height: min(85cqh, 40rem);
  width: 100%;
  height: auto;
}

/* --- Composer: one field, submit inside, no resize grip --- */

.prompt-section {
  width: 100%;
  max-width: 40rem;
  margin: 0 auto;
}
.app[data-mode="empty"] .prompt-section {
  align-self: center;
  max-width: 56rem;
}
.app[data-mode="session"] .prompt-section {
  max-width: none;
}
/* The empty-mode landing gets a real headline above the composer — a bare
   input floating on black had no hierarchy at all. Hidden in session mode
   (the docked one-line composer has no room, and the question has already
   been asked). */
.empty-hero-title {
  margin: 0 0 1.1rem;
  text-align: center;
  font-family: var(--wa-font-heading);
  font-size: clamp(1.4rem, 3.4vw, 1.9rem);
  font-weight: 700;
  color: var(--wa-ink);
  text-wrap: balance;
}
.app[data-mode="session"] .empty-hero-title {
  display: none;
}
.prompt-form {
  margin: 0;
}
/* The level select only ever affects the NEXT question (read at submit
   time in live.js — it has no effect on whatever is already playing), so
   it belongs inside the composer it modifies, not floating in its own row
   above/beside it disconnected from any control it visually relates to.
   Its text label is visually hidden (same technique as `.speed-label`
   below) — a quiet chip reading "Intermediate" inside the input row is
   self-explanatory the way "Level: Intermediate" as a standalone label
   was not. */
.level-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}
.level select {
  appearance: none;
  align-self: center;
  background: var(--wa-stage-raised);
  color: var(--wa-ink-faint);
  border: 1px solid var(--wa-line);
  border-radius: 999px;
  padding: 0.3rem 0.7rem;
  font: inherit;
  font-family: var(--wa-font-body);
  font-size: 0.78rem;
  cursor: pointer;
}
.level select:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 2px;
}
.prompt-field {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: end;
  gap: 0.5rem;
  padding: 0.75rem 0.75rem 0.75rem 1.1rem;
  background: var(--wa-stage-sunken);
  border: 1px solid var(--wa-line);
  border-radius: 22px;
}
.prompt-field:focus-within {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 2px;
}
.prompt-input {
  width: 100%;
  min-height: 5.5rem;
  max-height: 8rem;
  resize: none;
  background: transparent;
  color: var(--wa-ink);
  border: none;
  font: inherit;
  line-height: 1.4;
}
.app[data-mode="session"] .prompt-field {
  padding: 0.35rem 0.4rem 0.35rem 0.9rem;
  border-radius: 999px;
}
.app[data-mode="session"] .prompt-input {
  min-height: 2.15rem;
  max-height: 2.15rem;
}
.app[data-mode="session"] .prompt-submit {
  padding: 0.4rem 0.85rem;
}
.app[data-mode="session"] .controls {
  min-height: 2.25rem;
}
.prompt-input:focus {
  outline: none;
}
.prompt-input::placeholder {
  color: var(--wa-ink-faint);
}
/* The submit button is the composer's one action — it carries the accent as
   a confident solid fill (same "primary CTA" treatment as `.transport-toggle`),
   not the quiet outline a chip or secondary control gets. */
.prompt-submit {
  background: var(--wa-now);
  color: var(--wa-stage);
  border: none;
  border-radius: 999px;
  padding: 0.55rem 1.15rem;
  font: inherit;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition-property: transform, opacity, box-shadow;
  transition-duration: 140ms;
  transition-timing-function: var(--ease-out);
}
.prompt-submit:disabled {
  background: var(--wa-stage-raised);
  color: var(--wa-ink-faint);
  cursor: default;
}
.prompt-submit:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 2px;
}
@media (hover: hover) and (pointer: fine) {
  .prompt-submit:not(:disabled):hover {
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--wa-now) 22%, transparent);
  }
  .prompt-submit:not(:disabled):active {
    transform: scale(0.96);
  }
}

.history-area {
  width: 100%;
  max-width: 56rem;
  margin: 1.35rem auto 0;
}
.history-heading {
  margin: 0 0 0.75rem;
  padding: 0 0.15rem;
  font-family: var(--wa-font-heading);
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--wa-ink-faint);
}
.history-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  align-items: stretch;
}
.history-list-item {
  list-style: none;
  min-width: 0;
}
.history-list-status {
  grid-column: 1 / -1;
}
.header-action:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 2px;
}
/* A Recent entry is a card: title, optional prompt, then a meta row.
   The old single-line title+timestamp dump read as a log, not a library. */
.history-item {
  appearance: none;
  display: grid;
  grid-template-columns: 2.35rem 1fr;
  grid-template-rows: auto auto 1fr;
  column-gap: 0.75rem;
  row-gap: 0.28rem;
  align-self: stretch;
  width: 100%;
  height: 100%;
  min-width: 0;
  text-align: left;
  background: var(--wa-stage-raised);
  color: var(--wa-ink);
  border: 1px solid color-mix(in srgb, var(--wa-line) 55%, transparent);
  border-radius: 12px;
  padding: 0.95rem 1.05rem 0.8rem;
  font: inherit;
  cursor: pointer;
  box-shadow: 0 1px 0 color-mix(in srgb, var(--wa-ink) 4%, transparent);
  transition-property: background, border-color, box-shadow, transform;
  transition-duration: 140ms;
  transition-timing-function: var(--ease-out);
  animation: historyArrive 320ms var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 70ms);
}
@keyframes historyArrive {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.history-item:hover {
  background: color-mix(in srgb, var(--wa-stage-raised) 82%, var(--wa-ink) 4%);
  border-color: var(--wa-line);
  box-shadow: 0 8px 20px color-mix(in srgb, var(--wa-stage) 55%, transparent);
  transform: translateY(-1px);
}
.history-item:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 2px;
}
.history-item:not(:disabled):active {
  transform: scale(0.96);
}
.history-item[data-current="true"] {
  border-color: color-mix(in srgb, var(--wa-now) 45%, var(--wa-line));
  box-shadow: inset 3px 0 0 var(--wa-now);
}
.wa-icon,
.transport-icon,
.transport-btn [data-icon] {
  display: block;
  flex: none;
  background-color: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}
.wa-icon {
  width: 0.85rem;
  height: 0.85rem;
}
.history-item-mark {
  grid-column: 1;
  grid-row: 1 / -1;
  align-self: start;
  display: grid;
  place-items: center;
  width: 2.35rem;
  height: 2.35rem;
  border-radius: 8px;
  background: var(--wa-stage-sunken);
  color: var(--wa-ink-muted);
}
.history-item-mark .wa-icon {
  width: 1.2rem;
  height: 1.2rem;
}
.history-item-title {
  grid-column: 2;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  font-family: var(--wa-font-body);
  font-size: 1.02rem;
  font-weight: 650;
  letter-spacing: -0.015em;
  line-height: 1.28;
  text-wrap: balance;
}
.history-item-detail {
  grid-column: 2;
  color: var(--wa-ink-faint);
  font-size: 0.82rem;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  text-wrap: pretty;
}
.history-item-meta {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0;
  margin-top: auto;
  padding-top: 0.35rem;
  color: var(--wa-ink-faint);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}
.history-item-fact {
  display: inline-flex;
  align-items: center;
  gap: 0.28rem;
}

.history-item-fact + .history-item-fact::before {
  content: "·";
  margin: 0 0.5rem 0 0.35rem;
  color: color-mix(in srgb, var(--wa-ink-faint) 70%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .history-item {
    animation: none;
  }
}

.stage-frame {
  position: relative;
}
.stage {
  min-height: 26rem;
  padding: 2rem;
  background: var(--wa-stage-raised);
  border: 1px solid var(--wa-line);
  border-radius: 20px;
  position: relative;
  overflow: hidden;
}
.stage[data-arrive] > .pc-slide {
  animation: slideArrive 280ms var(--ease-out) both;
}

/* Title-card slides (no visual, no layout -- render.js emits header-only):
   centered on a quiet panel vignette, never a colored wash — --wa-diff-add
   is a status-light meaning now and does not decorate.

   The vignette below used to have a second background-image layer mixing
   in that same accent at low opacity — written as a bare color-mix(),
   which is not a valid <image> value on its own (it needs wrapping in a
   gradient to count as one), so the WHOLE declaration was invalid and the
   browser dropped it entirely rather than applying it in part. This
   vignette never actually rendered, in any mode, until that layer was
   removed — which also brings the code in line with the "never a colored
   wash" sentence above, which that stray layer had been quietly
   contradicting. */
.stage:has(> .pc-slide:not([data-item-type]):not([data-layout])) {
  display: grid;
  place-items: center;
  background-image: radial-gradient(
    120% 100% at 50% -10%,
    var(--wa-stage-raised) 0%,
    var(--wa-stage-sunken) 65%
  );
  background-repeat: no-repeat;
  background-size: 100% 100%;
  background-position: 0 0;
}
.pc-slide:not([data-item-type]):not([data-layout]) {
  text-align: center;
}
.pc-slide:not([data-item-type]):not([data-layout]) > .pc-header {
  display: grid;
  gap: 0.75rem;
  justify-items: center;
  max-width: 34rem;
  margin: 0 auto;
}
.pc-slide:not([data-item-type]):not([data-layout]) .pc-title {
  font-size: clamp(1.6rem, 4vw, 2.35rem);
  margin: 0;
}
.pc-slide:not([data-item-type]):not([data-layout]) .pc-text {
  margin: 0;
  max-width: 32rem;
}
@keyframes slideArrive {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.stage-status {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin: 0;
  padding: 2rem;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition-property: opacity;
  transition-duration: 220ms;
  transition-timing-function: var(--ease-out);
}
.stage-frame[data-pending] .stage-status {
  opacity: 1;
}
.stage-status-text {
  margin: 0;
  max-width: 26rem;
  text-wrap: balance;
  font-family: var(--wa-font-heading);
  font-weight: 400;
  font-size: 1rem;
  letter-spacing: 0.01em;
  color: var(--wa-ink-faint);
}
.stage-frame[data-pending="error"] .stage-status-text {
  color: var(--wa-alert);
}
/* The thinking orb (demo/thinking-orb.js) reads as motion proving content is
   still being generated -- shown for both the blank "wait" state and the
   "preview" state (intro title already on stage, nothing else moving yet).
   An error is a fixed, final state with nothing left to animate. */
.thinking-orb {
  display: none;
  width: 4.5rem;
  height: 4.5rem;
}
.stage-frame[data-pending="wait"] .thinking-orb,
.stage-frame[data-pending="preview"] .thinking-orb {
  display: block;
}
.stage-frame[data-pending="preview"] .stage-status { justify-content: flex-end; }

.stage:has(.pc-animation) {
  padding: 0;
  overflow: hidden;
}
.pc-slide[data-item-type="animation"] {
  min-height: inherit;
}
.pc-slide[data-item-type="animation"] > .pc-header {
  display: none;
}

/* Quiz slides: the slide itself IS the card (not `.pc-quiz` inside it) so
   the kicker (`.pc-header > .pc-title`, e.g. "Your turn") and the question
   + choices (`.pc-quiz`) share one bordered boundary, matching a reference
   the product asked to hit — a floating rounded card, not the flag-shaped
   entries the rest of the system uses. The same quiet radial vignette the
   title-card already gets stands in for the reference's colored wave: real
   depth behind the card with zero new hue, which is what "never a colored
   wash" (see this file's own header + DESIGN.md's Colors section) actually
   asks for. `.stage:has(...)` centers it in the sample player (`.pc-slide`
   is a plain block there); the session-mode override below cancels the
   `height: 100%` / two-row grid every other item type gets, so the card
   sizes to its content instead of stretching full-height. */
.stage:has(> .pc-slide[data-item-type="quiz"]) {
  display: grid;
  place-items: center;
  background-image: radial-gradient(
    120% 100% at 50% -10%,
    var(--wa-stage-raised) 0%,
    var(--wa-stage-sunken) 65%
  );
  background-repeat: no-repeat;
  background-size: 100% 100%;
  background-position: 0 0;
}
.app[data-mode="session"] .stage > .pc-slide[data-item-type="quiz"] {
  height: auto;
  display: block;
}
.pc-slide[data-item-type="quiz"] {
  width: 100%;
  max-width: 34rem;
  padding: 1.75rem 2rem;
  background: var(--wa-stage-sunken);
  border: 1px solid var(--wa-line);
  border-radius: 22px;
}
.pc-slide[data-item-type="quiz"] > .pc-header {
  /* `.pc-header` is the same class every per-slide header AND the page's
     own top header get (dom.js stamps every `<header>` vnode `pc-header`),
     so its `border-bottom` reads as a real section rule under a full-size
     title elsewhere — under this small kicker inside a card it reads as a
     stray divider, so the quiz card cancels it. */
  border-bottom: none;
  padding-bottom: 0;
  margin: 0 0 1rem;
}
/* The kicker: a named, one-off label ("Your turn"), not a tracked-uppercase
   eyebrow applied to every slide type — see craft-floor's eyebrow-as-grammar
   warning. Neutral ink-faint, not the accent: DESIGN.md's Playhead Rule
   reserves amber for position/primary-action/hazard-stripe, and a kicker
   label is none of those three named jobs. */
.pc-slide[data-item-type="quiz"] .pc-title {
  font-family: var(--wa-font-body);
  font-size: 0.82rem; /* DESIGN.md's Label step */
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--wa-ink-faint);
  margin: 0;
}

/* Order slides share the quiz card treatment: one floating panel for the
   kicker, question, reorderable steps, and check affordance. */
.stage:has(> .pc-slide[data-item-type="order"]) {
  display: grid;
  place-items: center;
  background-image: radial-gradient(
    120% 100% at 50% -10%,
    var(--wa-stage-raised) 0%,
    var(--wa-stage-sunken) 65%
  );
  background-repeat: no-repeat;
  background-size: 100% 100%;
  background-position: 0 0;
}
.app[data-mode="session"] .stage > .pc-slide[data-item-type="order"] {
  height: auto;
  display: block;
}
.pc-slide[data-item-type="order"] {
  width: 100%;
  max-width: 34rem;
  padding: 1.75rem 2rem;
  background: var(--wa-stage-sunken);
  border: 1px solid var(--wa-line);
  border-radius: 22px;
}
.pc-slide[data-item-type="order"] > .pc-header {
  border-bottom: none;
  padding-bottom: 0;
  margin: 0 0 1rem;
}
.pc-slide[data-item-type="order"] .pc-title {
  font-family: var(--wa-font-body);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--wa-ink-faint);
  margin: 0;
}
.pc-order { display: grid; gap: 1.1rem; }
.pc-steps { display: grid; gap: 0.6rem; counter-reset: order-step; }
.pc-step {
  counter-increment: order-step;
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.65rem;
  padding: 0.75rem 0.85rem;
  border: 1px solid var(--wa-line);
  border-radius: 14px;
  background: var(--wa-stage-sunken);
}
.pc-step::before {
  content: counter(order-step);
  display: grid;
  place-items: center;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 999px;
  border: 1px solid var(--wa-line);
  background: color-mix(in srgb, var(--wa-ink) 8%, transparent);
  color: var(--wa-ink-faint);
  font-family: var(--wa-font-body);
  font-size: 0.78rem;
  font-weight: 700;
}
.pc-step[data-state="correct"] {
  border-color: var(--wa-diff-add);
}
.pc-step[data-state="correct"]::before {
  background: var(--wa-diff-add);
  border-color: var(--wa-diff-add);
  color: var(--wa-stage);
}
button.pc-step-up,
button.pc-step-down {
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid var(--wa-line);
  border-radius: 8px;
  background: var(--wa-stage-raised);
  cursor: pointer;
  display: grid;
  place-items: center;
  transition-property: background, border-color, transform;
  transition-duration: 140ms;
  transition-timing-function: var(--ease-out);
}
button.pc-step-up::before,
button.pc-step-down::before {
  font-size: 0.95rem;
  line-height: 1;
  color: var(--wa-ink-muted);
}
button.pc-step-up::before { content: "↑"; }
button.pc-step-down::before { content: "↓"; }
@media (hover: hover) and (pointer: fine) {
  button.pc-step-up:hover,
  button.pc-step-up:focus-visible,
  button.pc-step-down:hover,
  button.pc-step-down:focus-visible {
    border-color: var(--wa-ink-faint);
    background: color-mix(in srgb, var(--wa-stage-raised) 70%, var(--wa-stage-sunken));
  }
  button.pc-step-up:active,
  button.pc-step-down:active {
    transform: scale(0.96);
  }
}

/* Captions sit in the reserved row under the stage so they never cover
   slide content. One treatment for every slide type on the live page. The
   sample player (`demo/index.html`, no `data-mode`) keeps its own in-flow
   caption spacing below. */
.app[data-mode] .stage-area .subtitle-area {
  flex-shrink: 0;
  min-height: 2.6rem;
  padding: 0.15rem 0.25rem 0;
  display: flex;
  justify-content: center;
  pointer-events: none;
}
.app[data-mode] .pc-subtitle {
  flex-wrap: nowrap;
  justify-content: center;
  white-space: nowrap;
}

.pc-title { font-family: var(--wa-font-heading); font-size: 1.35rem; font-weight: 700; margin-bottom: 0.4rem; }
.pc-text { color: var(--wa-ink-faint); margin-bottom: 1.5rem; max-width: 46rem; }

/* Layout groups (`cards`, `ul`, `ol`).

   Two things are fixed here that used to make every layout slide look like
   the same slab of different size. First, the group is measured, not
   stage-wide: on a wide display the old `repeat(auto-fit, minmax(12rem,
   1fr))` let two cards grow to 800px each while three cards on the next
   slide grew to 540px, so the identical markup changed size between slides.
   `--pc-group-measure` caps and centers the group so the block occupies the
   same column on every slide. Second, `render.js` now stamps
   `data-variant` (steps / rows / brief / split / trio / tiles) from the
   content's own shape, and each variant gets its own treatment below.

   `align-self: center` (not the grid default `stretch`): in session mode
   `.pc-slide`'s visual row is `minmax(0, 1fr)` so it fills the whole stage
   height — without this, a two-sentence card/list inherits that full
   height and each entry stretches into a mostly-empty box. Centering (not
   `start`) matches how a text-only title slide already centers itself. */
.pc-cards,
.pc-list {
  --pc-group-measure: 54rem;
  display: grid;
  gap: 0.75rem;
  align-self: center;
  justify-self: start;
  width: min(100%, var(--pc-group-measure));
}
.pc-list {
  --pc-group-measure: 46rem;
  gap: 0.5rem;
}
.pc-entry {
  padding: 1rem 1.1rem;
  border: 1px solid var(--wa-line);
  border-radius: 16px;
  background: var(--wa-stage-sunken);
}
.pc-entry-title { font-family: var(--wa-font-heading); font-weight: 700; font-size: 0.95rem; margin-bottom: 0.3rem; }
.pc-entry-text { color: var(--wa-ink-faint); font-size: 0.94rem; }

/* Cards: equal columns and `grid-auto-rows: 1fr` so peer claims read as
   peers — a card whose sentence wraps must not become the tall one. */
.pc-cards[data-variant="split"],
.pc-cards[data-variant="trio"],
.pc-cards[data-variant="tiles"] {
  grid-auto-rows: 1fr;
}
.pc-cards[data-variant="split"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.pc-cards[data-variant="trio"] { --pc-group-measure: 58rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pc-cards[data-variant="tiles"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* A two-way contrast is the one card shape that carries a stance: the two
   sides face each other across the gap, so each gets its outer corner
   rounded harder and its inner edge kept quiet. */
.pc-cards[data-variant="split"] { gap: 1rem; }
.pc-cards[data-variant="split"] .pc-entry { padding: 1.25rem 1.35rem; }
.pc-cards[data-variant="split"] .pc-entry:first-child { border-radius: 24px 12px 12px 24px; }
.pc-cards[data-variant="split"] .pc-entry:last-child { border-radius: 12px 24px 24px 12px; }
.pc-cards[data-variant="split"] .pc-entry-title { font-size: 1.05rem; }

/* Title-only cards carry a short label each, so they are set as rounded
   plates sized to their own text rather than stretched into a grid of
   mostly-empty boxes. */
.pc-cards[data-variant="brief"] {
  --pc-group-measure: 50rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.pc-cards[data-variant="brief"] .pc-entry {
  padding: 0.7rem 1.3rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--wa-stage-raised) 88%, transparent);
}
.pc-cards[data-variant="brief"] .pc-entry-title { margin-bottom: 0; font-size: 1.05rem; }

/* An ordered list is a procedure: a numbered marker column and a hairline
   running between the markers, so the reader sees one track rather than
   three unrelated boxes. `data-ordinal` is set by render.js, which also
   strips a number the author wrote into the title so it is not printed
   twice. */
.pc-list[data-variant="steps"] { gap: 0.55rem; }
.pc-list[data-variant="steps"] .pc-entry {
  position: relative;
  padding-left: 3.5rem;
  border-radius: 16px;
}
.pc-list[data-variant="steps"] .pc-entry[data-ordinal]::before {
  content: attr(data-ordinal);
  position: absolute;
  top: 0.95rem;
  left: 1rem;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  box-sizing: border-box;
  border: 1px solid var(--wa-line);
  border-radius: 50%;
  background: var(--wa-stage-raised);
  font-family: var(--wa-font-heading);
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--wa-ink);
}
.pc-list[data-variant="steps"] .pc-entry:not(:last-child)::after {
  content: "";
  position: absolute;
  top: 2.7rem;
  bottom: -0.55rem;
  left: 1.87rem;
  width: 1px;
  background: var(--wa-line);
}

/* An unordered list is a set, not a stack of cards: quiet rows sharing one
   left rule. This is the one place the old left-accent shape was right. */
.pc-list[data-variant="rows"] .pc-entry {
  border-left: 2px solid var(--wa-line);
  border-radius: 0 14px 14px 0;
  background: color-mix(in srgb, var(--wa-stage-sunken) 70%, transparent);
}

@media (max-width: 44rem) {
  .pc-cards[data-variant="split"],
  .pc-cards[data-variant="trio"],
  .pc-cards[data-variant="tiles"] {
    grid-template-columns: minmax(0, 1fr);
    grid-auto-rows: auto;
  }
  .pc-cards[data-variant="split"] .pc-entry:first-child,
  .pc-cards[data-variant="split"] .pc-entry:last-child {
    border-radius: 16px;
  }
}

.pc-code, .pc-pane {
  font-family: var(--wa-font-mono);
  font-size: 0.9rem;
  background: var(--wa-stage-sunken);
  border: 1px solid var(--wa-line);
  border-radius: 14px;
  padding: 0.9rem 0;
  counter-reset: line;
}
.pc-line {
  display: block;
  padding: 0 1rem 0 3.2rem;
  position: relative;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  border-left: 3px solid transparent;
}
.pc-line::before {
  content: attr(data-line);
  position: absolute;
  left: 0;
  width: 2.4rem;
  text-align: right;
  color: var(--wa-ink-faint);
}
/* Diff coloring: a colored left edge plus a low-opacity fill of the same
   hue — never hue alone. `changed` reads by PANE: the removal hue in
   `before`, the addition hue in `after` (design.js's `side` prop, emitted by
   dom.js as `data-side` on the `.pc-pane` ancestor — see render.js's
   `renderDiff`), so hue is never the only cue there either. Panel status
   lights: green = nominal/added, red = fault/removed. */
.pc-line[data-status="added"] {
  border-left-color: var(--wa-diff-add);
  background: color-mix(in srgb, var(--wa-diff-add) 15%, transparent);
}
.pc-line[data-status="removed"] {
  border-left-color: var(--wa-diff-del);
  background: color-mix(in srgb, var(--wa-diff-del) 15%, transparent);
}
.pc-pane[data-side="before"] .pc-line[data-status="changed"] {
  border-left-color: var(--wa-diff-del);
  background: color-mix(in srgb, var(--wa-diff-del) 15%, transparent);
}
.pc-pane[data-side="after"] .pc-line[data-status="changed"] {
  border-left-color: var(--wa-diff-add);
  background: color-mix(in srgb, var(--wa-diff-add) 15%, transparent);
}
.pc-line[data-status="empty"] { opacity: 0.25; }

/* Word-level diff spans: a stronger fill of the pane's own hue — never
   --wa-now. Distinct from the karaoke `.pc-word[data-state=...]` rules
   below: this is `data-status`, karaoke is `data-state`. */
.pc-pane[data-side="before"] .pc-word[data-status="changed"] {
  background: color-mix(in srgb, var(--wa-diff-del) 35%, transparent);
  border-radius: 2px;
}
.pc-pane[data-side="after"] .pc-word[data-status="changed"] {
  background: color-mix(in srgb, var(--wa-diff-add) 35%, transparent);
  border-radius: 2px;
}

.pc-diff { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }

@media (max-width: 40rem) {
  .app { padding: 1rem; }
  .stage { padding: 1rem; }
  .stage:has(.pc-animation) { padding: 0; }
  .pc-diff { grid-template-columns: 1fr; }
  .history-list { grid-template-columns: 1fr; }
  .app[data-mode="empty"] .prompt-section,
  .history-area { max-width: none; }
}

/* Syntax theme: derived from the tokens, never an off-the-shelf theme —
   each role targets its own WCAG contrast band on --wa-stage-sunken (this
   surface), NOT a flat floor, so keywords read as the most prominent token
   and comments/punctuation genuinely recede — see brand/tokens.css's own
   header for the band table. None of them is --wa-now. `comment` also gets
   italic as a second, non-color cue. */
.pc-token[data-kind="keyword"] { color: var(--wa-syntax-keyword); }
.pc-token[data-kind="string"] { color: var(--wa-syntax-string); }
.pc-token[data-kind="number"] { color: var(--wa-syntax-number); }
.pc-token[data-kind="comment"] { color: var(--wa-syntax-comment); font-style: italic; }
.pc-token[data-kind="function"] { color: var(--wa-syntax-function); }
.pc-token[data-kind="punctuation"] { color: var(--wa-syntax-punctuation); }

/* Every other content type (code, entries, quiz choices) gets a bordered,
   tokenized container even inside the borderless session-mode stage — the
   diagram was the one type presented as bare SVG floating directly on the
   stage background. Same frame language, same radius. */
/* `max-width: min(100%, 52rem)`, not a bare `100%`: an SVG's content
   (including its text, since Mermaid renders real `<text>` inside the
   viewBox, not DOM text) scales uniformly with however wide the element
   itself is stretched -- so a `.pc-diagram` with no cap of its own, sitting
   in a host page/layout that never bounds its width (`demo/recipes.html`'s
   gallery does not), stretched to fill hundreds of extra pixels, and every
   node and label in it grew proportionally huge along with it. Session mode
   already had this exact cap on its own override further up this file;
   this is that same safety net for every OTHER context a diagram can render
   in, so it can never depend on an ancestor happening to bound it. */
.pc-diagram {
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  width: 100%;
  max-width: min(100%, 52rem);
  max-height: 100%;
  min-height: 0;
  margin-inline: auto;
  overflow: hidden;
  background: var(--wa-stage-sunken);
  border: 1px solid var(--wa-line);
  border-radius: 14px;
  padding: 1.25rem;
}
.pc-diagram svg {
  display: block;
  max-width: 100%;
  max-height: 100%;
  /* Same reasoning as the session-mode override above: `width: auto` kept
     the diagram at the renderer's own small intrinsic pixel size instead of
     using the frame's available space. */
  width: 100%;
  height: auto;
}
.pc-adapter-fallback {
  white-space: pre;
  font-family: var(--wa-font-mono);
  font-size: 0.85rem;
  color: var(--wa-ink-faint);
}
.pc-math { text-align: center; font-size: 1.5rem; padding: 2rem 0; overflow-x: auto; }

.pc-quiz { display: grid; gap: 1.1rem; }
.pc-question { font-family: var(--wa-font-heading); font-weight: 700; font-size: 1.35rem; line-height: 1.3; } /* DESIGN.md's Headline step */
/* Enumerated by a CSS counter, not by threading an index through render.js
   — the letter badge is pure presentation, not new document data. */
.pc-choices { display: grid; gap: 0.6rem; counter-reset: choice; }
.pc-choice {
  font: inherit;
  color: inherit;
  text-align: start;
  width: 100%;
  counter-increment: choice;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.85rem 1.1rem;
  border: 1px solid var(--wa-line);
  border-radius: 14px;
  cursor: pointer;
  background: var(--wa-stage-sunken);
  transition-property: background, border-color;
  transition-duration: 160ms;
  transition-timing-function: var(--ease-out);
}
/* The letter badge: a ring, not a filled chip in a system color — it reads
   as an index, not a second status light. */
.pc-choice::before {
  content: counter(choice, upper-alpha);
  flex: none;
  display: grid;
  place-items: center;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 999px;
  border: 1px solid var(--wa-line);
  background: color-mix(in srgb, var(--wa-ink) 8%, transparent);
  color: var(--wa-ink-faint);
  font-family: var(--wa-font-body);
  font-size: 0.78rem;
  font-weight: 700;
}
/* `--wa-stage-raised` is documented in tokens.css as the "hover fills"
   surface — unused here before, which is why the choices had no felt
   affordance (recessed --wa-stage-sunken against a --wa-stage-raised panel,
   only a 1px hairline border separating the two). */
.pc-choice:hover,
.pc-choice:focus-visible {
  background: var(--wa-stage-raised);
  border-color: var(--wa-ink-faint);
}
.pc-choice:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 2px;
}
/* Quiz correct/incorrect borrow --wa-diff-add / --wa-alert (panel status
   lights), never --wa-now — carried on the full border plus the badge fill,
   a second cue beyond hue alone (the badge also swaps its ring for a solid
   fill, not color-only). */
.pc-choice[data-state="correct"] { border-color: var(--wa-diff-add); }
.pc-choice[data-state="correct"]::before { background: var(--wa-diff-add); border-color: var(--wa-diff-add); color: var(--wa-stage); }
.pc-choice[data-state="incorrect"] { border-color: var(--wa-alert); opacity: 0.6; }
.pc-choice[data-state="incorrect"]::before { background: var(--wa-alert); border-color: var(--wa-alert); color: var(--wa-stage); }
.pc-choice:disabled {
  cursor: default;
  opacity: 0.85;
}
.pc-choice:disabled:hover,
.pc-choice:disabled:focus-visible {
  background: inherit;
  border-color: var(--wa-line);
}
button.pc-continue,
button.pc-check,
button.pc-step-up,
button.pc-step-down {
  font: inherit;
  color: inherit;
}
button.pc-check {
  justify-self: start;
  padding: 0.55rem 1.1rem;
  border: 1px solid var(--wa-line);
  border-radius: 999px;
  background: var(--wa-stage-sunken);
  cursor: pointer;
  transition-property: background, border-color, transform;
  transition-duration: 140ms;
  transition-timing-function: var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  button.pc-check:hover,
  button.pc-check:focus-visible {
    border-color: var(--wa-ink-faint);
    background: color-mix(in srgb, var(--wa-stage-sunken) 70%, var(--wa-stage-raised));
  }
  button.pc-check:active {
    transform: scale(0.98);
  }
}
button.pc-continue {
  justify-self: start;
  padding: 0.6rem 1.25rem;
  border: 1px solid color-mix(in srgb, var(--wa-now) 45%, var(--wa-line));
  border-radius: 999px;
  background: color-mix(in srgb, var(--wa-now) 14%, var(--wa-stage-sunken));
  cursor: pointer;
  transition-property: background, border-color, transform;
  transition-duration: 140ms;
  transition-timing-function: var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  button.pc-continue:hover,
  button.pc-continue:focus-visible {
    background: color-mix(in srgb, var(--wa-now) 22%, var(--wa-stage-sunken));
    border-color: color-mix(in srgb, var(--wa-now) 55%, var(--wa-line));
  }
  button.pc-continue:active {
    transform: scale(0.98);
  }
}
.pc-feedback { color: var(--wa-ink-faint); }

/* Prize medal: a disc + star + two short ribbon tails. Gold borrows the
   amber pair because that is the only gold-family metal in the token set;
   this is the awarded prize, not a playhead or a badge. Silver and bronze
   are mixed from panel neutrals / quiet amber + alert so they stay on
   tokens. No text label — the metal is the information; aria-label names
   it. Hidden when the quiz completes without a medal. */
.pc-medal {
  --medal: var(--wa-ink-muted);
  --medal-deep: var(--wa-line);
  --medal-shine: var(--wa-ink);
  --medal-rim: var(--wa-line);
  --medal-star: var(--wa-stage);
  --medal-ribbon: var(--wa-line);
  position: relative;
  display: grid;
  place-items: center;
  justify-self: center;
  width: 4.75rem;
  height: 5.35rem;
  margin-block-start: 0.15rem;
  animation: pc-medal-enter 300ms cubic-bezier(0.2, 0, 0, 1);
}
.pc-medal:not([data-medal]) { display: none; }
.pc-medal[data-medal="gold"] {
  --medal: var(--wa-now);
  --medal-deep: var(--wa-now-quiet);
  --medal-shine: color-mix(in srgb, var(--wa-ink) 62%, var(--wa-now));
  --medal-rim: color-mix(in srgb, var(--wa-now-quiet) 70%, var(--wa-stage));
  --medal-star: var(--wa-stage);
  --medal-ribbon: var(--wa-now-quiet);
}
.pc-medal[data-medal="silver"] {
  --medal: var(--wa-ink-muted);
  --medal-deep: var(--wa-line);
  --medal-shine: var(--wa-ink);
  --medal-rim: color-mix(in srgb, var(--wa-line) 70%, var(--wa-ink-muted));
  --medal-star: var(--wa-stage);
  --medal-ribbon: color-mix(in srgb, var(--wa-line) 80%, var(--wa-ink-faint));
}
.pc-medal[data-medal="bronze"] {
  --medal: color-mix(in srgb, color-mix(in srgb, var(--wa-now-quiet) 58%, var(--wa-alert)) 70%, var(--wa-stage-raised));
  --medal-deep: color-mix(in srgb, var(--wa-now-quiet) 38%, var(--wa-stage));
  --medal-shine: color-mix(in srgb, var(--wa-ink-muted) 28%, var(--wa-now-quiet));
  --medal-rim: color-mix(in srgb, var(--wa-now-quiet) 36%, var(--wa-stage));
  --medal-star: var(--wa-stage);
  --medal-ribbon: color-mix(in srgb, color-mix(in srgb, var(--wa-now-quiet) 50%, var(--wa-alert)) 65%, var(--wa-stage));
}
.pc-medal::before,
.pc-medal::after {
  content: "";
  position: absolute;
  top: 3.2rem;
  width: 0.72rem;
  height: 1.4rem;
  background: var(--medal-ribbon);
  z-index: 0;
}
.pc-medal::before {
  left: 1.4rem;
  transform: skewX(16deg);
  border-radius: 0 0 2px 2px;
}
.pc-medal::after {
  right: 1.4rem;
  transform: skewX(-16deg);
  border-radius: 0 0 2px 2px;
}
.pc-medal-face {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 3.65rem;
  height: 3.65rem;
  border-radius: 999px;
  border: 3px solid var(--medal-rim);
  background:
    radial-gradient(circle at 32% 26%, var(--medal-shine) 0 14%, transparent 38%),
    radial-gradient(circle at 50% 58%, var(--medal) 0 56%, var(--medal-deep) 100%);
  color: var(--medal-star);
}
.pc-medal-face::before {
  content: "";
  position: absolute;
  inset: 0.38rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--medal-shine) 40%, transparent);
}
.pc-medal-face::after {
  content: "★";
  font-size: 1.55rem;
  line-height: 1;
  /* The star glyph sits optically high in the disc; nudge it down. */
  transform: translateY(0.06em);
}
@keyframes pc-medal-enter {
  from { opacity: 0; transform: scale(0.25); }
  to { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .pc-medal { animation: none; }
}

/* Width is capped and centered explicitly (`max-width` + `margin-inline:
   auto` + `justify-self`/`align-self: center`) rather than left to the grid
   row's default stretch -- a stretched box whose actual size is then pinned
   down by `aspect-ratio` (the 9:16 case in particular, which used to also
   set `width: auto`) settles against the row's start edge instead of
   centering, which read as "stuck to the left". Capping the width up front
   means every ratio centers the same way and a photo never spans the full
   stage width on a wide screen, which had made it feel oversized next to
   the narration line beneath it. */
.pc-image {
  display: grid;
  place-items: center;
  width: 100%;
  max-width: 40rem;
  min-height: 14rem;
  margin-inline: auto;
  justify-self: center;
  align-self: center;
  border: 1px dashed var(--wa-line);
  border-radius: 16px;
  color: var(--wa-ink-faint);
  text-align: center;
  padding: 1rem;
  position: relative;
  overflow: hidden;
  background: var(--wa-stage-sunken);
}
.pc-image[data-aspect-ratio="16:9"] { aspect-ratio: 16 / 9; }
.pc-image[data-aspect-ratio="4:3"]  { aspect-ratio: 4 / 3; }
.pc-image[data-aspect-ratio="1:1"]  { aspect-ratio: 1 / 1; max-width: 26rem; }
.pc-image[data-aspect-ratio="9:16"] { aspect-ratio: 9 / 16; max-width: 20rem; max-height: 100%; }

/* Resolved: the dashed "nothing here yet" border becomes a solid frame and
   the padding goes away. Deliberately NOT sized off `data-aspect-ratio`
   (render.js stops sending that prop once resolved) -- a resolved image has
   no preset ratio at all, so none of the `[data-aspect-ratio=...]` rules
   above ever match it. Instead the frame shrinks to fit the actual photo
   (`width: auto` overriding the base rule's `width: 100%`) and the photo
   itself is bounded by `max-width`/`max-height` on `.pc-image-media` with
   `width`/`height: auto` -- the standard "fit within a box, keep the real
   proportions, never crop" pattern. A soft shadow lifts the photo off the
   stage background, which the dashed placeholder never needed but a real
   photo card reads as unfinished without. */
.pc-image[data-state="resolved"] {
  display: grid;
  place-items: center;
  width: auto;
  min-height: 0;
  padding: 0;
  border-style: solid;
  border-color: var(--wa-line);
  background: var(--wa-stage-sunken);
  box-shadow: 0 20px 40px -16px color-mix(in srgb, var(--wa-ink) 45%, transparent);
}
.pc-image-media {
  display: block;
  max-width: 100%;
  /* A fixed cap, not a percentage: `.pc-image`'s own height is intrinsic
     (shrink-to-fit) in the resolved state, which is not a definite
     containing-block height a percentage on this child could resolve
     against -- `vh` is viewport-relative instead, so it always resolves,
     session mode or not. */
  max-height: min(60vh, 32rem);
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: inherit;
}
.pc-animation {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  width: 100%;
  /* The scene itself no longer paints a background (it's a pure drawing
     surface now) — this card frame is what a live explainer's
     animation sits inside: padding, a defining border, instead of the
     animation filling the box edge to edge. */
  padding: 1.75rem;
  background: var(--wa-stage-raised);
  border: 1px solid var(--wa-line);
  border-radius: inherit;
  box-sizing: border-box;
  /* Centers the iframe below within the padded area — needed because the
     iframe no longer stretches to 100%/100% (see its own rule): equal
     padding on a 16:9 box does not leave a 14.2:8 (the scene's own
     viewBox) content box, and stretching an off-ratio iframe would letterbox
     inside the SVG with visible gutters (the sandboxed iframe paints opaque
     white there, not transparent — see svg.js's `CARD_BACKGROUND`). */
  display: flex;
  align-items: center;
  justify-content: center;
}
.pc-animation iframe {
  /* Sized to the scene's own aspect ratio (14.2/8, exactly — not the
     card's 16/9) and fit within the padded box, rather than stretched to
     fill it — see `.pc-animation`'s own comment for why. `height:100%` +
     `width:auto` (not the reverse, and not both `100%`): CSS `aspect-ratio`
     only computes a dimension left `auto` — with both width and height
     definite it's ignored entirely, which is exactly the stretch-to-fill
     bug this replaces. The padded box here is wider, relative to its
     height, than 14.2:8, so height is the binding constraint and width is
     the one aspect-ratio must compute (clamped by `max-width` in case a
     future layout flips which side binds). */
  width: auto;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 14.2 / 8;
  opacity: 0;
  transition-property: opacity;
  transition-duration: 240ms;
  transition-timing-function: var(--ease-out);
}
.pc-animation[data-ready] iframe {
  opacity: 1;
}
.pc-animation[data-state="placeholder"],
.pc-animation[data-state="failed"] {
  display: grid;
  place-items: center;
  color: var(--wa-ink-faint);
  text-align: center;
  padding: 1rem;
}
.pc-diagram, .pc-math {
  transition-property: opacity;
  transition-duration: 220ms;
  transition-timing-function: var(--ease-out);
}
.pc-diagram[data-pending],
.pc-math[data-pending] {
  opacity: 0;
}
.pc-notice { color: var(--wa-ink-faint); }

/* --- Pending visual appear ---------------------------------------------
   Unfinished visuals stay on the same surface and fade in. No frost, no
   grey overlay. The playback accent stays off this. */
@media (prefers-reduced-motion: reduce) {
  .stage-status,
  .stage-area,
  .pc-animation iframe,
  .pc-diagram,
  .pc-math,
  .pc-cursor {
    transition-duration: 0s;
  }
  .stage[data-arrive] > .pc-slide {
    animation: none;
  }
  /* The thinking orb's own motion is driven by JS (demo/thinking-orb.js),
     which checks this same media query directly and renders one static,
     correctly-shaded frame instead of animating — nothing left for CSS to
     still here. */
  .app[data-mode="session"] .stage-area:not([data-shown]) {
    transform: none;
  }
  .transport-icon {
    transition-duration: 0s;
  }
}

/* `.pc-pointer` is now a pure JS marker (read by focus-cursor.js /
   narrated-cursor.js to measure what to point at) — it carries no styling of
   its own. The traveling cursor below is the only visible "this is what's
   being talked about right now" cue. */

/* `.pc-phrase` wraps the single word/short phrase a prose ref quotes
   (dom.js's `applyPointerTarget`) so the arrow can land on the term itself
   instead of the whole card. Deliberately no border -- range/area outlines
   were just retired in favor of the traveling `.pc-cursor` box above, and a
   boxed word would reintroduce exactly that look at word scale. A soft wash
   plus `box-decoration-break: clone` keeps the highlight readable across a
   line wrap without gaps at the break. */
.pc-phrase {
  background: color-mix(in oklab, var(--wa-now) 20%, transparent);
  border-radius: 3px;
  box-decoration-break: clone;
}

/* The traveling pointer cursor (replaces the old instant outline snap):
   one element that flies to whatever the current say points at. Position
   and size are set imperatively every frame by createPointerCursor's
   `frame()` (src/player-core/dom.js) -- never via a CSS transition, or the
   JS-driven exponential smoothing and a CSS transition would fight each
   other over the same properties. Opacity is the one property safe to let
   CSS transition, since it only toggles on appear/disappear. */
.pc-cursor {
  position: fixed;
  top: 0;
  left: 0;
  box-sizing: border-box;
  pointer-events: none;
  z-index: 3;
  border: 2px solid var(--wa-now);
  border-radius: 6px;
  transform-origin: center center;
  opacity: 0;
  transition: opacity 150ms;
}

.subtitle-area { min-height: 3.5rem; padding: 0.9rem 0.25rem; font-size: 1.15rem; }
.app[data-mode] .subtitle-area { font-size: 1.05rem; }
.pc-subtitle { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.pc-subtitle .pc-word { display: inline-block; }
/* Karaoke: spoken --wa-ink, current --wa-now (allowed accent use), pending
   --wa-ink-muted. */
.pc-word[data-state="spoken"] { color: var(--wa-ink); }
.pc-word[data-state="current"] { color: var(--wa-now); font-weight: 600; }
.pc-word[data-state="upcoming"] { color: var(--wa-ink-muted); }

.controls {
  display: flex;
  align-items: center;
  gap: 0.35rem 0.65rem;
  flex-wrap: wrap;
  min-height: 2.75rem;
}
.transport-btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  background: transparent;
  color: var(--wa-ink);
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
  transition-property: transform, background-color, border-color, opacity, box-shadow;
  transition-duration: 140ms;
  transition-timing-function: var(--ease-out);
}
.transport-btn:disabled {
  opacity: 0.4;
  cursor: default;
}
.transport-btn[aria-pressed="true"] {
  background: var(--wa-stage-raised);
  border-color: var(--wa-line);
  color: var(--wa-ink);
}
/* The toggle is the primary transport action — it carries the accent as a
   confident fill (Magnific/ElevenLabs' solid CTA pattern), not just the
   two-pixel playhead marker. Everything else in `.controls` stays quiet so
   this one button still reads as "the" action. */
.transport-toggle {
  background: var(--wa-now);
  color: var(--wa-stage);
}
.transport-toggle:disabled {
  background: var(--wa-stage-raised);
  color: var(--wa-ink-faint);
  opacity: 1;
}
@media (hover: hover) and (pointer: fine) {
  .transport-toggle:not(:disabled):hover {
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--wa-now) 22%, transparent);
  }
}
.transport-btn:focus-visible,
.speed select:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 2px;
}
.transport-btn svg {
  display: block;
  width: 1.15rem;
  height: 1.15rem;
}
.transport-icon-stack {
  position: relative;
  width: 1.15rem;
  height: 1.15rem;
}
.transport-icon {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  opacity: 0;
  transform: scale(0.25);
  pointer-events: none;
  transition-property: opacity, transform;
  transition-duration: 180ms;
  transition-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
.transport-toggle[data-action="play"] .transport-icon[data-icon="play"],
.transport-toggle[data-action="pause"] .transport-icon[data-icon="pause"],
.transport-toggle[data-action="replay"] .transport-icon[data-icon="replay"],
.transport-mute[aria-pressed="false"] .transport-icon[data-icon="speaker-high"],
.transport-mute[aria-pressed="true"] .transport-icon[data-icon="speaker-slash"] {
  opacity: 1;
  transform: scale(1);
}
/* Play triangle is heavier on the right; shift so it reads as centered. */
.transport-toggle[data-action="play"] .transport-icon[data-icon="play"] {
  transform: translateX(2px) scale(1);
}
@media (hover: hover) and (pointer: fine) {
  .transport-btn:not(:disabled):hover {
    border-color: var(--wa-line);
  }
  .transport-btn:not(:disabled):active {
    transform: scale(0.96);
  }
}

/* Transport/progress: the scrubber's thumb IS the accent dot travelling the
   timeline — the same object as the mark's dot. The track used to be a
   repeating gauge-tick pattern in `--wa-line` (documented at ~2.6:1
   contrast, a divider-only color by design) — at that contrast the track
   read as empty space rather than a scrubber; `--wa-ink-faint` (~5.5:1) as
   a solid fill keeps it legible at a glance without competing with the
   amber thumb. */
#scrubber {
  flex: 1 1 8rem;
  min-width: 6rem;
  appearance: none;
  height: 3px;
  border-radius: 999px;
  background: var(--wa-ink-faint);
  cursor: pointer;
}
#scrubber::-webkit-slider-thumb {
  appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--wa-now);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--wa-now) 22%, transparent);
  cursor: pointer;
  margin-top: -4.5px;
}
#scrubber::-moz-range-track {
  height: 3px;
  border-radius: 999px;
  background: var(--wa-ink-faint);
}
#scrubber::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--wa-now);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--wa-now) 22%, transparent);
  cursor: pointer;
}
#scrubber:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 4px;
  border-radius: 999px;
}

.clock {
  flex: none;
  color: var(--wa-ink-faint);
  font-family: var(--wa-font-body);
  font-variant-numeric: tabular-nums;
  font-size: 0.82rem;
  letter-spacing: 0.01em;
}
.speed {
  display: flex;
  align-items: center;
  flex: none;
  color: var(--wa-ink-faint);
  font-family: var(--wa-font-body);
  font-size: 0.82rem;
}
.speed select {
  appearance: none;
  background: transparent;
  color: var(--wa-ink-faint);
  border: 0;
  border-radius: 4px;
  padding: 0.35rem 0.2rem;
  font: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.speed-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}

/* Native thumb/track (not the custom pseudo-elements #scrubber uses) — a
   hand-centered webkit thumb needs a negative margin-top computed from
   thumb/track height, which drifts off-center at non-integer display
   scaling. `accent-color` lets the browser draw an already-centered thumb. */
.volume {
  flex: none;
  width: 4.5rem;
  accent-color: var(--wa-now);
  cursor: pointer;
}
.volume:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 4px;
  border-radius: 999px;
}

@media (max-width: 40rem) {
  .controls {
    display: grid;
    grid-template-columns: 2.75rem minmax(0, 1fr) auto auto auto auto;
    grid-template-areas:
      "toggle scrubber scrubber scrubber scrubber scrubber"
      ". clock speed mute volume captions";
    column-gap: 0.45rem;
    row-gap: 0.1rem;
    align-items: center;
  }
  .transport-toggle { grid-area: toggle; }
  #scrubber { grid-area: scrubber; min-width: 0; }
  .clock { grid-area: clock; }
  .speed { grid-area: speed; }
  #mute-toggle { grid-area: mute; }
  .volume { grid-area: volume; width: 3.5rem; }
  #transcript-toggle { grid-area: captions; }
}

.followups-area { display: grid; gap: 0.5rem; }
.pc-followups { display: flex; flex-wrap: wrap; gap: 0.6rem; }
/* Tinted amber pill, not a thin outline — a follow-up is an invitation to
   click, and a bare hairline chip read as inert. The tint is a mix, not a
   fill: it stays a chip, not a second `.prompt-submit`. */
.pc-followup {
  font: inherit;
  text-align: start;
  padding: 0.6rem 1rem;
  border: 1px solid color-mix(in srgb, var(--wa-now) 35%, var(--wa-line));
  border-radius: 999px;
  background: color-mix(in srgb, var(--wa-now) 10%, var(--wa-stage-sunken));
  color: var(--wa-ink);
  cursor: pointer;
  transition-property: background, border-color, transform;
  transition-duration: 140ms;
  transition-timing-function: var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .pc-followup:not([data-enabled="false"]):hover {
    background: color-mix(in srgb, var(--wa-now) 18%, var(--wa-stage-sunken));
  }
  .pc-followup:not([data-enabled="false"]):active {
    transform: scale(0.97);
  }
}
.pc-followup[data-enabled="false"] {
  opacity: 0.5;
  cursor: default;
  background: var(--wa-stage-sunken);
  border-color: var(--wa-line);
}
.pc-followup:not([data-enabled="false"]):focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 2px;
}

.transcript-area {
  background: var(--wa-stage-raised);
  border: 1px solid var(--wa-line);
  border-radius: 16px;
  padding: 1.25rem;
  max-height: 22rem;
  overflow-y: auto;
}
.pc-transcript-say { margin-bottom: 1rem; }
.pc-transcript-title { font-family: var(--wa-font-heading); font-weight: 700; font-size: 0.95rem; }
.pc-transcript-text { color: var(--wa-ink-faint); }

.note { color: var(--wa-ink-faint); font-size: 0.88rem; }

.status-area { color: var(--wa-ink-faint); font-size: 0.95rem; }

/* =========================================================================
   Display media (expand-display-media): chart, table, stat, figure,
   interactive, image pins, and the paper/blueprint/sketch looks.

   Each is its own visual family on purpose -- the point of the change is
   that an explainer no longer looks like one dark stroked scene after
   another. Every color is a `var(--wa-*)` token; the four `--wa-data-*`
   hues are the ONLY colors chart marks may take, and `--wa-now` stays off
   all of it (a bar is never "current").
   ========================================================================= */

/* --- Chart ---------------------------------------------------------------- */
.pc-chart {
  width: min(100%, 54rem);
  justify-self: center;
  align-self: center;
  min-height: 0;
}
.pc-chart-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: min(64vh, 34rem);
  font-family: var(--wa-font-body);
  overflow: visible;
}
.pc-chart-grid { stroke: color-mix(in srgb, var(--wa-line) 45%, transparent); stroke-width: 1; }
.pc-chart-axis { stroke: var(--wa-line); stroke-width: 1.25; }
.pc-chart-tick { fill: var(--wa-ink-faint); font-size: 12px; font-variant-numeric: tabular-nums; }
.pc-chart-category { fill: var(--wa-ink-muted); font-size: 13px; }
.pc-chart-axis-title { fill: var(--wa-ink-faint); font-size: 12px; letter-spacing: 0.02em; }
.pc-chart-value { fill: var(--wa-ink); font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
.pc-chart-legend-label { fill: var(--wa-ink-muted); font-size: 12px; }
.pc-chart-mark { stroke: var(--wa-ink-muted); stroke-width: 1.25; stroke-dasharray: 4 4; }
.pc-chart-mark-label { fill: var(--wa-ink-muted); font-size: 11px; }
.pc-chart-series-0 { --pc-series: var(--wa-data-a); }
.pc-chart-series-1 { --pc-series: var(--wa-data-b); }
.pc-chart-series-2 { --pc-series: var(--wa-data-c); }
.pc-chart-series-3 { --pc-series: var(--wa-data-d); }
.pc-chart-bar rect,
.pc-chart-legend-item rect { fill: var(--pc-series); }
.pc-chart-line { stroke: var(--pc-series); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; fill: none; }
.pc-chart-area { fill: color-mix(in srgb, var(--pc-series) 22%, transparent); }
.pc-chart-point { fill: var(--pc-series); stroke: var(--wa-stage); stroke-width: 1.5; }
.pc-chart-series-label { fill: var(--pc-series); font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
.pc-chart-slope[data-trend="down"] .pc-chart-line { stroke-dasharray: none; }
.pc-chart-current-guide { stroke: var(--wa-ink); stroke-width: 1; stroke-dasharray: 3 3; }
.pc-chart-current-point { fill: var(--wa-ink); stroke: var(--wa-stage); stroke-width: 2; }

/* --- Table ---------------------------------------------------------------- */
.pc-table {
  --pc-columns: 2;
  --pc-rest: 1;
  display: grid;
  width: min(100%, 52rem);
  justify-self: center;
  align-self: center;
  border: 1px solid var(--wa-line);
  border-radius: 16px;
  overflow: hidden;
  background: var(--wa-stage-sunken);
  font-variant-numeric: tabular-nums;
}
.pc-table-head,
.pc-table-row {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) repeat(var(--pc-rest), minmax(0, 1fr));
}
.pc-table-cell {
  padding: 0.7rem 1rem;
  border-bottom: 1px solid color-mix(in srgb, var(--wa-line) 60%, transparent);
  overflow-wrap: anywhere;
}
.pc-table-head .pc-table-cell {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--wa-ink-faint);
  background: var(--wa-stage-raised);
}
.pc-table-row:last-child .pc-table-cell { border-bottom: 0; }
.pc-table-row .pc-table-cell[data-column="0"] { font-weight: 700; color: var(--wa-ink); }
.pc-table-cell[data-align="right"],
.pc-table-cell[data-numeric="true"] { text-align: right; }
.pc-table-cell[data-align="center"] { text-align: center; }

/* --- Stat ----------------------------------------------------------------- */
.pc-stat {
  display: grid;
  justify-items: center;
  justify-self: center;
  align-self: center;
  text-align: center;
  gap: 0.5rem;
  max-width: 40rem;
}
.pc-stat-figure { display: flex; align-items: baseline; gap: 0.55rem; }
.pc-stat-value {
  font-family: var(--wa-font-heading);
  font-weight: 700;
  font-size: clamp(4rem, 13vw, 8.5rem);
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.pc-stat-unit {
  font-size: clamp(1.4rem, 4vw, 2.4rem);
  color: var(--wa-ink-muted);
  font-weight: 400;
}
.pc-stat-label { font-size: 1.2rem; color: var(--wa-ink-muted); }
.pc-stat-compare {
  font-size: 0.95rem;
  color: var(--wa-ink-faint);
  padding: 0.35rem 0.95rem;
  border: 1px solid var(--wa-line);
  border-radius: 999px;
}
.pc-stat-compare[data-trend="down"]::before { content: "\2193\00a0"; }
.pc-stat-compare[data-trend="up"]::before { content: "\2191\00a0"; }
.pc-stat-compare[data-trend="flat"]::before { content: "\2192\00a0"; }
.pc-slide[data-item-type="stat"] > .pc-header { text-align: center; border-bottom: none; }

/* --- Figure (author-drawn SVG) + looks ------------------------------------ */
.pc-figure {
  position: relative;
  width: min(100%, 56rem);
  max-height: 100%;
  min-height: 0;
  justify-self: center;
  align-self: center;
  display: grid;
  place-items: center;
  padding: 1.25rem;
  border: 1px solid var(--wa-line);
  border-radius: 16px;
  background: var(--wa-stage-raised);
  color: var(--wa-ink);
  overflow: hidden;
}
.pc-figure-canvas { width: 100%; min-height: 0; display: grid; place-items: center; }
.pc-figure-canvas svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: min(62vh, 34rem);
}
.pc-figure-canvas svg text { font-family: var(--wa-font-body); }
/* Semantic roles an author may put on any shape as a class, drawn with
   `currentColor`, so one drawing reads correctly on every look. */
.pc-figure .focal { color: var(--wa-data-a); }
.pc-figure .evidence { color: var(--wa-data-b); }
.pc-figure .warn { color: var(--wa-alert); }
.pc-figure .ok { color: var(--wa-diff-add); }
.pc-figure .muted { color: var(--wa-ink-faint); }
.pc-look-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.pc-figure[data-look="paper"],
.pc-figure[data-look="sketch"] {
  background: var(--wa-paper);
  color: var(--wa-paper-ink);
  border-color: var(--wa-paper-line);
  color-scheme: light;
}
.pc-figure[data-look="paper"] .muted,
.pc-figure[data-look="sketch"] .muted { color: var(--wa-paper-line); }
.pc-figure[data-look="sketch"] .pc-figure-canvas svg { filter: url(#wa-sketch); }
.pc-figure[data-look="blueprint"] {
  background-color: var(--wa-blueprint);
  background-image:
    linear-gradient(color-mix(in srgb, var(--wa-blueprint-line) 28%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--wa-blueprint-line) 28%, transparent) 1px, transparent 1px);
  background-size: 24px 24px;
  color: var(--wa-blueprint-ink);
  border-color: var(--wa-blueprint-line);
}
.pc-figure[data-look="blueprint"] .muted { color: var(--wa-blueprint-line); }

/* The animation card takes the same skins. The sandboxed scene paints its
   own ground from the `look` the host forwards in the build message
   (host.js `lookColors`), so only the frame around it changes here. */
.pc-animation[data-look="paper"],
.pc-animation[data-look="sketch"] { background: var(--wa-paper); border-color: var(--wa-paper-line); }
.pc-animation[data-look="blueprint"] { background: var(--wa-blueprint); border-color: var(--wa-blueprint-line); }
.pc-animation[data-look="sketch"] iframe { filter: url(#wa-sketch); }

/* --- Interactive ---------------------------------------------------------- */
.pc-interactive {
  display: grid;
  gap: 1rem;
  width: min(100%, 58rem);
  min-height: 0;
  justify-self: center;
  align-self: center;
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  grid-template-areas: "controls readouts";
}
.pc-interactive[data-has-graph="true"] {
  grid-template-areas:
    "controls graph"
    "readouts graph";
  grid-template-rows: auto 1fr;
}
.pc-controls { grid-area: controls; display: grid; gap: 0.75rem; align-content: start; }
.pc-control {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.25rem 0.75rem;
  align-items: baseline;
  padding: 0.85rem 1rem 0.7rem;
  border: 1px solid var(--wa-line);
  border-radius: 16px;
  background: var(--wa-stage-sunken);
}
.pc-control-label {
  font-size: 0.85rem;
  color: var(--wa-ink-faint);
  cursor: pointer;
}
.pc-control-value { font-weight: 700; font-variant-numeric: tabular-nums; }
.pc-range {
  grid-column: 1 / -1;
  width: 100%;
  margin: 0.2rem 0 0;
  accent-color: var(--wa-ink);
  cursor: pointer;
}
.pc-range:focus-visible {
  outline: 3px solid var(--wa-ink-muted);
  outline-offset: 4px;
  border-radius: 999px;
}
.pc-readouts {
  grid-area: readouts;
  display: grid;
  gap: 0.6rem;
  align-content: start;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}
.pc-readout {
  padding: 0.85rem 1rem;
  border: 1px solid var(--wa-line);
  border-radius: 16px;
  background: var(--wa-stage-raised);
  min-width: 0;
}
.pc-readout-label { font-size: 0.8rem; color: var(--wa-ink-faint); }
.pc-readout-value {
  font-family: var(--wa-font-heading);
  font-size: 1.55rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.pc-readout[data-state="error"] .pc-readout-value { color: var(--wa-alert); }
.pc-graph { grid-area: graph; min-width: 0; min-height: 0; align-self: center; }
.pc-graph .pc-chart-svg { max-height: min(52vh, 22rem); }

@media (max-width: 44rem) {
  .pc-interactive,
  .pc-interactive[data-has-graph="true"] {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "controls" "readouts" "graph";
    grid-template-rows: auto;
  }
  .pc-table-head, .pc-table-row { grid-template-columns: minmax(0, 1.2fr) repeat(var(--pc-rest), minmax(0, 1fr)); }
  .pc-table-cell { padding: 0.5rem 0.6rem; font-size: 0.9rem; }
}

/* --- Image pins ------------------------------------------------------------ */
/* Markers share the photo's grid cell (both `grid-area: 1 / 1`) so their
   percentage offsets are relative to the picture, not to the whole figure
   with its legend and credit rows. */
.pc-image[data-state="resolved"] > .pc-image-media { grid-area: 1 / 1; }
.pc-pin-layer {
  grid-area: 1 / 1;
  position: relative;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.pc-pin-marker {
  appearance: none;
  position: absolute;
  transform: translate(-50%, -50%);
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  display: grid;
  place-items: center;
  color: inherit;
  background: transparent;
  border: 0;
  font: inherit;
  cursor: help;
  pointer-events: auto;
}
.pc-pin-marker-index {
  width: 1.65rem;
  height: 1.65rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--wa-ink);
  color: var(--wa-stage);
  font-size: 0.8rem;
  font-weight: 700;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--wa-stage) 70%, transparent);
  transition:
    box-shadow var(--wa-motion-fast) var(--wa-ease-out),
    background-color var(--wa-motion-fast) var(--wa-ease-out);
}
.pc-pin-marker:focus-visible {
  outline: none;
}
.pc-pin-marker:focus-visible .pc-pin-marker-index,
.pc-pin-marker[aria-expanded="true"] .pc-pin-marker-index {
  box-shadow:
    0 0 0 3px var(--wa-stage),
    0 0 0 6px var(--wa-focus-ring);
}
@media (hover: hover) and (pointer: fine) {
  .pc-pin-marker:hover .pc-pin-marker-index {
    box-shadow:
      0 0 0 3px var(--wa-stage),
      0 0 0 5px var(--wa-ink-muted);
  }
}
.pc-pin-tooltip {
  box-sizing: border-box;
  position: absolute;
  z-index: var(--wa-z-popover);
  left: 50%;
  bottom: 100%;
  width: max-content;
  max-width: min(17rem, calc(100vw - 2rem));
  padding: var(--wa-space-sm) var(--wa-space-md);
  color: var(--wa-ink);
  background: var(--wa-stage-raised);
  border: 1px solid var(--wa-line);
  border-radius: var(--wa-radius-sm);
  box-shadow: var(--wa-shadow-overlay);
  font-size: 0.82rem;
  line-height: 1.35;
  text-align: left;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateX(-50%);
  transition:
    opacity var(--wa-motion-fast) var(--wa-ease-out),
    visibility 0s linear var(--wa-motion-fast);
}
.pc-pin-marker[data-tooltip-x="start"] .pc-pin-tooltip {
  left: 50%;
  transform: none;
}
.pc-pin-marker[data-tooltip-x="end"] .pc-pin-tooltip {
  right: 50%;
  left: auto;
  transform: none;
}
.pc-pin-marker[data-tooltip-y="below"] .pc-pin-tooltip {
  top: 100%;
  bottom: auto;
}
.pc-pin-tooltip-title {
  display: block;
  font-weight: 700;
}
.pc-pin-tooltip-text {
  display: block;
  margin-top: var(--wa-space-2xs);
  color: var(--wa-ink-muted);
}
.pc-pin-marker:focus-visible .pc-pin-tooltip,
.pc-pin-marker[aria-expanded="true"] .pc-pin-tooltip {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}
@media (hover: hover) and (pointer: fine) {
  .pc-pin-marker:hover .pc-pin-tooltip {
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
  }
}
.pc-pins {
  display: grid;
  gap: 0.35rem 1rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  width: 100%;
  padding: 0.75rem 1rem;
  text-align: left;
  background: var(--wa-stage-sunken);
  border-top: 1px solid var(--wa-line);
}
.pc-pin {
  display: grid;
  grid-template-columns: 1.4rem minmax(0, 1fr);
  gap: 0.05rem 0.55rem;
  align-items: baseline;
  padding: 0.2rem 0;
}
.pc-pin-index {
  display: grid;
  place-items: center;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  border: 1px solid var(--wa-line);
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--wa-ink);
}
.pc-pin[data-placed="true"] .pc-pin-index { background: var(--wa-ink); color: var(--wa-stage); border-color: var(--wa-ink); }
.pc-pin-title { font-weight: 700; font-size: 0.92rem; color: var(--wa-ink); }
.pc-pin-text { grid-column: 2; color: var(--wa-ink-faint); font-size: 0.85rem; }
.pc-image[data-state="placeholder"] .pc-pins { border-top: 0; background: transparent; }
