/* A conversation: bar, messages, composer, and the inspector beside it.

   Bubbles are for people; robots and data post cards at --media-w with one right edge. Text bubbles of a run sit
   2px apart, media and cards 6px, runs 12px. Time is said sparingly (a sticky day pill, a bare HH:MM after half an
   hour); any other time shows on hover. Actions never take room: a bar beside the bubble with a pointer, a
   labelled menu after a long press on touch. Only live messages move.

   Every colour, size and radius is a token or derived from one (#000 appears only as the opaque stop of a mask). */

.chat {
  flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr); background: var(--bg-content); position: relative;
  /* one layer: the pills, veils and menus inside are ordered against each other only. (Without this, the day
     pills of a conversation that is being left — its pane stays under the next one while that fades in — were
     painted on top of the conversation that had just been opened.) */
  isolation: isolate;
  --col: 860px;                                        /* the reading column */
  --gutter: 20px;                                       /* its side padding: bubbles and "+" share this edge */
  --field-x: 76px;                                      /* the composer's + and Aa with their gaps: where the field (and the @ list) begins */
  --bubble-h: calc(var(--lh-chat) + 14px);              /* a one-line bubble */
  --card-r: var(--bubble-r);                            /* cards, files and images share the bubble's corner */
  --nest-r: calc(var(--bubble-r) - 8px);                /* what sits inside a bubble or card: parent radius − padding */
  --field-r: calc(var(--bubble-r) + 2px);               /* the composer: a capsule at one line, soft corners when it grows */
  --pop-r: calc(var(--radius-lg) + 2px);                /* popovers that belong to the conversation */
  --me-soft: color-mix(in srgb, var(--bubble-me-text) 20%, transparent);   /* a ground inside my own bubble */
  --me-dim: color-mix(in srgb, var(--bubble-me-text) 72%, transparent);    /* secondary text inside it */
  --shade: var(--text);                                 /* what a fill is mixed with to carry white text */
  --white: var(--on-fill);                          /* paper white in both themes (embeds, glyphs on fills) */
}
:root[data-theme="dark"] .chat { --shade: var(--bg); }
.chat.with-insp { grid-template-columns: minmax(0, 1fr) var(--insp-w); }
.chat-main { position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.chat-none { flex: 1; display: flex; align-items: center; justify-content: center; background: var(--bg-content); }

/* ------------------------------------------------------------------ bar */
.chat-bar { display: flex; align-items: center; gap: 6px; flex: none; min-height: var(--bar-h); padding: 0 12px 0 14px; border-bottom: var(--hair) solid var(--hairline); }
.chat-back { display: none; }
.chat-id { display: flex; align-items: center; gap: 10px; flex: 0 1 auto; min-width: 0; max-width: min(620px, calc(100% - 96px)); padding: 5px 10px 5px 6px; margin-left: -6px; border-radius: var(--radius-lg); text-align: left; transition: background-color var(--t); }
@media (hover: hover) { .chat-id:hover { background: var(--bg-hover); } }
.chat-id:active { background: var(--bg-active); transition-duration: 0s; }
.chat-id:focus-visible { border-radius: var(--radius-lg); }
.chat-id .avatar { --dot-ring: var(--bg-content); }
.chat-id-t { display: flex; flex-direction: column; min-width: 0; }
.chat-id-t b { font-size: var(--fs-headline); line-height: var(--lh-headline); font-weight: 600; letter-spacing: var(--track-title); }
.chat-id-t span { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); transition: color var(--t); }
.chat-id-t span.is-typing { color: var(--link); }
.chat-acts { display: flex; align-items: center; gap: 2px; margin-left: auto; flex: none; }
.chat-offline { flex: none; display: flex; align-items: center; justify-content: center; gap: 10px; padding: 5px 16px; background: var(--warn-soft); color: var(--warn); font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 500; }
.chat-offline.ok { background: var(--ok-soft); color: var(--ok); }
.chat-offline button { position: relative; color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.chat-offline button::after { content: ""; position: absolute; inset: -10px -12px; }
.chat-offline button:active { opacity: .6; }

/* ------------------------------------------------------------------ message list */
.chat-body { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
.chat-scroll { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; }
/* the last lines dissolve into the composer's edge instead of being cut by it. (A veil of the ground, not a mask
   on the scroller: a masked ancestor stops backdrop blur for the pills and menus inside the list.) */
.chat-body::after { content: ""; position: absolute; left: 0; right: 10px; bottom: 0; z-index: 3; height: 14px; background: linear-gradient(color-mix(in srgb, var(--bg-content) 0%, transparent), var(--bg-content)); pointer-events: none; }
.chat-inner { display: flex; flex-direction: column; min-height: 100%; max-width: var(--col); margin: 0 auto; padding: 12px var(--gutter) 14px; }
.chat-inner > :first-child { margin-top: auto; }   /* a short conversation sits at the bottom, next to the composer */
.chat-inner > .empty, .chat-inner > .loading { margin: auto; }

/* the day: a pill that stays at the top of the list while its messages scroll under it */
.msg-day {
  position: sticky; top: 6px; z-index: 3; align-self: center; height: 22px; margin: 14px 0 10px; padding: 0 10px; border-radius: var(--pill);
  background: var(--material-pop); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); box-shadow: var(--shadow-card);
  font-size: var(--fs-xs); line-height: 22px; font-weight: 600; color: var(--text-2); white-space: nowrap; transition: opacity 200ms ease;
}
/* stuck at the top it speaks only while the list is moving (at rest it would sit on a message); the pills of
   earlier days, stuck underneath it, stay hidden (conversation.js markDays) */
.msg-day.stuck { opacity: 0; pointer-events: none; }
.chat-scroll.is-scrolling .msg-day.stuck { opacity: 1; }
.msg-day.under, .chat-scroll.is-scrolling .msg-day.under { opacity: 0; transition: none; }
/* the way up to the first unread message has that spot: the day does not show through from behind it */
.chat-body:has(.chat-unread-up) .chat-scroll .msg-day.stuck { opacity: 0; }
/* after half an hour of silence: the bare time */
.msg-time { margin: 16px 0 8px; text-align: center; font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: 500; color: var(--text-3); font-variant-numeric: tabular-nums; }
.msg-note { margin: 8px auto; max-width: 80%; text-align: center; font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); text-wrap: balance; }
.msg-day + .msg-note, .msg-time + .msg-note { margin-top: 0; }
/* where reading stopped: a tinted rule that ends in a count (blue — red means breach here) */
.msg-unread { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin: 14px 0 8px; }
.msg-unread::before { content: ""; flex: 1; height: 1px; border-radius: var(--pill); background: color-mix(in srgb, var(--tint) 34%, transparent); }
.msg-unread span { flex: none; height: 16px; padding: 0 7px; border-radius: var(--pill); background: var(--tint); color: var(--white); font-size: var(--fs-xs); line-height: 16px; font-weight: 700; font-variant-numeric: tabular-nums; }
.msg-unread + .msg-day { margin-top: -27px; }    /* a new day at the same place: its pill sits on the rule */

/* ---- a message row */
.msg { display: flex; gap: 8px; padding: 1px 0; position: relative; isolation: isolate; }
.msg.first { margin-top: 10px; }
.msg.media, .msg.is-bot { padding-block: 3px; }
.msg-day + div > .msg.first, .msg-time + div > .msg.first, .msg-unread + div > .msg.first { margin-top: 0; }
.msg.mine { justify-content: flex-end; }
.msg-col { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; max-width: min(78%, 34em + 26px); font-size: var(--fs-chat); }
.msg.mine .msg-col { align-items: flex-end; }
.msg-who { display: flex; align-items: baseline; gap: 6px; max-width: 100%; margin: 0 0 3px 12px; font-size: var(--fs-xs); line-height: 16px; color: var(--text-3); white-space: nowrap; }
.msg-who b { min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: var(--fs-sm); font-weight: 600; color: var(--text-2); }
.msg-who time { font-variant-numeric: tabular-nums; }
.msg.is-bot .msg-who, .msg:has(.msg-stack > .mcard:first-child) .msg-who { margin-left: 14px; }   /* over a card: its text edge */
.msg-bot { align-self: center; padding: 0 5px; border-radius: var(--radius-xs); background: var(--fill-2); font-size: var(--fs-xs); font-weight: 600; line-height: 15px; color: var(--text-3); }
.msg-line { display: flex; align-items: flex-end; gap: 6px; max-width: 100%; position: relative; }
.msg.mine .msg-line { flex-direction: row-reverse; }
.msg-stack { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; }
.msg.mine .msg-stack { align-items: flex-end; }
/* a time that is not printed shows while the pointer is on its message, beside the bubble where the actions are */
.msg-t { font-size: var(--fs-xs); color: var(--text-3); font-variant-numeric: tabular-nums; white-space: nowrap; opacity: 0; transition: opacity var(--t-fast); }
@media (hover: hover) { .msg:hover .msg-t, .msg:has(.msg-acts :focus-visible) .msg-t { opacity: 1; } }

/* ---- bubbles */
.bubble {
  --lh: var(--lh-chat);
  position: relative; max-width: 100%; padding: 7px 12px; border-radius: var(--bubble-r); background: var(--bubble-other); color: var(--text);
  font-size: var(--fs-chat); line-height: var(--lh); word-break: break-word; overflow-wrap: anywhere;
  transition: transform 200ms var(--ease-snappy), box-shadow var(--t);
}
.bubble-t { white-space: pre-wrap; text-wrap: balance; }
.bubble.tabular .bubble-t { font-variant-numeric: tabular-nums; }
.bubble-t a { color: var(--link); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--link) 40%, transparent); text-underline-offset: 2px; }
.bubble-t a:hover { text-decoration-color: currentColor; }
.bubble .mention { font-weight: 500; color: var(--link); }
.bubble .mention.me { margin: 0 -1px; padding: 1px 5px; border-radius: calc(var(--nest-r) - 4px); background: var(--accent-soft); }
.bubble .edited { margin-left: 6px; font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; }
/* runs: the corners on the sender's side tighten where two text bubbles meet */
.msg:not(.mine).flat-t .bubble { border-top-left-radius: var(--bubble-r-join); }
.msg:not(.mine).flat-b .bubble { border-bottom-left-radius: var(--bubble-r-join); }
.msg.mine.flat-t .bubble { border-top-right-radius: var(--bubble-r-join); }
.msg.mine.flat-b .bubble { border-bottom-right-radius: var(--bubble-r-join); }
/* mine: one gradient fixed to the window, so every bubble takes the shade of where it is on screen */
.msg.mine .bubble { background: var(--bubble-me); color: var(--bubble-me-text); }
@media (hover: hover) { .msg.mine .bubble { background: linear-gradient(var(--bubble-me), var(--bubble-me-2)) fixed; } }
.msg.mine .bubble-t a { color: inherit; text-decoration-color: var(--me-dim); }
.msg.mine .bubble .mention { color: inherit; font-weight: 600; }
.msg.mine .bubble .mention.me { background: var(--me-soft); }
.msg.mine .bubble .edited { color: var(--me-dim); }
.msg.mine .bubble ::selection { background: color-mix(in srgb, var(--bubble-me-text) 34%, transparent); }
/* one to three emoji and nothing else: large, no bubble */
.bubble.jumbo { background: none !important; padding: 0 2px; font-size: calc(var(--fs-chat) * 2.2); line-height: 1.15; }
/* the message whose text is in the composer */
.msg.editing .bubble { box-shadow: 0 0 0 2px var(--bg-content), 0 0 0 4px var(--tint); }

