/* =============================================================================
   The client chat dock — the one chat surface a page that talks to a client
   carries. A bubble at the lower right with the count of replies owed, and a
   372px panel that reads and replies in place, so the thing being discussed
   stays on screen while it is discussed. Engine: js/chat-dock.js.
   The message bubbles themselves (.chat, .chat-msg, .chat-compose-*) are the
   shared rules in css/pages/quotes.css; this file only re-grounds them for the
   dock's grey stream.
   Graduated from css/_talk-now.css (the exploration keeps its own copy until it
   closes). Loaded by every portal shell page.
   ============================================================================= */

/* ---- THE DOCK -------------------------------------------------------------- */

.cf-dock {
  position: fixed;
  right: 22px;
  /* 42, not 22: the page footer is 56px tall and puts the copyright at the bottom
     right, exactly where a 44px bubble 22px off the floor sits. Measured at
     1065×801 — the line occupied y 766–781 and the bubble 735–779, overlapping by
     13px whenever the page was scrolled to its end. 20px of lift puts the bubble
     clear of the text with 7px to spare, without moving it far enough off the
     corner to stop reading as a dock. */
  bottom: 42px;
  z-index: 191;               /* over .newmsg's 190, so an open conversation covers the
                                 news; still under .modal-overlay's 200, a dialog covers both */
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
}

/* 7px, not a circle. §2 exempts a true circle from the cap, but taking the
   exemption would have made this the only round control in the portal — the
   convention's shape, not this platform's. It is the same box everything else
   is, at the size a dock needs to be seen at. */
.cf-bubble {
  position: relative;
  width: 68px; height: 44px;
  border-radius: 7px;
  border: 0;
  background: var(--color-info);
  color: #fff;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 20px rgba(16, 24, 40, 0.26), 0 2px 6px rgba(16, 24, 40, 0.18);
  transition: background 140ms ease, transform 140ms ease;
}
.cf-bubble:hover { background: var(--color-info-ink); transform: translateY(-1px); }
.cf-bubble:focus-visible { outline: none; box-shadow: 0 0 0 4px rgba(76, 116, 166, 0.40), 0 6px 20px rgba(16, 24, 40, 0.26); }
.cf-bubble svg { width: 24px; height: 24px; }

/* Mark then glyph, side by side, centred as a pair with a hairline between them
   so they read as two things and not one smudge. The mark is 21:29, so it is
   sized on HEIGHT and left to find its own width — a square box would squash
   it; the glyph is square and takes the box. Both sit a touch under the 24px a
   lone icon would take, because two marks at full size fill the button. */
.cf-bubble { gap: 6px; }
.cf-bubble .cf-mark {
  height: 19px; width: auto; display: block; flex: none;
  padding-right: 6px; border-right: 1px solid rgba(255, 255, 255, 0.30);
}
/* The glyph is set a shade under the mark's height, not level with it: it is a
   wide two-bubble shape and reads heavier than the slim mark at equal size, so
   matching the numbers would let the borrowed icon out-shout the brand. */
.cf-bubble .cf-glyph { display: block; flex: none; }
.cf-bubble .cf-glyph,
.cf-bubble svg.cf-glyph { width: 17px; height: 17px; }

/* The count rides the corner. The number is replies owed, which §19 files under
   warn (someone owes an action), so it is amber — but the amber the SIDEBAR uses
   for a queue somebody must work (#F1C452, .nav-badge--alert), not the pale
   .badge-warn wash. On a cobalt bubble at the edge of the screen a beige tint and
   a brown ink read as a smudge; this is the one number on the page nobody should
   have to look for. Same hex as the rail badge it now replaces. 5px, not a pill. */
.cf-count {
  position: absolute;
  top: -11px; right: -3px;   /* half the badge (22.7px) rides above the button's top edge */
  min-width: 21px;
  padding: 1px 5px;
  border-radius: 5px;
  background: #F1C452;
  color: #1F2937;
  font-family: 'Hanken Grotesk', sans-serif;
  font-size: 11.5px; font-weight: 700;
  line-height: 1.45;
  text-align: center;
  border: 2px solid var(--color-bg);
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.30);
}
/* Nothing owed, nothing to shout: the count goes rather than reading "0". */
.cf-bubble[data-empty] .cf-count { display: none; }

