/* Pace Dot chat — marketing-site replica of the app's Dot conversation.
   Everything is scoped under .pace-dot-chat; all custom properties and
   @keyframes are pdc- prefixed so the host page can never collide.
   All units are px (no rem/em).

   Every colour, radius, border, shadow, padding, gap and font size below is a
   reading off the app, recorded in REFERENCE.md. Where a number is a decision
   rather than a reading it says so in the comment above it. */

.pace-dot-chat {
  /* CLS 0, structurally: the ratio is carried by the CLASS, not by the script,
     so an empty div reserves its full height before the script has run — and an
     opted-out or destroyed instance still holds its space. */
  aspect-ratio: 640 / 1010;
  position: relative;
  overflow: hidden;
  /* The twenty-nine inherited text properties are declared at the END of this
     rule, after the tokens, because five of them are the panel's own values and
     want to sit next to the stack they belong to. See "Insulation against the
     host page" below. */

  /* ── tokens (REFERENCE.md §2, light mode only) ────────────────────────────
     The app's own root-scope values, resolved in a browser against its compiled
     stylesheet. Declared HERE, on the component, and never on the document root:
     a marketing page must be able to drop this in without the embed reaching
     outside its own box. This panel is light-only by design, so there is no dark
     branch anywhere in this file. */
  --pdc-bg: #FFFFFF;
  --pdc-fg: #09090B;
  --pdc-muted: #F4F4F5;
  --pdc-muted-fg: #71717A;
  /* hsl(240 14% 95%). NOTE: pace-hero.css declares this token one unit high on
     both red and green — REFERENCE.md §1. Below is the browser's own answer,
     confirmed two independent ways; do not copy the hero's. */
  --pdc-border: #F0F0F4;
  /* `border-border/50`, kept as the source's own alpha rather than flattened:
     it composites over the white panel and over the muted bubble to different
     values, and both appear in this panel. */
  --pdc-border-50: rgba(240, 240, 244, 0.5);
  /* The empty state's Dot mark is `text-muted-foreground/40`. */
  --pdc-muted-fg-40: rgba(113, 113, 122, 0.4);
  /* `text-muted-foreground/70` — the trace summary, the step detail line and
     the hover action icons. */
  --pdc-muted-fg-70: rgba(113, 113, 122, 0.7);
  /* `bg-muted/50` — the answer table's even rows. */
  --pdc-muted-50: rgba(244, 244, 245, 0.5);
  /* emerald-500. The completed-step tick, and nothing else in this panel. */
  --pdc-emerald: #10B981;

  /* The app remaps Tailwind's radius scale off its 12.8px radius token, but
     extends lg/md/sm ONLY — so rounded-2xl stays Tailwind's stock 16px. The
     panel and the bubbles are 2xl; the composer box and the starter chips are
     lg; the send button is md. Getting this backwards is the single easiest way
     to make the replica read as "close but not it". */
  --pdc-radius-2xl: 16px;
  --pdc-radius-lg: 12.8px;
  --pdc-radius-md: 10.8px;
  --pdc-radius-full: 9999px;

  --pdc-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --pdc-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);

  /* ── panel box ────────────────────────────────────────────────────────────
     Width is the app's measured 420px, unchanged: every interior number in
     REFERENCE.md (the 394px composer, the 418px message area, the bubbles'
     85%/95% caps) is stated against that width, so widening the panel would
     invalidate all of them at once. The 640px canvas is sized to hug it —
     ~110px of clear ground either side — rather than the panel being stretched
     to fill a frame. See the note above DESIGN_W in pace-dot-chat.js.

     HEIGHT IS A DECISION, not a reading, and this one is now MEASURED. The
     app's panel is 600px, which gives a 483px message area — too short for the
     finished conversation this canvas has to hold without scrolling (user
     bubble, trace row, two paragraphs, a four-row table and a destination
     chip). The first cut was 720px, chosen before there was an end state to
     measure. Rendered headless, the finished conversation is 442px of content
     including the body's own 16px padding, which left 161px of empty panel
     under the destination chip — a quarter of the message area, which reads as
     a panel stretched to fill a frame rather than a chat.

     650px was the first retune: message area 533px, so the same 442px of
     content rested with 91px of clear space below it. That is breathing room a
     real conversation has, and still ~4 lines of prose of headroom against a
     host machine whose font stack wraps the answer differently. The canvas was
     820px, giving 85px of clear ground above and below the panel — comfortably
     clear of the ~30px reach of shadow-xl.

     840px IS THE SECOND RETUNE, and it is measured the same way. Task 10 gave
     the answer its second movement — a fourth checklist step, a third
     paragraph and a three-column week-over-week table — and the end state grew
     from 442px to 630px, which overflowed the 533px message area by 97px: the
     destination chip and the hover actions were being clipped by
     `.pdc-body { overflow: hidden }` rather than scrolling, so nothing on
     screen said anything was wrong.

       message area = panel − header 53 − composer 62 − 2 borders
       723          = 840   − 53       − 62          − 2
       723 − 630 content = 93px of slack

     The canvas grows with it, 840 + 2 × 85 = 1010, so the clear ground either
     side of the panel is unchanged. Width is untouched at 420/640 — every
     interior number in REFERENCE.md is stated against it.

     TASK 11 MOVED THE CONTENT AGAIN, AND THE PANEL DID NOT HAVE TO MOVE WITH
     IT. The account mention chip in the answer's first sentence is 151.8px
     wide where the words it stands in for are about 110px, so the first
     paragraph rewraps from three lines to four and the finished conversation
     goes 630 -> 650px. Re-measured by exactly the method below, which
     reproduced 630 for the same page with the chip swapped back to plain text
     — so the 20px is the chip, not a drifting harness. 723 − 650 leaves 73px
     of slack, still comfortably above the 60px floor shell.test.ts holds, so
     --pdc-content-h is the only number that changed.

     TASK 12 ENDED THE SEQUENCE OF RETUNES: THE MESSAGE AREA SCROLLS AND THE
     BOX DOES NOT MOVE AGAIN. A second exchange took the conversation from
     650px to 993px, and a panel tall enough to hold that would have put the
     canvas past 1,150px — a third growth, on a page element already 1,010px
     tall. A real chat scrolls, so this one does: the newest message is kept in
     view and the older exchange runs off the top. 420/840/640/1010 are
     therefore unchanged, and what the fit guard means changed instead.

     THE HEADER ROWS PROVED THAT DECISION RIGHT RATHER THAN TESTING IT. The
     final review reinstated the `<thead>` on all three tables — a headerless
     GFM table is a shape the markdown renderer cannot emit, so the panel was
     drawing something the product could not — and the conversation went 993 ->
     1060px. Re-measured by the method below, with a control: removing the three
     `<thead>`s from the rendered DOM gives 973px and a 343px last exchange,
     which reproduces the recorded Task 12 constant exactly, so the growth is
     the headers (+87px) net of a line the third paragraph shed when the header
     took over its framing (−20px). Because the panel scrolls, none of that
     touches the box: the message area is still 723px, the last exchange still
     fits it with 351px to spare, and 420/840/640/1010 did not move. That is
     what a scrolling panel is FOR — before Task 12 this edit would have forced
     a fourth retune. */
  --pdc-panel-w: 420px;
  --pdc-panel-h: 840px;

  /* ── the fit, and the five numbers that make it checkable ─────────────────
     WHAT THIS GUARD MEANS CHANGED AT TASK 12, AND IT WAS NOT DELETED.

     Until Task 12 it said one thing: the panel must be taller than the whole
     conversation. That is no longer true and no longer wanted — the message
     area scrolls, so the conversation is DELIBERATELY taller than the panel.
     What did not change is the reason the guard exists. A 97px overflow once
     hid in complete silence behind `.pdc-body { overflow: hidden }`, clipping
     the destination chip and the hover actions with nothing on screen to say
     so, and a scrolling area hides that failure even better: a panel too short
     for its newest answer looks like a chat that has simply scrolled.

     So it now asserts what is actually true, in three parts:

       1. THE NEWEST EXCHANGE RESTS WHOLE. The panel comes to rest scrolled to
          the bottom, so the visible window is the LAST --pdc-panel-h worth of
          content. --pdc-last-exchange-h is the follow-up question and its
          answer together; the message area has to be at least that, or the
          finished picture opens mid-way through the answer it is showing.
       2. THE SCROLL IS LOAD-BEARING. --pdc-content-h must EXCEED the message
          area. Grow the panel past the conversation and the scroll becomes
          dead code that no test exercises and nobody notices is unused; this
          is what stops a well-meant fourth retune retiring it in silence.
       3. The resting offset is contentH − messageArea, and it therefore lands
          ABOVE the newest exchange — which is (1) restated as the thing a
          reader actually cares about.

     What the guard deliberately does NOT claim: that no message anywhere is
     cut. The first answer runs off the top at rest, by design, because that is
     what a scrolled conversation looks like. The guarantee is about the
     exchange the panel is resting on.

     jsdom lays nothing out, so the suite cannot measure any of this. The guard
     is arithmetic against MEASURED CONSTANTS, and these are the terms of it.

     --pdc-content-h / --pdc-last-exchange-h — THE TWO THAT NEED RE-MEASURING.
     1060px and 372px at the final review, when the table header rows went back
     in (993px and 343px at Task 12; 650px and n/a at Task 11, when there was
     one exchange). Read headless in Chromium at deviceScaleFactor 1 with
     `prefers-reduced-motion: reduce`, off the demo index.html, with the panel
     deliberately shortened first (a tall panel clamps scrollHeight to
     clientHeight and reports nothing):

       root.style.setProperty('--pdc-panel-h', '300px');
       content       = body.scrollHeight
       lastExchange  = content − lastUserRow.offsetTop

     Both are independent of the panel height. RE-MEASURE AND UPDATE THESE
     WHENEVER THE ANSWER'S CONTENT CHANGES: a paragraph, a table row, a
     checklist step or a font-size all move them, and they are the only numbers
     here a code change cannot derive.

     --pdc-header-h / --pdc-composer-h — the two fixed bands either side of the
     message area, measured at 53px and 62px. They are not declared as heights
     on those elements, because both are content-driven from the paddings above
     and should stay that way; these are readings OF that layout. So that the
     readings cannot drift from it, shell.test.ts recomputes each from the
     paddings that produce it — 14 + 28 + 10 + 1 and 8 + 42 + 12 — rather than
     trusting the number written here.

     --pdc-ground-h — the clear ground above and below the panel, 2 × 85px,
     which is what separates shadow-xl's ~30px reach from the canvas edge. The
     canvas height is the panel plus this, and DESIGN_H in the script and the
     aspect-ratio at the top of this file both have to agree with the sum. */
  --pdc-header-h: 53px;
  --pdc-composer-h: 62px;
  --pdc-content-h: 1060px;
  --pdc-last-exchange-h: 372px;
  --pdc-ground-h: 170px;

  /* The app's actual stack — layout.tsx defines a font variable that nothing
     consumes, so `font-sans` falls through to Tailwind's default. No webfont. */
  font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
  font-size: 16px;
  line-height: 1.5;
  color: var(--pdc-fg);
  text-align: left;

  /* ── Insulation against the host page ─────────────────────────────────────
     Scoping stops a SELECTOR from matching. It does nothing about inheritance:
     every property below is inherited, so a host `body { … }` rule reaches every
     text node in this panel unless the root answers it here first. The five
     above (font-family, font-size, line-height, color, text-align) are the same
     defence; these thirteen continue it, and the eleven after them finish it.

     This block is copied from pace-hero.css, where each of the thirteen was
     observed leaking BEFORE it was added — this is a measured list, not a
     precautionary one. The worst was letter-spacing: at 0.6px on `body` it moved
     349 elements in the hero, widened chips by 34px and re-wrapped a card 23px
     taller. Verified again for THIS panel in REFERENCE.md §14: with all
     twenty-nine declared on a host `body` at once, and the host stylesheet
     loading AFTER this one so it wins every specificity tie, the rendered panel
     is byte-identical to a clean host.

     Every value is the property's own initial value, so none of them changes
     anything this panel renders. Longhands on this panel's OWN descendants still
     win — this only stops inheritance from OUTSIDE. */
  letter-spacing: normal;
  word-spacing: normal;
  font-weight: 400;
  font-style: normal;
  font-variant: normal;
  text-transform: none;
  direction: ltr;
  text-indent: 0;
  word-break: normal;
  line-break: auto;
  font-stretch: normal;
  font-kerning: auto;
  /* Leaked its computed value without moving anything, because the emitted
     markup has no collapsible whitespace to be affected — except the streaming
     answer's own text nodes, which is why it stays. */
  white-space: normal;

  /* Eleven MORE of exactly the same bug, found by sweeping every inherited text
     property Chromium implements rather than only the eighteen the hero had.
     Each of these was declared on a host `body` and measured against a clean
     render of this panel, and each one changed pixels — REFERENCE.md §14.2 has
     the counts. The hero's block is a subset, not the finished list; these are
     the ones a chat panel exposes that a dashboard screenshot did not, because
     this panel's whole subject is a paragraph of streamed prose.

     Every value is the property's own initial value. `-webkit-font-smoothing`
     and `-webkit-text-stroke` are non-standard, and are here because they are
     inherited and widely set on real host pages. */
  text-shadow: none;
  text-wrap: wrap;
  font-feature-settings: normal;
  font-variation-settings: normal;
  font-size-adjust: none;
  text-rendering: auto;
  -webkit-font-smoothing: auto;
  -webkit-text-stroke: 0;
  text-align-last: auto;
  text-emphasis: none;
  font-language-override: normal;
  /* NOT defended, deliberately: `visibility` and `display` are the host saying
     "do not show this", not the host styling text. A component that forced
     itself visible would break a host that hides a tab panel or a collapsed
     accordion containing it. Recorded as a known limit instead. */
}