/* a long message is folded to its first eight lines; the control says how much is behind it */
.bubble.fold .bubble-t { overflow: hidden; transition: max-height 220ms var(--ease-smooth); }
.bubble.folded .bubble-t {
  max-height: calc(var(--lh) * 8);
  -webkit-mask-image: linear-gradient(#000 calc(100% - var(--lh) * 1.4), transparent); mask-image: linear-gradient(#000 calc(100% - var(--lh) * 1.4), transparent);
}
/* (one line of small text: the padding gives the focus ring room, the ::after makes it a 28px target; the
   margins take the padding back, so the text stays on the bubble's text edge, 6px under the fold) */
.bubble-more { position: relative; display: block; margin: 4px 0 -2px -6px; padding: 2px 6px; border-radius: var(--radius-xs); font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 600; color: var(--link); text-align: left; }
.bubble-more::after { content: ""; position: absolute; inset: -6px -4px; }
.bubble-more:hover { text-decoration: underline; text-underline-offset: 2px; }
.bubble-more:focus-visible { border-radius: var(--radius-xs); }
.msg.mine .bubble-more { color: var(--bubble-me-text); }
.msg.mine .bubble-more:focus-visible { box-shadow: 0 0 0 2px var(--bubble-me-text); }

/* a reply: the quoted message inside the bubble, behind a bar */
.msg-quote {
  position: relative; display: grid; width: calc(100% + 10px); margin: -1px -5px 6px; padding: 4px 9px 4px 15px; border-radius: var(--nest-r);
  background: color-mix(in srgb, currentColor 8%, transparent); text-align: left; transition: background-color var(--t-fast);
}
/* the bar is a straight, round-capped stroke set in from the edge (an inset shadow would bend with the corner) */
.msg-quote::before, .composer-ctx-row::before { content: ""; position: absolute; left: 6px; top: 6px; bottom: 6px; width: 3px; border-radius: var(--pill); background: var(--link); }
.msg-quote:last-child { margin-bottom: -1px; }
.msg-quote b { font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 600; color: var(--link); }
.msg-quote span { max-width: 26em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-2); }
@media (hover: hover) { .msg-quote:hover { background: color-mix(in srgb, currentColor 13%, transparent); } }
.msg-quote:active { background: color-mix(in srgb, currentColor 18%, transparent); transition-duration: 0s; }
.msg-quote:focus-visible { border-radius: var(--nest-r); }
/* a reply with no words of its own (a picture, a card): the quote stands alone above what was
   sent, without a bubble around it */
.msg:not(.mine) .bubble.only-quote { padding: 0; background: none; box-shadow: none; }
.msg:not(.mine) .bubble.only-quote .msg-quote { width: 100%; margin: 0; padding: 6px 12px 6px 19px; border-radius: calc(var(--bubble-r) - 4px); }
.msg:not(.mine) .bubble.only-quote .msg-quote::before { left: 9px; top: 8px; bottom: 8px; }
.msg.mine .msg-quote { background: var(--me-soft); }
.msg.mine .msg-quote::before { background: var(--bubble-me-text); }
.msg.mine .msg-quote:focus-visible { box-shadow: 0 0 0 2px var(--bubble-me-text); }
.msg.mine .msg-quote b { color: var(--bubble-me-text); }
.msg.mine .msg-quote span { color: color-mix(in srgb, var(--bubble-me-text) 86%, transparent); }
@media (hover: hover) { .msg.mine .msg-quote:hover { background: color-mix(in srgb, var(--bubble-me-text) 27%, transparent); } }

/* ---- robots post one surface at one width: their text is a card like their cards */
/* (the column always has room for a card, even where 78% of a narrow window is less; text keeps its measure) */
.msg.is-bot .msg-col, .msg.media .msg-col { max-width: max(min(78%, 34em + 26px), min(100%, var(--media-w))); }
.msg.is-bot .msg-stack > * { width: var(--media-w); max-width: 100%; }
.msg.is-bot .bubble {
  --lh: var(--lh-body);
  padding: 12px 14px; border-radius: var(--card-r); background: var(--bg-elev); box-shadow: 0 0 0 1px var(--hairline); font-size: var(--fs-body);
}
.msg.is-bot .bubble-t { text-wrap: wrap; }
/* a table of figures is data whoever posts it: a trader's net / gross table takes the width of the robot's card
   next to it, so the two share one right edge */
.msg.media .bubble:has(> .tb) { width: var(--media-w); max-width: 100%; }
/* a picture posted with a card is that card's media area (its outline stops where the card's begins) */
.msg.is-bot .msg-imgs[data-n="1"]:has(+ .mcard) { margin-bottom: -6px; }
.msg.is-bot .msg-imgs[data-n="1"]:has(+ .mcard) .msg-img { border-radius: var(--card-r) var(--card-r) 0 0; clip-path: inset(-1px -1px 1px -1px); }
.msg.is-bot .msg-imgs[data-n="1"]:has(+ .mcard) .msg-img:focus-visible { clip-path: none; }   /* (the focus ring is drawn whole) */
.msg.is-bot .msg-imgs[data-n="1"] + .mcard { border-top-left-radius: 0; border-top-right-radius: 0; }

/* a dump of "label  figure  figure" lines, set as aligned figures (text.js parseDump) */
.bubble-t.tb {
  --tb-fig: minmax(6.5ch, max-content);                 /* a column of figures */
  --tb-note: max-content;                               /* the notes behind a column's figures */
  display: grid; grid-template-columns: minmax(0, 1fr) repeat(var(--n), var(--tb-fig)); column-gap: 14px; row-gap: 1px;
  white-space: normal; text-wrap: wrap; font-variant-numeric: tabular-nums;
}
.tb .rw, .tb .kf { display: contents; }       /* a row, and its label + first figure: cells of the grid here; lines of their own on a phone (below) */
.tb .k { min-width: 0; word-break: normal; overflow-wrap: break-word; }   /* a label wraps between words before it breaks one */
.tb .v { text-align: right; white-space: nowrap; }
.tb .v small { margin-left: 2px; font-size: var(--fs-sm); line-height: 1; color: var(--text-3); }   /* (line-height: the smaller note must not make its row taller; a space already stands before it) */
/* The note behind a figure ("256K (+28K)", "−1.3% (eod: 19.1%)") gets a track of its own next to its column
   (--cols, message.js): every figure of the column still ends on one edge, whether a note follows it or not, and
   a row with a note is as tall as a row without one. The cell spans both tracks and passes them on to the
   figure and the note. (Without subgrid the note simply follows its figure on the same line.) */
@supports (grid-template-columns: subgrid) {
  .bubble-t.tb { grid-template-columns: minmax(0, 1fr) var(--cols, repeat(var(--n), var(--tb-fig))); }
  .tb .k { grid-column: 1; }
  .tb .v.wn { display: grid; grid-column: span 2; grid-template-columns: subgrid; column-gap: 6px; align-items: baseline; }
  .tb .v.wn small { margin-left: 0; text-align: left; }
}
.tb .ln { grid-column: 1 / -1; min-width: 0; white-space: pre-wrap; }
.tb .gap { margin-top: 8px; }
.tb .lead { font-weight: 600; }
.tb .sec { grid-column: 1 / -1; margin-top: 6px; padding-top: 7px; border-top: var(--hair) solid var(--hairline); font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: 600; letter-spacing: .01em; color: var(--text-3); }
.tb .sec + .ln { color: var(--text-2); }      /* a remark under a heading ("no major change") */
.msg.mine .tb .sec { border-top-color: var(--me-soft); color: var(--me-dim); }
.msg.mine .tb .sec + .ln { color: inherit; }
.msg.mine .tb .v small { color: var(--me-dim); }

/* ---- formatted text: what people write with the composer's 格式 (md.js). The chat's own type, a little
   structure. Every secondary ink and ground is mixed from the text colour, so one set of rules reads on the grey
   bubble, the blue one and the light one of .doc below, in both themes. Blocks are 8px apart, a heading 12px
   from what precedes it; inside a list item, quote or callout 4px. */
.bubble-t.rt { white-space: normal; text-wrap: pretty; }
.rt p { white-space: pre-wrap; }              /* (the spaces a sender typed to line figures up are kept) */
.rt :where(p, ul, ol, pre, blockquote, hr, h1, h2, h3, h4, h5, h6, .md-tbl, .md-alert) { margin: 0; }   /* (:where, so the spacing below wins) */
.rt > * + * { margin-top: 8px; }
.rt > * + :is(h1, h2, h3, h4, h5, h6) { margin-top: 12px; }
.rt > :is(h1, h2, h3, h4, h5, h6) + * { margin-top: 4px; }
.rt :is(li, blockquote, .md-alert) > * + * { margin-top: 4px; }
.rt h1 { font-size: var(--fs-title3); line-height: var(--lh-title3); font-weight: 700; letter-spacing: var(--track-title); }
.rt h2 { font-size: var(--fs-headline); line-height: var(--lh-headline); font-weight: 600; }
.rt h3 { font-size: inherit; line-height: inherit; font-weight: 600; }
/* (below that a heading is a section label, the way a table of figures heads its sections) */
.rt :is(h4, h5, h6) { font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 600; letter-spacing: .02em; color: color-mix(in srgb, currentColor 64%, transparent); }
.rt strong { font-weight: 600; }
.rt :is(ul, ol) { padding-left: 1.35em; }
.rt ul { list-style: disc; } .rt ol { list-style: decimal; } .rt li ul { list-style: circle; }
.rt li + li, .rt li > :is(ul, ol) { margin-top: 2px; }
.rt li::marker { color: color-mix(in srgb, currentColor 55%, transparent); font-variant-numeric: tabular-nums; }
/* a to-do: its box stands where the bullet would, the words on the list's text edge; a done one steps back */
.rt li:has(> .md-check, > p:first-child > .md-check:first-child) { list-style: none; }
.rt .md-check { display: inline-block; width: .95em; height: .95em; margin: 0 .4em 0 -1.35em; vertical-align: -.13em; border-radius: .24em; box-shadow: inset 0 0 0 1.5px color-mix(in srgb, currentColor 45%, transparent); }
.rt .md-check.md-on {
  background: var(--ok-fill) center / 78% no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.2l2.4 2.4 4.6-5'/%3E%3C/svg%3E");
  box-shadow: none;
}
.rt li:has(> .md-on, > p:first-child > .md-on:first-child) { color: color-mix(in srgb, currentColor 62%, transparent); }
/* a quote: a straight, round-capped stroke like the reply quote's */
.rt blockquote { position: relative; padding-left: 13px; color: color-mix(in srgb, currentColor 80%, transparent); }
.rt blockquote::before { content: ""; position: absolute; left: 2px; top: 2px; bottom: 2px; width: 3px; border-radius: var(--pill); background: color-mix(in srgb, currentColor 32%, transparent); }
.rt code { padding: .1em .32em; border-radius: var(--radius-xs); background: color-mix(in srgb, currentColor 10%, transparent); font-family: var(--mono); font-size: .88em; }
.rt pre { padding: 8px 11px; border-radius: var(--nest-r); background: color-mix(in srgb, currentColor 7%, transparent); overflow-x: auto; white-space: pre; overflow-wrap: normal; word-break: normal; font-size: var(--fs-md); line-height: var(--lh-md); tab-size: 4; }
.rt pre code { padding: 0; border-radius: 0; background: none; font-size: inherit; }
.rt hr { height: var(--hair); border: 0; background: color-mix(in srgb, currentColor 20%, transparent); margin-block: 10px; }
.rt mark { margin: 0 -.06em; padding: .04em .16em; border-radius: 3px; background: var(--mark); color: inherit; -webkit-box-decoration-break: clone; box-decoration-break: clone; }
/* the colours a sender can use: red and green are the dashboards' loss and gain, orange their "attention". A robot's
   WeCom colours land on the same three (md.js): info green, warning orange, comment grey */