.cf-panel {
  width: 372px;
  max-width: calc(100vw - 44px);
  max-height: calc(100vh - var(--header-h) - 44px);   /* never past the window; the stream gives */
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 7px;
  box-shadow: 0 18px 48px rgba(16, 24, 40, 0.24), 0 2px 8px rgba(16, 24, 40, 0.10);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: cf-rise 150ms ease-out;
}
.cf-panel[hidden] { display: none; }
@keyframes cf-rise { from { transform: translateY(10px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .cf-panel { animation: none; } }

.cf-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
  padding: 11px 12px 11px 14px;
  background: var(--color-info);
  color: #fff;
}
.cf-head h2 { margin: 0; font-size: 14px; font-weight: 600; }
.cf-head-sub { font-size: 11.5px; opacity: 0.82; margin-top: 1px; }
.cf-x {
  flex: none;
  width: 26px; height: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 5px;
  background: rgba(255, 255, 255, 0.14);
  color: #fff; cursor: pointer;
}
.cf-x:hover { background: rgba(255, 255, 255, 0.26); }
.cf-x svg { width: 15px; height: 15px; }

/* ---- THE MESSAGES THEMSELVES ----------------------------------------------
   Graduated from css/pages/quotes.css, where they were written for the quote
   page's slide-over and stayed when the panel moved to the shell. That left them
   on three pages out of ninety-one: everywhere else the stream rendered with no
   bubble, no padding and a full-size layers icon, because the rules simply were
   not on the page. The exploration pages keep quotes.css's copy until they close.

   TWO SIDES, TOLD APART BY GROUND AND ALIGNMENT and nothing else — no tails, no
   avatars, nothing that costs width in a panel that is mostly quoted material
   names. Neither side is white: the panel already is (memory.md §13), so the
   client takes the control ground every nested box takes and the rep takes the
   primary tint that means "us" everywhere else on the platform.
   -------------------------------------------------------------------------- */

.chat { display: flex; flex-direction: column; gap: 10px; }
.chat-day {
  align-self: center; margin: 6px 0 2px;
  font-size: 11px; color: var(--color-text-muted);
}
.chat-msg {
  max-width: 88%;
  padding: 8px 11px 9px;
  border: 1px solid var(--color-control-border);
  border-radius: var(--radius);
  background: var(--color-control);
}
.chat-msg--us {
  align-self: flex-end;
  background: var(--color-primary-50);
  border-color: var(--color-primary-50);
}
.chat-meta { display: flex; align-items: baseline; gap: 8px; }
.chat-who { font-size: 12px; font-weight: 600; }
.chat-time { font-size: 11px; color: var(--color-text-subtle); }
/* WHAT THE MESSAGE IS ABOUT reads as a label on the message, not as a line of
   it: it is the one part a person scans for when they are looking for what was
   said about one material. */
.chat-re {
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 4px;
  font-size: 11px; color: var(--color-text-muted);
}
.chat-re svg { width: 12px; height: 12px; }
.chat-text { margin: 4px 0 0; font-size: 13px; line-height: 1.5; }

/* The composer is a grid, not a stack. Send used to have a row of its own under
   the box, which cost the stream the button's height plus a gap for a control
   that needs neither — nothing sits beside it and nothing ever will. Beside the
   box instead, bottom-aligned with it, and the conversation above gets that
   height back. About keeps a full row: it is a label and a select, and squeezing
   a third thing onto that line would take the width back off the box the same
   change just gave height to. */
.chat-compose-row { display: flex; align-items: center; gap: 8px; }
.chat-compose-l { font-size: 12px; color: var(--color-text-muted); }
.chat-compose-row .select { flex: 1; }

/* One height, not a mode. The stream holds the whole conversation because
   nothing else does; on a short screen it shrinks and the composer keeps its
   room. */
.cf-body {
  padding: 12px 13px;
  overflow-y: auto;
  background: var(--color-bg);
  min-height: 0;
  flex: 0 1 420px;
}

/* TWO SIDES ON A GREY GROUND. The stream sits on the page ground, not on the
   panel's white, so the pair quotes.css chose for the white slide-over — control
   grey for the client, primary-50 for the rep — lands within a few shades of the
   ground and neither reads as a box. memory.md §13 the other way up: on a ground
   that already has its own colour the boxes go white, so the client's message
   sits on the grey the way a card sits on the page, and the rep's takes a primary
   tint deep enough to be a colour rather than a hint. Border follows fill on the
   rep's side, as in quotes.css. */
.cf-body .chat-msg {
  background: var(--color-surface);
  border-color: var(--color-border);
}
.cf-body .chat-msg--us {
  background: color-mix(in srgb, var(--color-primary) 25%, var(--color-surface));
  border-color: color-mix(in srgb, var(--color-primary) 25%, var(--color-surface));
}

.cf-compose {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 7px 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--color-border);
  background: var(--color-surface);
}
/* About spans the pair; the box and the button share the line below it. */
.cf-compose .chat-compose-row { grid-column: 1 / -1; }
.cf-compose .textarea { font-size: 13px; }
/* A one-child wrapper now, kept because it is the engine's own markup and not
   worth a second edit to delete. It used to hold a line telling the writer the
   client would be notified — true, and true of every message ever sent from
   here, which is what made it furniture rather than information. */
