/* =============================================================================
   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 PANEL'S OWN WHITE and loses the tint the rest of the
   rail sits on, so the row and the conversation read as one surface. A marker in
   the primary carries it for anyone who cannot see the ground change. */
.cc-row.is-active { background: var(--color-surface); }
.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-surface); }
}

/* ---- 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 storefront, 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);
}