.rt .md-c-red { color: var(--neg); } .rt .md-c-green { color: var(--pos); } .rt .md-c-orange { color: var(--warn); }
.rt .md-c-gray { color: var(--text-3); } .rt .md-c-blue { color: var(--link); } .rt .md-c-purple { color: var(--c4); }
/* a robot's markdown may show a picture (people send theirs as attachments): the card's width, its own height */
.rt img { display: block; max-width: 100%; height: auto; border-radius: var(--nest-r); }
:root[data-theme="dark"] .rt img { filter: brightness(.9); }
/* a table: hairline rows, figures right-aligned on one edge (md.js marks a column of figures .md-n), never
   wrapped; wider than the bubble it scrolls inside it */
.rt .md-tbl { max-width: 100%; overflow-x: auto; scrollbar-width: thin; }
.rt table { border-collapse: collapse; overflow-wrap: normal; word-break: normal; font-size: var(--fs-md); line-height: var(--lh-md); font-variant-numeric: tabular-nums; }
.rt :is(th, td) { padding: 5px 0 5px 18px; text-align: left; vertical-align: baseline; border-bottom: var(--hair) solid color-mix(in srgb, currentColor 14%, transparent); }
.rt :is(th, td):first-child { padding-left: 0; white-space: nowrap; }   /* (a row's name stays on one line; a long table scrolls) */
.rt th { padding-top: 0; font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 600; white-space: nowrap; color: color-mix(in srgb, currentColor 62%, transparent); border-bottom-color: color-mix(in srgb, currentColor 26%, transparent); }
.rt tbody tr:last-child td { padding-bottom: 1px; border-bottom: 0; }
.rt :is(th, td).md-n { white-space: nowrap; }
.rt :is(th, td).md-r { text-align: right; } .rt :is(th, td).md-c { text-align: center; }
/* a callout ("> [!WARNING]"): a tinted panel with a stroke and a title in its colour */
.rt .md-alert { --al: var(--link); position: relative; padding: 7px 11px 8px 16px; border-radius: var(--nest-r); background: color-mix(in srgb, var(--al) 10%, transparent); }
.rt .md-alert::before { content: ""; position: absolute; left: 6px; top: 8px; bottom: 8px; width: 3px; border-radius: var(--pill); background: var(--al); }
.rt .md-alert > .md-alert-t + * { margin-top: 1px; }
.rt .md-alert-t { font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 600; color: var(--al); }
.rt .md-alert-tip { --al: var(--ok); } .rt .md-alert-important { --al: var(--c4); }
.rt .md-alert-warning { --al: var(--warn); } .rt .md-alert-caution { --al: var(--danger); }
/* My own bubble is saturated blue with white words; a message with colours, highlights, tables, code or callouts
   is read on a light ground of the same blue instead (md.js `doc`): still mine at a glance, every colour legible.
   The inks my bubble derives from its text colour are pointed at the light ground's. */
.msg.mine .bubble.doc {
  --bubble-me-text: var(--text); --me-soft: color-mix(in srgb, var(--link) 14%, transparent); --me-dim: var(--text-3);
  background: var(--bubble-me-doc); color: var(--text);
}
.msg.mine .bubble.doc :is(.bubble-t a, .mention, .bubble-more, .msg-quote b) { color: var(--link); }
.msg.mine .bubble.doc .bubble-t a { text-decoration-color: color-mix(in srgb, var(--link) 40%, transparent); }
.msg.mine .bubble.doc .msg-quote::before { background: var(--link); }
@media (hover: hover) { .msg.mine .bubble.doc .msg-quote:hover { background: color-mix(in srgb, var(--link) 22%, transparent); } }
.msg.mine .bubble.doc .bubble-more:focus-visible { box-shadow: var(--ring); }

/* ---- images: a ground that is there before the picture, which fades in when it arrives */
.msg-imgs { display: flex; max-width: 100%; }
.msg.mine .msg-imgs { justify-content: flex-end; }
/* (outlined like the cards it sits between — the same hairline, outside the box, so their edges line up — or a
   white chart would lose its sides on a white page) */
.msg-img { position: relative; display: block; flex: none; max-width: 100%; overflow: hidden; border-radius: var(--card-r); background: var(--fill-3); box-shadow: 0 0 0 1px var(--hairline); cursor: zoom-in; }
.msg-img img { display: block; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 200ms ease, filter var(--t-fast); }
.msg-img.ready img { opacity: 1; }
.msg-img.now img { transition: filter var(--t-fast); }
.msg-img::after { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; }   /* (a tile's focus mark, below) */
.msg-img:focus-visible { border-radius: var(--card-r); box-shadow: var(--ring); }
.msg.is-bot .msg-img { width: 100%; }
@media (hover: hover) { .msg-img:hover img, .news-i.first img:hover { filter: brightness(.96); } }
/* dark: every picture in a conversation is dimmed by the same amount (cards and markdown below), one white */
:root[data-theme="dark"] .msg-img img { filter: brightness(.9); }
@media (hover: hover) { :root[data-theme="dark"] .msg-img:hover img, :root[data-theme="dark"] .news-i.first img:hover { filter: brightness(.86); } }
/* two or more: one mosaic; the container owns the corner and the outline */
.msg-imgs:not([data-n="1"]) { display: grid; gap: 2px; width: 302px; grid-template-columns: 1fr 1fr; grid-auto-rows: 150px; overflow: hidden; border-radius: var(--card-r); box-shadow: 0 0 0 1px var(--hairline); }
.msg-imgs:not([data-n="1"]) .msg-img { width: auto; border-radius: 0; box-shadow: none; }
.msg-imgs:not([data-n="1"]) .msg-img:focus-visible { border-radius: 0; box-shadow: none; }
.msg-imgs:not([data-n="1"]) .msg-img:focus-visible::after { box-shadow: inset 0 0 0 2px var(--tint); }
.msg-imgs[data-n="3"] { grid-auto-rows: 110px; }
.msg-imgs[data-n="3"] .msg-img:first-child { grid-row: span 2; }
.msg-imgs[data-n="5"], .msg-imgs[data-n="6"] { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 100px; }
.msg-imgs[data-n="5"] .msg-img, .msg-imgs[data-n="6"] .msg-img { grid-column: span 2; }
.msg-imgs[data-n="5"] .msg-img:nth-child(-n+2) { grid-column: span 3; }
.msg-imgs[data-n="5"] { grid-template-rows: 150px 100px; }
.msg-img-more { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--scrim); color: var(--white); font-size: var(--fs-title2); font-weight: 600; font-variant-numeric: tabular-nums; }

/* ---- files */
.msg-file { display: flex; align-items: center; gap: 10px; width: var(--media-w); max-width: 100%; padding: 10px 12px; border-radius: var(--card-r); background: var(--bubble-other); color: var(--text); transition: background-color var(--t-fast); }
.msg-file:hover { text-decoration: none; }
@media (hover: hover) { .msg-file:hover { background: color-mix(in srgb, var(--bubble-other) 94%, var(--text)); } }
.msg-file:active { background: color-mix(in srgb, var(--bubble-other) 88%, var(--text)); transition-duration: 0s; }
.msg-file:focus-visible { border-radius: var(--card-r); }
.msg-file > .icon { color: var(--text-3); margin-right: 2px; }
.file-ext { display: flex; align-items: center; justify-content: center; flex: none; width: 36px; height: 40px; border-radius: calc(var(--nest-r) - 3px); background: var(--text-3); color: var(--white); font-size: var(--fs-xs); font-weight: 700; letter-spacing: .02em; }
.file-ext.x-XLS, .file-ext.x-CSV { background: color-mix(in srgb, var(--ok-fill) 78%, var(--shade)); }
.file-ext.x-PDF { background: color-mix(in srgb, var(--danger-fill) 86%, var(--shade)); }
.file-ext.x-DOC { background: color-mix(in srgb, var(--c1) 88%, var(--shade)); }
.file-ext.x-PPT { background: color-mix(in srgb, var(--warn-fill) 84%, var(--shade)); }
.file-m { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* a long name is cut in the middle: its end (version, extension) stays */
.file-n { display: flex; min-width: 0; font-size: var(--fs-body); line-height: var(--lh-body); font-weight: 500; }
.fn-a { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: pre; }
.fn-b { flex: none; white-space: pre; }
.file-s { font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--text-3); font-variant-numeric: tabular-nums; }

/* ---- pictures and files sent with words: one message, one bubble (message.js combo). The quote, the pictures and
   the file rows are panels in a 3px frame — corners of the bubble's radius less the frame where they meet its
   corners, --nest-r elsewhere — and the words stand on the bubble's text edge, each where it was written: under
   the pictures, or between them (parts.js). The bubble is as wide as its widest pictures (--mw, the picture's own
   width plus the frame), never narrower than a line of words: a narrower or tall picture is centred on a blurred
   copy of itself (letterboxed), never cropped. */
.bubble.combo {
  --in: calc(var(--bubble-r) - 3px);
  display: flex; flex-direction: column; gap: 3px;
  width: max(var(--mw, var(--media-w)), min(264px, 100%)); max-width: 100%; padding: 3px;
}
.bubble.combo > :is(.msg-quote, .msg-imgs, .msg-file) { border-radius: var(--nest-r); }
.bubble.combo > :is(.msg-quote, .msg-imgs, .msg-file):first-child { border-top-left-radius: var(--in); border-top-right-radius: var(--in); }
.bubble.combo > :is(.msg-imgs, .msg-file):last-child { border-bottom-left-radius: var(--in); border-bottom-right-radius: var(--in); }
.bubble.combo > .msg-quote { width: auto; margin: 0; padding: 6px 12px 6px 19px; }
.bubble.combo > .msg-quote::before { left: 9px; top: 8px; bottom: 8px; }
.msg .bubble.combo > .msg-imgs { position: relative; isolation: isolate; width: auto; justify-content: center; overflow: hidden; background: color-mix(in srgb, currentColor 7%, transparent); box-shadow: none; }
/* a narrow picture stands on a blurred copy of itself (Telegram's answer), not on an empty band */
.msg-imgs-bd { display: none; }
.bubble.combo .msg-imgs-bd {
  display: block; position: absolute; inset: -24px; z-index: -1; background: center / cover no-repeat;
  filter: blur(18px) saturate(1.2); opacity: 0; transition: opacity 200ms ease; pointer-events: none;
}
.bubble.combo .msg-imgs-bd.ready { opacity: .85; }
.bubble.combo .msg-imgs-bd.now { transition: none; }
:root[data-theme="dark"] .bubble.combo .msg-imgs-bd { filter: blur(18px) saturate(1.2) brightness(.75); }
.bubble.combo .msg-img, .bubble.combo .msg-img:focus-visible { border-radius: 0; box-shadow: none; }
.bubble.combo .msg-img:focus-visible::after { box-shadow: inset 0 0 0 2px var(--tint); }
.bubble.combo > .msg-file { width: auto; padding: 8px 12px 8px 8px; background: color-mix(in srgb, currentColor 7%, transparent); color: inherit; }
@media (hover: hover) { .bubble.combo > .msg-file:hover { background: color-mix(in srgb, currentColor 12%, transparent); } }
.bubble.combo > .msg-file:active { background: color-mix(in srgb, currentColor 16%, transparent); }
.bubble.combo > .msg-file:focus-visible { border-radius: var(--nest-r); }
.bubble.combo :is(.file-s, .msg-file > .icon) { color: color-mix(in srgb, currentColor 66%, transparent); }
.bubble.combo > .bubble-t { padding: 4px 9px; text-wrap: pretty; }
.bubble.combo.folded > .bubble-t { max-height: calc(var(--lh) * 8 + 8px); }
.bubble.combo > .bubble-more { align-self: flex-start; margin: -2px 0 4px 3px; }
.msg.is-bot .bubble.combo { padding: 3px; }
.msg.is-bot .bubble.combo > .bubble-t { padding: 9px 11px; }