.pace-dot-chat, .pace-dot-chat *, .pace-dot-chat *::before, .pace-dot-chat *::after {
  box-sizing: border-box;
}
.pace-dot-chat h1, .pace-dot-chat h2, .pace-dot-chat h3, .pace-dot-chat h4,
.pace-dot-chat p, .pace-dot-chat ul, .pace-dot-chat ol {
  margin: 0;
  padding: 0;
  font-size: inherit;
  font-weight: inherit;
}
.pace-dot-chat svg { display: block; max-width: none; }

.pdc-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 640px;
  height: 1010px;
  transform-origin: top left;
  /* The panel is centred rather than absolutely placed: the canvas is a fixed
     box, so centring holds for whatever --pdc-panel-h Task 6 settles on. */
  display: flex;
  align-items: center;
  justify-content: center;
  /* The page behind the panel is the marketing ground, not an app screen. */
  background: var(--pdc-bg);
}

/* ── panel ────────────────────────────────────────────────────────────────── */

.pdc-panel {
  width: var(--pdc-panel-w);
  height: var(--pdc-panel-h);
  display: flex;
  flex-direction: column;
  background: var(--pdc-bg);
  border: 1px solid var(--pdc-border-50);
  border-radius: var(--pdc-radius-2xl);
  /* shadow-xl is the app's own, and is the ambient separation from the page —
     nothing extra is layered under it. It is the only heavy shadow here. */
  box-shadow: var(--pdc-shadow-xl);
  overflow: hidden;
}

