/* console_render.css — the transcript's INTERIOR, declared once for every station.
 *
 * The conversation is ONE ARTIFACT and a workspace is a station looking at it (the
 * conversation-artifact single-transcript ADR, Decision 1). #1113 gave the artifact one
 * renderer (console_render.js); this file gives it one stylesheet (the transcript's CSS
 * goes single-origin with its markup, Decision 9). Until 2026-08-24 every rule below lived
 * TWICE, once per ticket-page workspace, under the copied-chrome allowance — and the copies
 * had already diverged in the only direction a copy can: by subtraction. Analysis carried
 * NO rule for the expandable order line, the handoff-note line, the reviewer feedback row
 * or the customer vote pill, so the day #1113 made those families render on a ticket they
 * rendered unstyled there (the order detail panel, whose fold is `display:none` until
 * `.open`, stood permanently open). Nothing could report it: each file was internally
 * consistent, and the day-divider pill had already shown the shape — one identical rule in
 * two files, one of which never emitted the markup it styled.
 *
 * ⚠ SCOPE. Every selector here hangs off `.msgs`, the transcript's root in both stations
 * (`<div class="msgs" id="msgs">`, written by each workspace), and NOT off a
 * `[data-workspace]` attribute — the whole point is that no station can own these. The
 * `.msgs` BOX itself (flex sizing, padding, its white paper) stays in each workspace: the
 * box is the station's viewport, the same split the shell makes for the zone head bars.
 *
 * ⚠ THE SPECIFICITY TRAP, which is why a check enforces the absence rather than trusting
 * it. `[data-workspace="x"] .msg` outranks `.msgs .msg`. A workspace that re-declares ANY
 * selector in this file silently wins for that station alone, and the transcript is two
 * transcripts again with this file still looking authoritative. Neither workspace
 * stylesheet may declare a transcript interior selector; `_verify_transcript_single_origin.py`
 * reads both and reddens on the first one.
 *
 * ⚠ The `.cwrap` / `.cbadge` pair is ONE visual in TWO placements (the comment badge: the
 * fourth feedback button under a Ralph answer, and the quiet `.cchip` beside a name in a
 * list row). The transcript placement is here; the LIST placement is the station's and
 * stays in its workspace under `.row`. The centring contract both share is the shell's
 * (`.count.badge, .cbadge` in console_shell.css).
 *
 * Served at /assets/render.css by console_assets.py (no-cache, like the renderer it
 * styles) and linked once from console_shell.html, before any workspace stylesheet.
 * Loaded once for every workspace, including the two that render no transcript; the
 * rules match nothing there, and the alternative — a per-workspace opt-in — is a list a
 * future station could be left off of, which is the original defect in a new coat.
 *
 * Every comment below travelled with its rule from support_workspace.css, where it was
 * written; a date or a name inside one refers to that history.
 */

  /* centered date divider between calendar-day groups */