/* ---- cards inside the conversation (shared dashboards, robot cards) */
.mcard {
  position: relative; width: var(--media-w); max-width: 100%; padding: 12px 14px; border-radius: var(--card-r); background: var(--bg-elev); box-shadow: 0 0 0 1px var(--hairline);
  font-size: var(--fs-body); line-height: var(--lh-body); text-align: left; --ring-bg: var(--bg-elev);
  transition: box-shadow var(--t-fast), transform var(--t-fast) var(--ease);
}
.mcard-src { display: flex; align-items: center; gap: 6px; width: 100%; min-height: 20px; margin-bottom: 6px; font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 600; color: var(--text-3); text-align: left; }
.mcard-src.plain { font-weight: 500; }
.mcard-t { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: var(--fs-headline); line-height: var(--lh-headline); font-weight: 600; letter-spacing: var(--track-title); }
.mcard-d { margin-top: 2px; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-3); }
.mcard-lines { display: flex; flex-direction: column; gap: 3px; margin-top: 6px; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-2); font-variant-numeric: tabular-nums; }
.mcard-lines > span { position: relative; padding-left: 12px; }
.mcard-lines > span::before { content: ""; position: absolute; left: 1px; top: calc(var(--lh-md) / 2 - 2px); width: 4px; height: 4px; border-radius: 50%; background: var(--text-4); }
.mcard-big { display: flex; flex-direction: column; margin: 8px 0 4px; }
.mcard-big b { font: 600 var(--fs-title1)/var(--lh-title1) var(--font-display); letter-spacing: var(--track-title); }
.mcard-big > span { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }
.mcard-kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 8px 0 0; font-size: var(--fs-md); line-height: var(--lh-md); }
.mcard-kv dt { color: var(--text-3); } .mcard-kv dd { margin: 0; text-align: right; min-width: 0; word-break: break-word; }
/* the way on: a footer row that spans the card */
.mcard-go {
  position: relative; display: flex; align-items: center; justify-content: space-between; gap: 8px; width: calc(100% + 28px); margin: 10px -14px -12px; padding: 9px 14px 11px;
  border-radius: 0 0 var(--card-r) var(--card-r); font-size: var(--fs-md); line-height: var(--lh-md); font-weight: 500; color: var(--link); text-align: left;
  transition: background-color var(--t-fast);
}
.mcard-go::before { content: ""; position: absolute; top: 0; left: 14px; right: 14px; height: var(--hair); background: var(--hairline); }
.mcard-go .icon { color: var(--text-4); }
.mcard-go:hover { text-decoration: none; }
@media (hover: hover) { a.mcard-go:hover, button.mcard-go:hover { background: var(--bg-hover); } }
a.mcard-go:active, button.mcard-go:active { background: var(--bg-active); transition-duration: 0s; }
.mcard-go:focus-visible { border-radius: 0 0 var(--card-r) var(--card-r); box-shadow: inset 0 0 0 2px var(--tint); }
.mcard-go:not(:last-child) { margin-bottom: 0; padding-bottom: 9px; border-radius: 0; }
.mcard-go + .mcard-go { margin-top: 0; color: var(--text-2); }
/* a shared dashboard: the whole card is one button that opens the dashboard */
.mcard.dash { padding: 0; overflow: hidden; }
.mcard-body { display: flex; flex-direction: column; width: 100%; padding: 12px 14px; text-align: left; transition: background-color var(--t-fast); }
.mcard-body .mcard-go { width: 100%; margin: 10px 0 0; padding: 9px 0 0; border-radius: 0; }
.mcard-body .mcard-go::before { left: 0; right: 0; }
.mcard-body:focus-visible { border-radius: var(--card-r); box-shadow: inset 0 0 0 2px var(--tint); }
@media (hover: hover) { .mcard.dash:hover { box-shadow: 0 0 0 1px var(--hairline-strong), var(--shadow-card); } }
.mcard.dash:has(.mcard-body:active) { transform: scale(.992); transition-duration: 0s; }
/* robot news: a lead story, then rows */
.mcard.news { padding: 0; overflow: hidden; }
.news-i { position: relative; display: flex; align-items: center; gap: 10px; padding: 10px 14px; color: var(--text); transition: background-color var(--t-fast); }
.news-i + .news-i::before { content: ""; position: absolute; top: 0; left: 14px; right: 0; height: var(--hair); background: var(--hairline); }
.news-i:hover { text-decoration: none; }
@media (hover: hover) { .news-i:hover { background: var(--bg-hover); } }
.news-i:active { background: var(--bg-active); transition-duration: 0s; }
.news-i:focus-visible { border-radius: 0; box-shadow: inset 0 0 0 2px var(--tint); }
.news-i img { width: 44px; height: 44px; border-radius: calc(var(--nest-r) - 2px); object-fit: cover; flex: none; order: 2; background: var(--fill-3); }
.news-m { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.news-t { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: var(--fs-body); line-height: var(--lh-body); font-weight: 500; }
.news-chev { flex: none; color: var(--text-4); }
.news-i.first { flex-direction: column; align-items: stretch; gap: 8px; padding: 12px 14px; }
.news-i.first img { order: 0; width: 100%; height: auto; max-height: 240px; object-fit: contain; border-radius: var(--nest-r); cursor: zoom-in; transition: filter var(--t-fast); }
.news-i.first .news-t { font-size: var(--fs-headline); line-height: var(--lh-headline); font-weight: 600; letter-spacing: var(--track-title); }
.news-d { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-3); }
:root[data-theme="dark"] .news-i img { filter: brightness(.9); }
/* template cards: the first link is the action, later ones are quieter (see .mcard-go + .mcard-go) */
.mcard.tpl .mcard-kv a { color: var(--link); }

/* ---- actions: a small bar beside the bubble while the pointer is on the row. It takes no room and moves nothing. */
.msg-side { position: absolute; top: 0; left: calc(100% + 6px); z-index: 2; display: flex; align-items: center; gap: 8px; height: var(--bubble-h); }
.msg.mine .msg-side { left: auto; right: calc(100% + 6px); flex-direction: row-reverse; }
/* no room beside a wide card in a narrow window: the bar sits on its top corner instead (message.js placeActs),
   high enough to cover only the card's padding, never its first line */
.msg.acts-over .msg-side { top: -23px; left: auto; right: 8px; height: 32px; }
.msg.mine.acts-over .msg-side { right: auto; left: 8px; }
.msg.acts-over .msg-side .msg-t { display: none; }
.msg-acts {
  position: relative; display: flex; align-items: center; flex: none; padding: 2px; border-radius: var(--pill); background: var(--bg-elev); box-shadow: var(--shadow-card), 0 0 0 var(--hair) var(--hairline);
  opacity: 0; pointer-events: none; transition: opacity var(--t-fast) 80ms;     /* leaves 80ms late: the pointer may be on its way to it */
}
.msg-acts::before { content: ""; position: absolute; inset: -6px -8px; z-index: -1; }   /* the gap between bubble and bar still counts as "on the row" */
.msg-acts button { position: relative; display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--pill); color: var(--text-3); transition: background-color var(--t-fast), color var(--t-fast); }
.msg-acts .lb { display: none; }
.msg-acts .sep { flex: none; width: 1px; height: 16px; margin: 0 2px; background: var(--hairline); }
.msg-acts button:focus-visible { border-radius: var(--pill); box-shadow: inset 0 0 0 2px var(--tint); }
.msg-acts button:active { background: var(--fill); transition-duration: 0s; }
@media (hover: hover) {
  /* (keyboard focus keeps it open; a button that was merely clicked does not, or the bar would stay behind) */
  .msg:hover .msg-acts, .msg:has(.msg-acts :focus-visible) .msg-acts { opacity: 1; pointer-events: auto; transition-delay: 0s; }
  .msg-acts button:hover { background: var(--fill-2); color: var(--text); }
  .msg-acts button.danger:hover { background: var(--danger-soft); color: var(--danger); }
}
/* touch: a long press (the bubble gives a little under the finger) opens the actions as a menu over the list */
@media (hover: none) {
  .bubble, .mcard, .msg-file, .msg-img { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
  .msg.pressing .bubble, .msg.pressing .msg-img, .msg.pressing .msg-file, .msg.pressing .mcard { transform: scale(.97); transition: transform 350ms ease-out; }
  .msg-img, .msg-file { transition: transform 200ms var(--ease-snappy); }
  .msg.acts-on { z-index: 6; }
  .msg.acts-on .bubble, .msg.acts-on .msg-img, .msg.acts-on .msg-file, .msg.acts-on .mcard { transform: scale(1.02); }
  /* (a picture or file inside a bubble gives with the bubble, not a second time on its own) */
  .msg .bubble.combo :is(.msg-img, .msg-file) { transform: none; }
  .msg-side { display: none; }
  .msg.acts-on .msg-side { display: block; top: calc(100% + 6px); left: 0; right: auto; height: auto; }
  .msg.mine.acts-on .msg-side { left: auto; right: 0; }
  .msg.acts-on.acts-up .msg-side { top: auto; bottom: calc(100% + 6px); }
  .msg-side .msg-t { display: none; }
  .msg.acts-on .msg-acts {
    /* opaque (it lies on bubbles of every colour and must read the same on all of them), and one step above the
       cards under it: the popover material laid over the card ground, the shade the attach menu has */
    flex-direction: column; align-items: stretch; min-width: 176px; padding: 5px; border-radius: var(--pop-r);
    background: linear-gradient(var(--material-pop), var(--material-pop)), var(--bg-elev); box-shadow: var(--shadow-pop);
    opacity: 1; pointer-events: auto; transform-origin: top left; animation: pop var(--t) var(--ease-snappy);
  }
  /* while a menu is open the rest of the list steps back: the pressed message and its menu are what is in front
     (a day pill that is hidden under the top edge stays hidden) */
  .chat-inner > * { transition: opacity 200ms ease; }
  .chat-inner:has(.msg.acts-on:not(.acts-out)) > :not(:has(.msg.acts-on), .msg-day.stuck) { opacity: .4; }
  /* dismissed: the bubble settles back and the menu fades (message.js keeps the row for 140ms) */
  .msg.acts-out .msg-acts { animation: none; opacity: 0; pointer-events: none; transition: opacity var(--t-out) ease; }
  .msg.acts-out .bubble, .msg.acts-out .msg-img, .msg.acts-out .msg-file, .msg.acts-out .mcard { transform: none; }
  .msg.mine.acts-on .msg-acts { transform-origin: top right; }
  .msg.acts-on.acts-up .msg-acts { transform-origin: bottom left; animation-name: pop-up; }
  .msg.mine.acts-on.acts-up .msg-acts { transform-origin: bottom right; }
  .msg-acts::before { content: none; }
  .msg-acts button { justify-content: space-between; gap: 16px; width: 100%; height: 44px; padding: 0 12px; border-radius: calc(var(--pop-r) - 5px); font-size: var(--fs-body); color: var(--text); }
  .msg-acts button .icon { color: var(--text-2); }
  .msg-acts button:active { background: var(--fill-2); }
  .msg-acts button.danger, .msg-acts button.danger .icon { color: var(--danger); }
  .msg-acts .lb { display: inline; }
  .msg-acts .sep { width: auto; height: var(--hair); margin: 5px 12px; }
}

/* ---- under the last bubble of a run: time and, for mine, who has read it */
.msg-foot { display: flex; align-items: center; gap: 4px; min-height: var(--lh-xs); margin: 3px 4px 0; font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--text-3); font-variant-numeric: tabular-nums; }
.msg.dm:not(.mine) .msg-foot { margin-left: 12px; }
.msg-foot i { font-style: normal; color: var(--text-4); }
/* (a word of 11px text: the padding gives the focus ring room, the ::after makes it a 28px target that reaches
   down into the gap under the row rather than up into the bubble; 44px on touch) */