/* ── header ───────────────────────────────────────────────────────────────── */

/* 53px tall: 14 + 28 + 10 + the 1px rule. */
.pdc-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px 10px;
  border-bottom: 1px solid var(--pdc-border-50);
  flex-shrink: 0;
}
.pdc-header .pdc-dot { color: var(--pdc-muted-fg); flex-shrink: 0; }
.pdc-header-title {
  flex: 1;
  min-width: 0;
  font-size: 14px;
  /* `leading-tight` — 1.25, not text-sm's 20px. */
  line-height: 17.5px;
  font-weight: 600;
  color: var(--pdc-fg);
}
.pdc-header-btns { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.pdc-header-btn {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--pdc-muted-fg);
}
/* `h-3.5 w-3.5` on the glyph inside the 28px square. */
.pdc-header-btn .pdc-icon { width: 14px; height: 14px; }

/* ── body ─────────────────────────────────────────────────────────────────── */

.pdc-body {
  flex: 1;
  min-height: 0;
  padding: 16px;
  position: relative;
  /* THE APP SCROLLS HERE AND SO DOES THIS, THROUGH `scrollTop` RATHER THAN A
     SCROLL CONTAINER. Two exchanges are 1060px of content in a 723px area, so
     the newest message is kept in view as the conversation grows (§5,
     scrollToNewest). `overflow: hidden` stays, and is not a leftover:

       - an overflow-hidden box is still scrollable programmatically, so the
         behaviour is real;
       - it takes NO TAB STOP. Chrome makes a keyboard-focusable scroller out of
         any `overflow: auto` box with no focusable children, which this panel
         would be by construction, and zero focusable descendants is a promise
         this component keeps;
       - it cannot swallow the host page's wheel events, which on a full-width
         scaled embed would stop the visitor's scroll dead over the panel;
       - and it draws no scrollbar inside a transform-scaled canvas, where a
         native one renders at the wrong visual width.

     The cost is that a panel too short for its newest answer would clip in
     silence, exactly as the 97px overflow did at Task 10. That is what the fit
     guard at the top of this file, and shell.test.ts, exist to catch. */
  overflow: hidden;
}