.cf-send { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }

/* ---- THE CHANNEL RAIL -----------------------------------------------------
   The panel stops being one conversation and becomes several. A salesperson has
   more than one client writing at once, from five different places, so the panel
   carries them all in a column at its left and the header says which one is open.

   .cf-panel keeps its border, radius, shadow and entrance; what changes is that
   its children now sit in a row, and the header / stream / composer stack moves
   into .cc-main at the 372px it has always had. The rail is 280px on top of that,
   which is what lets a row carry a face, a full name and the reference under it
   without a single one of them being cut.
   -------------------------------------------------------------------------- */

.cc-panel {
  flex-direction: row;
  width: auto;                       /* the rail's width plus the stream's */
  /* The panel shares the seat with the doorway that opens it, so it cannot claim
     the doorway's room: 42px below the panel, 12px of gap, the bubble's 44px, and
     22px of margin at the top. Lifting the dock 20px takes 20px off this. */
  max-height: calc(100vh - var(--header-h) - 120px);
}
/* 460, not 372. 372 was the width of the old single-conversation dock, kept when
   the rail was added beside it — so the column you actually TYPE in never grew,
   it only gained a neighbour. A message of any length wrapped every seven or
   eight words. The rail is unchanged at 280: it holds names, and names did not
   get longer. */
.cc-main {
  width: 460px;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The rail's own column: a head that names it, and the list scrolling under that
   head rather than past it. It sits on the alt ground — a column of things to
   choose between is not the surface being read. */
.cc-side {
  flex: none;
  width: 280px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--color-surface-alt);
  border-right: 1px solid var(--color-border);
}

/* IT KEEPS THE PANEL HEAD'S LINE. 61px is what .cf-head computes to — 11px of
   padding either side of a 14px title over an 11.5px subtitle — and this matches
   it so the rule under this label meets the bottom edge of the blue header
   exactly, rather than a few pixels off it. Measured, not guessed; if .cf-head's
   type or padding changes, this number follows it. The label is quiet on purpose:
   the panel head names the client, and a second loud title beside it would argue
   with it. */
.cc-rail-head {
  flex: none;
  display: flex;
  align-items: center;
  min-height: 61px;
  padding: 0 14px;
  border-bottom: 1px solid var(--color-border);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-text-subtle);
}

.cc-rail {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

/* A row is a button, not a link — it changes what the panel shows, it does not go
   anywhere. */
.cc-row {
  position: relative;
  /* Two lines, three columns: the tile spans both, the name and the reference take
     the middle, and the right-hand column carries WHEN over HOW MANY. A grid and
     not nested flex boxes because the count has to leave that column entirely at
     narrow widths and ride the tile's corner instead. */
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  column-gap: 10px;
  row-gap: 2px;
  align-items: center;
  width: 100%;
  padding: 11px 12px 11px 14px;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
  transition: background 120ms ease;
}
/* The list ends where the rows end; a rule under the last one draws a line across
   nothing. */
.cc-row:last-child { border-bottom: 0; }
.cc-row:hover { background: var(--color-control); }
.cc-row:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }

/* THE OPEN CHANNEL TAKES THE HOUSE SELECTED GROUND — primary-50 under a primary-700
   name, the same pair .subtab.active and .fchip.on use. It used to take the panel's
   white, which against the rail's own #F5F8FA was a two-percent step: the marker on
   the left edge was doing the whole job, and hover (--color-control) read louder than
   the row that was actually open. The tint sits clear of both grounds, and the name
   colour keeps it apart from hover. */
.cc-row.is-active { background: var(--color-primary-50); }
.cc-row.is-active .cc-name { color: var(--color-primary-700); }
.cc-row.is-active::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--color-primary);
}