.msg-foot button { position: relative; margin: 0 -3px; padding: 0 3px; border-radius: var(--radius-xs); font-size: inherit; font-weight: 500; color: var(--text-3); transition: color var(--t-fast); }
.msg-foot button::after { content: ""; position: absolute; inset: -4px -4px -10px; }
.msg-foot button:focus-visible { border-radius: var(--radius-xs); }
@media (hover: hover) { .msg-foot button:hover { color: var(--link); } }
@media (hover: none) { .msg-foot button::after { inset: -14px -8px; } .bubble-more::after { inset: -13px -8px; } }
.msg-foot button:active { color: var(--link); }
.msg-foot .msg-fail { color: var(--danger); font-weight: 500; }
/* the receipt: a ring that fills as people read; a full disc with a check once everyone has. Both in the ink of
   the time beside them — it reports, it cannot be pressed, so it is never blue */
.rcpt { flex: none; width: 12px; height: 12px; margin-right: 1px; border-radius: 50%; background: conic-gradient(var(--text-3) calc(var(--p, 0) * 1%), transparent 0); box-shadow: inset 0 0 0 1.5px var(--text-3); }
.rcpt.all {
  background: var(--text-3) center / 8px no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23fff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.2l2.4 2.4 4.6-5'/%3E%3C/svg%3E");
  box-shadow: none;
}
/* sending: nothing for the first 600ms (most sends are done by then), then a small clock. Failed: the bubble keeps
   its colour and a red mark sits beside it. */
.msg-clock { flex: none; align-self: flex-end; margin-bottom: 5px; color: var(--text-3); opacity: 0; animation: fade var(--t) ease 600ms forwards; }
.msg-bang { flex: none; align-self: center; width: 18px; height: 18px; border-radius: 50%; background: var(--danger-fill); color: var(--white); font-size: var(--fs-sm); line-height: 18px; font-weight: 700; text-align: center; }

/* ---- marks on a row */
/* jumped to (quote, search hit): a wash behind the row that fades */
.msg.highlight::before { content: ""; position: absolute; inset: -3px -8px; z-index: -1; border-radius: var(--nest-r); background: var(--accent-soft); animation: msg-wash 1.8s ease-out forwards; }
@keyframes msg-wash { 0%, 33% { opacity: 1; } 100% { opacity: 0; } }
/* mentions me and was unread when the conversation opened */
.msg.at-me::after { content: ""; position: absolute; left: -10px; top: 2px; bottom: 2px; width: 3px; border-radius: var(--pill); background: var(--tint); }
.msg.at-me.first::after { top: 20px; }
/* arrival: only messages that come in while the conversation is open */
.msg.enter { animation: msg-in 180ms var(--ease) both; }
.msg.mine.enter { animation: msg-out 220ms var(--ease-snappy) both; transform-origin: 100% 100%; }
@keyframes msg-in { from { opacity: 0; transform: translateY(8px); } }
@keyframes msg-out { from { opacity: 0; transform: translateY(12px) scale(.96); } }

/* ---- states of the list */
/* first load: the shape of a conversation, anchored at the bottom, only if it takes longer than 150ms */
.chat-skel { display: flex; flex-direction: column; gap: 12px; padding: 4px 0 8px; }
.chat-skel.first { opacity: 0; animation: fade 0s linear 150ms forwards; }
.chat-skel .sk-row { display: flex; align-items: flex-end; gap: 8px; min-height: 0; }
.chat-skel .sk-row.mine { justify-content: flex-end; }
.chat-skel .sk-row.join { margin-top: -10px; }
.chat-skel .sk-b { height: var(--bubble-h); border-radius: var(--bubble-r); }
.chat-skel.older { padding-bottom: 12px; }
/* a conversation nobody has written in: who is here */
.chat-intro { display: flex; flex-direction: column; align-items: center; margin: auto 0; padding: 24px 16px 20px; text-align: center; }
.chat-inner > .chat-intro:first-child { margin-top: auto; }
.chat-intro b { margin-top: 12px; font-size: var(--fs-title3); line-height: var(--lh-title3); font-weight: 600; letter-spacing: var(--track-title); }
.chat-intro > span { margin-top: 2px; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-3); }

/* ---- over the list: the way to the first unread message (top) and back to the latest (above the composer) */
.chat-unread-up, .chat-jump { background: var(--material-pop); -webkit-backdrop-filter: var(--blur); backdrop-filter: var(--blur); box-shadow: var(--shadow-pop); transition: background-color var(--t-fast), scale var(--t-fast) var(--ease); }
.chat-unread-up { position: absolute; top: 10px; left: 50%; translate: -50% 0; z-index: 4; display: flex; align-items: center; gap: 5px; height: 28px; padding: 0 12px 0 9px; border-radius: var(--pill); color: var(--link); font-size: var(--fs-sm); font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
.chat-jump { position: absolute; bottom: calc(100% + 8px); right: max(var(--gutter), calc((100% - var(--col)) / 2 + var(--gutter))); z-index: 4; display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; color: var(--text-2); }
.chat-jump .badge { position: absolute; top: -8px; left: 50%; translate: -50% 0; background: var(--tint); box-shadow: 0 0 0 2px var(--bg-content); }
@media (hover: hover) { .chat-jump:hover, .chat-unread-up:hover { background: var(--bg-elev); } .chat-jump:hover { color: var(--text); } }
.chat-jump:active, .chat-unread-up:active { scale: .96; transition-duration: 0s; }
.chat-jump:focus-visible { border-radius: 50%; box-shadow: var(--ring), var(--shadow-pop); }
.chat-unread-up:focus-visible { border-radius: var(--pill); box-shadow: var(--ring), var(--shadow-pop); }
.jump-enter-active, .jump-leave-active { transition: opacity 150ms ease, scale 150ms var(--ease); }
.jump-enter-from, .jump-leave-to { opacity: 0; scale: .8; }

/* ------------------------------------------------------------------ composer */
.chat-foot { flex: none; position: relative; padding: 0 var(--gutter) calc(12px + var(--safe-b)); transition: box-shadow var(--t) var(--ease); }
.chat-foot.raised { box-shadow: inset 0 var(--hair) 0 var(--hairline); }   /* messages are under its edge */
/* who is typing: the line is always there (nothing moves when it speaks), led by three dots */
.typing { display: flex; align-items: center; gap: 6px; height: 18px; max-width: calc(var(--col) - 2 * var(--gutter)); margin: 0 auto; padding-left: 12px; font-size: var(--fs-sm); line-height: 18px; color: var(--text-3); white-space: nowrap; overflow: hidden; opacity: 0; transition: opacity var(--t); }
.typing.on { opacity: 1; }
.typing-dots { display: inline-flex; align-items: center; gap: 3px; flex: none; }
.typing-dots i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; opacity: .3; }
.typing.on .typing-dots i { animation: typing-dot 1.2s ease-in-out infinite; }
.typing.on .typing-dots i:nth-child(2) { animation-delay: 150ms; }
.typing.on .typing-dots i:nth-child(3) { animation-delay: 300ms; }
@keyframes typing-dot { 0%, 60%, 100% { opacity: .3; } 30% { opacity: 1; } }

.composer { position: relative; max-width: calc(var(--col) - 2 * var(--gutter)); margin: 0 auto; }
.composer-row { display: flex; align-items: flex-end; gap: 6px; }
/* beside the field: add (+) and the 格式 bar's switch (Aa) */
.composer-tools { display: flex; align-items: center; gap: 6px; flex: none; padding-bottom: 3px; }
.composer-attach { flex: none; display: flex; }
.composer-tools .icon-btn { width: 32px; height: 32px; border-radius: 50%; background: var(--fill-2); color: var(--text-2); }
.composer-tools .icon-btn:focus-visible { border-radius: 50%; }
@media (hover: hover) { .composer-tools .icon-btn:hover { background: var(--fill); color: var(--text); } }
.composer-attach .icon-btn .icon { transition: transform var(--t) var(--ease-snappy); }
.composer-attach .icon-btn.active { background: var(--fill); color: var(--text); }
.composer-attach .icon-btn.active .icon { transform: rotate(45deg); }
.composer-attach .menu { left: 0; bottom: calc(100% + 8px); min-width: 148px; }   /* placed against .composer, clear of the field */
/* (on: tinted like a chosen chip — the bar is showing) */
.composer-tools .composer-aa.active { background: var(--accent-soft); color: var(--link); }
@media (hover: hover) { .composer-tools .composer-aa.active:hover { background: color-mix(in srgb, var(--accent-soft), var(--link) 10%); color: var(--link); } }