/* ── empty state ──────────────────────────────────────────────────────────── */

.pdc-empty {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 0 8px;
  /* Centred, which is the app's own `text-center` here and the one place in the
     panel that is not left-aligned. The chips below re-assert left. */
  text-align: center;
}
.pdc-empty .pdc-dot { color: var(--pdc-muted-fg-40); }
.pdc-empty-title {
  font-size: 16px;
  line-height: 24px;
  font-weight: 600;
  margin-bottom: 2px;
}
.pdc-empty-sub {
  font-size: 14px;
  line-height: 20px;
  color: var(--pdc-muted-fg);
}

.pdc-chips {
  display: grid;
  gap: 6px;
  width: 100%;
  max-width: 340px;
  /* §5 fades the chips out as the caret appears, so the starter prompts and the
     typed question are never both on screen. Declared here rather than beside
     .pdc-chips-out so there is one rule per selector. */
  transition: opacity 300ms ease;
}
.pdc-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 14px;
  border: 1px solid var(--pdc-border-50);
  border-radius: var(--pdc-radius-lg);
  background: transparent;
  font-size: 14px;
  line-height: 20px;
  text-align: left;
}
.pdc-chip-label {
  /* The chips are picked short enough to sit on one line; this stops a host
     font substitution from turning a near-miss into a two-line chip. */
  white-space: nowrap;
}
.pdc-chip-arrow {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  transform: rotate(45deg);
  /* Fully transparent until hover in the app. This panel has no hover state, so
     it rests here — occupying its width, showing nothing. */
  color: rgba(113, 113, 122, 0);
}