.msgs .day-divider { text-align:center; font-size:var(--text-tiny); color:#202020; margin:6px auto 18px; max-width:820px; }

.msgs .day-divider span { background:var(--ink-0); border:1px solid var(--border); padding:4px 12px; border-radius:var(--radius-pill); }
  /* centered quiet system-event line (e.g. "… closed this conversation") */

.msgs .sys-line { text-align:right; font-size:var(--text-label); color:var(--ink-600); margin:4px auto 16px; max-width:820px; }
  /* The glass calculator's record (glass calculator record ADR, Decision 13). A BLOCK,
     not a bubble and not a one-line notice: it is a deterministic safety verdict a
     reviewer checks against the manufacturer's chart, so it gets the card's own vertical
     structure. Centered and neutral rather than sided — sides mean "who said this", and
     nobody said this. */

.msgs .glass-rec { max-width:820px; margin:8px auto 18px; border:1px solid var(--border);
    border-radius:var(--radius-surface); padding:14px 18px; background:var(--ink-0); }

.msgs .glass-eyebrow { font-size:var(--text-tiny); letter-spacing:.12em; text-transform:uppercase;
    font-weight:700; color:var(--ink-400); margin-bottom:8px; }

.msgs .glass-spec { font-size:var(--text-label); margin-bottom:10px; }

.msgs .glass-empty { font-size:var(--text-label); color:var(--ink-400); font-style:italic; }
  /* Event chrome for the `opened` / `closed` rows (three event renderings, only the
     result renders as a card — the glass calculator record ADR, Decision 19). Deliberately
     NOT a bordered card: these are timeline markers, and giving them the verdict's chrome
     would make three rows read as three verdicts. Quiet, centered, and small enough that
     a transcript scans as "card opened … card closed" with the verdict standing out
     between them. */

.msgs .glass-event { max-width:820px; margin:6px auto; font-size:var(--text-meta); color:var(--ink-400);
    text-align:center; font-style:normal; }
  /* The two event lines take the SIDE of whoever acted, upright (the opened line is
     Ralph's action, the glass calculator record ADR, Amendment 2026-09-02, Decision 22, and
     its dated note of the same day on the closed line). OPENED is Ralph's: left, in the
     same 820px column his bubbles sit in. CLOSED is the customer's: right, where their
     bubbles sit. Neither is italic — they were the only italic lines in the thread's main
     flow, and the flow is what an operator reads top to bottom (Connor, 2026-09-02, twice).
     The centred base rule above survives only for the payload-less result fallback. */

.msgs .glass-event[data-event="opened"] { text-align:left; }

.msgs .glass-event[data-event="closed"] { text-align:right; }
  /* The customer's own answers (the customer's selections are recorded, the glass
     calculator record ADR, Decision 20). Left-aligned inside the centered event line,
     because a factor/answer pair is read as a list, not as prose. */

.msgs .glass-picks-h { font-size:var(--text-tiny); letter-spacing:.1em; text-transform:uppercase;
    font-weight:700; color:var(--ink-400); margin:12px 0 5px;
    border-top:1px solid var(--border); padding-top:10px; }

.msgs .glass-picks { list-style:none; margin:4px 0 0; padding:0; font-style:normal;
    text-align:left; }

.msgs .glass-picks li { font-size:var(--text-label); padding:1.5px 0; }

.msgs .glass-pick-k { color:var(--ink-400); }

.msgs .glass-pick-k::after { content:":"; }
  /* The stress each answer contributed — the audit trail from answers to the total. */

.msgs .glass-pick-p { color:#b8860b; font-weight:700; font-size:var(--text-meta); }

.msgs .glass-picks-none { font-size:var(--text-meta); color:var(--ink-400); font-style:italic;
    margin-top:4px; }
  /* Inside a CLOSE event the pairs are not a card section — they are a footnote on the
     event line above. Constrained and centered as a group (with the text still
     left-aligned, because a label/value list is read as a list), so they read as attached
     to that line. Shipped without this they sat at the far-left of the 820px event block
     while the line itself was centered, and read as stray text. */

.msgs .glass-event .glass-picks-h, .msgs .glass-event .glass-picks { max-width:360px;
    margin-left:auto; margin-right:auto; }
  /* Under a right-aligned CLOSED line the footnote hugs the right edge with it, so it still
     reads as attached to the line above rather than floating in the middle. */

.msgs .glass-event[data-event="closed"] .glass-picks-h,
.msgs .glass-event[data-event="closed"] .glass-picks { margin-right:0; }

.msgs .glass-event .glass-picks-h { margin-top:9px; padding-top:8px; }

.msgs .glass-event .glass-picks li { font-style:normal; }

.msgs .glass-nr { font-size:var(--text-label); font-weight:700; color:var(--danger); margin-bottom:10px; }

.msgs .glass-tier { margin-bottom:10px; }

.msgs .glass-tier:last-of-type { margin-bottom:0; }
  /* The tier heading carries the customer's own wording; the color echoes the card's
     severity ramp so the two read the same at a glance. */

.msgs .glass-tier-h { font-size:var(--text-meta); font-weight:700; margin-bottom:3px; }

.msgs .glass-tier[data-tier="any_glass"] .glass-tier-h { color:var(--success); }

.msgs .glass-tier[data-tier="heat_strengthened"] .glass-tier-h { color:#b8860b; }

.msgs .glass-tier[data-tier="tempered"] .glass-tier-h { color:#c2560a; }

.msgs .glass-tier[data-tier="not_recommended"] .glass-tier-h { color:var(--ink-400); }
  /* VERTICAL, one product line per row — the whole point. A comma-run is neither what
     the customer saw nor checkable against the chart row by row. */

.msgs .glass-films { list-style:none; margin:0; padding:0; }

.msgs .glass-films li { font-size:var(--text-label); padding:2px 0 2px 10px; }

.msgs .glass-films a { color:var(--flex-blue-700, #0069a3); text-decoration:underline; }

.msgs .glass-shades { color:var(--ink-400); }

.msgs .glass-disc { font-size:var(--text-tiny); color:var(--ink-400); margin-top:10px;
    padding-top:8px; border-top:1px solid var(--border); }
  /* customer link-click event line (2026-08-05): same quiet register as .sys-line but
     tucked under its anchor bubble rather than floating between days */

.msgs .click-line { margin-top:-8px; }

.msgs .click-line a { color:var(--ink-600); text-decoration:underline; }

.msgs .click-line a:hover { color:var(--ink-900, #000); }
  /* folded navigation run (2026-08-18; a run of four or more adjacent navigation lines
     collapses to its newest three, customer presence ADR, Decision 20). Same quiet gray
     register as every nav line. The envelope and the count are the two click targets;
     the tucked pair sit indented under the envelope, and the hidden remainder shows
     only while the run carries `.open` (held in the render module across repaints). */

.msgs .nav-run { max-width:820px; margin:-8px auto 16px; }

.msgs .nav-run .nav-line { margin:4px auto 0; }

.msgs .nav-run .nav-envelope, .msgs .nav-run .nav-more { cursor:pointer; user-select:none; }

.msgs .nav-run .nav-envelope:hover, .msgs .nav-run .nav-more:hover { color:var(--ink-900, #000); }

  /* ⚠ NO right indent on the tucked block, the hidden block or the count line (Connor,
     2026-08-19, on the live pane: "They should be right aligned on the same y axis as the
     other event lines, but retain their size and color"). The 28px indent from the right
     edge (2026-08-18) is retired; the smaller 11.5px / --ink-400 register below is what
     says these are the folded lines now. */
.msgs .nav-run .nav-tucked, .msgs .nav-run .nav-hidden { padding-right:0; }

.msgs .nav-run .nav-tucked .nav-line, .msgs .nav-run .nav-hidden .nav-line, .msgs .nav-run .nav-more {
    margin-top:2px; font-size:var(--text-meta); color:var(--ink-400); }

.msgs .nav-run .nav-hidden { display:none; }

.msgs .nav-run.open .nav-hidden { display:block; }

  /* The expandable order line (2026-08-21; the expandable order line, storefront cart
     observation ADR, Amendment 2026-08-21, Decision 15). Same fold mechanics as
     .nav-run above — an `.open` class held in the render module across repaints, never
     a native <details>, which the poll tick would snap shut. The summary keeps the
     quiet right-aligned .sys-line register it already had; only the caret is new. */

.msgs .order-run { max-width:820px; margin:-8px auto 16px; }

  /* user-select:text, deliberately (the selectable order line, Connor's ask,
     2026-08-31): the operator copies order numbers and totals off these lines.
     The toggle's selection guard keeps click-to-expand working beside it. */

.msgs .order-run .order-summary { cursor:pointer; user-select:text; margin:0 auto; }

.msgs .order-run .order-summary:hover { color:var(--ink-900, #000); }

  /* The caret leads the line, on its LEFT, half again the line's size (Connor,
     2026-08-31: the right-side caret read too small and sat too far from the
     message on a right-aligned line). line-height:1 keeps the bigger glyph from
     growing the line box. The glyph and geometry are the caret family's rungs
     (the console design tokens record, its caret-family amendment 2026-08-31);
     this recipe SEEDED the family, so the rungs resolve to exactly what #1280
     shipped. The trailing " " is this site's spacing, concatenated so the glyph
     rung stays a pure glyph. */

.msgs .order-run .order-summary::before { content:var(--caret-closed) " "; color:var(--ink-400); font-size:var(--caret-scale); line-height:1; vertical-align:var(--caret-dip); }

.msgs .order-run.open .order-summary::before { content:var(--caret-open) " "; }

  /* The panel sits under the summary, hugging the same right edge the lines use, and
     narrower than the 820px block so it reads as a detail OF the line above rather than
     a new section. Smaller/--ink-400 register, the same thing that marks the folded
     navigation lines as subordinate. */
.msgs .order-run .order-detail { display:none; }

.msgs .order-run.open .order-detail {
    display:block; max-width:360px; margin:6px 0 0 auto;
    font-size:var(--text-meta); color:var(--ink-400);
    border:1px solid var(--border); border-radius:var(--radius-control); padding:8px 10px; }

.msgs .order-run .od-item, .msgs .order-run .od-money {
    display:flex; gap:8px; align-items:baseline; text-align:left; padding:2px 0; }

.msgs .order-run .od-name { flex:1 1 auto; min-width:0; overflow-wrap:anywhere; }

.msgs .order-run .od-qty { flex:0 0 auto; color:var(--ink-400); }

  /* Tabular figures so the money column lines up digit-for-digit down the panel. */
.msgs .order-run .od-price { flex:0 0 auto; font-variant-numeric:tabular-nums; }

.msgs .order-run .od-rule { border-top:1px solid var(--border); margin:6px 0; }

.msgs .order-run .od-total { color:var(--ink-600); font-weight:600; }

  /* An order placed before the capture shipped cannot expand, and says so instead of
     opening an empty panel (the no-reach-back rule, same amendment, Decision 17). One
     register quieter than the line it trails, because it is an absence, not content. */
.msgs .order-nodetail { color:var(--ink-400); font-size:var(--text-meta); }

.msgs .nav-run .nav-more { padding-right:0; }

.msgs .nav-run .nav-more:hover { text-decoration:underline; text-underline-offset:2px; }
  /* handoff-note event line (2026-08-05): the same quiet register as the click line
     above, so the timeline reads as one family of small events rather than two
     competing ones. The note body is nudged darker than the label — it is the part
     worth reading, and at --ink-400 a full sentence disappears. */


  /* presence transition lines (2026-08-06): the ONE deliberate exception to the quiet
     register above (the two transition lines take semantic color, customer presence ADR,
     Decision 13). Same two tokens as the presence dot and the list-row status spills, so
     the transcript agrees with the dot rather than inventing a third color vocabulary. */

.msgs .presence-line.is-gone { color:var(--danger); }

.msgs .presence-line.is-back { color:var(--success); }
  /* operator close event (2026-08-05): same quiet register again — the transcript's
     small events are one family (customer clicked, note added, ticket closed), and
     giving this one its own weight would make resolving a thread look louder than
     anything the customer did. */

.msgs .event-line { margin-top:-8px; text-align:center; }  /* a STAFF act (close, re-route) stays centered — right is the customer's side (Connor, 2026-08-18) */
  /* feedback lines (feedback narrates on the timeline — hallucination-handling ADR,
     Decision 19): a STAFF vote or comment takes the same centered staff-act register
     as .event-line; a customer's keeps the plain right-aligned .sys-line register.
     The inline comment body is nudged darker exactly like the note body above. */

.msgs .vote-line.staff, .msgs .comment-line.staff { margin-top:-8px; text-align:center; }

.msgs .comment-line .comment-body { color:var(--ink-800, #14181b); }
  /* locus line (2026-08-05): where the customer was standing for the turn below it —
     same quiet centered register as .sys-line, tightened toward the bubble it introduces
     so it reads as that bubble's heading rather than as a free-floating event. */

.msgs .locus-line { margin-bottom:8px; }
  /* the rule under the surface message (Connor, 2026-08-24, superseding his 2026-08-18
     short right-aligned seam): ONE full-width line, the centered surface message above
     it, the capability-button row (where present) and the customer's first bubble below
     it. He kept this line from the first deploy's accidental one — a class collision had
     drawn the header bar's border here — and dropped the 96px seam. The locus line's 8px
     above it is a THIRD of the 24px below it; that proportion is the design, so change
     both together or neither. Lives in the same 820px column as every other line. */

.msgs .head-divider { max-width:820px; height:1px; margin:0 auto 24px;
    background:var(--border-strong); }
  /* ...with ONE exception (2026-08-06): the internal-traffic locus line is the one locus
     line that is not quiet. Every other one tells an operator where a customer was standing;
     this one tells them the thread below is OURS, and an operator who skims past it reads a
     whole demo transcript as customer work. Same hue as the list row's STAFF badge, so the
     badge they scanned and the line they land on read as one signal rather than two. (That
     hue is Bohannan AI's accent blue as of 2026-08-11; it was a yellow, and the pairing with
     the badge is the invariant, not the colour.)

     A filled CHIP rather than coloured type: the original reason was that yellow text on
     white paper is unreadable at 11.5px, so "make it yellow" applied literally would have
     left this line HARDER to see than the gray it replaced. Blue ink would survive on paper
     where yellow could not, so the chip is no longer forced — it is KEPT, because the chip
     is what makes this line the one loud locus line, and that was always the point.
     ⚠ --demo-ink, not --demo-fg: --demo-fg is paper, sized for the solid badge, and on this
     8%-opacity wash it is white-on-white. */

  /* ⚠ SUPERSEDED IN PART 2026-08-24 (the opening locus line amendment, faithful transcript
     records ADR): the chip is no longer the internal line's exception — it is the HEAD's
     uniform. The thread's opening locus line ("the surface message", Connor) is a fact
     about the thread, not an act of the customer's, so it leaves the right-aligned
     customer-event register: centered, and chipped for EVERY audience, so a customer
     thread and an internal one open the same way. The ours-not-theirs warning is carried
     by the list row's STAFF badge and the sentence itself ("Internal traffic — …"), not by
     whether the box is drawn. Later locus lines (a move, a surface re-statement) are
     change events and keep the quiet right-aligned register above. */

  /* ⚠ `.opening`, not `.head`: `.thread .head` is the zone-3 header bar (flex + a bottom
     border), and this line lives inside `.thread`. The first deploy used `head` and
     inherited both — left-aligned, with a stray full-width rule (Connor's look,
     2026-08-24). */

.msgs .locus-line.opening { text-align:center; }

.msgs .locus-line.opening .ltag { display:inline-block; background:var(--demo-soft);
    border:1px solid var(--demo-line); color:var(--demo-ink); font-weight:600;
    padding:3px 10px; border-radius:var(--radius-inset); }
  /* message group: avatar gutter + bubble + metadata line below; centered in 820px */

  /* ⚠ EVERY VALUE IN THIS BLOCK IS THE CUSTOMER'S OWN CHAT PANEL, and it is pinned to
     `web_ui_app/src/lib/chatPresentation.js` by `_verify_chat_presentation.py` — the ONE
     map both panels read (console chat panel parity ADR, the map stated once, Decision 2).
     Do not tune a colour or a side here alone: this file is one of TWO console copies plus
     the widget, and the whole point of the map is that the three cannot disagree about who
     said what.

     ⚠ THE SIDES ARE NOT WHAT THIS CONSOLE SHIPPED UNTIL 2026-08-12. It rendered the
     customer LEFT and everything else RIGHT — the support-inbox convention, inherited from
     a reference model rather than from the widget — which mirrored every conversation it
     claimed to reproduce. The customer is on the RIGHT because that is where the customer
     saw themselves (parity extends past bodies, Decision 1).

     ⚠ Literal hexes, not `var(--…)`, and that is a constraint rather than sloppiness: the
     console and the widget define overlapping token names with DIFFERENT values, so a
     token here would resolve to the console's answer and quietly un-do the parity. See the
     map's header for the four measured cases.

     ⚠ EXCEPT the label's font-family, which is deliberately a token — see .agentlabel. */

/* THE TURN — the artifact's parent unit (the conversation-artifact single-transcript ADR,
   the turn-as-parent amendment 2026-08-29). A customer block plus the replies to it, with
   every trailing line anchored inside it. `820px` is the row measure; the 22px of padding
   sits OUTSIDE that, so the turn is 864 wide and the rows still centre on the same line
   they always did.

   THE RHYTHM IS THE POINT (same amendment, Decision 15): rows sit CLOSE inside a turn, turns
   sit FAR apart, so the eye finds a turn's boundary before it reads a word. It replaces one
   flat 18px on every row, which could not express that because nothing in the markup knew
   which rows belonged together.

   ⚠ THE BORDER IS ALWAYS THERE, TRANSPARENT WHEN UNSELECTED (same amendment, Decision 16).
   Selecting a turn changes colour and nothing else, so it cannot nudge the transcript — the
   property the drawn frame it replaces had to protect by hand with a separate SVG layer. */
.msgs .turn { max-width:864px; margin:0 auto 28px; padding:12px 22px;
    border:2px solid transparent; border-radius:var(--radius-surface); }

  /* ⚠ THE CONSOLE'S OWN PANEL COLOUR, AT TWICE ITS WEIGHT — `2px solid var(--border)`. The
     COLOUR is the rule on `#layout > .detail` and every zone edge (Connor, 2026-08-29: "match
     that of the borders drawing the different panels… it looks like a light gray 1px"); the
     WIDTH went back to 2px on his second look, having been 1px in between.

     ⚠ The two halves were separate calls and the split is the point, so nobody "finishes" the
     match by thinning it again. What was wrong with the original 2px BLACK was the ink: it read
     as an annotation drawn ON the transcript by something outside the console, rather than as
     part of the console's own furniture — a selected turn is a REGION of the surface, and the
     surface already has a vocabulary for dividing itself. Taking the colour fixes that. The
     weight is a separate judgement about how loudly a selection should speak, and a selection
     is a louder claim than a permanent divider, so it earns the extra pixel. */
.msgs .turn.turn-sel { border-color:var(--border); }

/* THE CONNECTOR — the only part of the selection frame still DRAWN (the per-turn analysis
   evidence ADR, selection-frame amendment 2026-08-29; the enclosure became the border above).
   It crosses out of the transcript toward the detail panel and no element spans that gap, so
   it stays an SVG: one horizontal path from the selected turn's right edge to the scroller's,
   at the turn's mid-height. Absolutely positioned inside `.msgs` and inert to the pointer, so
   it scrolls with the transcript and a click passes through it to the turn underneath.
   ⚠ It is not drawn at all when the detail panel is closed — a line pointing at a panel that
   is not there points at nothing. `paintTurnFrame()` in the workspace owns that call. */
.msgs .turn-frame { position:absolute; top:0; left:0; pointer-events:none; overflow:visible;
    z-index:2; display:block; }

  /* ⚠ The connector tracks the box's WEIGHT as well as its colour: it leaves the box's edge,
     so a 1px connector on a 2px box reads as the line thinning as it travels. */
/* A turn is CLICKABLE wherever a message is, because clicking one is how the detail panel is
   pointed at it (the named-views amendment, analysis console support-parity rebuild ADR,
   Amendment 2026-08-29). It lives here rather than in the panel's stylesheet because it
   styles a transcript node, and the transcript's CSS is single-origin. */
.msgs .msg[data-mid] { cursor:pointer; }

.msgs .turn-frame path { fill:none; stroke:var(--border); stroke-width:2px; stroke-linecap:butt; }

.msgs .turn > :last-child { margin-bottom:0; }

.msgs .mrow { display:flex; gap:11px; margin:0 auto 18px; align-items:flex-end; max-width:820px; }

.msgs .turn .mrow { margin-bottom:8px; }   /* inside a turn — see the rhythm note above */

.msgs .mrow.right { flex-direction:row-reverse; }

  /* ⚠ `.mav` (the customer's initials circle) is DELETED, not hidden — the widget shows no
     avatar on the customer's side, and the `sender • time` line below every message already
     carries the name, so it was a second copy of a fact already on screen (the avatar
     retires, Decision 4). */

.msgs .mrow .mcol { display:flex; flex-direction:column; min-width:0; flex:1 1 auto; max-width:100%; }

.msgs .mrow.right .mcol { align-items:flex-end; }

.msgs .msg { font-size:var(--text-read); line-height:1.6; color:#000; white-space:pre-wrap; word-wrap:break-word; }

  /* The customer — the ONE role with a bubble. */
.msgs .msg.user { max-width:78%; padding:12px 18px; line-height:1.55;
    background:#e6f4fb; border:1px solid #8dcdee; border-radius:var(--radius-bubble); }

  /* AI Ralph — NO bubble. Prose directly on the panel, exactly as the customer read it. */
.msgs .msg.assistant { padding:0; }

  /* A staff reply — no bubble either; the left rule and the name label above it are the
     whole distinction (the map's `agent` entry).

     ⚠ The RULE lives on the wrapper, not on `.msg`, so it spans the label AND the body —
     which is what `.ar-agent-msg` does on the widget. The label is kept OUT of `.msg`
     because Analysis resolves quote anchors against `.msg[data-mid]`'s `textContent`: an
     operator's name inside that node would shift every anchor offset in the message and
     make the name itself quotable. Support anchors nothing today and takes the same shape
     anyway, because these two files are not allowed to diverge here (Decision 8). */
.msgs .agentwrap { border-left:2px solid #4fb4e4; padding-left:12px; min-width:0; }

.msgs .msg.agent { padding:0; }

  /* ⚠ `font-family` IS A TOKEN HERE ON PURPOSE, and it is the ONE exception to the
     literals rule above. The console dropped Quantico for a single family, carrying the
     display role on weight, size and letterspacing instead (Connor, 2026-08-10 — the
     reasoning is at the --font-display token in console_shell.css, which also records that
     the widget keeps Quantico BY DESIGN). Writing the widget's stack here would reverse
     that decision through a stylesheet nobody would think to check. The map carries what
     the surfaces must AGREE on; typeface is something they have deliberately decided to
     DISAGREE on, so the map carries none and this resolves per host. */
.msgs .agentwrap .agentlabel { font-family:var(--font-display); font-weight:700;
    text-transform:uppercase; font-size:var(--text-micro); letter-spacing:.14em; color:#0082b3;
    margin-bottom:4px; white-space:normal; }
  /* ── Rendered message bodies (markdown parity, split by role — faithful transcript
     records ADR, D4) ────────────────────────────────────────────────────────────────
     `.msg` keeps white-space:pre-wrap because a USER bubble is raw text and its newlines
     must survive, exactly as the widget renders it (Message.jsx renders msg.content bare
     with pre-wrap and no markdown). An assistant/agent body is real HTML, where pre-wrap
     would double every blank line between block elements — so `.md` turns it off and the
     block elements own the spacing. Sized to sit inside the existing bubble rather than
     restyle it: no font/color overrides, only block rhythm. */

.msgs .msg .md { white-space:normal; }

  /* ── Citation marks on the selected turn (the per-turn analysis evidence ADR, Amendment
     2026-09-17, Decision 15) ──────────────────────────────────────────────────────────
     An EMPTY element painted from `data-cite`, so the bubble's textContent stays the clean
     text the Analysis anchors resolve against. The glyphs are Unicode superscripts and
     already sit raised in the font, so no vertical-align and no size change: the mark reads
     as the number it is, in the rank badge's blue, because it IS the rank badge's number in
     a second place. Lit is a fill and nothing more (design rules 7, 8 and 10). */
.msgs .msg .cite::after { content:attr(data-cite); }

.msgs .msg .cite { font-weight:700; color:var(--flex-blue-600); cursor:pointer;
    margin-left:.12em; padding:0 .1em; border-radius:var(--radius-mark); }

.msgs .msg .cite:hover, .msgs .msg .cite.lit { background:var(--attn-bg); }

.msgs .msg .md > :first-child { margin-top:0; }

.msgs .msg .md > :last-child { margin-bottom:0; }

.msgs .msg .md p { margin:0 0 9px; }

.msgs .msg .md ul, .msgs .msg .md ol { margin:0 0 9px; padding-left:20px; }

.msgs .msg .md li { margin:2px 0; }

.msgs .msg .md li > p { margin:0; }

.msgs .msg .md h1, .msgs .msg .md h2, .msgs .msg .md h3,
  .msgs .msg .md h4, .msgs .msg .md h5, .msgs .msg .md h6 {
    margin:12px 0 6px; font-size:1em; font-weight:700; line-height:1.4; }

.msgs .msg .md strong { font-weight:700; }

.msgs .msg .md code { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:.9em;
    background:rgba(0,0,0,.05); padding:1px 4px; border-radius:var(--radius-mark); }

.msgs .msg .md pre { margin:0 0 9px; padding:9px 11px; overflow-x:auto; border-radius:var(--radius-inset);
    background:rgba(0,0,0,.05); }

.msgs .msg .md pre code { background:none; padding:0; }

.msgs .msg .md blockquote { margin:0 0 9px; padding-left:11px; border-left:2px solid var(--border);
    color:var(--ink-500); }

.msgs .msg .md a { color:var(--flex-blue-600); text-decoration:underline; text-underline-offset:2px; }

.msgs .msg .md a:hover { color:var(--flex-blue-700); }
  /* A wide table scrolls inside its own container instead of blowing out the bubble —
     the console mirror of the widget's `ar-table-wrap` (Message.jsx wraps <table> for
     exactly this reason inside its much narrower panel). */

.msgs .msg .mdtable { overflow-x:auto; margin:0 0 9px; }

.msgs .msg .md table { border-collapse:collapse; font-size:var(--text-label); }

.msgs .msg .md th, .msgs .msg .md td { border:1px solid var(--border); padding:5px 9px; text-align:left;
    vertical-align:top; }

.msgs .msg .md th { background:rgba(0,0,0,.04); font-weight:700; }
  /* ── Affordances the customer was shown (affordance payload persistence, faithful
     transcript records ADR, D1) ─────────────────────────────────────────────────────
     Rendered BELOW the bubble, in the message column, matching where the widget mounts
     them under Ralph's words. Deliberately styled as what they were — a button is a
     button, a chip is a chip — because the whole point is that an operator can tell at a
     glance which affordance fired without decoding prose. Clickable and new-tab so the
     destination can be checked; `.aff-note` is the caption under a REPLICA — the handoff
     stack and the mirrored leave-a-message form — the sentence that makes a faithful copy
     read as a record rather than an offer. */

.msgs .affrow { display:flex; flex-wrap:wrap; gap:8px; margin-top:9px; }

.msgs .mrow.right .affrow { justify-content:flex-end; }

.msgs .aff-cta { display:inline-flex; align-items:center; gap:6px; padding:8px 14px; border-radius:var(--radius-control);
    background:var(--flex-blue-600); color:#fff; font-size:var(--text-label); font-weight:600; text-decoration:none;
    box-shadow:var(--shadow-1); }

.msgs .aff-cta:hover { background:var(--flex-blue-700); color:#fff; }

.msgs .aff-chip { display:inline-flex; align-items:center; padding:6px 12px; border-radius:var(--radius-pill);
    border:1px solid var(--flex-blue-200); background:var(--flex-blue-50); color:var(--flex-blue-800);
    font-size:var(--text-label); font-weight:600; text-decoration:none; }

.msgs .aff-chip:hover { background:var(--flex-blue-100); color:var(--flex-blue-900); }
  /* Inert replicas of the handoff buttons: shown so the operator knows the card was
     offered and WHICH variant (the Call button only exists in-hours), not to be used. */

.msgs .aff-inert { display:inline-flex; align-items:center; padding:6px 12px; border-radius:var(--radius-control);
    border:1px dashed var(--border); background:var(--ink-0); color:var(--ink-500);
    font-size:var(--text-label); font-weight:600; cursor:default; }

.msgs .aff-note { font-size:var(--text-tiny); color:var(--ink-400); }
  /* No `align-self` on the caption, on purpose. It carried `align-self:center` from the
     days it sat in the horizontal affordance ROW, where centring meant vertical centring
     beside a chip; inside the two COLUMNS that carry it today (the handoff stack, the
     mirrored form) the same value centred it horizontally, and the caption read as a stray
     indent under a left-aligned stack (Connor, 2026-09-05). Flush with what it labels. */
  /* ── The handoff replicas ────────────────────────────────────────────────────────
     ⚠ `.aff-inert` above is RETIRED as the handoff rendering (2026-08-21) and kept only
     for any other caller: the replicas now reproduce the customer's actual buttons (the
     mounted affordance joins the shared map, console chat-panel parity ADR, Amendment
     2026-08-21, Decision 9). A dashed gray chip reading "Call" could not tell an operator
     whether a phone number had been offered or whether we simply did not know.

     ⚠ THESE RULES CARRY LAYOUT ONLY. Every colour, radius, padding and weight arrives as
     an INLINE STYLE from the shared schema module, because the widget and the console
     define overlapping token names with different values and a `var()` in a shared rule
     resolves against whichever host renders it (the explicit-values rule, same ADR,
     Decision 6). Adding a colour here would fork the replica from the button it
     reproduces — quietly, and only on this surface.

     Stacked and LEFT-aligned: the customer read them top to bottom beneath Ralph's words,
     and Ralph sits on the left here. */

.msgs .aff-handoff { display:flex; flex-direction:column; align-items:flex-start; gap:7px; }

.msgs .aff-handoff-btns { display:flex; flex-direction:column; align-items:flex-start; gap:8px; }

.msgs .aff-handoff-dismiss { display:block; }
  /* Unmistakably NOT pressable — no pointer, no hover, nothing announced as actionable.
     This is how the replica honours "never a false affordance" in both directions at
     once: it looks like what the customer saw, and it does not offer the operator a
     control that would do nothing (same amendment, Decision 10). */

.msgs .aff-replica { cursor:default; user-select:text; }
  /* ── The mirrored leave-a-message form ───────────────────────────────────────────
     The customer's submission, rendered as the form they actually filled (a
     leave-a-message submission is stamped on the user turn, faithful transcript records
     ADR, Amendment 2026-08-21, Decision 17). Sits on the CUSTOMER'S side — it is their
     act — and REPLACES the bubble rather than sitting beside one, which rendered the
     message twice. */

.msgs .formcard { display:inline-flex; flex-direction:column; gap:6px; margin-top:8px;
    padding:11px 13px; border:1px solid var(--border); border-radius:var(--radius-control);
    background:var(--ink-0); max-width:78%; text-align:left; }

.msgs .mrow.right .formcard { align-self:flex-end; }

.msgs .fc-title { font-size:var(--text-label); font-weight:700; color:var(--ink-800); }

.msgs .fc-rows { display:flex; flex-direction:column; gap:3px; }

.msgs .fc-row { font-size:var(--text-label); color:var(--ink-800); line-height:1.5; }

.msgs .fc-label { color:var(--ink-500); font-weight:600; }

.msgs .fc-value { white-space:pre-wrap; }
  /* The message value doubles as the quote-anchor node, so it carries `.msg` — which
     brings the bubble's 15px type with it. Scoped back to the card's own size here;
     `.msg` bare has no fill or border, so nothing else leaks in. */

.msgs .formcard .msg { font-size:var(--text-label); line-height:1.5; }
  /* The photos the card CANNOT show. They ride the team's ticket email and are stored
     nowhere (no durable storage, leave-a-message photo-attachments ADR, D2), so the count
     is named rather than the card sitting silent and implying none were sent. */

.msgs .fc-photos { font-size:var(--text-meta); color:var(--ink-500); font-style:italic; }
  /* An email send is OUR act, so it takes the centered staff-act register; a control
     press is the customer's and keeps the plain right-aligned .sys-line one (the register
     paragraph, customer presence and reply delivery ADR, Decision 21). */

.msgs .send-line.staff { margin-top:-8px; text-align:center; }
  /* Internal-team reviewer flag (the internal-team console reviewer flag — hallucination-
     handling ADR). Copy + thumbs under each AI Ralph answer. Kept VISUALLY identical to the
     customer surfaces' shared React row (web_ui_app/src/components/feedback/FeedbackButtons.jsx):
     same 14px icons + SVG paths, same 4px pad / 12px gap / 8px top, both thumbs active in
     flex-blue-600 and fill-on-active. Structural DRY isn't possible across the React app and
     this vanilla console, so if you change one, mirror the other. */

.msgs .fbrow { display:flex; gap:12px; margin-top:8px; }

.msgs .mrow.right .fbrow { justify-content:flex-end; }

.msgs .fbbtn { display:inline-flex; align-items:center; justify-content:center; padding:4px;
    border:none; background:none; border-radius:var(--radius-inset); color:var(--ink-400); cursor:pointer;
    transition:background .12s, color .12s; }

.msgs .fbbtn:hover { background:var(--hover); color:var(--ink-700); }

.msgs .fbbtn svg { width:14px; height:14px; fill:none; }

.msgs .fbbtn.on { color:var(--flex-blue-600); }

.msgs .fbbtn.on svg { fill:currentColor; }
  /* Comment-thread icon + counter (shared reviewer comment threads — hallucination-
     handling ADR, Decisions 12-14). ONE visual, two placements (DRY via
     commentBadgeHtml): the fourth .fbbtn under a Ralph answer (interactive, opens the
     thread) and the quiet .cchip beside the customer name in list rows (indicator).
     The count badge rides the icon's top-right corner; zero renders no badge. */

.msgs .cwrap { position:relative; display:inline-flex; }

  /* ⚠ `line-height:14px` + `text-align:center` USED TO CENTRE THIS BADGE'S DIGIT and are
     GONE. They were the console's SECOND way of centring a number in a badge — the rail
     count used flexbox — and two mechanisms is exactly how the two drifted. Centring now
     comes from the counter-badge contract in console_shell.css, for every badge.
     ⚠ Note this badge was the one that had it RIGHT, by accident: pinning line-height to
     its own height is what the rail count was missing. It still loses its local copy,
     because the value being correct here is not the same as the rule being owned here.
     ⚠ padding-left/right rather than the `padding` shorthand — vertical padding shifts the
     digit against a fixed height. What stays is genuinely this badge's own: where it
     hangs, how big it is, what colour. */
.msgs .cbadge { position:absolute; top:-6px; right:-8px; min-width:14px; height:14px;
    padding-left:3px; padding-right:3px;
    border-radius:var(--radius-pill); background:var(--flex-blue-600); color:#fff; font-size:var(--text-micro); font-weight:700;
    font-family:var(--font-body); }

  /* Customer down-vote marker: a quiet red pill in the action row (persisted vote
     state) — the customer's own thumb, distinct from the team's interactive thumbs. */

  /* The jump-to-bottom control (the conversation discussion ADR, Amendment 2026-09-10,
     Decision 18): a sticky, zero-height LAST child of the scroller, so it floats above the
     last row without taking a row's space and survives the poll's `#turns` repaint. The
     wrap is centred on the transcript's 820px measure like every row; the pill is white
     with a hairline border (design rules 4 and 8) and the shared one-pixel lift `--shadow-1`
     (6%, the resting gutter card's — Connor's call over the 13% cap, 2026-09-10). Label
     size, not prose: the control is glanced at, not read (design rule 5). */
.msgs .jump-wrap { position:sticky; bottom:14px; height:0; max-width:820px; margin:0 auto;
    display:flex; justify-content:center; align-items:flex-end; pointer-events:none; z-index:3; }
.msgs .jump-wrap[hidden] { display:none; }
.msgs .jump { pointer-events:auto; display:inline-flex; align-items:center; gap:6px;
    padding:6px 12px; border-radius:var(--radius-pill); background:#fff; color:var(--ink-900);
    border:1px solid var(--border-strong); font-size:var(--text-label); font-weight:600;
    font-family:var(--font-body); cursor:pointer; box-shadow:var(--shadow-1); }   /* the resting gutter card's 6% lift (Connor, 2026-09-10), never the cap */
.msgs .jump:hover { border-color:var(--ink-900); }
.msgs .jump .jump-arrow { font-weight:400; }

.msgs .cvote { display:inline-flex; align-items:center; gap:4px; padding:2px 8px; border-radius:var(--radius-pill);
    background:var(--danger-soft); color:var(--danger); font-size:var(--text-tiny); font-weight:600; }

.msgs .cvote svg { width:11px; height:11px; stroke:currentColor; fill:none; stroke-width:1.8;
    stroke-linecap:round; stroke-linejoin:round; }
  /* metadata line below each bubble: "Flexfilm team • 5:04 PM" */

.msgs .mmeta { font-size:var(--text-tiny); color:#202020; margin-top:5px; padding:0 2px; }
  /* The per-bubble translation tag (staff conversation translation ADR): marks a
     bubble whose text is the cached English rather than the original. Quieter than
     .mmeta on purpose — it is a provenance note, not furniture the eye needs. */

.msgs .trtag { font-size:var(--text-micro); color:var(--ink-500, #6b7280); font-style:italic;
    margin-top:3px; padding:0 2px; letter-spacing:.02em; }

  /* The labeled legacy-snapshot fallback (the review ticket sheds its transcript — the
     rebuild ADR, Decision 2): a conversation with no messages rows renders the ticket's
     stored facts WITH this fidelity note, never silently.
     ⚠ --demo-ink, not --demo-fg — paper ink on this tint is white-on-white; and this note is
     a whole PARAGRAPH, so the unreadable version would be the largest such block on screen. */

.msgs .snapshot-note { max-width:820px; margin:0 auto 18px; padding:10px 14px; border:1px solid var(--demo-line);
    background:var(--demo-soft); color:var(--demo-ink); border-radius:var(--radius-control); font-size:var(--text-label); }