/* the 格式 bar: the field's first row, quiet until it is used. It scrolls sideways where it does not fit. */
.cfmt { display: flex; align-items: center; gap: 1px; min-width: 0; padding: 5px 7px 0 8px; overflow-x: auto; scrollbar-width: none; }
.cfmt::-webkit-scrollbar { display: none; }
.cfmt-b { position: relative; display: flex; align-items: center; justify-content: center; flex: none; width: 28px; height: 26px; border-radius: var(--radius-ctl); color: var(--text-2); transition: background-color var(--t-fast), color var(--t-fast); }
@media (hover: hover) { .cfmt-b:hover { background: var(--fill-3); color: var(--text); } }
.cfmt-b:active { background: var(--fill-2); transition-duration: 0s; }
.cfmt-b.on { background: var(--accent-soft); color: var(--link); }
.cfmt-b:focus-visible { border-radius: var(--radius-ctl); }
.cfmt-sep { flex: none; width: var(--hair); height: 16px; margin: 0 5px; background: var(--hairline-strong); }
.cfmt .grow { min-width: 10px; }
.cfmt-color { display: flex; flex: none; }   /* (not positioned: its menu is placed against .composer, out of the bar's scrolling) */
.cfmt-bar { position: absolute; left: 8px; right: 8px; bottom: 3px; height: 3px; border-radius: var(--pill); }
.cfmt-red { background: var(--neg); } .cfmt-green { background: var(--pos); } .cfmt-orange { background: var(--warn); } .cfmt-gray { background: var(--text-3); }
.cfmt-menu { left: var(--x, 0px); bottom: calc(100% + 8px); min-width: 200px; }
.cfmt-dot { flex: none; width: 12px; height: 12px; margin: 0 3px 0 2px; border-radius: 50%; }
.cfmt-hint { margin-left: auto; padding-left: 12px; font-size: var(--fs-xs); color: var(--text-3); }
.menu > button:hover .cfmt-hint, .menu > button:focus-visible .cfmt-hint { color: inherit; opacity: .8; }

/* preview (the message exactly as it will be sent) and the 格式 sheet: one panel over the end of the list */
.cpop {
  position: absolute; left: 0; right: 0; bottom: calc(100% + 8px); z-index: 18; display: flex; flex-direction: column; max-height: min(460px, 55vh);
  border-radius: var(--pop-r); background: var(--material-pop); -webkit-backdrop-filter: var(--blur-thick); backdrop-filter: var(--blur-thick);
  box-shadow: var(--shadow-pop); transform-origin: bottom center; animation: pop-up var(--t) var(--ease-snappy);
}
.cpop-h { display: flex; align-items: baseline; gap: 8px; flex: none; padding: 10px 8px 6px 14px; font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }
.cpop-h b { font-size: var(--fs-md); font-weight: 600; color: var(--text); }
.cpop-x { position: relative; align-self: center; display: flex; align-items: center; justify-content: center; flex: none; width: 24px; height: 24px; border-radius: 50%; color: var(--text-3); transition: background-color var(--t-fast), color var(--t-fast); }
@media (hover: hover) { .cpop-x:hover { background: var(--fill-2); color: var(--text); } }
.cpop-x:focus-visible { border-radius: 50%; }
.cpop-b { min-height: 0; overflow-y: auto; padding: 2px 14px 14px; overscroll-behavior: contain; }
/* (the preview draws a real message row: its gutter and hover time have nothing to do here) */
.cpop-preview .cpop-b { padding-top: 4px; }
.cpop .msg { padding: 0; }
.cpop .msg.first { margin-top: 0; }
.cpop-empty { padding: 10px 0 6px; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-3); text-align: center; }
.fh { display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content; }   /* (no column gap: the row lines run unbroken) */
.fh-row { display: contents; }
.fh-row > * { padding: 7px 14px 7px 0; border-bottom: var(--hair) solid var(--hairline); }
.fh-row > :last-child { padding-right: 0; }
.fh-row:last-of-type > * { border-bottom: 0; }
.fh-src { font-family: var(--mono); font-size: var(--fs-sm); line-height: var(--lh-md); color: var(--text-2); white-space: pre; }
.fh-out { min-width: 0; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text); overflow-wrap: anywhere; }
.fh-out.rt table, .fh-out.rt pre { font-size: var(--fs-sm); line-height: var(--lh-sm); }
.fh-key { font: 500 var(--fs-xs)/var(--lh-md) var(--font); color: var(--text-3); text-align: right; white-space: nowrap; }
.fh-note { grid-column: 1 / -1; margin-top: 10px; font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }
/* the field: everything that belongs to the message being written lives inside it. At rest its edge is a
   hairline like every other field's and card's (half a pixel on a 2x screen); focused, a full pixel of tint */
.composer-field { flex: 1; min-width: 0; display: flex; flex-direction: column; border-radius: var(--field-r); background: var(--bg-content); box-shadow: inset 0 0 0 var(--hair) var(--hairline-strong); transition: box-shadow var(--t-fast); }
.composer-field:focus-within { box-shadow: inset 0 0 0 1px var(--tint); }      /* no ring: it is focused all day */
.composer-field.dropping { box-shadow: inset 0 0 0 1px var(--tint), 0 0 0 3px color-mix(in srgb, var(--tint) 20%, transparent); }   /* something dragged is over it */
.composer-in { display: flex; align-items: flex-end; min-height: 38px; padding-right: 4px; }
/* What is being written (composer.js, parts.js): text, and between two texts one picture or file. The parts flow
   like words: a text takes a row of its own, tiles stand side by side in rows between the texts, and an empty
   text between two tiles (.gap) takes no room until the caret is put there. One line of text is 38px, as the
   field always was; a long message scrolls inside the field. (The left padding is the text's edge — and the room
   the bar that shows where a dragged picture will land needs before a row's first tile.) */
.composer-parts {
  --tile-h: 76px;
  position: relative; flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: flex-end; align-content: flex-start;
  max-height: min(380px, 46vh); padding: 5.5px 0 5.5px 14px; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; cursor: text;
}
.composer-parts.scrolled { -webkit-mask-image: linear-gradient(transparent, #000 6px); mask-image: linear-gradient(transparent, #000 6px); }
/* (the mirror is a text's copy in a block of the same width and type: composer.js asks it where the lines are) */
.cpt, .cpt-mirror { font-size: var(--fs-chat); line-height: var(--lh-chat); white-space: pre-wrap; overflow-wrap: break-word; word-break: normal; }
.cpt { flex: 0 0 100%; min-width: 0; display: block; padding: 3px 0; border: 0; outline: none; resize: none; overflow: hidden; background: none; color: var(--text); }
/* (a focused text field always matches :focus-visible: without this the shared ring is drawn around the text,
   inside the capsule. The field's own edge above is the one focus mark.) */
.cpt:focus-visible { box-shadow: none; border-radius: 0; }
@supports (field-sizing: content) { .cpt { field-sizing: content; min-height: calc(var(--lh-chat) + 6px); } }
.cpt::placeholder { color: var(--text-4); }
.cpt.gap { flex: 0 0 0; width: 0; height: 0; min-height: 0; padding: 0; opacity: 0; pointer-events: none; }
.cpt-mirror { position: fixed; left: -9999px; top: 0; visibility: hidden; pointer-events: none; }
/* a picture or file in the text: a tile that looks like what it will be. It is a large letter — a click on its
   left or right half puts the caret before or after it — and it can be dragged to another place. */
.cpm {
  position: relative; flex: none; display: flex; align-items: center; max-width: calc(100% - 6px); margin: 3px 6px 3px 0; overflow: hidden;
  border-radius: calc(var(--field-r) - 10px); background: var(--fill-3); cursor: grab; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
  transition: opacity var(--t-fast), box-shadow var(--t-fast);
}
.cpm.pic { height: var(--tile-h); box-shadow: 0 0 0 1px var(--hairline); }
.cpm.pic img { display: block; height: 100%; width: auto; min-width: 44px; max-width: 200px; object-fit: cover; transition: opacity var(--t), filter var(--t-fast); }
:root[data-theme="dark"] .cpm.pic img { filter: brightness(.9); }
@media (hover: hover) {
  .cpm.pic:hover img { filter: brightness(.95); }
  :root[data-theme="dark"] .cpm.pic:hover img { filter: brightness(.84); }
}
.cpm.file { gap: 8px; height: 48px; max-width: min(240px, 100% - 6px); padding: 0 30px 0 6px; }
.cpm.file .file-ext { width: 32px; height: 36px; }
.cpm-m { display: flex; flex-direction: column; min-width: 0; }
.cpm-m .file-n { font-size: var(--fs-md); line-height: var(--lh-md); }
.cpm-s { font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--text-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* uploading: dimmed, a light running along its foot; failed: outlined, with 重试 */
.cpm.busy img { opacity: .5; }
.cpm.busy::after { content: ""; position: absolute; left: 0; bottom: 0; width: 50%; height: 2px; background: linear-gradient(90deg, transparent, var(--tint), transparent); animation: cpm-busy 1.1s linear infinite; }
@keyframes cpm-busy { from { transform: translateX(-100%); } to { transform: translateX(200%); } }
.cpm.err::before { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit; box-shadow: inset 0 0 0 1.5px var(--danger); pointer-events: none; }
.cpm.err img { opacity: .4; }
.cpm.err .cpm-s { color: var(--danger); }
.cpm-retry { position: relative; z-index: 2; flex: none; padding: 2px 9px; border-radius: var(--pill); background: var(--fill-2); font-size: var(--fs-xs); line-height: var(--lh-xs); font-weight: 600; color: var(--link); cursor: pointer; }
.cpm.pic .cpm-retry { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); background: var(--scrim); color: var(--white); white-space: nowrap; }
.cpm-retry:focus-visible { border-radius: var(--pill); }
/* (a veil of the text colour over either ground: a shade darker on light, a shade lighter on dark) */
.cpm-retry::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--text); opacity: 0; transition: opacity var(--t-fast); pointer-events: none; }
@media (hover: hover) { .cpm-retry:hover::after { opacity: .08; } }
.cpm-retry:active::after { opacity: .16; transition-duration: 0s; }
/* marked by a first Backspace / Delete (the second removes it), and while it is dragged */
.cpm.armed { box-shadow: 0 0 0 2px var(--tint); }
.cpm.armed img { opacity: .72; }
.cpm.moving { opacity: .35; }
.cpm-x { position: absolute; top: 4px; right: 4px; z-index: 2; display: flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; background: var(--scrim); color: var(--white); cursor: pointer; transition: background-color var(--t-fast), transform var(--t-fast); }
.cpm.file .cpm-x { top: 50%; right: 7px; margin-top: -9px; background: var(--text-3); color: var(--bg-elev); }
@media (hover: hover) {
  .cpm.pic .cpm-x:hover { transform: scale(1.12); }
  .cpm.file .cpm-x:hover { background: var(--text-2); }
}
.cpm-x:active { transform: scale(.92); transition-duration: 0s; }
.cpm-x:focus-visible { border-radius: 50%; }
/* where a dragged picture or file will land: a bar between two lines of text, or beside a tile */
.cpd { position: absolute; z-index: 3; border-radius: var(--pill); background: var(--tint); pointer-events: none; }
.cpd.h { left: 14px; right: 8px; height: 2px; }
.cpd.v { width: 2px; }
/* send exists only when there is something to send (its place is kept, so the text never re-wraps) */
.composer-send {
  position: relative; display: flex; align-items: center; justify-content: center; flex: none; width: 30px; height: 30px; margin: 4px 0 4px 6px; border-radius: 50%;
  background: var(--accent); color: var(--accent-text); transform: scale(.6); opacity: 0; pointer-events: none;
  transition: transform 180ms var(--ease-snappy), opacity var(--t-out), background-color var(--t-fast);
}
.composer-send.on { transform: none; opacity: 1; pointer-events: auto; }
.composer-send.on:disabled { opacity: .4; pointer-events: none; }
.composer-send:focus-visible { border-radius: 50%; }
@media (hover: hover) { .composer-send.on:hover { background: var(--accent-hover); } }
.composer-send.on:active { transform: scale(.92); transition-duration: 0s; }
/* what the message answers to, or replaces: a strip that unfolds at the top of the field */
.composer-ctx { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 160ms var(--ease); }
.composer-ctx.open { grid-template-rows: 1fr; }
.composer-ctx-in { min-height: 0; overflow: hidden; }
.composer-ctx-row { position: relative; display: flex; align-items: center; gap: 8px; height: 38px; margin: 6px 6px 0; padding: 0 5px 0 17px; border-radius: calc(var(--field-r) - 6px); background: var(--fill-3); }
.composer-ctx-row::before { left: 7px; top: 7px; bottom: 7px; background: var(--tint); }
.composer-ctx-pic { flex: none; width: 26px; height: 26px; border-radius: calc(var(--nest-r) - 4px); object-fit: cover; }
.composer-ctx-t { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.composer-ctx-t b { font-size: var(--fs-sm); line-height: 15px; font-weight: 600; color: var(--link); }
.composer-ctx-t span { font-size: var(--fs-sm); line-height: 15px; color: var(--text-2); }
.composer-ctx-x { position: relative; display: flex; align-items: center; justify-content: center; flex: none; width: 28px; height: 28px; border-radius: 50%; color: var(--text-3); transition: background-color var(--t-fast), color var(--t-fast); }
@media (hover: hover) { .composer-ctx-x:hover { background: var(--fill-2); color: var(--text); } }
.composer-ctx-x:active { background: var(--fill); transition-duration: 0s; }
.composer-ctx-x:focus-visible { border-radius: 50%; }
/* @ candidates: a popover above the field */
.mention-pop { position: absolute; left: var(--field-x); bottom: calc(100% + 8px); z-index: 20; width: 280px; max-width: calc(100% - var(--field-x)); padding: 5px; border-radius: var(--pop-r); background: var(--material-pop); -webkit-backdrop-filter: var(--blur-thick); backdrop-filter: var(--blur-thick); box-shadow: var(--shadow-pop); transform-origin: bottom left; animation: pop-up var(--t) var(--ease-snappy); }
.mention-pop button { display: flex; align-items: center; gap: 9px; width: 100%; min-height: 36px; padding: 0 8px; border-radius: calc(var(--pop-r) - 5px); text-align: left; font-size: var(--fs-body); }
.mention-pop button b { font-weight: 500; flex: none; }
.mention-pop button span:last-child { flex: 1; font-size: var(--fs-sm); color: var(--text-3); }
.mention-pop button.active { background: var(--accent); color: var(--accent-text); }
.mention-pop button.active span:last-child { color: color-mix(in srgb, var(--accent-text) 80%, transparent); }
.mention-pop .mention-all { width: 26px; height: 26px; font-size: var(--fs-md); --av: var(--c6); }
/* files dragged over the conversation: the list is veiled, the field stays clear — dropped on it, a picture goes
   to the line under the pointer */
.chat-foot.dragging { z-index: 31; }
.chat-foot.dragging .composer-field:not(.dropping) { box-shadow: inset 0 0 0 1px var(--tint); }
.chat-drop { position: absolute; inset: 0; z-index: 30; display: flex; background: color-mix(in srgb, var(--bg-content) 80%, transparent); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); pointer-events: none; }
.chat-drop-in { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; margin: 12px; border-radius: var(--radius-xl); outline: 2px dashed var(--tint); outline-offset: -2px; color: var(--link); text-align: center; }
.chat-drop-in b { margin-top: 8px; font-size: var(--fs-headline); line-height: var(--lh-headline); font-weight: 600; }
.chat-drop-in span { font-size: var(--fs-sm); line-height: var(--lh-sm); color: var(--text-3); }
.drop-enter-active, .drop-leave-active { transition: opacity var(--t-out) ease; }
.drop-enter-from, .drop-leave-to { opacity: 0; }