/* ── composer ─────────────────────────────────────────────────────────────── */

/* 62px tall: 8 + 42 + 12. */
.pdc-composer {
  flex-shrink: 0;
  padding: 8px 12px 12px;
}
.pdc-composer-box {
  display: flex;
  align-items: flex-end;
  height: 42px;
  border: 1px solid var(--pdc-border-50);
  border-radius: var(--pdc-radius-lg);
  background: var(--pdc-bg);
}
.pdc-input {
  flex: 1;
  min-width: 0;
  height: 40px;
  padding: 10px 12px;
  font-size: 14px;
  line-height: 20px;
  background: transparent;
  white-space: nowrap;
  overflow: hidden;
}
/* The placeholder colour. Task 6 removes this class as the question types in,
   which is what turns placeholder grey into real body ink. */
.pdc-input-empty { color: var(--pdc-muted-fg); }
.pdc-send {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  margin: 0 6px 4px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--pdc-radius-md);
  /* The only high-contrast fill in the panel, and the composer's only colour. */
  background: var(--pdc-fg);
  color: var(--pdc-bg);
}
.pdc-send .pdc-icon { width: 16px; height: 16px; }
/* `disabled:opacity-30` — the resting state with an empty field. */
.pdc-send-disabled { opacity: 0.3; }

/* ── §4 message primitives ───────────────────────────────────────────────────
   The pieces Task 6 assembles into a conversation. Everything below is either
   a reading off REFERENCE.md or a decision that says so.

   NONE OF IT IS INTERACTIVE. The destination chip, the trace disclosure and the
   Copy/Retry actions all look like controls and are all inert <div>/<span>s.
   The trace in particular is deliberately NOT a <details>/<summary>: that pair
   takes a tab stop and expands on Enter, which in a decorative panel is a
   keyboard trap that leads nowhere. primitives.test.ts asserts the absence. */

/* ── message rows ─────────────────────────────────────────────────────────── */

/* 16px between messages — the message area's `space-y-4` (REFERENCE.md §4).
   Expressed as an adjacent-sibling rule rather than a gap so the empty state,
   which is not a message, keeps its own centred layout. */
.pdc-msg { display: flex; }
.pdc-msg + .pdc-msg { margin-top: 16px; }
.pdc-msg-user { justify-content: flex-end; }
.pdc-msg-assistant { justify-content: flex-start; }

.pdc-bubble {
  min-width: 0;
  font-size: 14px;
  line-height: 20px;
}

/* REFERENCE.md §4. The asymmetric corner IS the tail: the user bubble blunts
   its bottom-RIGHT, the assistant its bottom-LEFT — each toward its own side of
   the panel. 16px to 10.8px, a flattening rather than a point. Order is
   TL TR BR BL. */
.pdc-bubble-user {
  max-width: 85%;
  padding: 10px 16px;
  background: var(--pdc-bg);
  border: 1px solid var(--pdc-border);
  border-radius: var(--pdc-radius-2xl) var(--pdc-radius-2xl) var(--pdc-radius-md) var(--pdc-radius-2xl);
  box-shadow: var(--pdc-shadow-sm);
}

.pdc-bubble-assistant {
  max-width: 95%;
  padding: 12px 16px;
  background: var(--pdc-muted);
  border: 1px solid var(--pdc-border);
  border-radius: var(--pdc-radius-2xl) var(--pdc-radius-2xl) var(--pdc-radius-2xl) var(--pdc-radius-md);
  /* The app crossfades the fill over 200ms as the bubble settles out of its
     streaming state (REFERENCE.md §4). It lives here rather than on
     .pdc-bubble-streaming because the transition has to be on the element in
     BOTH states — declared only on the streaming class it would play on the way
     in and snap on the way out. */
  transition: background-color 200ms;
}
/* `space-y-2` inside the bubble: trace -> prose -> actions, 8px apart. */
.pdc-bubble-assistant > * + * { margin-top: 8px; }

/* The streaming bubble (REFERENCE.md §5). Task 6 adds this class while Dot is
   working and drops it when the answer settles. */
