/* =====================================================================
   HOLDING — the core "who holds this item" primitive (CONTRACT.md,
   UI-SPEC.md §8). NEW shared layer: cross-module anatomy meant to outlive
   its Travel pilot — a second module adopts it by writing templates only,
   zero new CSS, the same guarantee SafeUrl/SafeColor give other cross-
   module facts. Every colour/spacing value below resolves through an
   existing --src-* token; nothing here is a new hex or a new shadow tint.
   Loaded via templates/_head_assets.html.twig, positioned after
   components.css (it composes .btn/.card/.avatar/.dropdown, defined
   there) and before layouts.css.
   ===================================================================== */

.holding-control {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--src-space-2);
    flex-wrap: wrap;
}

.holding-identity {
    display: flex;
    align-items: center;
    gap: var(--src-space-2);
    min-width: 0;
}

.holding-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--src-space-1);
    padding: 3px var(--src-space-2) 3px 3px;
    border-radius: var(--src-radius-pill);
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-semibold);
    border: 1px solid transparent;
}

.holding-pill--held {
    background: var(--src-surface-raised);
    border-color: var(--src-border-medium);
    color: var(--src-text-secondary);
}

/* Same visual grammar as the pre-Holding "Unassigned" dashed pill — unheld
   already correctly reads as "needs attention" and Holding does not change
   what unheld means, only what a viewer can do about it. */
.holding-pill--unheld {
    background: var(--src-warning-bg);
    border: 1px dashed var(--src-warning-border);
    color: var(--src-warning-text);
    padding: 3px var(--src-space-2);
}

.holding-holder-name {
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Role tags (OP/CO/RM) — UI-SPEC-trip-bar-roles addendum. <abbr> defaults to
   a dotted underline; reset it so it doesn't read as a second, unrelated
   affordance next to a row that already has real clickable ones. Shared home
   because both the holder/collaborator tags (rendered by identityPill above,
   travel's own roleLabels only) and travel's own RM tag use this class. */
.holding-role-tag {
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-semibold);
    letter-spacing: var(--src-tracking-wide);
    text-transform: uppercase;
    text-decoration: none;
    color: var(--src-text-tertiary);
}

.holding-avatars {
    display: flex;
}

.holding-avatars .avatar {
    margin-left: -8px;
    border: 2px solid var(--src-surface-solid);
}

.holding-avatars .avatar:first-child {
    margin-left: 0;
}

.holding-avatar-more {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    margin-left: -8px;
    border-radius: var(--src-radius-round);
    background: var(--src-surface-raised);
    border: 2px solid var(--src-surface-solid);
    color: var(--src-text-tertiary);
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-semibold);
}

.holding-status-note {
    color: var(--src-text-tertiary);
    font-size: var(--src-text-xs);
    font-style: italic;
}

/* Direction A — "Toolbar Zones" (Martin-approved prototype,
   SRC-Prototype/person-row-design/index.html). Collaborate/Assign/Hand
   over/Pick up/Revoke read as ONE bordered tool, never as loose buttons that
   happen to sit in a row — the exact "bolted on" complaint the prototype was
   built to answer. Each verb is still its own <form> or <details> (a picker
   needs its own trigger), so the segmented look is pure framing around them:
   the container supplies the border/radius/divider, every .btn inside gives
   up its own. Applies at every size this control mounts (trip bar, service
   card, trip cards, the pool, reservations, team-talk) — the label text
   renders at every size (below); btnSize alone (btn-xs vs btn-sm, set in
   holding/_control.html.twig) is what varies by size. */
.holding-actions {
    display: flex;
    align-items: stretch;
    gap: 0;
    flex-wrap: nowrap;
    border: 1px solid var(--src-border-medium);
    border-radius: var(--src-radius-interactive);
    background: var(--src-surface-solid);
}
.holding-actions:empty {
    display: none;
    border: 0;
}
.holding-actions > form,
.holding-actions > details {
    display: flex;
}
.holding-actions > * + * {
    border-left: 1px solid var(--src-border-subtle);
}
.holding-actions .btn {
    border: 0;
    border-radius: 0;
}
.holding-actions > *:first-child .btn {
    border-top-left-radius: calc(var(--src-radius-interactive) - 1px);
    border-bottom-left-radius: calc(var(--src-radius-interactive) - 1px);
}
.holding-actions > *:last-child .btn {
    border-top-right-radius: calc(var(--src-radius-interactive) - 1px);
    border-bottom-right-radius: calc(var(--src-radius-interactive) - 1px);
}
/* Deliberate departure from the prototype's mockup CSS: it clips the group
   with `overflow: hidden` on the container. Here the container hosts real
   <details> picker menus that open below/beside it — overflow:hidden on an
   ancestor clips an absolutely-positioned descendant's menu regardless of
   what it is positioned against, so the corner-rounding above is done per
   edge button instead. Same visual result, no clipped dropdown. */

/* Every verb carries its text label at EVERY size (Martin's ruling,
   2026-09-12): "Pick up" and "Assign" render as a person-plus and a
   person-check glyph, which do not read as distinct actions once the
   label is gone — the icon-only degrade this comment used to describe
   (`.holding-actions--sm .holding-verb-label { display: none }`) is
   retired outright, not narrowed. The icon may still sit beside the
   label (kept for scan speed); it may never stand alone. What actually
   made size 'sm' tight was never the label text itself — a card's
   `allowedVerbs` never yields more than TWO cardVerbs at once
   (App\Service\Holding\HoldingContext::build(), the `match(true)` over
   isHeld/isHolder/isCollaborator — pick_up and collaborate are mutually
   exclusive by construction) — so the fix was sizing the compact mounts
   for that real ceiling, not hiding text to fit a three-verb ceiling
   that never occurs. See travel.css's trips-redesign owner column
   (`.trips-redesign .trip-ledger-cols, .trips-redesign .trip-header`)
   for the one mount that needed its column widened. */

/* The <details> wrapping a target picker reuses .dropdown/.dropdown-menu
   verbatim (components.css) — only the menu's own inner layout is new. */
.holding-picker-menu {
    padding: var(--src-space-2);
    min-width: 220px;
}