/* read receipts dialog: who has not read it yet, who has */
.rc { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.rc-h { margin-bottom: 6px; font-size: var(--fs-sm); line-height: var(--lh-sm); font-weight: 600; color: var(--text-3); font-variant-numeric: tabular-nums; }
.rc-u { display: flex; align-items: center; gap: 8px; min-height: 34px; font-size: var(--fs-body); min-width: 0; }

/* ------------------------------------------------------------------ inspector */
.insp { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg-insp); border-left: var(--hair) solid var(--hairline); }
.insp.anim:not(.overlay) { animation: insp-in var(--t-base) var(--ease); }
@keyframes insp-in { from { opacity: 0; transform: translateX(16px); } }
.insp > .modal-grab { display: none !important; }
.insp-bar { display: flex; align-items: center; gap: 4px; flex: none; min-height: var(--bar-h); padding: 0 10px 0 16px; border-bottom: var(--hair) solid var(--hairline); }
.insp-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-headline); line-height: var(--lh-headline); font-weight: 600; letter-spacing: var(--track-title); }
.insp-bar .seg { flex: 0 1 auto; min-width: 0; margin-right: 6px; }
.insp-body { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 12px 20px; overscroll-behavior: contain; }
.insp-body.flush { padding: 0; }
.insp-body iframe { width: 100%; height: 100%; border: 0; background: var(--white); display: block; }
/* dashboards inside the inspector: one column, compact header */
.insp-body .page { padding: 0 4px 12px; overflow: visible; }
/* the inspector's own bar already names the dashboard: its header is the status line and the actions, in the
   flow (not a second toolbar) */
.insp-body .page > .dash-h { position: static; min-height: 0; margin: 0 0 14px; padding: 2px 0 0; background: none; -webkit-backdrop-filter: none; backdrop-filter: none; box-shadow: none; }
.insp-body .dash-h h1 { display: none; }
.insp-body .dash-h-t { min-width: 0; flex-basis: 100%; }
.insp-body .dash-h .select { width: 100%; max-width: none; }   /* the picker takes the pane's width: its label is not cut while room is free beside it */
/* a dashboard's strip of tabs is wider than the pane and scrolls sideways: an edge fades while there is more
   behind it (conversation.js markStrips sets base.css's .fade-r, and .fade-l once the strip has moved) */
.insp-body .grid-2, .insp-body .grid-3 { grid-template-columns: minmax(0, 1fr); }
.insp-body .span-2 { grid-column: auto; }
/* a row of four figure tiles stays two by two (as on a phone): one per row would push everything else off the pane */
.insp-body .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.insp-body .grid-4 .kpi-v { font-size: var(--fs-title2); }
/* ---- a pinned dashboard at the pane's width
   A dashboard is laid out for its own page, by the window's breakpoints; in this pane it has the width of a phone
   whatever the window is. So the narrow layout of the shared pieces — what a phone's `@media (max-width: 767px)`
   rules do to them — is repeated here behind `.insp-body`; a dashboard repeats its own narrow rules in its style
   sheet the same way (or sizes itself by container queries, which needs neither). Only what the width asks for:
   a phone's touch sizes, and the lines it drops to save height, are not copied (this is still a pointer and a
   tall column). Not on phones, where the phone rules already apply. */
@media (min-width: 768px) {
  /* what a phone leaves out for want of width; cards on one 16px text edge */
  .insp-body .page .hide-mobile { display: none !important; }
  .insp-body .page .card:not(.flush) { padding: 16px; }
  .insp-body .page .card.flush > .card-h { padding: 16px 16px 0; }
  .insp-body .page .card.flush .table th:first-child, .insp-body .page .card.flush .table td:first-child { padding-left: 16px; }
  .insp-body .page .card.flush .table th:last-child, .insp-body .page .card.flush .table td:last-child { padding-right: 16px; }
  .insp-body .page .grid-4 > .card { padding: 14px 16px; }
}
/* a narrow window: the inspector slides over the conversation */
.insp-mask { position: absolute; inset: 0; z-index: 39; background: var(--overlay); animation: fade var(--t) ease; }
.insp.overlay { position: absolute; top: 0; right: 0; bottom: 0; z-index: 40; width: min(var(--insp-w), 100%); border-left: 0; box-shadow: var(--shadow-sheet); animation: slide-in var(--t-base) var(--ease-sheet); }
@keyframes slide-in { from { transform: translateX(100%); } to { transform: none; } }