.pdc-bubble-streaming {
  --pdc-border-w: 1.5px;
  position: relative;
  background: rgba(244, 244, 245, 0.6);
  border: var(--pdc-border-w) solid transparent;
  background-clip: padding-box;
}
.pdc-bubble-streaming::before {
  content: '';
  position: absolute;
  inset: calc(-1 * var(--pdc-border-w));
  border-radius: inherit;
  padding: var(--pdc-border-w);
  z-index: -1;
  /* Warm sand -> dusty rose -> lilac -> slate blue -> sage, closing back to
     sand. Six stops, first and last identical so the ring joins seamlessly. */
  background: conic-gradient(
    from var(--pdc-thinking-angle),
    #c4a882,
    #c9918e,
    #b8a0c4,
    #92afc9,
    #9ac4a6,
    #c4a882
  );
  /* Carves the fill out of the middle, leaving only the 1.5px ring. */
  -webkit-mask:
    linear-gradient(#fff 0 0) content-box,
    linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  animation: pdc-thinking-spin 3s linear infinite;
}

/* ── prose ────────────────────────────────────────────────────────────────── */

/* Task 6 streams into this node. It starts genuinely empty — no placeholder
   text, no &nbsp; — so a timeline that never runs shows an empty bubble rather
   than a stub sentence. */
.pdc-prose { font-size: 14px; line-height: 20px; }
.pdc-prose p { margin-bottom: 12px; }
.pdc-prose p:last-child { margin-bottom: 0; }

/* ── thinking indicator ───────────────────────────────────────────────────── */

.pdc-thinking {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  line-height: 20px;
}
.pdc-thinking .pdc-dot { color: var(--pdc-muted-fg); flex-shrink: 0; }
.pdc-thinking-label { font-weight: 500; }

/* ── live checklist ───────────────────────────────────────────────────────── */

/* REFERENCE.md §4 trap: the app's markup carries `mb-1` here, but the parent's
   `space-y-2` zeroes it because the checklist is never the first child. What
   the app RENDERS is 0, so 0 is what this replicates. The collapsed trace, which
   IS the first child, keeps its 4px — see .pdc-trace below. */
.pdc-checklist { margin-bottom: 0; }
.pdc-step + .pdc-step { margin-top: 8px; }

.pdc-step {
  font-size: 14px;
  line-height: 20px;
  animation: pdc-tool-activity-step-in 0.35s ease both;
}
/* Rows are built up front and revealed one at a time, because a replica has to
   supply the stagger the app gets free from the network (REFERENCE.md §7).
   `display: none` — not opacity — so the step-in animation genuinely restarts
   when the row is revealed, and so the list has no reserved dead space. */
.pdc-step-pending { display: none; }

.pdc-step-row { display: flex; align-items: center; gap: 8px; }
/* h-4 w-4 — the live checklist's icons are 16px, the settled trace's are 12px.
   Swapping the two is the fastest way to make the panel read as almost-right. */
.pdc-step-icon { width: 16px; height: 16px; flex-shrink: 0; color: var(--pdc-muted-fg); }
.pdc-step-icon[data-icon='loader2'] { animation: pdc-spin 1s linear infinite; }
.pdc-step-icon[data-icon='check'] { color: var(--pdc-emerald); }
.pdc-step-label { color: var(--pdc-muted-fg); }
.pdc-step-running .pdc-step-label { font-weight: 500; color: var(--pdc-fg); }

/* ── collapsed trace ──────────────────────────────────────────────────────── */

/* 4px. Unlike the checklist's, this element's `mb-1` DOES land, because the
   trace is the assistant bubble's first child and `space-y-2` only zeroes the
   siblings after it. Measured, not reasoned — REFERENCE.md §4. */
.pdc-trace { margin-bottom: 4px; }
.pdc-trace-summary {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  line-height: 16.5px;
  font-weight: 500;
  color: var(--pdc-muted-fg-70);
}
/* h-3 w-3, resting unrotated: the replica's trace is always collapsed, so the
   chevron never reaches the app's `group-open:rotate-90`. */
.pdc-trace-chevron { width: 12px; height: 12px; flex-shrink: 0; }

/* ── answer table ─────────────────────────────────────────────────────────── */

/* Two columns, label and value. A four-across table is what a wide layout would
   use; at 420px it would either wrap every cell or scroll, so this reads down
   instead of across.

   THERE IS A HEADER ROW, AND DROPPING IT WAS A MISTAKE THIS FIXES. An earlier
   cut of the panel had none, on the grounds that a "Metric / Value" header
   labels two columns the labels already name and costs a row of scarce height.
   That is a fine argument about taste and the wrong argument here: a GFM table
   REQUIRES a delimiter row and the row above it IS the header, so remarkGfm
   always emits a `<thead>`. A headerless table is not a table Dot could write —
   same class of error as a sparkline. The header TEXT is where the taste went
   instead; see §1 of pace-dot-chat.js.

   Everything below — cell metrics, borders, radius, the header's fill and its
   500 weight, striping — is the app's, REFERENCE.md §9. Nothing here is a
   decision now. */
.pdc-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  line-height: 16px;
  margin: 12px 0;
  border-radius: var(--pdc-radius-md);
  overflow: hidden;
  background: var(--pdc-bg);
}
.pdc-table td {
  padding: 6px 12px;
  /* Left, for both columns including the numbers. That is the app, and it
     matches Pace's standing left-align rule. */
  text-align: left;
  border: 1px solid var(--pdc-border);
  background: transparent;
}
/* The header row, REFERENCE.md §9 exactly: same padding as a body cell, weight
   500 rather than bold, left-aligned like every other column, `bg-muted` fill
   and the same border. The weight and the alignment are the two a hand-styled
   header would have got wrong — browsers default `th` to bold and centred, and
   the app overrides both. */