/* The trip-bar identity disclosure (UI-SPEC addendum §3/§6) — a native
   <details> wrapping the held-pill/avatars, opening the roster (tiers +
   revoke only — history moved to the item's own history surface, Martin's
   ruling 2026-09-06) below it. Same anatomy tier as the picker menu above:
   shared, reusable, no per-module override needed. */
.holding-identity-trigger {
    display: inline-flex;
    align-items: center;
    gap: var(--src-space-1);
    cursor: pointer;
    border-radius: var(--src-radius-interactive);
}
/* No chevron in this family (UI-SPEC-person-row.md §4) — the affordance is
   cursor:pointer, this hover tint, and a `title`, nothing else. */
.holding-identity-trigger:hover {
    background: var(--src-primary-tint);
}
.holding-identity-trigger:hover .holding-pill--held,
.holding-identity-trigger:hover .holding-pill--unheld { border-color: var(--src-border-strong); }

/* RM's own <details> (travel/_trip_view_bar.html.twig, UI-SPEC-person-row.md
   §1/§2) makes the <summary> itself the pill, so the hover tint that used to
   come from .trip-editable (retired for this field) is restated here,
   scoped to the interactive (summary) form only — the read-only <span>
   variant of the same classes is never a trigger and must not look hoverable. */
summary.holding-pill:hover {
    background: var(--src-primary-tint);
}

/* The person-slot menu (RM/Client's own <details> in
   travel/_trip_view_bar.html.twig, UI-SPEC-person-row.md §1/§2) — same
   anatomy tier as .holding-picker-menu below: a native <details> hosting the
   shared platform picker. Named apart from .holding-picker-* because this
   menu's HOST is travel's own markup, not holding's shared partial (naming a
   MEMBER inside a shared file is coupling); the CSS class itself is shared
   design-system vocabulary, which is not the same thing. */
.person-slot-menu {
    padding: var(--src-space-2);
    min-width: 220px;
}
.person-slot-form {
    display: flex;
    flex-direction: column;
    gap: var(--src-space-2);
}
.person-slot-form .src-picker-input {
    padding: 6px 8px;
    font-size: var(--src-text-sm);
}
.person-slot-submit {
    align-self: flex-end;
}

.holding-roster-menu {
    padding: var(--src-space-3);
    min-width: 280px;
    max-width: 340px;
}

.holding-picker-form {
    display: flex;
    flex-direction: column;
    gap: var(--src-space-2);
}

/* The target control is the platform people picker (CONTRACT.md D6), whose
   vocabulary is .src-picker-* in components.css and is not restated here.
   What this module owns is the HOST: the menu's inner layout and the two
   affordances the picker has no opinion about. */
.holding-picker-reason {
    width: 100%;
    padding: 6px 8px;
    border: 1px solid var(--src-border-input);
    border-radius: var(--src-radius-interactive);
    background: var(--src-surface-input);
    font-size: var(--src-text-sm);
}

/* The picker's input inherits .form-input; only its metrics are brought down
   to this menu's scale. The results box is an overlay inside .src-picker
   (position: relative), so it hangs below the input without resizing the menu
   or clipping against it. */
.holding-picker-form .src-picker-input {
    padding: 6px 8px;
    font-size: var(--src-text-sm);
}

/* "Myself" is its own form above the search (D3 — Collaborate leads with it),
   separated by a hairline so the shortcut reads as a shortcut and not as the
   first row of the search below it. */
.holding-picker-self {
    padding-bottom: var(--src-space-2);
    margin-bottom: var(--src-space-2);
    border-bottom: 1px solid var(--src-border);
}

.holding-picker-self .holding-picker-submit {
    align-self: stretch;
}

/* Nobody left to hand this to — a sentence, because there is no control to
   render. Muted: it is a state of the item, not an error. */
.holding-picker-empty {
    margin: 0;
    font-size: var(--src-text-sm);
    color: var(--src-text-tertiary);
}

.holding-picker-submit {
    align-self: flex-end;
}

.holding-empty {
    padding: var(--src-space-2) 0;
}

.holding-tiers {
    display: flex;
    flex-direction: column;
    gap: var(--src-space-3);
}

.holding-tier-label {
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--src-text-tertiary);
    margin-bottom: var(--src-space-1);
}

.holding-row {
    display: flex;
    align-items: center;
    gap: var(--src-space-2);
    padding: var(--src-space-1) 0;
}

.holding-row-name {
    font-weight: var(--src-weight-semibold);
}

.holding-row-meta {
    color: var(--src-text-tertiary);
    font-size: var(--src-text-xs);
}

.holding-row-revoke {
    margin-left: auto;
}

.holding-validation-copy {
    margin: 0;
    font-size: var(--src-text-xs);
}

/* The read-only "you're not holding this" hint (UI-SPEC §6). */
.holding-hint {
    margin: 0;
    font-style: italic;
}

.holding-hint-action {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-style: italic;
    text-decoration: underline;
    color: inherit;
    cursor: pointer;
}

/* ── Mobile — existing steps only, no new breakpoint invented ────────── */

@media (max-width: 780px) {
    .holding-actions--sm .btn {
        padding: 4px 8px;
    }

    .holding-avatars .avatar {
        display: none;
    }

    .holding-avatars .avatar:nth-child(-n+2) {
        display: flex;
    }
}

/* The holding picker is a NATIVE <details> — its menu must be visible on
   the [open] state on EVERY page. components.css shows .dropdown-menu only
   through the JS-toggled .active class, and the native rule that exists
   is page-scoped (.trip-view-page … [open], travel.css) — so a picker on a
   list page opened invisibly (Martin, first look 2026-08-28). Scoped to
   the picker's own menu so no other dropdown's contract changes. */
details.dropdown[open] > .holding-picker-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* Same reasoning, same fix, for the OP/CO roster-identity disclosure
   (.holding-roster-menu) — it relied on travel's page-scoped rule
   (.trip-view-page details.dropdown[open] > .dropdown-menu, travel.css)
   until UI-SPEC-person-row.md §5 made CO's pill render unconditionally at
   bar size: team-talk's item facts also mount this control at size 'bar'
   (templates/team-talk/_item_facts.html.twig) but is not a .trip-view-page, so an
   unheld item there previously showed no trigger at all and the missing
   rule was inert. It is live now that CO's pill always appears there too. */