/* ------------------------------------------------------------------ phone */
@media (max-width: 767px) {
  .chat { --gutter: 12px; --bubble-h: calc(var(--lh-chat) + 16px); }
  .chat-bar { padding: var(--safe-t) 6px 0 2px; min-height: calc(var(--bar-h) + var(--safe-t)); gap: 0; background: var(--bg-content); }
  .chat-back { position: relative; display: flex !important; align-items: center; flex: none; min-width: 36px; height: 44px; padding: 0 2px 0 6px; color: var(--link); }
  .chat-back::after { content: ""; position: absolute; inset: 0 -4px; }   /* a 44px target; the chevron stays where it is */
  .chat-back:active { opacity: .5; }
  .chat-back .badge { margin-left: -3px; }
  .chat-id { margin-left: 0; padding: 4px 6px; gap: 9px; flex: 1; max-width: none; }
  .chat-id-t b { font-size: var(--fs-headline); line-height: 21px; }
  .chat-id-t span { font-size: var(--fs-xs); line-height: var(--lh-xs); }
  .chat-acts .icon-btn { width: 40px; height: 40px; }
  .chat-inner { padding-top: 8px; }
  .msg-day { box-shadow: 0 0 0 var(--hair) var(--hairline); }   /* (cards have no shadow on phones: an edge instead) */
  .msg-col { max-width: 82%; }
  .bubble { padding: 8px 13px; }
  .bubble.combo > .bubble-t { padding: 5px 10px; }
  .rt :is(th, td) { padding-left: 12px; }
  .bubble.combo.folded > .bubble-t { max-height: calc(var(--lh) * 8 + 10px); }
  /* cards, files and robot posts all take one width: the column less a margin on the far side, so whose it is
     still reads from the side it starts on (mine keep the same width, from the right) */
  .msg.is-bot .msg-col, .msg.media .msg-col { flex: 1 1 auto; max-width: calc(100% - 36px); }
  .msg.is-bot .msg-line, .msg.media .msg-line { align-self: stretch; }
  .msg.is-bot .msg-stack, .msg.media .msg-stack { flex: 1 1 auto; }
  .msg.is-bot .bubble { padding: 12px 14px; }
  /* figures in a narrow column: a size smaller, and every row is one line — the figure columns are only as wide
     as their figures, the note behind a figure is a caption on the same line, and the label is what gives way
     (cut with an ellipsis; the whole of it is in its title and in what 复制 copies) */
  .bubble:has(> .tb) { --lh: var(--lh-md); }
  .bubble-t.tb { --tb-fig: max-content; column-gap: 12px; font-size: var(--fs-md); }   /* (tracks no wider than their figures: the gap alone keeps two columns of figures apart) */
  .tb .k { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tb .v small { font-size: var(--fs-xs); }
  .tb .v.wn { column-gap: 4px; }
  /* … and it gives way last. A track for the first figures is as wide as the widest of them ("−0.2% → 0.3%"),
     which would cut "SONY GROUP" in a row whose own figure is "0.8%". So the label and the first figure share a
     line across what would be their two tracks (.tb.flow, message.js): the figures still end on one edge, and a
     label is cut only when its own row has no room for it. */
  @supports (grid-template-columns: subgrid) {
    .bubble-t.tb.flow { grid-template-columns: minmax(0, 1fr) var(--cols-rest); }
    .tb.flow .kf { grid-column: 1; display: flex; align-items: baseline; justify-content: space-between; gap: 8px; min-width: 0; }
    .tb.flow .kf .k { flex: 0 1 auto; }
    .tb.flow .kf .v { flex: none; }
    /* a section without notes (message.js markPlain) does not keep the note track free: each row is one line,
       the figures on the bubble's edge, the name with all the room that is left */
    .tb.flow .rw.plain { grid-column: 1 / -1; display: flex; align-items: baseline; gap: 12px; min-width: 0; }
    .tb.flow .rw.plain .kf { flex: 1 1 auto; }
    .tb.flow .rw.plain > .v { display: block; flex: none; min-width: calc(var(--w, 0) * 1ch + .6ch); }
  }
  .msg-imgs:not([data-n="1"]) { width: min(302px, 100%); }
  .msg.at-me::after { left: -7px; }   /* inside the 12px gutter, clear of the screen's edge and its corner curve */
  .chat-foot { padding-bottom: calc(8px + var(--safe-b)); background: var(--bg-content); }
  .typing { height: 16px; line-height: 16px; padding-left: 56px; }
  .composer-tools { gap: 4px; padding-bottom: 2px; }
  .composer-tools .icon-btn { width: 36px; height: 36px; }
  .cfmt { gap: 2px; padding: 4px 6px 0; }
  .cfmt-b { width: 36px; height: 34px; }
  .cpop { max-height: min(420px, 50dvh); }
  .fh { grid-template-columns: max-content minmax(0, 1fr); }
  .fh-row > .fh-src { padding-right: 10px; }
  .fh-row > .fh-out { padding-right: 0; }
  .fh-key { display: none; }
  .composer-in { min-height: 40px; }
  /* (--fs-chat is 16.5px here: no zoom on focus in iOS. One line is 40px; the field takes at most two fifths of
     the screen, the keyboard has the rest) */
  .composer-parts { --tile-h: 64px; max-height: min(300px, 38dvh); padding-top: 6px; padding-bottom: 6px; }
  .cpm.pic img { max-width: 150px; }
  .composer-send { width: 32px; height: 32px; }
  .composer-send::after, .composer-ctx-x::after { content: ""; position: absolute; inset: -8px; }
  .cpm-x { width: 22px; height: 22px; }
  .cpm.file .cpm-x { margin-top: -11px; }
  .cpm-x::after { content: ""; position: absolute; inset: -6px; }
  .mention-pop { left: 0; max-width: 100%; }
  .mention-pop button { min-height: 44px; }
  .chat-jump { width: 40px; height: 40px; right: 10px; }
  /* the inspector is a bottom sheet with a grabber */
  .insp.overlay { position: fixed; top: auto; left: 0; right: 0; bottom: 0; z-index: 90; width: 100%; height: min(86dvh, 100dvh - 56px - var(--safe-t)); background: var(--bg); border-radius: var(--radius-xl) var(--radius-xl) 0 0; animation: sheet-up var(--t-slow) var(--ease-sheet); padding-bottom: var(--safe-b); overflow: hidden; }
  /* Dark: the page under the sheet is black, and the grouped ground is black too — a sheet in that colour is not
     a layer. So the sheet takes the raised step every other sheet has, and what lies on it steps up with it:
     cards one step, the wells inside them one more. The dashboards' own rules keep asking for
     --bg / --bg-elev / --bg-sunken; inside this sheet those names point one step higher. (The two steps are
     read on the sheet, where the names still mean the page's, and handed to the body.) */
  :root[data-theme="dark"] .insp.overlay { --bg: var(--bg-elev); --insp-card: var(--bg-sunken); --insp-well: var(--bg-active); }
  :root[data-theme="dark"] .insp.overlay .insp-body { --bg-elev: var(--insp-card); --bg-sunken: var(--insp-well); --ring-bg: var(--insp-card); }
  .insp > .modal-grab { display: block !important; position: relative; z-index: 1; margin-bottom: -12px; }
  .insp-mask { position: fixed; z-index: 89; }
  .insp-bar { min-height: 52px; padding: 12px 12px 0 14px; gap: 8px; border-bottom: 0; }   /* title and close on the cards' edges */
  .insp:has(.insp-body.is-scrolled) .insp-bar { box-shadow: 0 var(--hair) 0 var(--hairline); }   /* an edge once cards are under it */
  .insp-bar .seg { margin-right: 0; }
  .insp-title { font-size: var(--fs-headline); color: var(--text); }
  /* the bar's buttons are one kind: filled circles — small beside a title, the height of the segmented control
     when they share the row with one (the tabs) */
  .insp-bar .icon-btn { width: var(--ctl-h-sm); height: var(--ctl-h-sm); border-radius: 50%; background: var(--fill-2); }
  .insp-bar.tabbed .icon-btn { width: var(--ctl-h); height: var(--ctl-h); }
  .insp-bar .icon-btn:active { background: var(--fill); }
  .insp-bar .icon-btn:focus-visible { border-radius: 50%; }
  .insp-body { padding: 4px 12px 16px; }
  .rc { gap: 12px; }
}

/* ------------------------------------------------------------------ sheets owned by the conversation */
/* group settings: grouped lists on a grey ground, like a settings sheet */
.modal:has(.gs) { --gs-ground: var(--bg-sunken); background: var(--gs-ground); }
:root[data-theme="dark"] .modal:has(.gs) { --gs-ground: var(--bg); }
.modal:has(.gs) .modal-b { padding-top: 6px; }
/* the header's hairline only once content has moved under it (group-settings.js sets .gs-scrolled) */
.modal:has(.gs) .modal-h { transition: box-shadow var(--t) var(--ease); }
.modal.is-scroll:has(.gs):not(.gs-scrolled) .modal-h { box-shadow: none; }
.modal:has(.gs) .modal-f { box-shadow: inset 0 var(--hair) 0 var(--hairline); }   /* 保存 / 取消 sit on the sheet's own ground, under an edge */
.gs-head { display: flex; flex-direction: column; align-items: center; margin-bottom: 20px; text-align: center; }
.gs-head .avatar { --dot-ring: var(--gs-ground); }
.gs-head b { margin-top: 10px; font-size: var(--fs-title3); line-height: var(--lh-title3); font-weight: 600; letter-spacing: var(--track-title); }
.gs-head span { margin-top: 2px; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-3); }
.gs-l { display: block; margin-bottom: 8px; }
.gs .list-row textarea.input { display: block; height: auto; margin: 0; padding: 8px 10px; border-radius: var(--radius-sm); background: var(--fill-3); text-align: left; resize: none; }
@supports (field-sizing: content) { .gs .list-row textarea.input { field-sizing: content; min-height: 0; max-height: 220px; } }   /* the whole notice shows: no clipped last line */
.gs .list-row textarea.input:focus { background: var(--accent-soft); }
.gs-h-row { display: flex; align-items: center; justify-content: space-between; padding-right: 8px; }
.gs-h-row .btn { margin: -4px 0; }
.gs-add { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; margin-bottom: 10px; }
.gs-add .pick { align-self: stretch; }
.gs-add .pick-list, .gs-add .input { background: var(--bg-elev); }
.gs-member { min-height: 48px; }
.gs-member + .gs-member::before { left: 60px; }      /* the hairline starts at the name, past the avatar */
.gs-hook .row { min-height: 24px; }
.gs-hook .row > .icon { color: var(--text-3); }
.gs-hook-add { margin-top: 8px; }
.gs-hook-add .input { flex: 1; background: var(--bg-elev); }
.gs-url { display: block; margin-top: 8px; padding: 7px 9px; border-radius: var(--radius-sm); background: var(--fill-3); font-size: var(--fs-xs); line-height: var(--lh-sm); word-break: break-all; user-select: all; -webkit-user-select: all; }
.gs-leave { margin-top: 28px; }
/* pinned dashboards: chips that toggle. A chosen chip is tinted with a check, never filled: the filled accent is
   for the one primary button of the dialog (创建, 保存) */
.dash-pick { display: flex; flex-wrap: wrap; gap: 6px; }
.dash-pick button { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px 0 10px; border-radius: var(--pill); background: var(--fill-2); font-size: var(--fs-md); color: var(--text-2); transition: background-color var(--t-fast), color var(--t-fast), transform var(--t-fast) var(--ease); }
@media (hover: hover) { .dash-pick button:hover { background: var(--fill); } }
.dash-pick button:active { transform: scale(.97); transition-duration: 0s; }
.dash-pick button:focus-visible { border-radius: var(--pill); }
.gs .dash-pick button { background: var(--bg-elev); box-shadow: var(--shadow-card); }
.gs .dash-pick button:focus-visible { box-shadow: var(--ring); }
@media (hover: hover) { .gs .dash-pick button:hover { background: color-mix(in srgb, var(--bg-elev) 94%, var(--text)); } }
.dash-pick button.on { --on: var(--accent-soft); background: var(--on); color: var(--link); }
.gs .dash-pick button.on { background: linear-gradient(var(--on), var(--on)), var(--bg-elev); }   /* on the settings sheet a chip is a small card: chosen, it is the same card with the tint on it */
@media (hover: hover) { .dash-pick button.on:hover { --on: color-mix(in srgb, var(--accent-soft), var(--link) 8%); } }
/* people pickers (new group, add members, new direct chat) */
.pick { display: flex; flex-direction: column; gap: 8px; }
.pick.tall .pick-list { max-height: min(420px, 52vh); min-height: 220px; }
/* the highlighted row is where Return goes: it is drawn only while the search field can receive that Return (on a
   phone nothing has the focus until the field is tapped, so no row looks chosen). A row under the pointer keeps it. */
.pick.tall:not(:focus-within) .pick-item.active:not(:hover) { background: none; }
.pick-item.two { width: 100%; min-height: 52px; text-align: left; }
.pick-item.two .grow { display: flex; flex-direction: column; min-width: 0; }
.pick-item.two b { font-weight: 500; line-height: var(--lh-body); }
.pick-none { padding: 28px 12px; text-align: center; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-3); }
/* confirm dialogs and the share sheet's preview */
.confirm-t { white-space: pre-line; font-size: var(--fs-body); line-height: var(--lh-body); color: var(--text-2); }
.share-preview { padding: 12px 14px; margin-bottom: 14px; border-radius: var(--radius); background: var(--bg-sunken); }
.share-preview h4 { margin-bottom: 4px; }
.share-preview ul { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; font-size: var(--fs-md); line-height: var(--lh-md); color: var(--text-2); }
@media (max-width: 767px) {
  .modal:has(.gs) { --gs-ground: var(--bg); }
  /* dark: the grouped ground is black like the page under the sheet. The sheet takes the raised step every other
     sheet has, and its groups step up with it (the lists, pickers and chips inside ask for --bg-elev) */
  :root[data-theme="dark"] .modal:has(.gs) { --gs-ground: var(--bg-elev); }
  :root[data-theme="dark"] .gs { --bg-elev: var(--bg-sunken); }
  .dash-pick button { height: 36px; padding: 0 14px 0 12px; }
  .gs .dash-pick button { box-shadow: none; }
}