.pdc-table th {
  padding: 6px 12px;
  font-weight: 500;
  text-align: left;
  border: 1px solid var(--pdc-border);
  background: var(--pdc-muted);
  color: var(--pdc-fg);
}
/* The quieter of the two columns. Muted ink, the same `text-muted-foreground`
   the app gives every secondary label — it is what tells label from value at a
   glance, which the header alone does not do row by row. */
.pdc-table td.pdc-table-label { color: var(--pdc-muted-fg); }
/* The week-over-week table's third column.

   NO COLOUR, and that is a decision rather than an omission. Every dashboard
   tints a delta green and red; Dot cannot. Its answers render through markdown
   with no raw-HTML plugin and a component map that overrides only p, li, td,
   strong and a — there is no way for the model to express a coloured cell, so a
   tinted delta here would be the panel claiming a rendering the product does
   not produce, which is the same lie a sparkline would tell.

   NO RIGHT ALIGN either: Pace left-aligns every column, numbers included, which
   is what `.pdc-table td` above already says, and the brief's right-align would
   have contradicted both that rule and the app's own table.

   What the column does get is tabular figures, so the three percentages line up
   under one another. That is the browser fitting the glyphs, not a rendering
   the markdown has to carry. */
.pdc-table td.pdc-table-change {
  font-variant-numeric: tabular-nums;
  /* The change is the reason the table exists, so it is not the quieter
     column — it takes the same ink as the value beside it. */
  white-space: nowrap;
}
.pdc-table tbody tr:nth-child(even) td { background: var(--pdc-muted-50); }

/* ── account mention chip ─────────────────────────────────────────────────────
   The account's name, drawn inline inside the answer's first sentence as the
   app draws it: a coloured initial, the name, the platform mark. REFERENCE.md
   §15, every value [computed] off the app's own compiled stylesheet with
   AccountMention's class string verbatim. Nothing below is a design choice.

   THE NEGATIVE MARGINS ARE LOAD-BEARING. The chip is 22px tall and the prose
   line is 20px, so `-my-0.5` is what stops a chip mid-sentence from opening the
   line box up under it. Measured: the app's line grows 20 -> 20.3125px with the
   chip in it, which is the whole cost.

   NO SHADOW. The destination chip below has one; this does not. Two chips in
   one answer that looked alike would say they do the same thing, and they do
   not — one names a place to go, this one names an account. */
.pdc-mention {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: -2px 0;
  padding: 2px 6px;
  border: 1px solid var(--pdc-border);
  border-radius: var(--pdc-radius-md);
  background: var(--pdc-muted);
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  color: var(--pdc-fg);
  vertical-align: middle;
}
/* w-4 h-4 rounded-full, white 9px bold initial, centred. The fill is the only
   thing set from the script, because it is data: the colour is hashed out of
   the account's name by the app's own no-store fallback. */
.pdc-mention-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  border-radius: 9999px;
  color: #FFFFFF;
  font-size: 9px;
  line-height: 16px;
  font-weight: 700;
}
/* `truncate max-w-[140px]`. It does not truncate at this account's name, but a
   longer one from screening meets the app's own cap instead of pushing the chip
   through the side of the bubble. */
.pdc-mention-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 140px;
}
/* The chip's own `[&_svg]:w-4 [&_svg]:h-4` beats PlatformIcon's size="sm"
   (w-6 h-6), so the mark inside a mention is 16px rather than 24. */