details.dropdown[open] > .holding-roster-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* =====================================================================
   FOCUS MODES — the platform vocabulary (CONTRACT.md D12).

   A shell root carries data-mode ∈ read | reply | collaborate, written by
   public/js/src-focus-mode.js or rendered straight from ?mode= with no
   scripting at all. Read is the default and the resting state.

   RECEDING IS FOLD, NEVER REMOVE. A receded region keeps its markup — it is
   shut, and it comes back. NO MODE ANIMATES A HEIGHT: a height transition on
   a server-rendered region is a reflow storm and a scroll-position bug.

   There is no generic rule here. A fold's display value is a property of the
   element being folded (flex, block, inline-flex), so it is authored beside
   that element — the team-talk pane's own rules below, a host's for its own
   side A.
   ===================================================================== */

/* =====================================================================
   MESSAGE BUBBLE — the shared chat-row primitive. One message table
   (chat.messages) renders in two places — the chat popup and Team Talk on an
   item (CONTRACT.md D8) — and both draw a row as this: .msg (row, --in on the
   left with an avatar, --out on the right without), .msg-body-col,
   .msg-bubble (--in / --out, the tail on the sender's corner), Markdown
   inside a bubble, the deleted bubble, .msg-bubble-quotes with the
   .msg-ref-card quote / deleted / denied shapes, the attachment chips,
   .msg-time / .msg-edited, and .msg-actions revealed on :hover and
   :focus-within. Moved verbatim from modules/chat.css on 2026-09-13 (chat's
   computed styles diffed identical before/after). Module-owned extras stay
   in their module: chat's reply affordance, editor, entity cards and
   reactions in chat.css; Team Talk's sender line and grouping below.
   ===================================================================== */

/* ── Messages ───────────────────────────────────────────────────── */
.msg { display: flex; gap: 8px; max-width: 100%; position: relative; }
.msg--in { flex-direction: row; align-items: flex-end; }
.msg--out { flex-direction: row-reverse; justify-content: flex-start; }

.msg-avatar-wrap { flex-shrink: 0; }
.msg-avatar {
    width: 24px;
    height: 24px;
    border-radius: 7px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.5rem;
    font-weight: var(--src-weight-bold);
    color: rgba(255, 255, 255, 0.92);
    letter-spacing: 0.02em;
}

/* Both carry .avatar-stack in their templates now (chat/_message_row.html.twig,
   chat/_window.html.twig) — components.css' .avatar-stack img rule owns the
   img positioning/sizing (initials-underneath, image-on-top), so there is no
   second copy of that grammar here. */

.msg-body-col { display: flex; flex-direction: column; gap: 3px; max-width: 270px; }
.msg-body-col--out { align-items: flex-end; }

.msg-bubble {
    padding: 9px 12px;
    border-radius: 12px;
    font-size: var(--src-text-sm);
    line-height: 1.5;
    overflow-wrap: break-word;
}
.msg-bubble--in {
    background: var(--src-sapphire-light);
    color: var(--src-text-primary);
    border-bottom-left-radius: 3px;
}
.msg-bubble--out {
    background: var(--src-sandstone-light);
    color: var(--src-text-heading);
    border-bottom-right-radius: 3px;
}

/* Rendered Markdown inside bubbles */
.msg-bubble p { margin: 0; }
.msg-bubble p + p { margin-top: 6px; }
.msg-bubble ul, .msg-bubble ol { margin: 4px 0 0; padding-left: 18px; }
.msg-bubble code {
    font-size: 0.6875rem;
    background: rgba(2, 36, 60, 0.07);
    border-radius: 4px;
    padding: 1px 4px;
}
.msg-bubble--out code { background: rgba(2, 36, 60, 0.08); }
.msg-bubble pre {
    margin: 4px 0 0;
    padding: 8px 10px;
    background: rgba(2, 36, 60, 0.06);
    border-radius: var(--src-radius-micro);
    overflow-x: auto;
}
.msg-bubble--out pre { background: rgba(2, 36, 60, 0.07); }
.msg-bubble a { color: var(--src-sapphire); text-decoration: underline; }
.msg-bubble--out a { color: var(--src-sapphire-hover); }

.msg--deleted .msg-bubble {
    background: transparent;
    border: 1px dashed var(--src-border-medium);
    color: var(--src-text-tertiary);
    font-style: italic;
    /* Neutralise directional tails — a deleted bubble has no orientation */
    border-bottom-left-radius: var(--src-radius-medium);
    border-bottom-right-radius: var(--src-radius-medium);
}

.msg-time {
    font-size: 0.5625rem;
    color: var(--src-text-tertiary);
    letter-spacing: 0.02em;
    padding: 0 2px;
}
/* "· edited" marker — same size and colour as the timestamp it follows */
.msg-edited {
    font-size: 0.5625rem;
    color: var(--src-text-tertiary);
    letter-spacing: 0.02em;
}

/* ── Nested quote block — message-quote refs inside the bubble ──────────
   Renders at the top of the bubble, above the reply's own text.
   Styled like a WhatsApp reply quote: left accent bar, receded background,
   sender name + truncated preview + time. Clickable (scroll-to-original).  */
.msg-bubble-quotes {
    display: flex;
    flex-direction: column;
    gap: 4px;
    /* Extend to the bubble edge (bubble has 9px 12px padding); negative
       margins let the quote bar reach the sides while the text below breathes */
    margin: -9px -12px 8px -12px;
    border-radius: 10px 10px 0 0; /* match bubble top-radius minus 2px for nesting feel */
    overflow: hidden;
}

/* Inside the bubble the quote card reuses .msg-ref-card--quote but needs
   dimension overrides for the nested position */
.msg-bubble-quotes .msg-ref-card {
    max-width: none;   /* full bubble width */
    border-radius: 0;  /* square — parent clips the top corners */
}

/* Quote card in the bubble — accent bar on the left stays navy/sapphire,
   background lifts cleanly off the bubble surface for legibility.
   Incoming (light bubble): soft navy tint. Outgoing (sandstone-light): white veil. */