.cc-name {
  grid-column: 2; grid-row: 1;
  font-size: 13px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cc-src {
  grid-column: 2; grid-row: 2;
  font-size: 11px;
  color: var(--color-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* WHEN THE CLIENT LAST WROTE — not when anyone last did. A conversation you
   answered an hour ago and one that has been waiting two days are the same row
   without this, and which is which is the first thing you need from a list of
   people waiting. Tabular figures so the column does not jitter between rows. */
.cc-when {
  grid-column: 3; grid-row: 1;
  justify-self: end;
  font-family: 'Hanken Grotesk', sans-serif;
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  color: var(--color-text-subtle);
  white-space: nowrap;
}
/* THE FACE COMES FIRST, and at 280px it costs the name nothing: Riverside Stone &
   Tile fits beside a 34px tile with room to spare, which it did not at 140px. The
   tile is what makes the column scannable — you find a client by their initials
   before you have read anything. */
.cc-ava {
  grid-column: 1; grid-row: 1 / span 2;
  display: inline-flex;
  width: 34px; height: 34px;
  font-size: 12px;
}

/* A row's count is that client's own replies owed; the bubble's is all of them
   added up. Same amber, one size down. */
.cc-n {
  grid-column: 3; grid-row: 2;
  justify-self: end;
  min-width: 19px;
  padding: 1px 5px;
  border-radius: 5px;
  background: #F1C452;
  color: #1F2937;
  font-family: 'Hanken Grotesk', sans-serif;
  font-size: 11px; font-weight: 700;
  text-align: center;
}
/* Nothing owed, nothing to shout — as on the bubble, the count goes rather than
   reading zero. */
.cc-row:not(.has-n) .cc-n { display: none; }

/* NARROW: THE NAMES GIVE WAY BEFORE THE PANEL DOES. 280 + 372 + borders is 654,
   which needs 698px of window; below that the rail keeps its place but drops to
   the tiles alone, and the conversation takes whatever is left. The channels stay
   visible at every width — losing the column is losing the point of the panel. */
@media (max-width: 740px) {
  /* Take the room rather than shrink-wrapping to the composer: with the rail down
     to 56px the conversation should have everything left, not whatever its widest
     control happens to need. */
  .cc-panel { width: calc(100vw - 44px); }
  .cc-side { width: 56px; }
  .cc-rail-head { display: none; }   /* no room for a word, and the tiles say it */
  .cc-rail { align-items: center; }
  .cc-main { width: auto; flex: 1 1 auto; }
  .cc-row { display: flex; justify-content: center; padding: 9px 0; }
  .cc-name, .cc-src, .cc-when { display: none; }
  .cc-n {
    position: absolute;
    top: 4px; right: 5px;
    min-width: 16px;
    padding: 0 3px;
    font-size: 10px;
    border: 2px solid var(--color-surface-alt);
  }
  .cc-row.is-active .cc-n { border-color: var(--color-primary-50); }
}

/* ---- THE CHAT EVERY PAGE CARRIES ------------------------------------------
   The dock above belongs to the page that IS the conversation — a quote — where
   the panel opens over the thing being discussed. This is what every page in the
   portal carries, that one included: the bubble with the number of clients
   waiting for a reply, and above it the card for the newest message — who wrote,
   where they wrote from, what they said, and the way to answer them.

   IT IS ALWAYS THERE. The bubble does not arrive, does not put itself away and
   is on no clock. A chat that is on a page sometimes and not others is one the
   user has to go looking for, which is the thing this replaces.

   Only the user puts the card away, with the ×, and only the bubble brings it
   back. Nothing but a reply clears the count: closing the card, reading it or
   walking past it are not answers. Engine: js/new-message.js.
   -------------------------------------------------------------------------- */

.newmsg {
  position: fixed;
  right: 22px;
  /* 42, not 22: the page footer is 56px tall and puts the copyright at the bottom
     right, exactly where a 44px bubble 22px off the floor sits. Measured at
     1065×801 — the line occupied y 766–781 and the bubble 735–779, overlapping by
     13px whenever the page was scrolled to its end. 20px of lift puts the bubble
     clear of the text with 7px to spare, without moving it far enough off the
     corner to stop reading as a dock. */
  bottom: 42px;
  z-index: 190;               /* the dock's seat, one layer under it — an open panel covers
                                 the news, and a dialog covers them both */
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
}
/* A flex box ignores the attribute on its own, and the card inside is switched
   by `hidden`, so it needs saying. The root never is: the bubble is permanent. */
.newmsg [hidden] { display: none; }

.newmsg__card {
  width: 340px;
  max-width: calc(100vw - 44px);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 7px;
  box-shadow: 0 18px 48px rgba(16, 24, 40, 0.24), 0 2px 8px rgba(16, 24, 40, 0.10);
  animation: cf-rise 150ms ease-out;   /* the panel's own arrival, above */
}
@media (prefers-reduced-motion: reduce) { .newmsg__card { animation: none; } }

/* WHERE IT CAME FROM, FIRST. Five places in the portal can produce a client
   message — a quote, a showroom, a campaign, the showroom, a concierge session
   — and which one it is decides what the message even means before the words are
   read. It is a category, not a state, so it is grey in every case (memory.md
   §19): the icon tells the five apart, the word removes the doubt, and no source
   gets to look more urgent than another. The band also gives the close its home,
   which leaves the name row to the name. */
.newmsg__src {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 7px 6px 12px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface-alt);
  border-radius: 7px 7px 0 0;
  font-size: 11.5px; font-weight: 600;
  color: var(--color-text-muted);
}
.newmsg__src [data-newmsg-source] { flex: 1; min-width: 0; }
.newmsg__ico { display: inline-flex; flex: none; color: var(--color-text-subtle); }
.newmsg__ico svg { width: 14px; height: 14px; }

.newmsg__head { display: flex; align-items: center; gap: 10px; padding: 11px 13px 0; }
.newmsg__ava { width: 30px; height: 30px; font-size: 11px; }
.newmsg__who { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.newmsg__name { font-size: 13.5px; font-weight: 600; color: var(--color-text); }
/* What it is about goes UNDER the name rather than beside it — a client, a
   reference and a material on one line is one crowded line. */
.newmsg__re {
  font-size: 11px;
  color: var(--color-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.newmsg__x {
  flex: none;
  width: 26px; height: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 5px;
  background: transparent;
  color: var(--color-text-subtle);
  cursor: pointer;
}
.newmsg__x:hover { background: var(--color-surface-alt); color: var(--color-text); }
.newmsg__x svg { width: 15px; height: 15px; }

/* The message itself, in the page's ink and on the card's own ground: it is the
   one thing here to read, and a box around it would make it a quotation. */
.newmsg__text { margin: 8px 13px 0; font-size: 13px; line-height: 1.5; color: var(--color-text); }

.newmsg__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 11px 12px 11px 13px;
}
.newmsg__when { font-size: 11px; color: var(--color-text-subtle); }

/* The card is already inside the window at 375px (340 capped at 100vw − 44), so
   only the two things a finger has to hit change. */
@media (max-width: 768px) {
  .newmsg__x { width: 44px; height: 44px; }
  .newmsg__foot .btn { min-height: 44px; }
}


/* ---- THE DARK THEMES ------------------------------------------------------
   The chat is there when it is needed and quiet when it is not, so on a dark
   page it does not get to be the brightest thing on the screen. Cobalt on
   near-black was: --color-info resolves to #5B8CFF on BOTH dark themes, so the
   dock lit up like a call-to-action on every page, and Elegant — a neutral grey
   theme whose own accent is the far darker #2F6FEB — was wearing Evening's blue.

   The ground is --color-primary-50, which is each theme's own dark end of its
   accent family and therefore already the right answer twice: #1D2637, a dark
   blue, on Evening; #383838, a dark neutral grey, on Elegant. One rule, no per
   theme value to keep in step, and it says the intention rather than a hex.

   What carries the dock now is the white mark on it, not the fill behind it —
   which is the rule the rest of the platform already follows on these themes
   (Elegant states it outright: hue is reserved for marks). White on both
   grounds is 15.2:1 and 11.7:1, so the marks got LOUDER by this change, not
   quieter. The light theme is untouched: it keeps the cobalt that reads
   correctly on a white page, where a pale ground is what recedes.
   -------------------------------------------------------------------------- */
:is([data-theme="evening"], [data-theme="elegant"]) .cf-bubble,
:is([data-theme="evening"], [data-theme="elegant"]) .cf-head {
  background: var(--color-primary-50);
}

/* A border, because the shadow beneath cannot do this job here: it is
   rgba(16,24,40,…), a navy-black authored to fall on a white page, and on a
   #0B0E14 ground it is invisible. Without an edge the dock reads as a smudge
   rather than a control. --color-border-strong is the edge every other raised
   thing on these themes takes. */
:is([data-theme="evening"], [data-theme="elegant"]) .cf-bubble {
  border: 1px solid var(--color-border-strong);
}

/* Hover lifts within the family instead of jumping out of it. --color-info-ink
   is #8AB0FF and #8FB4FF on these themes — a PALE blue, which on hover would
   have put back exactly the glare this block removes, and put it on the grey
   theme too. --color-border-strong is one step up the same ramp: #2A344A and
   #4A4A4A, still 12.4:1 and 8.9:1 under the white marks. */
:is([data-theme="evening"], [data-theme="elegant"]) .cf-bubble:hover {
  background: var(--color-border-strong);
}

/* The ring was rgba(76,116,166,0.40) — the LIGHT theme's primary, hardcoded, so
   a keyboard user on either dark theme got a focus ring in a colour from a
   palette they are not looking at. Each theme's own accent instead. */
:is([data-theme="evening"], [data-theme="elegant"]) .cf-bubble:focus-visible {
  box-shadow: 0 0 0 4px color-mix(in oklab, var(--color-primary) 45%, transparent);
}

/* THE PANEL AND THE POP-UP GET AN EDGE, because their shadow is not merely weak
   here — it is inverted. rgba(16,24,40,…) is the triple (16,24,40) and Evening's
   page is (11,14,20): the shadow is LIGHTER than the ground in every channel, so
   it composites to #0C1019 on #0B0E14 and lifts the page instead of casting on
   it. On Elegant it lands at 1.0:1, and tints a theme whose whole argument is
   that area carries no hue.

   Black at least falls the right way, but on near-black it can only ever be
   worth a hundredth, so the border is what actually separates the panel from the
   page — --color-border-strong takes the edge from 1.38:1 to 1.55:1 on Evening
   and 1.34:1 to 2.02:1 on Elegant. It matters more here than on a white page:
   the stream inside is --color-bg, the SAME value as the page behind it, so
   without a readable edge the panel has nothing else marking where it stops. */
:is([data-theme="evening"], [data-theme="elegant"]) :is(.cf-panel, .newmsg__card) {
  border-color: var(--color-border-strong);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.35);
}
:is([data-theme="evening"], [data-theme="elegant"]) .cf-bubble {
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
}

/* ---- WHICH SIDE THE DOCK SITS ON -------------------------------------------
   The bubble can be dragged across the window, and the side it is dropped on is
   where it sits on EVERY page from then on (localStorage, js/client-chat.js
   SIDE_KEY). Only the SIDE is remembered, not a free position: the bubble, the
   card and the panel are one stack, and a 372px panel dropped at an arbitrary
   point is a panel hanging half off the screen. The other axis is the block
   below: a corner, never a coordinate.

   LEFT MEANS THE CONTENT'S LEFT EDGE, NOT THE WINDOW'S. The sidebar is a grid
   column rather than a fixed layer, so a dock at left:22px would paint over the
   navigation instead of beside it. It clears the rail at whatever width the rail
   is currently wearing: 174px normally, 64px when the rail is collapsed by hand
   or by a tablet-width window. */
.newmsg--left {
  --newmsg-rail: var(--sidebar-w);
  right: auto;
  left: calc(var(--newmsg-rail) + 22px);
  align-items: flex-start;
}
@media (max-width: 900px) {
  .newmsg--left { --newmsg-rail: var(--sidebar-w-collapsed); }
}
/* The dock is a SIBLING of .app, so the collapsed rail cannot reach it by
   cascade. If :has() is ever unavailable the dock simply keeps the wider offset
   and stands clear of the rail - a gap, never an overlap. */
body:has(.app.collapsed) .newmsg--left { --newmsg-rail: var(--sidebar-w-collapsed); }

/* ON THE LEFT THE STACK HAS THE RAIL'S WIDTH LESS ROOM, AND HAS TO BE TOLD.
   Both widths below are written as "the window, less the two 22px margins",
   which is true only against a window edge: measured on the left at 375px the
   panel started at 86 and ended at 417, 42px past the screen. It is one cap for
   both because the card and the panel share the seat and the arithmetic. */
.newmsg--left .cf-panel,
.newmsg--left .newmsg__card { max-width: calc(100vw - var(--newmsg-rail) - 44px); }

/* ---- THE FOUR SEATS --------------------------------------------------------
   The floor on the left, the floor on the right, the header bar, and the sidebar
   just above Live Support - nothing between them, because the bubble, the card
   and the panel are one stack, and a 372px panel dropped at an arbitrary point is
   a panel hanging half off the screen. The top corners are not seats: the page
   title sits in the left one and the page's setting buttons in the right. Stored
   as two keys (js/client-chat.js SIDE_KEY, VPOS_KEY = bar | rail | bottom).

   THE BAR AND THE SIDEBAR ARE DIFFERENT IN KIND: neither has a side. See their
   own blocks below. */

/* ---- THE SEAT IN THE BAR ---------------------------------------------------
   IN THE BAR, NOT UNDER IT. A dock 12px below the bar is a dock floating on the
   page: it covered the breadcrumb and clipped the page title, measured at y
   68-112 against a breadcrumb at 75, which is why the top corners are not seats
   at all. The header is sticky at
   z-index 30 and the dock is 190, so the dock simply paints in the bar: 44px of
   bubble centred in a 56px bar, six clear above and below.

   ITS SEAT IS BESIDE THE MINIMIZER, and there is only one of them. The bar's
   left end holds the sidebar toggle and nothing else; its right end is search,
   help, notifications and the user cluster, whose width differs page by page,
   so a dock pinned right would collide with a different control on every page.
   Left is the only end with room that is the same everywhere: the rail, the
   header's own 20px padding, the 34px toggle, then 12px of air - rail + 66.

   *** THIS BLOCK MUST STAY BELOW .newmsg--left. *** It is one
   class like they are and it overrides right, left, align-items and the panel's
   two caps, so it wins on source order alone. Write every property out even
   where the value looks redundant. In practice .newmsg--left is never on the
   element at the same time - place() withholds it, because this seat has no side
   - but the file must not depend on that to be correct. */
.newmsg--bar {
  --newmsg-rail: var(--sidebar-w);
  top: calc((var(--header-h) - 44px) / 2);
  bottom: auto;
  right: auto;
  left: calc(var(--newmsg-rail) + 66px);
  align-items: flex-start;
  flex-direction: column-reverse;
}
@media (max-width: 900px) {
  .newmsg--bar { --newmsg-rail: var(--sidebar-w-collapsed); }
}
/* The dock is a SIBLING of .app, so a rail collapsed by hand cannot reach it by
   cascade - the same reason .newmsg--left needs this. */
body:has(.app.collapsed) .newmsg--bar { --newmsg-rail: var(--sidebar-w-collapsed); }

/* Half the bar sits above the bubble, so what is left below it is the window
   less half the header, less the bubble's own lower half (22), its gap (12) and
   the 22 it must keep off the floor. */
.newmsg--bar .cf-panel { max-height: calc(100vh - var(--header-h) / 2 - 56px); }
/* Both are sized "the window less two 22px margins" by default, which is only
   true against a window edge. Up here the left edge is rail + 66. */
.newmsg--bar .cf-panel,
.newmsg--bar .newmsg__card { max-width: calc(100vw - var(--newmsg-rail) - 88px); }

/* THE COUNT HAS TO COME INSIDE THE BAR. It rides 11px above the button's top
   edge, which anywhere else is empty air and in a 56px bar is 5px off the top of
   the window - measured, clipped. At -4 it spans y 2 to 24.7 and stays in the
   bar, hover's 1px lift included. Its ring is the PAGE's ground by default,
   which is the wrong ground up here: on the bar it reads as a grey halo. */
.newmsg--bar .cf-count { top: -4px; border-color: var(--color-header-bg); }

/* The bar seat grows downward, so the entrance falls into place instead of
   rising into it - a panel hanging off the header that rose would read as going
   back up into it. */
.newmsg--bar .cf-panel, .newmsg--bar .newmsg__card { animation-name: cf-fall; }
@keyframes cf-fall { from { transform: translateY(-10px); opacity: 0; } to { transform: none; opacity: 1; } }

/* Below 560px the bar has no room for the bubble, and a Next-up or walk-in strip
   can take its slot; either way js/client-chat.js place() moves the dock to the
   sidebar seat and leaves the stored seat alone. */

/* ---- THE SEAT IN THE SIDEBAR -----------------------------------------------
   Just above Live Support, and a menu row rather than the button: on the navy
   rail a cobalt button is blue over blue, and a thing that sits in the menu reads
   as one of its items. js/client-chat.js holds a slot open in the sidebar
   (.sidebar-chat) and pins the dock over it by measurement, so the nav never runs
   under the row. The card and the panel open to the row's RIGHT, bottom-aligned
   with it: above it they would cover the menu. */
.sidebar-chat { display: none; flex: none; height: 35px; margin: 4px 8px; }
.sidebar-chat.is-on { display: block; }

.newmsg--rail {
  top: auto;
  right: auto;
  left: var(--newmsg-slot-left, 8px);
  bottom: var(--newmsg-slot-bottom, 60px);
  flex-direction: row-reverse;   /* the bubble is the last child, so it lands leftmost */
  align-items: flex-end;
}
.newmsg--rail .cf-panel,
.newmsg--rail .newmsg__card {
  max-width: calc(100vw - var(--newmsg-slot-left, 8px) - var(--newmsg-slot-w, 145px) - 34px);
}

/* The row takes .nav-link's measure: 13px label, 16px icon, 6/10 padding, 5px. */
.newmsg--rail .cf-bubble {
  width: var(--newmsg-slot-w, 145px);
  height: var(--newmsg-slot-h, 31px);
  justify-content: flex-start;
  gap: 10px;
  padding: 6px 10px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--color-sidebar-text);
  font: inherit;
  font-size: 13px;
  box-shadow: none;
  transform: none;
}
.newmsg--rail .cf-bubble:hover { background: var(--color-sidebar-hover-bg); color: var(--color-sidebar-hover-text); }
.newmsg--rail .cf-mark { display: none; }
.newmsg--rail .cf-glyph,
.newmsg--rail svg.cf-glyph { width: 16px; height: 16px; }
.cf-label { display: none; }
.newmsg--rail .cf-label { display: inline; white-space: nowrap; }
.newmsg--rail .cf-count {
  position: static;
  margin-left: auto;
  min-width: 18px;
  padding: 0 5px;
  border: 0;
  box-shadow: none;
  font-size: 10px;
}

/* The rail is icons only when collapsed, by hand or under 900px: so is the row,
   and its count rides the icon's corner the way it rides the button's. */
@media (max-width: 900px) {
  .newmsg--rail .cf-bubble { justify-content: center; padding: 0; }
  .newmsg--rail .cf-label { display: none; }
  .newmsg--rail .cf-glyph, .newmsg--rail svg.cf-glyph { width: 20px; height: 20px; }
  .newmsg--rail .cf-count { position: absolute; top: 1px; right: 1px; margin: 0; }
}
body:has(.app.collapsed) .newmsg--rail .cf-bubble { justify-content: center; padding: 0; }
body:has(.app.collapsed) .newmsg--rail .cf-label { display: none; }
body:has(.app.collapsed) .newmsg--rail .cf-glyph,
body:has(.app.collapsed) .newmsg--rail svg.cf-glyph { width: 20px; height: 20px; }
body:has(.app.collapsed) .newmsg--rail .cf-count { position: absolute; top: 1px; right: 1px; margin: 0; }

/* A finger dragging the bubble must not also scroll the page under it. */
.cf-bubble { touch-action: none; }

/* Only the bubble travels; the card and the panel stay where they are, because
   dragging the bubble is aiming it rather than moving the conversation. The
   transition comes off for the duration or the bubble lags a frame behind the
   pointer, and this rule is last so it outranks the hover lift at equal weight. */
.newmsg--dragging .cf-bubble {
  transform: translate(var(--newmsg-dx, 0px), var(--newmsg-dy, 0px));
  transition: none;
  cursor: grabbing;
}
.newmsg--dragging .newmsg__card,
.newmsg--dragging .cf-panel { opacity: 0.55; }
/* The sidebar row has no ground of its own - it borrows the sidebar's - so out
   over the page it was light text on a light page, only its count visible. It
   takes the sidebar's navy with it while it travels. */
.newmsg--rail.newmsg--dragging .cf-bubble {
  background: var(--color-sidebar-bg);
  color: var(--color-sidebar-text);
  box-shadow: 0 6px 20px rgba(16, 24, 40, 0.26), 0 2px 6px rgba(16, 24, 40, 0.18);
}

/* THE SEATS A DROP CAN LAND IN, outlined while the bubble travels (js/client-chat.js
   showSeats). JS sets the bubble's own box; the negative margin and padding grow it
   3px past the bubble on every side - never more than 6, because the bar seat has
   exactly 6px above the bubble and an 8 was measured 2px off the top of the window.
   One layer under the dock, so the bubble passes over them, and over the header's
   30, so the bar seat shows in the bar. */
.newmsg-seat {
  position: fixed;
  z-index: 189;
  box-sizing: content-box;
  margin: -3px;
  padding: 1px;
  /* An electric light blue, not the old grey-blue wash: that one matched the
     page ground and was all but invisible. The dash is the same hue, saturated. */
  border: 2px dashed #2F7BFF;
  border-radius: 7px;
  background: #D4E6FF;
  pointer-events: none;
}
/* On the navy sidebar the cobalt dash and the page-light fill both vanish, so the
   sidebar seat takes a white dash over a light-blue wash - the sky of Live
   Support's own marker. */
.newmsg-seat--rail {
  border-color: rgba(255, 255, 255, 0.85);
  background: rgba(125, 211, 252, 0.28);
}