.pdc-mention-platform {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/* ── destination chip ─────────────────────────────────────────────────────── */

.pdc-dest {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 2px 0;
  padding: 4px 8px;
  border: 1px solid var(--pdc-border);
  border-radius: var(--pdc-radius-md);
  background: var(--pdc-bg);
  box-shadow: var(--pdc-shadow-sm);
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  color: var(--pdc-fg);
  vertical-align: middle;
}
.pdc-dest-icon { width: 14px; height: 14px; flex-shrink: 0; color: var(--pdc-muted-fg); }
.pdc-dest-arrow { width: 12px; height: 12px; flex-shrink: 0; color: var(--pdc-muted-fg); }

/* ── hover actions ────────────────────────────────────────────────────────── */

/* The app reveals these on `group-hover/msg`. Nothing in this panel can be
   hovered meaningfully, so the reveal is a class Task 6 flips on cue — the
   transition is the app's own 150ms either way. */
.pdc-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  padding-top: 4px;
  opacity: 0;
  transition: opacity 150ms;
}
.pdc-actions-visible { opacity: 1; }
/* p-1 around a 14px glyph -> a 22px square. */
.pdc-action {
  padding: 4px;
  border-radius: var(--pdc-radius-md);
  color: var(--pdc-muted-fg-70);
  display: flex;
}
.pdc-action .pdc-icon { width: 14px; height: 14px; }

/* ── §5 timeline ──────────────────────────────────────────────────────────────
   Only the states the timeline moves through live here. Everything the panel
   RESTS at is above, which is why a panel whose timeline never runs is still a
   finished picture. */

/* Held out of the flow until the answer settles. `display: none`, not
   `visibility: hidden`: a table and chip reserving their space from the moment
   the trace appears would leave the bubble sitting half-empty for four seconds,
   which no real answer does. The paragraphs' own heights ARE reserved — see
   undressForAnswer() — so the streaming text never rewraps anything below it. */
.pdc-pending { display: none; }

/* The composer's caret while the question types itself. Drawn as a pseudo
   element on the field, so it needs no node of its own and cannot end up in
   the composition the end-state comparison reads. */
.pdc-input-typing::after {
  content: '';
  display: inline-block;
  width: 1px;
  height: 15px;
  margin-left: 1px;
  vertical-align: -2px;
  background: var(--pdc-fg);
  animation: pdc-caret-blink 1.06s step-end infinite;
}

@keyframes pdc-caret-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* The starter chips leave before the question does, so the two are never on
   screen at once. Faded rather than cut — the empty state is still there
   underneath until the send beat clears it. The transition itself is declared
   on .pdc-chips in the empty-state block above, so there is one rule for the
   selector rather than two that can drift. */
.pdc-chips-out { opacity: 0; }

/* The user bubble's entrance. A class rather than a bare rule on .pdc-msg, and
   taken off again once it has played: an animation class left on a row replays
   every time that row is re-parented, and §5 rebuilds the body at the collapse. */
.pdc-msg-new { animation: pdc-msg-in 220ms ease-out both; }

@keyframes pdc-msg-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

/* The Thinking row is a body child rather than a message, so it needs the
   message rhythm spelled out. */
.pdc-msg + .pdc-thinking { margin-top: 16px; }

/* ── ported app animations ────────────────────────────────────────────────── */

/* All three are lifted from src/app/globals.css:241-314 with pdc- names.
   The registered custom property is load-bearing: an unregistered
   --pdc-thinking-angle is just a string to the engine, so it cannot be
   interpolated and the gradient sits still. */
@property --pdc-thinking-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@keyframes pdc-thinking-spin {
  to { --pdc-thinking-angle: 360deg; }
}

/* One highlight sweeping left to right with a pause, not a repeating wave —
   the bright stop sits at 25% and the back half of the gradient is flat. */
@keyframes pdc-tool-activity-shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}
.pace-dot-chat .pdc-shimmer {
  background: linear-gradient(90deg, #71717A 0%, #09090B 25%, #71717A 50%, #71717A 100%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: pdc-tool-activity-shimmer 1.8s linear infinite;
}

@keyframes pdc-tool-activity-step-in {
  from { opacity: 0; transform: translateY(3px); }
  to { opacity: 1; transform: none; }
}

/* Tailwind's stock `animate-spin`, which is what the app's Loader2 carries. */
@keyframes pdc-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .pace-dot-chat .pdc-step,
  .pace-dot-chat .pdc-step-icon[data-icon='loader2'],
  .pace-dot-chat .pdc-bubble-streaming::before,
  /* §5's own two. The timeline never runs under this preference, so neither
     class is ever applied — these are the belt to that braces, for a host page
     that reproduces the markup by hand or a preference that changes after the
     panel has already played. */
  .pace-dot-chat .pdc-input-typing::after,
  .pace-dot-chat .pdc-msg-new {
    animation: none;
  }
  .pace-dot-chat .pdc-chips { transition: none; }

  /* Not just `animation: none`: a parked shimmer leaves the label sitting on
     the left half of its gradient, which is a two-tone word the app never
     paints. Drop the clip and give the text back its own colour. */
  .pace-dot-chat .pdc-shimmer {
    animation: none;
    background: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    color: var(--pdc-fg);
  }
}