.msg-bubble-quotes .msg-ref-card--quote {
    background: rgba(2, 36, 60, 0.09);
    padding: 7px 10px 7px 12px;
}
.msg-bubble--out .msg-bubble-quotes .msg-ref-card--quote {
    /* White veil over sandstone-light (#e8d5b0) → ~#f0e9d8; navy text ~8:1 contrast (WCAG AA+) */
    background: rgba(255, 255, 255, 0.62);
    border-left-color: var(--src-sandstone);
}
.msg-bubble-quotes .msg-ref-card--quote:hover {
    background: rgba(2, 36, 60, 0.13);
}
.msg-bubble--out .msg-bubble-quotes .msg-ref-card--quote:hover {
    background: rgba(255, 255, 255, 0.78);
}

/* Entity card in the bubble — same nested treatment as the quote card above.
   Left accent bar = sandstone; full bubble width; square corners; white veil on outgoing. */
.msg-bubble-quotes .msg-ref-card--entity {
    max-width: none;
    border-radius: 0;
    background: rgba(2, 36, 60, 0.09);
    padding: 7px 10px 7px 12px;
}
.msg-bubble--out .msg-bubble-quotes .msg-ref-card--entity {
    /* Mirror the quote treatment: white veil over sandstone-light for legibility */
    background: rgba(255, 255, 255, 0.62);
    border-left-color: var(--src-sandstone);
}
.msg-bubble-quotes .msg-ref-card--entity:hover {
    background: rgba(2, 36, 60, 0.13);
}
.msg-bubble--out .msg-bubble-quotes .msg-ref-card--entity:hover {
    background: rgba(255, 255, 255, 0.78);
}

/* Deleted / denied states inside the bubble */
.msg-bubble-quotes .msg-ref-card--deleted,
.msg-bubble-quotes .msg-ref-card--denied {
    background: rgba(2, 36, 60, 0.04);
    padding: 6px 10px 6px 12px;
}

/* Own-message actions — quiet on first look, discoverable on hover.
   Aligned to the bubble bottom: msg-time is 0.5625rem × 1.5lh ≈ 13.5px + 3px gap. */
.msg-actions {
    display: flex;
    gap: 2px;
    align-self: flex-end;
    margin-bottom: 17px;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--src-transition-state);
}
.msg:hover .msg-actions,
.msg:focus-within .msg-actions {
    opacity: 1;
    pointer-events: auto;
}
.msg-action-btn {
    width: 24px;
    height: 24px;
    border: 1px solid transparent;
    background: var(--src-surface-solid);
    border-radius: var(--src-radius-micro);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--src-text-tertiary);
    font-size: 11px;
    cursor: pointer;
    transition: background var(--src-transition-state), color var(--src-transition-state), border-color var(--src-transition-state);
}
.msg-action-btn:hover {
    background: var(--src-hover-bg);
    border-color: var(--src-border-medium);
    color: var(--src-text-secondary);
}

/* Attachment chip (slice 4 renders these) */
.msg-attachment {
    display: flex;
    align-items: center;
    gap: 8px;
    background: rgba(2, 36, 60, 0.05);
    border: 1px solid var(--src-border-medium);
    border-radius: var(--src-radius-interactive);
    padding: 7px 10px 7px 8px;
    max-width: 240px;
    margin-top: 2px;
}
.attachment-icon {
    width: 28px;
    height: 28px;
    border-radius: 6px;
    background: rgba(163, 1, 30, 0.09);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--src-ruby);
    font-size: 14px;
}
.attachment-meta { display: flex; flex-direction: column; line-height: 1.2; flex: 1; min-width: 0; }
.attachment-name {
    font-size: var(--src-text-sm);
    font-weight: var(--src-weight-medium);
    color: var(--src-text-heading);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.attachment-size { font-size: 0.5625rem; color: var(--src-text-tertiary); }
.attachment-dl {
    width: 26px;
    height: 26px;
    border: none;
    background: transparent;
    border-radius: var(--src-radius-micro);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--src-text-tertiary);
    font-size: 12px;
    cursor: pointer;
    transition: background var(--src-transition-state);
    flex-shrink: 0;
}
.attachment-dl:hover { background: var(--src-hover-bg); color: var(--src-text-secondary); }

/* ── Slice 4: Attachments rendered under message content ───────────── */

/* Wrapper for the set of attachments under a bubble */
.msg-attachments {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 4px;
}

/* Image thumbnail attachment */
.msg-attachment--image {
    max-width: 220px;
    border-radius: var(--src-radius-interactive);
    overflow: hidden;
    border: 1px solid var(--src-border-medium);
}
.msg-attachment-thumb-link {
    display: block;
    line-height: 0; /* kills baseline gap under <img> */
}
.msg-attachment-thumb {
    display: block;
    width: 100%;
    max-width: 220px;
    max-height: 180px;
    object-fit: cover;
    border-radius: var(--src-radius-interactive);
    transition: opacity var(--src-transition-state);
}
.msg-attachment-thumb:hover { opacity: 0.88; }

.msg-ref-card {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    padding: 6px 10px;
    border-radius: var(--src-radius-interactive);
    font-size: var(--src-text-xs);
    line-height: 1.4;
    max-width: 260px;
    border-left: 3px solid transparent;
}

/* Entity cards render as real anchors — neutralize link defaults so the
   card keeps its own typography regardless of tag. */
a.msg-ref-card {
    text-decoration: none;
    color: inherit;
}

/* Message quote shape — clickable */
.msg-ref-card--quote {
    background: rgba(2, 36, 60, 0.05);
    border-left-color: var(--src-sapphire);
    flex-direction: column;
    cursor: pointer;
    transition: background var(--src-transition-state);
}
.msg-ref-card--quote:hover { background: rgba(2, 36, 60, 0.09); }
.msg-ref-card--quote:focus-visible {
    outline: 2px solid var(--src-border-focus);
    outline-offset: 1px;
}

.msg-ref-card__sender {
    font-weight: var(--src-weight-semibold);
    color: var(--src-sapphire);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.msg-ref-card__preview {
    color: var(--src-text-secondary);
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.msg-ref-card__time {
    color: var(--src-text-tertiary);
    font-size: 0.5rem;
    margin-top: 1px;
}

/* Deleted-message shape */
.msg-ref-card--deleted {
    background: transparent;
    border-left-color: var(--src-border-medium);
    color: var(--src-text-tertiary);
    font-style: italic;
}
.msg-ref-card--deleted .msg-ref-card__icon {
    color: var(--src-text-tertiary);
    font-size: 11px;
    flex-shrink: 0;
    margin-top: 1px;
}
.msg-ref-card--deleted .msg-ref-card__label { font-size: var(--src-text-xs); }

/* Access-denied shape */
.msg-ref-card--denied {
    background: transparent;
    border-left-color: var(--src-border-medium);
    color: var(--src-text-tertiary);
}
.msg-ref-card--denied .msg-ref-card__icon {
    font-size: 11px;
    flex-shrink: 0;
    margin-top: 1px;
}
.msg-ref-card--denied .msg-ref-card__label { font-size: var(--src-text-xs); }


/* =====================================================================
   TEAM TALK — Side B, the shared internal conversation on any mountable
   item (CONTRACT.md). A CORE surface: every host mounts the same pane and
   writes no CSS, which is why this lives here beside Holding and not in a
   module sheet. "Thread" is a reserved word meaning the ticket (D18) and
   appears in no class below. The conversation is FLAT (D16) — there is no
   indent rule here, and adding one would be re-introducing nesting through
   the stylesheet.

   THE SHAPE: the host's own reading surface fills the pane and the team
   conversation is a SHEET over it — at rest a bar along the foot, open a
   surface the reader can drag. The host's feed never folds and never moves;
   only the clearance at its foot changes. What the ITEM is (its status, its
   holder) is not on this surface at all — that is _item_facts.html.twig,
   mounted where the item is named.

   Every value resolves through an existing --src-* token. Four custom
   properties cross this section's own boundary and each has one home:
     --tt-status     a HOST-OWNED status colour (D11: statuses are the
                     module's data, not the core's), set per element through
                     safe_hex_color() and falling back to a token.
     --tt-peek-h     the resting bar's height, declared on .tt-pane and read
                     by everything that draws or measures the bar.
     --src-sheet-h   the open sheet's height, and
     --src-sheet-pad the clearance the surface behind it keeps — both are
                     public/js/src-sheet.js's to overwrite, and the values
                     here are what a reader with no scripting gets.
   ===================================================================== */

.tt-pane {
    /* The pane's width is its HOST's column, not the window (the inbox canvas
       is the third of three columns), so everything inside it responds to the
       pane through container queries, never viewport media queries. */
    container: tt-pane / inline-size;
    /* And the pane IS the sheet's containing block: the sheet is positioned
       over the host's canvas, and the canvas is this element. */
    position: relative;
    /* The resting bar's height, written ONCE here and read wherever the bar is
       drawn or measured: the sheet's resting height, the floor a drag can
       shrink it to, the bar's own flex basis, and the clearance below. */
    --tt-peek-h: 52px;
    /* What side A keeps clear at its foot so the sheet never covers its last
       line — the bar plus the gap it floats in. This is the resting value AND
       the no-JS value; src-sheet.js overwrites it in px while the sheet is
       open and removes its override again on close, so this stays the only
       place the resting value is written. */
    --src-sheet-pad: calc(var(--tt-peek-h) + var(--src-floating-gap));
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--src-surface-card);
    border-radius: var(--src-radius-structural);
    box-shadow: var(--src-shadow-card);
    overflow: hidden;
}

/* Docked into a host that is already the card: the pane takes the host's
   free height and carries no chrome of its own. */
.tt-pane--fill {
    flex: 1 1 auto;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
}

/* ── The item's facts — its status and who holds it, one row ─────────── */

/* The row itself — controls only, no chrome, because it mounts in a HOST's own
   header (beside the item's name) as often as in the pane. Quiet by
   construction: it states what the item is, it is not the canvas's headline.
   A host places it; it never restyles it. */
.tt-facts {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    flex-shrink: 0;
    min-width: 0;
    gap: var(--src-space-2);
    row-gap: var(--src-space-2);
}

/* The pane's OWN strip (team-talk/_pane.html.twig's `facts` default) — the
   chrome belongs to that position, not to the facts. Its own stacking context,
   above the sheet: the status disclosure and the holding roster open DOWNWARD,
   over whatever is beneath them, and the sheet is beneath them. */
.tt-pane > .tt-facts {
    padding: var(--src-space-2) var(--src-space-4);
    border-bottom: 1px solid var(--src-border-subtle);
    position: relative;
    z-index: 30;
}

/* The holding control is the wide half of the row, so it is the half that
   gives way: it shrinks rather than pushing the status pill off the line. One
   row needs ~810px with the full verb set, and the row wraps below that. */
.tt-facts-holding { min-width: 0; flex: 0 1 auto; max-width: 100%; }

/* The pill opens a menu; the chevron is what says so, quietly. */
.tt-status-chev { font-size: 9px; opacity: 0.6; }

/* ── Side A — the host's own reading surface, rendered in EVERY mode ─── */

/* Its markup and its CSS are the host's; the core gives it the free height
   and the clearance the sheet needs. It is never folded — the sheet overlays
   this surface instead of replacing it, so a reader in the middle of the team
   conversation can still read the thing they are talking about. */
.tt-side-a {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    /* Padding on the flex CONTAINER, never a rule naming the host's own scroll
       box: the core must not name a class a module owns. A padded flex column
       shortens whatever scrolls inside it by exactly this much, so the last
       line clears the sheet whichever way the host built its feed. */
    padding-bottom: var(--src-sheet-pad);
    transition: padding-bottom 0.32s cubic-bezier(0.32, 0.72, 0, 1);
}

.tt-side-a:empty { display: none; }

/* ── The sheet — the conversation, pulled up over the host's canvas ───
   At rest: a slim bar along the foot of the pane carrying the latest team
   line. Open (data-mode="collaborate", reached by the resting bar's ?mode=
   link or by dragging it up): a surface over the canvas at its middle
   detent, which public/js/src-sheet.js can drag and snap to full height.

   HEIGHT IS THE ONE THING THAT ANIMATES, and it is the SHEET's own height —
   never a region of server-rendered content. src-focus-mode.js's "no mode
   animates a height" is about the latter, a stream whose rows reflow mid-
   transition; this box's content is clipped behind it while it grows, and
   side A above it only changes its bottom padding, so no reader loses a
   scroll position to it.

   The open state IS the mode attribute. There is no second open/closed flag
   to keep in step with it, and that is what keeps the no-JS path whole: the
   server renders ?mode=collaborate already open. */

.tt-sheet {
    position: absolute;
    left: var(--src-floating-gap);
    right: var(--src-floating-gap);
    bottom: var(--src-floating-gap);
    height: var(--tt-peek-h);
    min-height: var(--tt-peek-h);
    z-index: 20;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: var(--src-radius-secondary);
    background: var(--src-surface-glass-thick);
    backdrop-filter: blur(18px) saturate(1.4);
    -webkit-backdrop-filter: blur(18px) saturate(1.4);
    box-shadow: var(--src-shadow-3), inset 0 0 0 1px var(--src-border-subtle);
    transition:
        height 0.42s cubic-bezier(0.32, 0.72, 0, 1),
        left 0.42s cubic-bezier(0.32, 0.72, 0, 1),
        right 0.42s cubic-bezier(0.32, 0.72, 0, 1),
        bottom 0.42s cubic-bezier(0.32, 0.72, 0, 1),
        border-radius 0.42s cubic-bezier(0.32, 0.72, 0, 1);
}

/* Open: the sheet meets the canvas's edges and becomes a surface rather than
   a floating bar. --src-sheet-h is src-sheet.js's; the 62% fallback is the
   middle detent and is exactly what a reader with no scripting gets. */
[data-mode="collaborate"] .tt-sheet {
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--src-sheet-h, 62%);
    border-radius: var(--src-radius-secondary) var(--src-radius-secondary) 0 0;
    background: var(--src-surface-solid);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: 0 -18px 48px rgba(2, 36, 60, 0.13);
}

/* A dragged sheet follows the pointer; a transition would make it lag it. */
.tt-sheet.is-dragging { transition: none; }

.tt-sheet-inner {
    display: none;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

[data-mode="collaborate"] .tt-sheet-inner { display: flex; }

/* At rest — the peek. A LINK (?mode= on the host page's URL), so the sheet
   opens with scripting off; it is also the drag handle, which is why
   src-sheet.js suppresses the click that ends a drag. */
.tt-peek {
    display: flex;
    align-items: center;
    gap: var(--src-space-2);
    flex: 0 0 var(--tt-peek-h);
    width: 100%;
    padding: 0 var(--src-space-3) 0 var(--src-space-4);
    text-decoration: none;
    /* The browser must not claim the gesture as a scroll or a text selection
       before the drag handler sees it. */
    touch-action: none;
    cursor: pointer;
}

.tt-peek:hover { background: var(--src-surface-card-hover); }
[data-mode="collaborate"] .tt-peek { display: none; }

.tt-peek-title {
    font-size: var(--src-text-sm);
    font-weight: var(--src-weight-bold);
    color: var(--src-text-heading);
}

.tt-peek-fill { flex: 1; }

.tt-peek-line {
    display: flex;
    align-items: center;
    gap: var(--src-space-2);
    flex: 1;
    min-width: 0;
}

.tt-peek-line .avatar-sm { flex-shrink: 0; }

.tt-peek-who {
    flex-shrink: 0;
    font-size: var(--src-text-sm);
    font-weight: var(--src-weight-bold);
    color: var(--src-text-heading);
}

/* The one line that has to give way — everything else on the bar keeps its
   natural width. */
.tt-peek-msg {
    flex: 1;
    min-width: 0;
    font-size: var(--src-text-sm);
    color: var(--src-text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tt-peek > :not(.tt-peek-line):not(.tt-peek-fill) { flex-shrink: 0; }
.tt-peek-chev { color: var(--src-text-tertiary); font-size: 12px; }

/* Open — the sheet's own header. It names the surface and carries the three
   controls that act on the surface: mute, height, close. Nothing about the
   ITEM is here; that is _item_facts.html.twig's, mounted where the item is
   named. The whole header is the grab handle. */
.tt-sheet-head {
    flex-shrink: 0;
    padding: var(--src-space-2) var(--src-space-2) var(--src-space-2) var(--src-space-5);
    border-bottom: 1px solid var(--src-border-subtle);
    cursor: grab;
    touch-action: none;
    user-select: none;
}

.tt-sheet.is-dragging .tt-sheet-head { cursor: grabbing; }

.tt-grabber {
    display: block;
    width: 38px;
    height: 5px;
    margin: 0 auto var(--src-space-2);
    border-radius: var(--src-radius-sm);
    background: var(--src-border-strong);
}

.tt-sheet-head-row {
    display: flex;
    align-items: center;
    gap: var(--src-space-2);
}

.tt-sheet-title {
    margin: 0;
    font-size: var(--src-text-sm);
    font-weight: var(--src-weight-bold);
    color: var(--src-text-heading);
}

/* The item is gone — TeamTalkHost::title() answered null. Said plainly beside
   the surface's own name, never as a fabricated heading. */
.tt-sheet-gone {
    min-width: 0;
    font-size: var(--src-text-xs);
    color: var(--src-text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.tt-sheet-tools {
    display: flex;
    align-items: center;
    gap: var(--src-space-1);
    margin-left: auto;
}

/* The tools are chrome on a surface the reader already chose to open, so they
   read as glyphs rather than as bordered buttons. */
.tt-sheet-tool.tt-iconbtn {
    border-color: transparent;
    background: transparent;
    color: var(--src-text-tertiary);
}

.tt-sheet-tool.tt-iconbtn:hover {
    background: var(--src-surface-card-hover);
    color: var(--src-text-heading);
}

/* Full height swaps the expand glyph for its collapse twin — one button, two
   glyphs, so the control never moves under the pointer. */
.tt-sheet .bi-arrows-angle-contract { display: none; }
.tt-sheet.is-full .bi-arrows-angle-contract { display: inline-block; }
.tt-sheet.is-full .bi-arrows-angle-expand { display: none; }

.tt-unread-pill {
    padding: 2px var(--src-space-2);
    border-radius: var(--src-radius-pill);
    background: var(--src-info-bg);
    color: var(--src-info);
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-bold);
    white-space: nowrap;
}

.tt-iconbtn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: 1px solid var(--src-border-input);
    border-radius: var(--src-radius-interactive);
    background: var(--src-surface-solid);
    color: var(--src-text-secondary);
    cursor: pointer;
    text-decoration: none;
    flex-shrink: 0;
}

.tt-iconbtn:hover {
    background: var(--src-surface-raised);
    color: var(--src-text-heading);
}

.tt-iconbtn.is-on {
    background: var(--src-sandstone-tint);
    border-color: var(--src-sandstone);
    color: var(--src-sandstone-text);
}

/* ── Status pill — the host's vocabulary, the core's chrome ─────────── */

.tt-status-wrap { flex-shrink: 0; }

.tt-status-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--src-space-1);
    padding: 3px var(--src-space-2);
    border: 0;
    border-radius: var(--src-radius-pill);
    background: var(--src-surface-raised);
    color: var(--src-text-secondary);
    font-family: inherit;
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-bold);
    white-space: nowrap;
    cursor: pointer;
    list-style: none;
}

.tt-status-pill::-webkit-details-marker { display: none; }
.tt-status-pill--static { cursor: default; }

.tt-status-dot {
    width: 7px;
    height: 7px;
    border-radius: var(--src-radius-round);
    background: var(--tt-status, var(--src-gray-400));
    flex-shrink: 0;
}

/* The pill leads its host's header, so the menu opens from the pill's left edge
   inward; .dropdown-menu's right: 0 would push it past the canvas's clip edge. */
.tt-status-menu {
    left: 0;
    right: auto;
    padding: var(--src-space-1);
    min-width: 170px;
}

details.dropdown[open] > .tt-status-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.tt-status-item {
    display: flex;
    align-items: center;
    gap: var(--src-space-2);
    width: 100%;
    padding: 7px var(--src-space-2);
    border: 0;
    border-radius: var(--src-radius-interactive);
    background: transparent;
    color: var(--src-text-primary);
    font-family: inherit;
    font-size: var(--src-text-sm);
    font-weight: var(--src-weight-semibold);
    text-align: left;
    cursor: pointer;
}

.tt-status-item:hover { background: var(--src-surface-card-hover); }
.tt-status-item.is-current { color: var(--src-text-heading); }

/* ── The stream (the live region) ────────────────────────────────────── */

.tt-stream {
    flex: 1 1 auto;
    min-height: 0;
    /* A definite runway, owned rather than computed: the pane is a component
       inside somebody else's page, so it cannot inherit a height from a
       viewport chain it is not part of, and a stream with no bound grows the
       host page without limit as the conversation does. */
    max-height: 46vh;
    overflow-y: auto;
    padding: var(--src-space-3) var(--src-space-4);
    background: var(--src-surface-warm);
    scrollbar-width: thin;
}

.tt-pane--fill .tt-stream { max-height: none; }

/* Inside the sheet the stream has a definite runway already — the sheet's own
   height, which the reader chose. A second cap on top of it would leave dead
   space below a dragged-to-full sheet. */
.tt-sheet .tt-stream { max-height: none; }

.tt-stream-empty {
    margin: 0;
    padding: var(--src-space-4) 0;
    color: var(--src-text-tertiary);
    font-size: var(--src-text-sm);
    text-align: center;
}

.tt-daysep,
.tt-newsep {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: var(--src-space-3) 0 var(--src-space-2);
}

.tt-daysep-label {
    padding: 2px var(--src-space-3);
    border: 1px solid var(--src-border-subtle);
    border-radius: var(--src-radius-pill);
    background: var(--src-surface-solid);
    color: var(--src-text-tertiary);
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-semibold);
}

.tt-newsep {
    border-top: 1px solid var(--src-info-border);
    margin-top: var(--src-space-4);
}

.tt-newsep-label {
    margin-top: -9px;
    padding: 1px var(--src-space-2);
    border-radius: var(--src-radius-pill);
    background: var(--src-info);
    color: var(--src-text-on-dark);
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* ── One message — the shared MESSAGE BUBBLE primitive above (.msg,
   .msg-bubble, quotes, attachments, time, actions) plus what only an
   N-party item conversation needs: the sender line, grouping, a wider bubble
   and a disclosure editor. FLAT: no indent, no branch rail, no depth (D16). */

.tt-stream .msg + .msg { margin-top: var(--src-space-3); }

/* A continuation of the same sender's run: no name, no avatar, close up. */
.tt-stream .msg + .tt-msg--grouped { margin-top: 3px; }

/* The avatar column keeps its width on a grouped row, so the run's bubbles
   stay on one left edge. */
.tt-stream .msg-avatar-wrap { width: 24px; }

/* The pane is a canvas, not a popup: the bubble may take most of the row. */
.tt-stream .msg-body-col { max-width: min(560px, 76%); }

.tt-msg-name {
    padding: 0 2px;
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-semibold);
    color: var(--src-text-secondary);
}

.tt-msg-action-form { display: inline-flex; margin: 0; }

.tt-msg-edit { position: relative; display: inline-flex; }
.tt-msg-edit > summary { list-style: none; }
.tt-msg-edit > summary::-webkit-details-marker { display: none; }

/* An open editor keeps its row's actions shown, or the form would vanish the
   moment the pointer leaves the row. */
.tt-msg .msg-actions:has(.tt-msg-edit[open]) {
    opacity: 1;
    pointer-events: auto;
}

.tt-msg-edit-form {
    position: absolute;
    right: 0;
    bottom: calc(100% + 4px);
    z-index: 5;
    display: flex;
    flex-direction: column;
    gap: var(--src-space-2);
    width: min(360px, 70vw);
    padding: var(--src-space-2);
    border: 1px solid var(--src-border-medium);
    border-radius: var(--src-radius-medium);
    background: var(--src-surface-solid);
    box-shadow: var(--src-shadow-card);
}

.tt-msg-edit-actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--src-space-2);
}

/* A resolved mention — the SERVER decides what is resolved (D7), so this
   only says how a resolved span reads. An unresolved @token is plain text
   and correctly gets nothing. */
.tt-mention {
    padding: 0 3px;
    border-radius: var(--src-radius-micro);
    background: var(--src-sapphire-tint);
    color: var(--src-sapphire);
    font-weight: var(--src-weight-bold);
}

/* ── On this item — the tag on a HOST's own message row ──────────────── */

/* Drawn on the message itself (team-talk/_on_item.html.twig), inside whatever
   the host's row puts it in, so it takes no position of its own — only the
   shape of a tag with a remove. Sandstone rather than the info blue of "N
   new": this is a statement about the message, not a notification. */
.tt-onitem {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 1px 2px 1px var(--src-space-2);
    border-radius: var(--src-radius-pill);
    background: var(--src-sandstone-tint);
    color: var(--src-sandstone-text);
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-semibold);
    white-space: nowrap;
}

.tt-onitem-remove-form { display: inline-flex; }

/* Quiet until the pointer is on it: taking a message off the item is a real
   edit of what the ticket is about, and it sits inside a reading surface. */
.tt-onitem-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: 0;
    border-radius: var(--src-radius-round);
    background: transparent;
    color: inherit;
    opacity: 0.55;
    cursor: pointer;
}

.tt-onitem-remove:hover {
    opacity: 1;
    background: var(--src-error-bg);
    color: var(--src-error);
}

/* ── Composer — OUTSIDE the live region, by design ───────────────────── */

.tt-composer {
    flex-shrink: 0;
    padding: var(--src-space-3) var(--src-space-4);
    border-top: 1px solid var(--src-border-subtle);
    background: var(--src-surface-card);
}

.tt-composer-readonly {
    margin: 0;
    font-size: var(--src-text-sm);
    font-style: italic;
    color: var(--src-text-tertiary);
}

.tt-composer-form { display: flex; flex-direction: column; gap: var(--src-space-2); }

.tt-cbox {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--src-space-2);
    padding: var(--src-space-2);
    border: 1px solid var(--src-border-input);
    border-radius: var(--src-radius-secondary);
    background: var(--src-surface-solid);
}

.tt-cbox:focus-within {
    border-color: var(--src-border-focus);
    box-shadow: 0 0 0 3px var(--src-primary-tint);
}

.tt-cbox-area {
    /* The text takes the box's whole first row, starting at the box's own
       padding; attach · image · send sit on the row beneath it. Beside the
       two icon buttons it started ~90px in from the edge. */
    order: -1;
    flex: 1 1 100%;
    min-width: 0;
    margin: 0;
    padding: 2px;
    min-height: 64px;
    max-height: 180px;
    border: 0;
    outline: none;
    resize: vertical;
    background: transparent;
    font-family: inherit;
    font-size: var(--src-text-base);
    line-height: 1.5;
    color: var(--src-text-primary);
}

.tt-cbtn,
.tt-send {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--src-radius-interactive);
    cursor: pointer;
    flex-shrink: 0;
}

.tt-cbtn {
    border: 0;
    background: transparent;
    color: var(--src-text-tertiary);
}

.tt-cbtn:hover { background: var(--src-surface-raised); color: var(--src-text-heading); }

.tt-send {
    margin-left: auto;
    border: 0;
    background: var(--src-primary);
    color: var(--src-text-on-dark);
}

.tt-send:hover { background: var(--src-primary-hover); }

/* The reply strip — what this message is answering, above the box. */
.tt-reply-strip {
    display: flex;
    align-items: flex-start;
    gap: var(--src-space-2);
    padding: var(--src-space-2);
    border: 1px solid var(--src-sandstone-light);
    border-left: 3px solid var(--src-sandstone);
    border-radius: var(--src-radius-medium);
    background: var(--src-surface-solid);
}

.tt-reply-strip .bi-reply { color: var(--src-sandstone-hover); flex-shrink: 0; }
.tt-reply-strip-body { flex: 1; min-width: 0; }

.tt-reply-strip-who {
    display: block;
    font-size: var(--src-text-xs);
    font-weight: var(--src-weight-bold);
    color: var(--src-text-heading);
}

.tt-reply-strip-text {
    display: block;
    font-size: var(--src-text-xs);
    color: var(--src-text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── The mention overlay ─────────────────────────────────────────────── */

/* Same box as the picker's results (components.css owns its appearance —
   nothing is restated here), opening UPWARD because the composer sits at
   the foot of the pane and a list hanging below it would fall off the
   surface it belongs to. Geometry only; every other property is inherited
   from .src-picker-results. */
.src-mention-results {
    top: auto;
    bottom: 100%;
    margin: 0 0 4px;
    max-height: 240px;
}

/* ── Mobile — the existing steps, no new breakpoint invented ─────────── */

@media (max-width: 780px) {
    .tt-stream { max-height: 60vh; }
    .tt-stream .msg-attachment-thumb { max-width: 140px; }
}

/* Narrow pane — the latest line is the bar's whole point: the sender's name
   folds and the line keeps the width. */
@container tt-pane (max-width: 480px) {
    .tt-peek-who { display: none; }
    .tt-sheet { left: 0; right: 0; bottom: 0; border-radius: var(--src-radius-medium) var(--src-radius-medium) 0 0; }
}


/* =====================================================================
   LIVE CHANGE — the brief mark on a data point that just changed under the
   reader. public/js/src-mercure.js adds .is-live-changed to the finest unit
   the markup keys (a card, a table row, one cell, a pill) and removes it
   after 1.6 s; this is the only rule that draws it, so every module's live
   surface reads the same.

   Sandstone, the palette's selection role, washed at its firm tint and faded
   to nothing. It is painted as an INSET SHADOW, not a background, for two
   reasons: a pill's own tint is often an inline background (status colours
   are data, set per element), and the shadow sits above that background and
   below the content without replacing either; and it follows the element's
   own border-radius, so a pill tints as a pill and a card as a card. A table
   row has no reliable box of its own, so its cells carry the mark.
   Keyframes rather than a transition: the element was just inserted, so there
   is no earlier state to transition from.
   ===================================================================== */

@keyframes src-live-changed {
    0%, 30% { box-shadow: inset 0 0 0 100vmax var(--src-sandstone-tint-strong); }
    100%    { box-shadow: inset 0 0 0 100vmax transparent; }
}

.is-live-changed:not(tr),
tr.is-live-changed > td,
tr.is-live-changed > th {
    animation: src-live-changed 1.6s ease-out;
}

/* No motion: the mark holds still at the quieter tint and simply goes when
   the class does. */
@media (prefers-reduced-motion: reduce) {
    .is-live-changed:not(tr),
    tr.is-live-changed > td,
    tr.is-live-changed > th {
        animation: none;
        box-shadow: inset 0 0 0 100vmax var(--src-sandstone-tint);
    }
}

