/* =============================================================================
   Team Spaces stylesheet manifest
   -----------------------------------------------------------------------------
   Split when the single file passed the 1,000-line limit (CLAUDE.md rule 3),
   mirroring the teams.css manifest beside it.

   Do not reorder these imports: later modules intentionally override rules from
   earlier ones (the modal restyles EasyMDE's own CSS, the attachment gallery
   sits on top of the post card).

   Note the Space HEADER is not here — it renders the chat header's markup and
   is styled in teams/teams-conversation.css beside the chat's own, so the two
   cannot drift.
   ============================================================================= */

/* ==========================================================================
   Team Spaces — the post feed
   --------------------------------------------------------------------------
   A separate feature stylesheet: Teams styles are modularized under
   chat_page/css/teams/, so the post feed remains independently maintainable.

   Scoping (CLAUDE.md rule 2): every selector is prefixed `.space-` or nested
   under `.spaces-panel`. The chat page loads many stylesheets at once, so a
   bare `.card` / `.post` here would collide with the admin panel and the widget.

   Theming ALIASES the Teams `--tm-*` tokens rather than restating colours.
   This block used to hardcode its own palette (a #4a6cf7 indigo against the
   app's #2d5f84 slate-blue), so every Spaces button was visibly not from this
   product — and its dark mode was keyed on `prefers-color-scheme`, which this
   app does not use: the theme is `body.dark-theme`. Aliasing fixes both at
   once, and any future change to the Teams palette carries here for free.
   ========================================================================== */

/* The token block is declared on the panel AND on every detached Space surface.
   Overlays (`.space-post-detail`, `.space-post-modal`, `.space-media-viewer`)
   are appended to `document.body`, so they are NOT inside `.spaces-panel` and
   inherit nothing from it. Declaring the tokens only on the panel left every
   `var(--sp-*)` in those three files resolving to nothing — which is why the
   post-discussion send button rendered as an invisible white square: its
   `background: var(--sp-accent)` was simply void.

   Custom-property declarations style nothing on their own, so listing extra
   roots here cannot collide with another stylesheet (CLAUDE.md rule 2). The
   LAYOUT rules further down stay panel-only, which is why they are split out
   below rather than sharing this selector. */
.spaces-panel,
.space-post-detail,
.space-post-modal,
.space-media-viewer,
.space-members-modal {
    /* Aliases, not values — see the header. */
    --sp-gap: 16px;
    --sp-radius: var(--tm-r-lg, 12px);
    --sp-card-bg: var(--tm-surface, #f8f5f0);
    --sp-card-border: var(--tm-border, #d8d3c8);
    --sp-text: var(--tm-msg-text, #1f1e1d);
    --sp-text-muted: var(--tm-text-1, #6f6e6d);
    --sp-accent: var(--tm-accent, #2d5f84);
    --sp-accent-hover: var(--tm-accent-hover, #245070);
    --sp-hover: var(--tm-bg-alt, #f0ede6);
    --sp-danger: var(--tm-danger, #c0392b);
    --sp-pin: #f0a500;
}

/* Layout is the PANEL's alone — an overlay that inherited `overflow: hidden`
   and `height: 100%` would clip its own dialog. */
.spaces-panel {
    /* Same reason as spacesRoot(): this sits inside a column flex container, so
       it must GROW, not merely declare a height. Without flex:1 the whole panel
       shrinks to its content and the composer dock stops reaching the bottom. */
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    overflow: hidden;
    color: var(--sp-text);
    font-family: var(--tm-font-ui, system-ui, sans-serif);
}
.space-post-detail,
.space-post-modal,
.space-media-viewer,
.space-members-modal {
    color: var(--sp-text);
    font-family: var(--tm-font-ui, system-ui, sans-serif);
}

/* ── Space members / guests dialog ───────────────────────────────────────────
   The member LIST itself is teams-members.js' markup and is styled with the
   Teams modal rules already; only the guest block below is ours. */
.space-members-modal .space-guests {
    margin-top: 18px;
    border-top: 1px solid var(--sp-card-border);
    padding: 15px;
    background: var(--tm-bg);
    border-radius: 8px;
}
.space-members-modal .space-guests__heading {
    margin: 0 0 4px;
    font-size: 14px;
    font-weight: 600;
    color: var(--sp-text);
}
.space-members-modal .space-guests__note {
    margin: 0 0 10px;
    font-size: 12px;
    line-height: 1.45;
    color: var(--sp-text-muted);
}
.space-members-modal .space-guests__list { margin-top: 10px; }
.space-members-modal .space-guests__row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 0;
}
.space-members-modal .space-guests__name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 14px;
}
.space-members-modal .space-guests__badge {
    flex: 0 0 auto;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--sp-hover);
    color: var(--sp-text-muted);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.space-members-modal .space-guests__remove { flex: 0 0 auto; }
.space-members-modal .space-guests__empty {
    padding: 8px 0;
    font-size: 13px;
    color: var(--sp-text-muted);
}

/* No dark-theme block here, and the reason is worth stating so nobody adds one:
   `--tm-*` is redefined under `body.dark-theme .teams-panel` (which contains the
   panel) and under `body.dark-theme .teams-modal-overlay` (which BOTH
   `.space-post-detail` and `.space-post-modal` carry as a second class). Since
   `--sp-*` are aliases, they follow automatically. `.space-media-viewer` is the
   one Space surface with neither ancestor nor that class, so it is added to the
   existing dark selector list in teams/teams-foundation.css rather than getting
   a competing set of overrides here. */

/* ── Shared header ───────────────────────────────────────────────────────────
   The Feed/Chats tab strip is GONE. The Space feed renders the same header a
   chat renders (headerChassis() in teams-conversation-header.js).

   ITS BUTTONS ARE STYLED IN teams/teams-conversation.css, BESIDE THE CHAT'S
   OWN — not here. They were styled here first, with `--sp-*` under a different
   scope, and the result was a header whose icons visibly did not match the same
   header one click away. Anything that renders inside the chassis belongs with
   the chassis; only the layout AROUND it belongs to Spaces. */

/* The panel's content column: header, feed, dock. An earlier refactor dropped
   this rule, and without it the column had no growth model at all — the dock
   stopped short of the bottom and the pane showed bare background beneath it. */
.spaces-panel__body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.spaces-panel__context {
    flex: 0 0 auto;
}

.space-icon-btn {
    appearance: none;
    width: 34px;
    height: 34px;
    border-radius: 8px;
    border: 1px solid var(--sp-card-border);
    background: var(--sp-card-bg);
    color: var(--sp-text-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.space-icon-btn:hover { background: var(--sp-hover); color: var(--sp-text); }
.space-icon-btn--on { color: var(--sp-accent); border-color: var(--sp-accent); }

/* An explicitly muted space must LOOK muted — otherwise the commonest support
   question is "why am I not getting notifications". */
.space-icon-btn--muted { color: var(--sp-danger); border-color: var(--sp-danger); }

/* ── Scroller ────────────────────────────────────────────────────────────── */

.space-feed__scroller {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 24px 16px 24px;
}

/* Phones only: the feed dissolves at both edges instead of being guillotined by
   the header above it and the pane edge below. A MASK rather than two gradient
   overlays — the panel can be showing a user's background photo, and an overlay
   painted in `--tm-bg` would sit on that picture as two grey bars. The mask
   fades whatever is actually behind.

   Applied to the scroller, whose floating children ("N new posts") are its
   SIBLINGS under `[data-feed-host]`, so neither is faded with the content.
   Desktop keeps hard edges: the feed there is a column inside a wide stage with
   its own gutters, and a fade only muddies the top of the first card. */
@media (max-width: 768px) {
    .spaces-panel .space-feed__scroller {
        -webkit-mask-image: linear-gradient(
            to bottom,
            transparent 0,
            #000 22px,
            #000 calc(100% - 30px),
            transparent 100%);
        mask-image: linear-gradient(
            to bottom,
            transparent 0,
            #000 22px,
            #000 calc(100% - 30px),
            transparent 100%);
    }
}

/* Virtualization spacers. The feed keeps a bounded number of rendered post
   nodes and replaces the rest with these two height-only elements, so scroll
   position and scrollbar length stay honest while the DOM stays small. */
.space-feed__spacer { width: 100%; flex: 0 0 auto; }

.space-feed__list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-gap);
    max-width: 720px;
    margin: 0 auto;
}

/* ── "N new posts" ───────────────────────────────────────────────────────────
   Posts that arrived while the reader was reading. FLOATS over the feed rather
   than sitting in the flow: inserting it in the document would push the post
   being read down by its own height the moment it appears, which is the exact
   disruption the whole deferred-insert design exists to avoid.

   ANCHORED TO THE BOTTOM. It is an offer, not an alert — it must be reachable
   without being in the way of what is being read, and the bottom edge is where
   a thumb already rests on a phone. The arrow points up because the posts it
   reveals go in at the TOP of the feed, which is where clicking it lands you.

   Anchored to `[data-feed-host]`, which is already `position: relative`. */
.space-feed__new {
    position: absolute;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;

    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 16px;
    border: none;
    border-radius: 999px;
    background: var(--sp-accent);
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    /* Reads as a floating control rather than a card that scrolled into place. */
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
}

.space-feed__new:hover:not(:disabled) { filter: brightness(1.08); }
.space-feed__new:disabled { opacity: 0.7; cursor: default; }

/* Clear of the mobile chrome at the bottom of the pane, and clear of the
   home-indicator inset on iOS — otherwise the pill sits under the composer
   bar or the system gesture area and cannot be tapped. */
@media (max-width: 768px) {
    .space-feed__new {
        bottom: calc(16px + env(safe-area-inset-bottom, 0px));
        /* A tap target, not a hover affordance: this control has no other way
           to be reached on a phone. */
        min-height: 40px;
        padding: 9px 18px;
    }
}

/* ── Back to top ──────────────────────────────────────────────────────────
   Appears once the reader is two screens down (see TO_TOP_AFTER_SCREENS).

   ANCHORED TO THE FEED COLUMN, NOT THE PANE, which is the only part of this
   worth arguing about. The obvious `right: 16px` on the host would put it in
   the pane's bottom-right corner — and on a wide screen that corner belongs to
   the sticky rail, so the button would float over its cards. The dock is
   centred at the feed track's own width instead, so the button lands on the
   FEED's right edge when the gutters exist and on the pane's edge when they do
   not, with no JavaScript involved either way.

   It cannot collide with the new-posts pill at any width: that one is centred,
   this one is right-aligned, and they would only meet on a pane narrower than
   any phone. The dock ignores pointer events so it never steals a click from
   the post underneath it. */
.space-feed__dock {
    position: absolute;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%);
    /* The feed track's own width, from spaces-stage.css. One number in two
       files: a dock any wider puts the button over the rail. */
    width: min(100%, 720px);
    box-sizing: border-box;
    padding: 0 16px;
    z-index: 5;
    display: flex;
    justify-content: flex-end;
    /* The dock spans the feed; only the button in it may be clicked, or an
       invisible strip would swallow clicks on the post underneath. */
    pointer-events: none;
}

.space-feed__totop {
    pointer-events: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 999px;
    border: 0.5px solid var(--sp-card-border, #d8d3c8);
    /* Quieter than the new-posts pill on purpose: that one is news, this is
       only a way back. Two accent-coloured floating controls in one corner of
       one screen compete for the same glance. */
    background: var(--sp-card-bg, #f8f5f0);
    color: var(--sp-text-muted, #6f6e6d);
    font-size: 14px;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.16);
}

.space-feed__totop:hover {
    background: var(--sp-hover, #f0ede6);
    color: var(--sp-text, #1f1e1d);
}

@media (max-width: 768px) {
    /* Clear of the mobile chrome and the home-indicator inset, exactly as the
       pill is — and a full tap target rather than a hover affordance. */
    .space-feed__dock { bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
    .space-feed__totop { width: 42px; height: 42px; }
}

/* `hidden` loses to any class that sets `display` — the UA rule is
   element-level, so `.space-feed__new { display: inline-flex }` beats it and
   the pill would render permanently. The same trap the composer's assist row
   and the drop veil both fell into. */
.space-feed__totop[hidden] { display: none; }
.space-feed__new[hidden] { display: none; }

/* =============================================================================
   Team Spaces — post card, reactions, comments, popovers
   -----------------------------------------------------------------------------
   Part of the spaces.css manifest. Scoping (CLAUDE.md rule 2): every selector is
   prefixed `.space-` or nested under `.spaces-panel` / `.space-post-modal`.
   ============================================================================= */

/* ── Post card ───────────────────────────────────────────────────────────── */

.space-post {
    /* NOT `--sp-card-bg`: that resolves to `--tm-surface`, the same tone the feed
       itself is painted in, so the cards dissolved into their own background and
       only the border said where one post ended. `--tm-real-white` is the raised
       surface and IS theme-aware (#fff light, #363f4a dark — teams-foundation.css),
       so this stays correct in both themes. */
    background: var(--tm-real-white);
    border: 1px solid var(--sp-card-border);
    border-radius: var(--sp-radius);
    padding: 14px 16px;
}

/* A post and its optional comment preview are siblings.  Keeping their shared
   shell unpainted means the preview follows the post rather than interrupting
   its content, engagement, or action rows. */
.space-post-shell { width: 100%; }
.space-post--has-comment-preview {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}

.space-post--pinned {
    border-color: var(--sp-pin);
    box-shadow: inset 3px 0 0 var(--sp-pin);
}

.space-post--pinned-for-me {
    box-shadow: inset 3px 0 0 #6f42c1;
}

.space-post .space-post__body a {
    color: var(--tm-space-href);
}

.space-post--important {
    border-left: 5px solid var(--sp-danger);
}

.space-post__head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-bottom: 20px;
}

.space-post__avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    flex: 0 0 auto;
    background: var(--sp-accent);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 14px;
    /* overflow: hidden; */
}

.space-post__avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover; 
}

.space-post__avatar--agent {
    background: transparent;
}

.space-post__byline { 
    flex: 1 1 auto;
    min-width: 0;
    align-self: anchor-center;
    gap: 2px;
    display: grid;
    align-items: center;
}

.space-post__author { 
    font-weight: 600; 
    font-size: 14px; 
}

/* "(inactive)" — the author's account was disabled (they left). */
.space-post__author .teams-inactive-suffix,
.space-comment__author .teams-inactive-suffix {
    font-weight: 400;
    font-style: italic;
    color: var(--sp-text-muted);
}

.space-post__meta {
    font-size: 12px;
    color: var(--sp-text-muted);
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

.space-post__pin-status--everyone { color: var(--sp-pin); }
.space-post__pin-status--self { color: #6f42c1; }

/* Author badges. `pr` is deliberately prominent: a stranger posting into your
   office feed has to be attributable to be trusted. */
.space-post__badge {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 4px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: text-top;
}

.space-post__badge--pr {
    background: #6f42c1;
    color: #fff;
}

.space-post__badge--admin {
    background: var(--sp-accent);
    color: #fff;
}

/* A company-wide announcement, surfaced in the feed as a global post. Warm and
   distinct from the team badges above: it comes from nobody's team, and a
   reader scanning the merged feed needs to tell that at a glance rather than
   reading the author name. */
.space-post__badge--announcement {
    background: #b45309;
    color: #fff;
}

/* An authored post addressed to the whole company. Distinct from
   `--announcement` (a mandatory-announcement carrier, which has no human
   author) so the two are never read as the same thing. */
.space-post__badge--global {
    background: var(--sp-accent, #2563eb);
    color: #fff;
}

.space-post__badge--agent {
    background: #0e9f6e;
    color: #fff;
}

/* ── "Open the full announcement" ────────────────────────────────────────────
   Offered only on an announcement that actually carries designed HTML, which
   the card itself cannot draw — it has no sandbox, and admin-authored HTML
   rendered without one is stored XSS against every reader. This opens the
   sandboxed review modal instead, so the announcement looks the same whether it
   is reached from here or from the Notification Center.

   Styled as a full-width banner rather than a text link: it stands in for
   content the reader can SEE is missing from the card, so it has to read as the
   rest of the announcement, not as a footnote about it. The amber matches the
   ANNOUNCEMENT badge above it, so the card has one accent and not two. */
.space-post__announcement-open {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    margin-top: 12px;
    padding: 12px 14px;
    border: 1px solid color-mix(in srgb, #b45309 35%, transparent);
    border-radius: 10px;
    background: color-mix(in srgb, #b45309 8%, transparent);
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.space-post__announcement-open:hover:not(:disabled) {
    background: color-mix(in srgb, #b45309 14%, transparent);
    border-color: color-mix(in srgb, #b45309 55%, transparent);
}

.space-post__announcement-open:disabled {
    opacity: 0.6;
    cursor: progress;
}

.space-post__announcement-open-icon {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: #b45309;
    color: #fff;
    font-size: 14px;
}

/* `min-width: 0` so a long localized string wraps instead of pushing the
   arrow out of the card — the usual flex-child overflow trap. */
.space-post__announcement-open-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}

.space-post__announcement-open-text strong {
    font-size: 14px;
    font-weight: 600;
}

.space-post__announcement-open-text small {
    font-size: 12px;
    opacity: 0.75;
}

.space-post__announcement-open-go {
    flex: 0 0 auto;
    font-size: 12px;
    opacity: 0.6;
}

/* Pin state is visible directly on the post card, not only in the secondary
   header. The colour difference makes global and private pins distinguishable. */
.space-post__action--pin.is-pinned-everyone {
    color: var(--sp-pin);
    background: color-mix(in srgb, var(--sp-pin) 12%, transparent);
}
.space-post__action--pin.is-pinned-self {
    color: #6f42c1;
    background: color-mix(in srgb, #6f42c1 12%, transparent);
}
.space-post__action--pin.is-pinned-everyone:hover,
.space-post__action--pin.is-pinned-self:hover {
    filter: brightness(0.9);
}

/* Which space a post came from — only rendered in the merged "All spaces"
   feed, where a card without it is ambiguous. */
.space-post__origin {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-weight: 600;
    color: var(--sp-accent);
    cursor: pointer;
}

/* WHO A TARGETED PUBLIC ANNOUNCEMENT REACHED — rendered only for holders of
   `spaces.can_post_global_announcement`, and only on a post that actually
   narrows its audience. The server omits the field for everyone else, so this
   rule is styling, never the access control.

   Warmer than `--origin` beside it and deliberately so: the two sit in the same
   meta row and say different kinds of thing — one is WHERE the post lives and
   is clickable, this one is WHO it was aimed at and is not. Same weight would
   read as a second Space. */
.space-post__audience {
    display: inline-flex;
    align-items: center;
    font-weight: 600;
    color: var(--sp-warning, #b5451b);
}

.space-post__title {
    font-size: 16px;
    font-weight: 700;
    margin: 2px 0 6px;
    line-height: 1.35;
}

/* ── Engagement + actions ────────────────────────────────────────────────── */

/* Reactions and comment total form a quiet read-only summary immediately after
   media; the interaction controls live on the row below. */
.space-post__engagement {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    min-height: 34px;
    margin-top: 10px;
    padding: 7px 0;
    border-bottom: 1px solid var(--sp-card-border);
}

.space-post__reaction-summary { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }

/* Reach, beside the discussion count. Quieter than it deliberately — how many
   people saw a post is context, and the comment count is a control. Not a
   button and never will be: the product shows the number and never the names,
   and making this clickable is the first step toward a surveillance surface. */
.space-post__views {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
    margin-left: auto;
    padding: 3px 0;
    color: var(--sp-text-muted);
    font-size: 12px;
    font-weight: 600;
    cursor: default;
}

/* The comment count owned `margin-left: auto` before the view count existed.
   With both present the views take it and the comments sit to their right, so
   the pair stays a group rather than splitting across the card. */
.space-post__views + .space-post__comment-count { margin-left: 14px; }

.space-post__comment-count {
    appearance: none;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
    padding: 3px 0;
    border: 0;
    background: transparent;
    color: var(--sp-text-muted);
    cursor: pointer;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
}

.space-post__comment-count:hover { color: var(--sp-accent); }

/* A post that HAS a discussion reads darker, with the filled glyph, so it is
   distinguishable while scanning before the number is read. */
.space-post__comment-count.has-comments {
    color: var(--sp-text);
}
.space-post__comment-count.has-comments i {
    color: var(--sp-accent);
}

/* One gentle pulse the first time a commented post scrolls into view. Applied
   by SpacesFeed's IntersectionObserver, once per card. */
@keyframes space-comment-reveal {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.18); }
    100% { transform: scale(1); }
}
.space-post__comment-count.is-revealed i {
    animation: space-comment-reveal 0.55s ease-out;
    transform-origin: center;
}

/* The stronger, deliberate one: the viewer just posted a comment, the dialog
   has closed, and the only evidence is a number one higher than before. */
@keyframes space-comment-bump {
    0%   { transform: scale(1);    background: transparent; }
    25%  { transform: scale(1.22); background: var(--tm-accent-subtle); box-shadow: 0 0 0 5px var(--tm-accent-subtle); }
    55%  { transform: scale(1.08); background: var(--tm-accent-subtle); }
    100% { transform: scale(1);    background: transparent; }
}
.space-post__comment-count.is-bumped {
    animation: space-comment-bump 2.4s ease-out;
    border-radius: 999px;
    padding: 3px 8px;
    color: var(--sp-accent);
}

/* Motion here is decoration on a number that is already correct, so it is the
   first thing to drop when the reader has asked for less of it. */
@media (prefers-reduced-motion: reduce) {
    .space-post__comment-count.is-revealed i,
    .space-post__comment-count.is-bumped {
        animation: none;
    }
}

.space-post__footer {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 6px;
    padding-top: 4px;
    flex-wrap: wrap;
}

.space-react {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border-radius: 999px;
    border: 1px solid var(--sp-card-border);
    background: transparent;
    font-size: 13px;
    cursor: pointer;
    color: var(--sp-text-muted);
}

.space-react:hover { background: var(--sp-hover); }
/* Was a hardcoded rgba of #4a6cf7 — the indigo this feature was supposed to have
   stopped using. Aliased like every other colour here so a palette change carries. */
.space-react--mine {
    border-color: var(--sp-accent);
    color: var(--sp-accent);
    background: var(--tm-accent-subtle);
}
.space-react__count { font-size: 12px; font-weight: 600; }

/* The reaction landed. The tray closes the moment an emoji is picked, so
   without this the glyph the user clicked just disappears and a chip they were
   not looking at silently ticks up. */
@keyframes space-reaction-applied {
    0%   { transform: scale(1);    box-shadow: 0 0 0 0 var(--tm-accent-subtle); }
    35%  { transform: scale(1.22); box-shadow: 0 0 0 6px var(--tm-accent-subtle); }
    100% { transform: scale(1);    box-shadow: 0 0 0 0 transparent; }
}
.space-react.is-bumped {
    animation: space-reaction-applied 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (prefers-reduced-motion: reduce) {
    .space-react.is-bumped { animation: none; }
}

/* ── Reaction offer: one face at rest, the emoji slide out of it ──────────── */
/* The quick emoji used to sit permanently in the action row, where they were
   indistinguishable from the APPLIED-reaction chips in the summary row above.
   Collapsing them behind the outline face makes the offer a verb and leaves the
   tally the only emoji visible at rest. */

/* The anchor the tray slides out from. `relative` is what takes the tray OUT
   of the footer's flow — see the tray rule below. */
.space-post__reactions {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-width: 0;
}

.space-post__reaction-toggle.is-active,
.space-post__reaction-toggle:hover { color: var(--sp-accent); }

/* Slides out to the RIGHT of the toggle, OVER the rest of the row.
   `position: absolute` is the whole point: an animated `max-width` kept the
   tray in flow, so opening it shoved "Comment" and "Continue in chat" sideways
   and the row visibly reflowed on every click. Out of flow, the buttons stay
   exactly where they were and the tray passes across them.

   It therefore needs its own opaque surface — it is now painted on top of live
   controls rather than beside them — so it reads as a floating pill and the
   text underneath cannot show through.

   `visibility` still does the work `opacity` cannot: a fully transparent tray
   is still clickable and still focusable, and tabbing into invisible reaction
   buttons is how a keyboard user gets lost. */
.space-post__reaction-tray {
    position: absolute;
    z-index: 3;
    left: 100%;
    top: 50%;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px 6px;
    border-radius: 999px;
    background: var(--tm-real-white, #fff);
    box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
    opacity: 0;
    visibility: hidden;
    white-space: nowrap;
    transform: translate(-8px, -50%);
    transition:
        opacity 0.18s ease,
        transform 0.24s ease,
        visibility 0s linear 0.24s;
}

.space-post__reaction-tray.is-open {
    opacity: 1;
    visibility: visible;
    transform: translate(4px, -50%);
    transition:
        opacity 0.2s ease,
        transform 0.26s ease,
        visibility 0s;
}

.space-post__quickreact {
    appearance: none;
    width: 28px;
    height: 28px;
    flex: 0 0 auto;
    border: 0;
    border-radius: 999px;
    background: transparent;
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    transition: background 0.12s ease, transform 0.12s ease;
}

.space-post__quickreact:hover { background: var(--sp-hover); transform: scale(1.12); }
.space-post__reaction-more { margin-left: 2px; flex: 0 0 auto; }

@media (prefers-reduced-motion: reduce) {
    .space-post__reaction-tray { transition: none; transform: none; }
    .space-post__reaction-tray.is-open { transition: none; }
}

.space-post__action {
    appearance: none;
    background: none;
    border: 0;
    padding: 4px 8px;
    border-radius: 6px;
    font-size: 13px;
    color: var(--sp-text-muted);
    cursor: pointer;
}

.space-post__action:hover { background: var(--sp-hover); color: var(--sp-text); }

/* ── The owner group, and its narrow-pane overflow ────────────────────────
   Pin / edit / delete / moderate travel together, so a pane too narrow for the
   whole footer can collapse them behind one "…" rather than wrapping the row
   in two — which is what a card with every control enabled did on a phone.

   `display: contents` is what makes this free on wide screens: the group's
   buttons stay direct flex children of the footer, in the same order, with the
   same spacing they had before the wrapper existed.

   The reaction, comment and chat controls are deliberately NOT collapsed. They
   are what a reader does with a post; the group behind the "…" is what is done
   TO it — its author's controls, its moderator's, and Repost, which puts a copy
   somewhere else. That is the half worth a second tap. */
.space-post__own { display: contents; }
.space-post__overflow { display: none; }

@media (max-width: 768px) {
    .space-post__own { display: none; }
    /* Not `inline-flex` on a shared class: `.space-post__action` sets no
       display, so the button keeps its own and only becomes visible here. */
    .space-post__overflow { display: inline-flex; align-items: center; }
}

/* Rows in the overflow menu carry the control's own icon, so the menu reads as
   the same set of controls rather than a separate list of commands. */
.space-popover--menu button > i {
    width: 16px;
    margin-right: 8px;
    text-align: center;
}
.space-popover button[disabled] { opacity: 0.45; cursor: not-allowed; }

/* Disabled rather than hidden, with a tooltip stating the limit — a control
   that silently disappears reads as a missing feature (the same reasoning as
   the Teams message edit button). */
.space-post__action[disabled] { opacity: 0.45; cursor: not-allowed; }
.space-post__action[disabled]:hover { background: none; color: var(--sp-text-muted); }

/* ── Popovers (reaction picker, moderation menu) ─────────────────────────── */
/* Appended to <body> and positioned with fixed coordinates, so they escape the
   feed scroller's overflow clipping. That means they cannot inherit the
   .spaces-panel token block and must declare their own colours. */

.space-popover {
    background: #ffffff;
    border: 1px solid #e3e6ea;
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
    padding: 6px;
    display: flex;
    gap: 2px;
}

.space-popover--menu { flex-direction: column; min-width: 180px; }

.space-popover button {
    appearance: none;
    background: none;
    border: 0;
    cursor: pointer;
    border-radius: 6px;
    padding: 7px 10px;
    font-size: 14px;
    color: #1f2329;
    text-align: left;
    white-space: nowrap;
}

.space-popover button:hover { background: #f4f6f8; }

body.dark-theme .space-popover {
    background: #24272d;
    border-color: #3a3d45;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}

body.dark-theme .space-popover button { color: #e8eaed; }
body.dark-theme .space-popover button:hover { background: #33363d; }

/* ── Comments ────────────────────────────────────────────────────────────── */

.space-comments { margin-top: 12px; display: flex; flex-direction: column; gap: 10px; }

/* Feed previews are intentionally bounded by the server to two comments.
   They sit BELOW the completed card, as a compact continuation of the post —
   not as message bubbles competing with the post body. */
.space-post__comment-previews {
    box-sizing: border-box;
    padding: 12px 16px 11px;
    border: 1px solid var(--sp-card-border);
    border-top: 0;
    border-radius: 0 0 calc(var(--sp-radius) - 2px) calc(var(--sp-radius) - 2px);
    background: var(--tm-space-post-comments);
    border-left: 3px solid var(--td-sparkle-color);
}

.space-post__comment-previews .space-comments {
    position: relative;
    margin: 0;
    gap: 11px;
}

/* The line connects the two preview avatars without turning a compact preview
   into a second, full discussion UI. */
.space-post__comment-previews .space-comments::before {
    content: '';
    position: absolute;
    top: 24px;
    bottom: 7px;
    left: 12px;
    width: 1px;
    background: var(--sp-card-border);
}

.space-post-detail .space-post-detail__dialog {
    max-width: 820px;
}

.space-post__comment-previews .space-comment { position: relative; gap: 9px; }
.space-post__comment-previews .space-comment__avatar {
    position: relative;
    z-index: 1;
    width: 25px;
    height: 25px;
    font-size: 10px;
    border: 2px solid var(--tm-real-white);
}
.space-post__comment-previews .space-comment__bubble {
    padding: 0;
    border-radius: 0;
    background: transparent;
    font-size: 13px;
    line-height: 1.42;
}
.space-post__comment-previews .space-comment__author { margin-right: 5px; }
.space-post__comment-previews .space-comment__time { font-size: 11px; }
/* A preview is a glimpse, not the comment: at most two lines, then an ellipsis.
   A comment can be a whole formatted write-up (headings, lists, line breaks),
   and line-clamp only counts lines it can flow, so every descendant is
   flattened into one inline run — block edges and <br>s become spaces. Media
   and link embeds stay in the discussion dialog, which the preview opens. */
.space-post__comment-previews [data-comment-body] {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    margin-top: 1px;
}
.space-post__comment-previews [data-comment-body] * {
    display: inline;
    margin: 0;
    padding: 0;
    font-size: inherit;
    line-height: inherit;
}
.space-post__comment-previews [data-comment-body] :is(p, div, li, h1, h2, h3, h4, h5, h6, blockquote, pre)::after {
    content: ' ';
}
.space-post__comment-previews [data-comment-body] br,
.space-post__comment-previews [data-comment-body] :is(img, video, audio, iframe, figure, .space-link-embed) {
    display: none;
}
.space-post__comment-previews .space-comment { cursor: pointer; }

.space-comment { display: flex; gap: 8px; }

.space-comment__avatar {
    width: 28px;
    height: 28px;
    font-size: 12px;
    border-radius: 50%;
    flex: 0 0 auto;
    background: var(--sp-text-muted);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    overflow: hidden;
}

.space-comment__bubble {
    background: var(--sp-hover);
    border-radius: 10px;
    padding: 7px 11px;
    font-size: 13.5px;
    line-height: 1.5;
    min-width: 0;
    overflow-wrap: anywhere;
}

.space-comment__author { font-weight: 600; margin-right: 6px; }
.space-comment__time { font-size: 11px; color: var(--sp-text-muted); }

.space-comments__more {
    appearance: none;
    background: none;
    border: 0;
    align-self: flex-start;
    padding: 2px 0 2px 36px;
    color: var(--sp-accent);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

.space-post__comment-previews .space-comments__more {
    margin: 0 0 8px;
    padding: 0 0 0 34px;
    font-size: 12.5px;
}

/* ── Embedded links ──────────────────────────────────────────────────────────
   Media a reader PASTED into the body, as opposed to attachments they uploaded
   (which are the gallery above). Scoped under `.space-link-embed`, applied by
   spaces-link-embed.js.

   These rules exist because the shared chat renderer's own styling is scoped to
   `.bot-message` / `.user-message`, so its markup arrived in a post card with no
   styling at all — which is what put an unstyled "giphy.gif / Open image" row
   under a pasted GIF. */

.spaces-panel .space-link-embed {
    margin: 10px 0 0;
    border-radius: var(--sp-radius);
    overflow: hidden;
    background: var(--sp-hover);
}

.spaces-panel .space-link-embed--image .chat-rich-embed-link {
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: zoom-in;
}

/* An image the author placed in the text (markdown). It keeps its own shape and sits at
   the LEFT edge, like the text around it: no stretched box, no grey panel behind a
   narrow picture. (The chat renderer used to force `width:100%; height:260px` inline —
   spaces-link-embed.js drops that style, so this is the only place the look is set.)
   Specificity is deliberate: it must beat `.space-post .space-post__body
   img:not([width][height])` in spaces-attachments.css. */
.spaces-panel .space-link-embed--image {
    width: fit-content;
    background: none;
}
.space-post .space-post__body .space-link-embed--image .chat-rich-embed-link img.chat-rich-embed-image-el {
    display: block;
    width: auto;
    max-width: 100%;
    height: auto;
    max-height: 260px;
    margin: 0;
    object-fit: contain;
    background: none;
}
/* Until the bytes arrive (a protected image is fetched first) there is no `src` and the
   image would be 0px tall, so the post jumps when it lands: hold a little room. */
.space-post .space-post__body .space-link-embed--image .chat-rich-embed-link img.chat-rich-embed-image-el:not([src]) {
    min-height: 160px;
    min-width: 160px;
    background: var(--sp-hover);
}

/* 16:9 without a wrapper hack. An iframe has no intrinsic ratio, so without
   this it collapses to the HTML default of 150px tall. */
.spaces-panel .space-link-embed--video iframe {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
    background: #000;
}

/* The original link, kept under every player: an embed can be refused by an
   enterprise proxy or by an owner who disabled embedding, and a dead black
   rectangle with no way to reach the video is worse than a plain link. */
.spaces-panel .space-link-embed__source {
    display: block;
    padding: 6px 10px;
    color: var(--sp-text-muted);
    font-size: 12px;
    text-decoration: none;
}
.spaces-panel .space-link-embed__source:hover {
    color: var(--sp-accent);
    text-decoration: underline;
}

/* ── Your drafts ─────────────────────────────────────────────────────────────
   The strip above the feed listing posts you started and have not published.

   LOUD ON PURPOSE. The whole point of a draft is not forgetting about it, and a
   tastefully quiet reminder is one nobody acts on. The amber rail is the only
   place in Spaces that colour is used to mean "this is yours and unfinished" —
   it must not read as an error (red) or as an announcement (the accent blue). */

.spaces-panel .space-drafts {
    flex: 0 0 auto;
    margin: 10px 16px 0;
    padding: 10px 12px;
    border: 1px solid var(--sp-pin);
    border-left-width: 4px;
    border-radius: var(--sp-radius);
    background: color-mix(in srgb, var(--sp-pin) 8%, transparent);
}

/* `hidden` loses to any class that sets `display`; this strip is toggled with
   the attribute, so it needs the guard at higher specificity. */
.spaces-panel .space-drafts[hidden] { display: none; }

.spaces-panel .space-drafts__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    margin-bottom: 8px;
}

.spaces-panel .space-drafts__title {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 600;
    color: var(--sp-text);
}

.spaces-panel .space-drafts__count {
    min-width: 18px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--sp-pin);
    color: #3b2a00;
    font-size: 11px;
    font-weight: 700;
    text-align: center;
}

.spaces-panel .space-drafts__hint {
    color: var(--sp-text-muted);
    font-size: 11px;
}

.spaces-panel .space-drafts__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.spaces-panel .space-drafts__item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 0;
}
.spaces-panel .space-drafts__item + .space-drafts__item {
    border-top: 1px solid color-mix(in srgb, var(--sp-pin) 30%, transparent);
}

.spaces-panel .space-drafts__text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.spaces-panel .space-drafts__preview {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    color: var(--sp-text);
}

.spaces-panel .space-drafts__meta {
    display: flex;
    gap: 8px;
    color: var(--sp-text-muted);
    font-size: 11px;
}

.spaces-panel .space-drafts__actions {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 6px;
}

.spaces-panel .space-drafts__discard {
    padding: 4px 6px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--sp-text-muted);
    cursor: pointer;
}
.spaces-panel .space-drafts__discard:hover { color: var(--sp-danger); }

.spaces-panel .space-drafts__toggle {
    margin-top: 6px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--sp-text-muted);
    font-size: 12px;
    cursor: pointer;
}
.spaces-panel .space-drafts__toggle:hover { text-decoration: underline; }

@media (max-width: 640px) {
    .spaces-panel .space-drafts__item { flex-wrap: wrap; }
    .spaces-panel .space-drafts__actions { width: 100%; justify-content: flex-end; }
}

/* "You were mentioned" — the one thing on a card that is about the READER
   rather than the post, so it is the one thing allowed to use the accent while
   the rest of the engagement row stays muted. It sits with the counts because
   that is where the discussion is. */
.space-post__mentions-you {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
    margin-left: auto;
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--tm-accent-subtle);
    color: var(--sp-accent);
    font-size: 11.5px;
    font-weight: 700;
    white-space: nowrap;
}

/* Whichever of these appears first takes the auto margin; the rest sit beside
   it so the group stays together on the right instead of splitting apart. */
.space-post__mentions-you ~ .space-post__views,
.space-post__mentions-you ~ .space-post__comment-count { margin-left: 14px; }

/* Lock indicator: a locked post must say so, or the missing Comment button
   reads as a bug. */
.space-post__comments-locked {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
    margin-left: 14px;
    padding: 3px 0;
    font-size: 13px;
    color: var(--sp-text-muted, #6b7280);
}

/* The card's body slot — prose, headings, inline images, embeds, tags. Split
   out of spaces-post.css when a body could be formatted markup as well as
   markdown; loaded immediately after it, because it styles what that file
   lays out. */
/* ── The post BODY ───────────────────────────────────────────────────────────
 *
 * Everything inside `.space-post__body`: the prose, its headings and rules, the
 * images an author placed in the text, the embedded links and the tag chips
 * that follow it.
 *
 * SPLIT OUT OF spaces-post.css (2026-08-31) when a post body could be FORMATTED
 * markup as well as markdown and that file reached the 1000-line ceiling. The
 * seam is a real one rather than a convenience: the CARD is a layout — header,
 * body slot, engagement, footer — and this is what goes in the slot. Both kinds
 * of body land on the same rules, which is the point: a post written in the
 * formatting editor and one written in markdown are the same document to a
 * reader, so they must not be styled by two different files.
 *
 * SCOPING (CLAUDE.md rule 2). Everything here is a `.space-post__body`
 * descendant — the one exception being the `tm-al-*` alignment classes, which
 * are declared once beside the sanitizers that allow them, in
 * teams/teams-rich-editor.css, because the composer and the card must agree
 * about what "centred" looks like.
 */

.space-post__body {
    font-size: 14px;
    line-height: 1.6;
    overflow-wrap: anywhere;
}

.space-post__body ul,
.space-post__body ol {
    padding-left: 15px;
}

.space-post__body p { margin: 0 0 8px; }
.space-post__body p:last-child { margin-bottom: 0; }
.space-post__body img { max-width: 100%; height: auto; border-radius: 8px; }

/* ── A FORMATTED body (migration 314) ────────────────────────────────────────
   The markdown renderer emits headings too, so these are not "the rich post's
   headings" — they are the card's, and both kinds of body land on them. Written
   here rather than left to the browser because a UA `h2` inside a 14px card is
   two and a half times the body size, which reads as a different document
   pasted into the feed.

   Alignment classes (`tm-al-*`) are NOT declared here: they live once, beside
   the sanitizers that allow them, in teams/teams-rich-editor.css. Declaring
   them a second time is how the composer and the card start disagreeing about
   what "centred" looks like. */
.space-post__body h1,
.space-post__body h2,
.space-post__body h3,
.space-post__body h4 {
    margin: 14px 0 6px;
    font-weight: 700;
    line-height: 1.3;
}
.space-post__body h1:first-child,
.space-post__body h2:first-child,
.space-post__body h3:first-child,
.space-post__body h4:first-child { margin-top: 0; }
.space-post__body h1 { font-size: 1.35em; }
.space-post__body h2 { font-size: 1.2em; }
.space-post__body h3 { font-size: 1.08em; }
.space-post__body h4 { font-size: 1em; }

.space-post__body hr {
    margin: 14px 0;
    border: 0;
    border-top: 1px solid var(--sp-card-border);
}

/* ── Inline images ───────────────────────────────────────────────────────────
   An image the author placed IN the update, as markdown. The chat renderer's
   filename + "Open image" row is stripped in spaces-link-embed.js, so the image
   is the only affordance left — which means the image itself has to say it is
   clickable. `zoom-in` plus a lift on hover; the click opens the media modal. */
.space-post__body .chat-rich-embed-image {
    max-width: min(560px, 100%);
    margin: 10px 0;
}

.space-post__body .chat-rich-embed-link {
    display: block;
    padding: 0;
    border: 0;
    background: none;
    border-radius: 8px;
    cursor: zoom-in;
    transition: box-shadow 0.14s ease, transform 0.14s ease;
}

.space-post__body .chat-rich-embed-link:hover,
.space-post__body .chat-rich-embed-link:focus-visible {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
    transform: translateY(-1px);
}

/* A FORMATTED post's inline image is a bare <img> — the same affordance, put on
   the image itself (wireInlineImageViewer in spaces-post-images.js). */
.space-post__body .space-post__inline-image {
    cursor: zoom-in;
    border-radius: 8px;
    transition: box-shadow 0.14s ease, transform 0.14s ease;
}

.space-post__body .space-post__inline-image:hover,
.space-post__body .space-post__inline-image:focus-visible {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
    transform: translateY(-1px);
}

.space-post__body .space-post__inline-image:focus-visible {
    outline: 3px solid var(--sp-accent);
    outline-offset: 2px;
}

.space-post__body .chat-rich-embed-link img { display: block; }

/* Wide content must scroll inside its own box — the page itself must never
   scroll sideways. */
.space-post__body pre,
.space-post__body table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
}

/* A TALL post is collapsed with a fade + "Show more" so one essay — or one
   post carrying three screenshots — cannot occupy the entire feed. The height
   is measured in JS (CLAMP_PX in spaces-post.js); keep the two in step. */
.space-post__body--clamped {
    max-height: 250px;
    overflow: hidden;
    position: relative;
}

.space-post__body--clamped::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 64px;
    background: linear-gradient(to bottom, transparent, var(--sp-card-bg));
}

.space-post__more {
    appearance: none;
    background: none;
    border: 0;
    padding: 4px 0;
    color: var(--sp-accent);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

.space-post__tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }

/* A tag is a LINK, and has to look like one. It was a grey pill with no hover
   and no cursor, so nobody discovered that the feed can be filtered by tag at
   all — the affordance is the whole feature. Styled as a button element (it is
   one now) rather than an anchor: it filters the feed in place, and an <a> with
   no href is a promise of navigation this does not keep. */
.space-post__tag {
    font: inherit;
    font-size: 12px;
    color: var(--sp-accent);
    background: transparent;
    padding: 0;
    border: none;
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
}

.space-post__tag:hover,
.space-post__tag:focus-visible {
    font-weight: 600;
}

/* `blockquote` (an author's pull-quote — e.g. a "Quote of the day" post) has
   no rule of its own in here, so it falls back to the UA default `margin: 1em
   40px`. That 80px of combined side margin barely registers on a ~700px
   desktop card, but on a mobile card (~300-340px wide) it eats a third of the
   available width, wrapping a short sentence into a cramped three-line column.
   Zeroing just the side margin (not adding a border/left-padding accent, which
   would fight a CENTER-aligned quote — see `tm-al-center` in
   teams-rich-editor.css) is the minimal fix; the vertical margin and the
   quote's own text styling are untouched. Scoped to mobile only — desktop
   keeps its current look exactly. */
body.mobile-standalone .space-post__body blockquote {
    margin-left: 0;
    margin-right: 0;
}

/* =============================================================================
   Team Spaces — composer, dock, empty states, banner
   -----------------------------------------------------------------------------
   Part of the spaces.css manifest. Scoping (CLAUDE.md rule 2): every selector is
   prefixed `.space-` or nested under `.spaces-panel` / `.space-post-modal`.
   ============================================================================= */

/* ── Composer ────────────────────────────────────────────────────────────── */

.space-composer {
    background: var(--sp-card-bg);
    border: 1px solid var(--sp-card-border);
    border-radius: var(--sp-radius);
    padding: 12px 14px;
    max-width: 720px;
    margin: 0 auto var(--sp-gap);
    width: 100%;
    /* Without border-box this card is `100% + 28px padding + 2px border` wide,
       so the FEED — not just the composer — scrolled sideways by 30px on a
       phone. (No global border-box reset in this app.) */
    box-sizing: border-box;
}

/* "You may read here but not post" — stated quietly rather than left as an
   unexplained absence of a composer. */
.space-composer--readonly {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    font-size: 13px;
    color: var(--sp-text-muted);
    background: transparent;
    border-style: dashed;
}

.space-composer__row { display: flex; align-items: center; gap: 8px; }
.space-composer__row + .space-composer__row { margin-top: 8px; }

/* The target picker is NOT optional decoration. With multi-space filtering
   there is no single "current" space, so the destination must be stated
   rather than inferred — a post landing in the wrong space is worse than one
   extra click. */
.space-composer__target {
    appearance: none;
    border: 1px solid var(--sp-card-border);
    border-radius: 8px;
    background: var(--sp-card-bg);
    color: var(--sp-text);
    font-size: 13px;
    font-weight: 600;
    padding: 6px 10px;
    max-width: 260px;
}

.space-composer__title,
.space-composer__body {
    width: 100%;
    border: 0;
    background: transparent;
    color: var(--sp-text);
    font-family: inherit;
    resize: none;
    outline: none;
}

.space-composer__title { font-size: 15px; font-weight: 700; padding: 4px 0; }

.space-composer__body {
    font-size: 14px;
    line-height: 1.55;
    min-height: 44px;
    max-height: 320px;
    padding: 4px 0;
}

.space-composer__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--sp-card-border);
}

.space-composer__tools { display: flex; align-items: center; gap: 4px; }

.space-btn {
    appearance: none;
    border-radius: 8px;
    border: 1px solid transparent;
    padding: 7px 14px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    background: var(--sp-accent);
    color: #fff;
}

.space-btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.space-btn--ghost { background: transparent; border-color: var(--sp-card-border); color: var(--sp-text-muted); }
.space-btn--ghost:hover { background: var(--sp-hover); color: var(--sp-text); }

.space-composer__hint { font-size: 12px; color: var(--sp-text-muted); }
.space-composer__error { font-size: 12px; color: var(--sp-danger); margin-top: 6px; }

/* ── States ──────────────────────────────────────────────────────────────── */

.space-empty, .space-error {
    text-align: center;
    padding: 48px 24px;
    color: var(--sp-text-muted);
    max-width: 520px;
    margin: 0 auto;
}

.space-empty__icon { font-size: 32px; margin-bottom: 12px; opacity: 0.5; }
.space-empty h3 { font-size: 16px; color: var(--sp-text); margin: 0 0 6px; }
.space-empty p { font-size: 13.5px; margin: 0; line-height: 1.5; }

.space-feed__loading { text-align: center; padding: 18px; color: var(--sp-text-muted); font-size: 13px; }

/* The post skeletons (`.space-skeleton`) live in spaces-skeleton.css with the
   gutter skeletons and the shared `space-pulse` keyframes. */

/* ── Responsive ──────────────────────────────────────────────────────────── */

@media (max-width: 768px) {
    .space-feed__scroller { 
        padding: 0 10px 20px;
        overflow-x: hidden; 
    }
    .space-post { border-radius: 10px; padding: 12px; }
    .space-composer { border-radius: 10px; }
    .space-composer__target { max-width: 100%; }
}

/* ── Composer trigger ────────────────────────────────────────────────────────
   A one-line affordance that OPENS the New-post modal. It looks like a
   composer and behaves like a button, so post creation keeps a single code
   path while the familiar social-feed cue survives. */

.space-composer--trigger {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    text-align: left;
    cursor: pointer;
    color: var(--sp-text-muted);
    font-size: 14px;
}

.space-composer--trigger:hover {
    border-color: var(--sp-accent);
    color: var(--sp-text);
}

.space-composer--trigger i { color: var(--sp-accent); }

/* ── First-run banner ────────────────────────────────────────────────────────
   Deliberately NOT a seeded post — see SpacesFeed._renderEmpty for why (N
   identical welcome posts in the merged feed, author_kind, one language). */

.space-empty__actions {
    display: flex;
    gap: 8px;
    justify-content: center;
    margin-top: 18px;
    flex-wrap: wrap;
}

.space-welcome p { max-width: 46ch; margin-inline: auto; }

/* ── Responsive: the Space header on a narrow pane ───────────────────────────
   The header is the chat's, so it already reflows — these only cover the parts
   Spaces adds. The participant summary is the first thing to go: on a phone the
   ACTIONS are what the header is for, and a truncated roster earns none of the
   width it takes. */

@media (max-width: 768px) {
    .spaces-panel__context .teams-conversation__participants {
        display: none;
    }
    /* Line the header up with the feed's own 16px gutter. At 10px the title and
       the New-post button sat closer to the screen edges than every card below
       them, which reads as the header being unpadded rather than compact. */
    .spaces-panel__context .teams-conversation__header {
        padding: 12px 16px;
    }
    /* The banner is centred prose; give it the full narrow column. */
    .space-welcome p { max-width: none; }
    .space-empty__actions { flex-direction: column; align-items: stretch; }
    .space-post-modal__dialog { max-width: 100%; }
    .space-post-modal__textarea { min-height: 120px; }
}

/* Very narrow: the merged-feed subtitle is the next thing that stops earning
   its line, since the title above it already says where you are. */
@media (max-width: 520px) {
    .spaces-panel__context .space-scope-sub { display: none; }
}

.spaces-panel__context .space-scope-clear {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--sp-accent);
    cursor: pointer;
    font: inherit;
    font-size: 12px;
    font-weight: 600;
}

/* The only post-creation entry point in Spaces: compact, visible in both
   My Feed and one-Team views, and intentionally styled as a header action. */
.spaces-panel__context .teams-conversation__new-post {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 1px solid var(--tm-border, var(--sp-card-border));
    border-radius: 8px;
    background: var(--sp-card-bg);
    color: var(--tm-text-2);
    cursor: pointer;
    font-size: 15px;
}

.spaces-panel__context .teams-conversation__new-post:hover {
    background: var(--sp-accent);
    border-color: var(--sp-accent);
    color: #fff;
}

/* =============================================================================
   Team Spaces — the New-post modal
   -----------------------------------------------------------------------------
   Part of the spaces.css manifest. Scoping (CLAUDE.md rule 2): every selector is
   prefixed `.space-` or nested under `.spaces-panel` / `.space-post-modal`.
   ============================================================================= */

/* ══════════════════════════════════════════════════════════════════════════════
   NEW-POST MODAL
   ------------------------------------------------------------------------------
   Scoped under `.space-post-modal` throughout, so it cannot collide with the
   Teams modals whose shell (`.teams-modal`) it borrows. Everything colour-wise
   comes from the aliased --sp-* tokens, so it follows the app into dark mode.
   ══════════════════════════════════════════════════════════════════════════ */

/* ⚠️ `[hidden]` MUST WIN OVER OUR OWN `display`.
   The UA stylesheet's `[hidden] { display: none }` is an element-level rule, so
   ANY class here that sets `display` silently beats it and the element stays on
   screen. That is not hypothetical: it left the drag-and-drop veil painted over
   the post text permanently, and showed the moderator-only "Mark as important"
   checkbox to everyone — a control the server then ignores.
   Specificity (0,2,0) here beats the (0,1,0) class rules below, so this needs
   no !important. Every `hidden` toggle in this modal depends on it. */
.space-post-modal [hidden] { display: none; }

/* THE DEFAULT IS THE SMALL-SCREEN ANSWER, and it is deliberately unchanged.

   `calc(100vw - 32px)` is what keeps the dialog off the bezels on a phone; the
   680px cap is what stops a line of body text growing past a comfortable
   measure on a laptop. Both are still exactly what they were. */
.teams-modal-overlay .space-post-modal__dialog {
    max-width: 680px;
    width: min(680px, calc(100vw - 32px));
    max-height: min(88vh, 900px);
    display: flex;
    flex-direction: column;
    padding: 0 10px;
}

/* Very large screens only.

   People composing on a 27" monitor asked for a bigger dialog, and the obvious
   change — raising the base width — is the wrong one: `width: min(880px, …)`
   widens it on EVERY viewport past ~912px, so a 1280px laptop gets the large-
   screen treatment too. That is the opposite of "leave smaller screens alone",
   and it is invisible in testing unless you happen to be on a laptop.

   A min-width query is the only shape that says what was actually meant. 1600px
   clears 1280 and 1440 laptops entirely and starts at the desktop monitors this
   is for. `calc(100vw - 32px)` stays in the min() so a narrow window on a wide
   display still cannot push the dialog past its edges.

   (A paired `max-width: 800vw` is what the first attempt used. It is eight
   times the viewport, so it caps nothing at all — the width declaration was
   doing the whole job on its own, which is why the regression reached every
   screen size.) */
@media (min-width: 1600px) {
    .teams-modal-overlay .space-post-modal__dialog {
        max-width: 990px;
        width: min(990px, calc(100vw - 32px));
    }
}

.space-post-modal__body {
    position: relative;      /* the drop veil anchors to this */
    display: flex;
    flex-direction: column;
    gap: 18px;
    overflow-y: auto;
}

.space-post-modal__row { display: flex; gap: 14px; }
.space-post-modal__field { display: flex; flex-direction: column; gap: 7px; }
.space-post-modal__field--grow { flex: 1 1 auto; min-width: 0; }

.space-post-modal__label,
.space-post-modal__location-group {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--sp-text-muted);
}

.space-post-modal__label em {
    font-style: normal;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    opacity: 0.7;
}

/* `box-sizing` is NOT decoration here. This app has no global border-box reset,
   so `width: 100%` + 24px of padding + 2px of border made every one of these
   controls 26px wider than the column holding it — the dialog scrolled
   sideways and the Tags field ran off the right edge of a phone screen.
   Declared per-control rather than as a `.space-post-modal__dialog *` blanket:
   a blanket rule reaches into the formatting editor this dialog hosts, whose
   toolbar and editable surface size themselves, and a component that is styled
   in one file should not be resized from a second one. */
.space-post-modal__input,
.space-post-modal__select,
.space-post-modal__textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 10px 12px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-real-white, #fff);
    color: var(--sp-text);
    font: inherit;
    font-size: 14px;
}

.space-post-modal__input::placeholder,
.space-post-modal__textarea::placeholder { color: var(--tm-text-3); }

.space-post-modal__input:focus,
.space-post-modal__select:focus,
.space-post-modal__textarea:focus {
    outline: none;
    border-color: var(--sp-accent);
    box-shadow: 0 0 0 3px var(--tm-accent-subtle);
}

/* One space in scope: state the destination rather than offering a picker of
   one, which is a control that cannot be used. */
.space-post-modal__fixed {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--sp-hover);
    font-size: 14px;
    font-weight: 650;
    color: var(--sp-text);
}


/* "Choose a space to post to" — the answer to a paste that could not be
   uploaded yet, sitting DIRECTLY under the control that is blocking it.

   It used to live in the attachment tray, beside the chips those files would
   eventually become. That was wrong twice: the tray is below the editor and
   usually off-screen, and the tray is not what the author has to act on. A
   notice has to sit against the control it is talking about. */
.space-post-modal__deferred {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    padding: 9px 12px;
    border-radius: var(--tm-r, 8px);
    background: var(--tm-accent-subtle);
    color: var(--sp-accent);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.4;
}

/* And point at the picker unmistakably. The first version was a 2px ring, which
   the author reported as "I barely see a highlight" — a hint competing with a
   whole dialog for attention has to win, not merely exist. */
.space-post-modal .is-awaiting-choice {
    border-radius: var(--tm-r, 8px);
    box-shadow: 0 0 0 3px var(--sp-accent);
    animation: space-post-await 1.6s ease-in-out 2;
}

@keyframes space-post-await {
    0%, 100% { box-shadow: 0 0 0 3px var(--sp-accent); }
    50%      { box-shadow: 0 0 0 7px var(--tm-accent-subtle); }
}

/* Respect a reader who has asked the OS for less motion; the ring alone still
   carries the message. */
@media (prefers-reduced-motion: reduce) {
    .space-post-modal .is-awaiting-choice { animation: none; }
}

/* Choosing the company-wide destination is not a thing to discover after
   pressing Post. */
.space-post-modal__global-note {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 4px;
    padding: 8px 10px;
    border-radius: var(--tm-r, 8px);
    background: var(--tm-accent-subtle);
    color: var(--sp-accent);
    font-size: 12.5px;
    font-weight: 600;
}

/* Department and OFFICE targeting for a GLOBAL announcement. Shown only while
   the global destination is chosen — a team post is addressed by membership,
   and the server drops both for every other space. Offices reuse this class
   and `__department-list`: two identical checkbox grids styled twice is how
   they come to disagree. */
.space-post-modal__departments {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 8px;
}

/* LOCATIONS + DREAMPORT (migration 365) — the shared admin control
   (js/shared/location-targeting.js), given this host's classes. Its
   TagMultiselect picks up the `.space-post-modal .admin-tagms*` restyle below. */
.space-post-modal .space-post-modal__location-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.space-post-modal .space-post-modal__location-select { cursor: pointer; }
.space-post-modal .space-post-modal__location-warning {
    margin: 0;
    font-size: 12.5px;
    color: var(--sp-danger);
}

/* THE OFFICE PICKER is the admin panel's TagMultiselect, reused rather than
   reimplemented — the same control the announcement form and the AI rule editor
   target offices with. Its own stylesheet lives with the admin panel and is
   written against admin tokens (`--admin-gray-300` and friends), which do not
   exist here, so the component is restyled for THIS host instead of loading
   that sheet onto the chat page where it would meet a different token set.

   Scoped under `.space-post-modal` on purpose: these rules carry a host
   ancestor, so they cannot leak into the admin panel where the component is
   already styled correctly. */
.space-post-modal .admin-tagms {
    display: flex;
    flex-direction: column;
    gap: 6px;
    background: var(--sp-surface, #fff);
    border: 1px solid var(--sp-border, #e3e1de);
    border-radius: 8px;
    padding: 6px 8px;
}

.space-post-modal .admin-tagms-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.space-post-modal .admin-tagms-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--sp-surface-2, #f4f3f1);
    color: var(--sp-text, #1f1e1d);
    font-size: 12px;
}

.space-post-modal .admin-tagms-chip-remove {
    border: 0;
    background: transparent;
    color: var(--sp-text-muted, #6f6e6d);
    cursor: pointer;
    line-height: 1;
    padding: 0;
}

.space-post-modal .admin-tagms-input-wrap { position: relative; }

.space-post-modal .admin-tagms-input {
    width: 100%;
    border: 0;
    outline: none;
    background: transparent;
    color: var(--sp-text, #1f1e1d);
    font-size: 13px;
    padding: 2px 0;
}

/* The dropdown is positioned inside the wrap and must sit above the fields
   below it — the composer's own stacking context, not the page's. */
.space-post-modal .admin-tagms-dropdown {
    position: absolute;
    z-index: 5;
    left: 0;
    right: 0;
    top: calc(100% + 4px);
    max-height: 220px;
    overflow-y: auto;
    background: var(--sp-surface, #fff);
    border: 1px solid var(--sp-border, #e3e1de);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.space-post-modal .admin-tagms-option {
    padding: 7px 10px;
    font-size: 13px;
    color: var(--sp-text, #1f1e1d);
    cursor: pointer;
}

.space-post-modal .admin-tagms-option:hover,
.space-post-modal .admin-tagms-option.is-active {
    background: var(--sp-surface-2, #f4f3f1);
}

.space-post-modal .admin-tagms-empty,
.space-post-modal .admin-tagms-emptyhint {
    padding: 7px 10px;
    font-size: 12px;
    color: var(--sp-text-muted, #6f6e6d);
}

/* HOW THE TWO PICKERS COMBINE, said in the composer rather than left to be
   discovered. Dimensions intersect, and a narrower reach than an author
   expected reads as a bug unless the form says so first. Sits under both
   pickers because it describes the pair, not either one. */
.space-post-modal__targeting-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-top: 8px;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--sp-surface-2, #f4f3f1);
    color: var(--sp-text-muted, #6f6e6d);
    font-size: 12px;
    line-height: 1.45;
}

.space-post-modal__targeting-note i {
    margin-top: 2px;
    flex: 0 0 auto;
}

.space-post-modal__department-list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    max-height: 120px;
    overflow-y: auto;
}

.space-post-modal__department {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--tm-text-2);
    font-size: 13px;
    cursor: pointer;
}

.space-post-modal__department-empty {
    color: var(--tm-text-3);
    font-size: 12.5px;
    font-style: italic;
}

/* The autosave line. Quiet on purpose: a background save that announces itself
   loudly is worse than the loss it prevents. */
.space-post-modal__autosave {
    margin-left: auto;
    padding-right: 10px;
    color: var(--tm-text-3);
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 40%;
}

/* ── The editor ─────────────────────────────────────────────────────────────
   The formatting editor (`TeamsRichEditor`, inline mode) mounts inside this
   field. Its own chrome — toolbar, buttons, swatch menu, editable surface — is
   styled once in teams/teams-rich-editor.css under three scopes, `.space-post-
   modal` among them; what is here is only how that component sits INSIDE this
   dialog's field box.

   The component draws its own border and radius for a bubble edit, which is one
   border too many inside a field that already has one, so the inline shell is
   flattened below rather than restyled from scratch. */

.space-post-modal__field--editor {
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-real-white, #fff);
    padding: 10px 12px 8px;
    gap: 0;
}

.space-post-modal__field--editor .space-post-modal__label { margin-bottom: 8px; }

.space-post-modal__editor { min-width: 0; }

.space-post-modal .teams-rich--inline {
    border: 0;
    border-radius: 0;
    background: transparent;
}

.space-post-modal .teams-rich--inline .teams-rich__bar {
    padding: 0 0 7px;
    background: transparent;
    border-bottom: 1px solid var(--sp-card-border);
}

/* A post is long-form: the writing surface starts at the height the markdown
   editor used to occupy, so the dialog does not visibly shrink for authors who
   knew the old one. */
.space-post-modal .teams-rich--inline .teams-rich__editor {
    min-height: 300px;
    max-height: 46vh;
    padding: 10px 0 0;
    font-size: 14.5px;
    line-height: 1.6;
    color: var(--sp-text);
}

.space-guests .teams-modal-overlay .teams-modal__search {
    padding: 9px 12px 9px 9px;
}

.space-guests .teams-modal-overlay .teams-modal__add {
    padding: 0;
}

/* The editor could not be mounted: the bare textarea is the editor, and it must
   not look unfinished. Hidden by the modal when the editor DID mount, so this
   only ever paints on the fallback path. */
.space-post-modal__field--editor .space-post-modal__textarea {
    border: 0;
    padding: 8px 0 0;
    background: transparent;
    resize: vertical;
    min-height: 190px;
}

.space-post-modal__field--editor .space-post-modal__textarea:focus { box-shadow: none; }

/* ── Tools row ──────────────────────────────────────────────────────────────
   Attach + emoji sit INSIDE the editor box, where a chat composer puts them,
   so the two writing surfaces feel like the same product. */

.space-post-modal__tools {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--sp-card-border);
    margin-top: 8px;
}

.space-post-modal__tools-left { display: flex; align-items: center; gap: 2px; }

.space-post-modal__tool {
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--sp-text-muted);
    cursor: pointer;
    font-size: 14px;
}

.space-post-modal__tool:hover { background: var(--sp-hover); color: var(--sp-text); }

.space-post-modal__count {
    font-size: 11.5px;
    color: var(--tm-text-3);
    font-variant-numeric: tabular-nums;
}

/* ── Footer + errors ───────────────────────────────────────────────────── */

.space-post-modal .teams-modal__footer { align-items: center; }

.space-post-modal__foothint {
    margin-right: auto;
    font-size: 12px;
    color: var(--tm-text-3);
}

.space-post-modal__check {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: 13.5px;
    color: var(--sp-text);
}

.space-post-modal__error {
    font-size: 13px;
    color: var(--sp-danger);
    padding: 9px 11px;
    border: 1px solid var(--sp-danger);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-danger-bg);
}

@media (max-width: 640px) {
    .space-post-modal__dialog { max-height: 94vh; }
    .space-post-modal__row { flex-direction: column; }
    .space-post-modal .teams-rich--inline .teams-rich__editor,
    .space-post-modal__field--editor .space-post-modal__textarea { min-height: 140px; }
    .space-post-modal__foothint { display: none; }
}

/* ── Draft assist ────────────────────────────────────────────────────────────
   Improve / Shorten / Translate, below the editor. Deliberately quiet: it is an
   offer beside the author's own writing, not a call to action competing with
   Post. Scoped to the modal (CLAUDE.md rule 2). */

.space-post-modal .space-draft-assist {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed var(--sp-card-border);
}

.space-post-modal .space-draft-assist__label {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-right: 2px;
    color: var(--sp-text-muted);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.space-post-modal .space-draft-assist__btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 10px;
    border: 1px solid var(--sp-card-border);
    border-radius: 999px;
    background: transparent;
    color: var(--sp-text-muted);
    font-size: 12px;
    cursor: pointer;
}
.space-post-modal .space-draft-assist__btn:hover:not(:disabled) {
    border-color: var(--sp-accent);
    background: var(--sp-hover);
    color: var(--sp-accent);
}
.space-post-modal .space-draft-assist__btn:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Undo is the safety net for a rewrite that replaced text the author WROTE, so
   it reads as the recovery action rather than a fifth suggestion. */
.space-post-modal .space-draft-assist__undo {
    border-color: var(--sp-accent);
    color: var(--sp-accent);
}

.space-post-modal .space-draft-assist__status {
    flex: 1 1 100%;
    min-width: 0;
    color: var(--sp-text-muted);
    font-size: 11px;
}

/* The AI mark in front of the label. An <img> rather than a Font Awesome glyph
   because this is the product's own Ask-AI icon, and the row was previously
   easy to miss entirely — the whole point of adding it. `aria-hidden` and an
   empty alt: the adjacent text already names the control, so announcing the
   image would just repeat it. */
.space-post-modal .space-draft-assist__icon {
    width: 18px;
    height: 18px;
    object-fit: contain;
    /* Keeps its own colours in both themes; it is a brand mark, not a glyph
       that should inherit the muted label colour. */
    flex: 0 0 auto;
}

/* Write / Generate lead the row: they produce something from nothing, whereas
   the rewrites need a draft to act on. Filled rather than outlined so the
   distinction survives at a glance. */
.space-post-modal .space-draft-assist__btn--lead {
    border-color: var(--sp-accent);
    color: var(--sp-accent);
    font-weight: 600;
}

/* The `hidden` attribute loses to any class that sets `display` — the UA rule is
   element-level, so `.space-draft-assist__btn { display: inline-flex }` beats it
   and the Undo button would render while hidden. This has bitten the feature
   twice already (the drop veil, the moderator-only checkbox).
   (0,2,1) beats the (0,2,0) rule above, so no `!important` is needed — and this
   stylesheet uses none anywhere, which a test enforces. */
.space-post-modal .space-draft-assist [hidden] { display: none; }

/* Save-as-draft sits between Cancel and Post: it is a real commitment (the post
   is stored) but not the primary one, so it reads as a secondary action rather
   than competing with Post for the eye. */
.space-post-modal .space-post-modal__draft {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.space-post-modal .space-post-modal__draft:disabled { opacity: 0.5; cursor: default; }

/* Preview reads as the quietest footer control: it changes nothing, it only
   shows you what you already have. Same shape as Save-as-draft so the footer
   stays one row of like-sized buttons. */
.space-post-modal .space-post-modal__preview {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.space-post-modal .space-post-modal__preview:disabled { opacity: 0.5; cursor: default; }

/* ── Post preview ────────────────────────────────────────────────────────────
   A second layer ON TOP of the composer, which is itself an overlay. The
   composer stays mounted underneath — closing this must return the author to
   their draft exactly as they left it, so nothing here may unmount it.
   Scoped to .space-preview (CLAUDE.md rule 2). */

.space-preview-overlay {
    /* Above the composer. The shared .teams-modal-overlay z-index puts the two
       on the same layer, where paint order alone decides — and a preview that
       renders BEHIND the dialog that opened it looks like a dead button. */
    z-index: 12000;
}

.space-preview { max-width: 780px; width: 100%; }

.space-preview__body {
    max-height: 70vh;
    overflow-y: auto;
}

.space-preview__note {
    margin: 0 0 14px;
    color: var(--sp-text-muted);
    font-size: 12px;
}

/* The stage carries the feed's own background so the card sits on what it will
   actually sit on. A card previewed on the modal's flat surface reads lighter
   than it does in the feed, which is the one thing a preview must not do. */
.space-preview__stage {
    background: var(--sp-bg, transparent);
    border-radius: 10px;
}

/* The real card is width-constrained by the feed's list; reproduce that here
   rather than letting it stretch to the dialog, or long lines wrap differently
   in the preview than they will in the feed. */
.space-preview__stage .space-post {
    max-width: 720px;
    margin: 0 auto;
}

/* An inline image in a preview must never be able to push the dialog wider
   than the viewport. The feed gets this from its own list width; this stage
   has to say so itself. */
.space-preview__stage img {
    max-width: 100%;
    height: auto;
}

/* ── The "what do you want?" popover ─────────────────────────────────────────
   Write a post / Generate image ask for an instruction before they run. This
   replaced a one-line <input> in the assist row, which fit the layout and
   nothing else: a brief long enough to be useful scrolled away inside a field
   about fourteen words wide, so the author could not read back what they had
   written before committing to it.

   Layered ABOVE the composer, which stays visible and untouched behind it. */

.space-assist-prompt-overlay {
    /* Same layer as the post preview: both sit on top of the composer, which
       is itself an overlay. Without this the shared .teams-modal-overlay
       z-index leaves paint order to decide, and a popover that renders BEHIND
       the dialog that opened it looks like a dead button. */
    z-index: 11000;
}

.space-assist-prompt { max-width: 560px; width: 100%; }

.space-assist-prompt__body {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.space-assist-prompt__label {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--sp-text-muted);
}

/* The whole point: big enough to READ BACK a long instruction. Resizable, and
   `min-height` rather than rows alone so a seeded prompt that arrives longer
   than six rows still opens showing most of itself. */
.space-assist-prompt__input {
    width: 100%;
    box-sizing: border-box;      /* no global border-box reset in this app */
    min-height: 140px;
    padding: 12px 14px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-real-white, #fff);
    color: var(--sp-text);
    font: inherit;
    font-size: 14px;
    line-height: 1.55;
    resize: vertical;
}

.space-assist-prompt__input:focus {
    outline: none;
    border-color: var(--sp-accent);
    box-shadow: 0 0 0 3px var(--tm-accent-subtle);
}

.space-assist-prompt__input::placeholder { color: var(--tm-text-3); }

.space-assist-prompt__meta {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    font-size: 11.5px;
    color: var(--sp-text-muted);
}

.space-assist-prompt__count { flex: 0 0 auto; font-variant-numeric: tabular-nums; }

.space-assist-prompt .teams-btn:disabled { opacity: 0.5; cursor: default; }

@media (max-width: 600px) {
    /* A phone keyboard takes most of the screen; a 140px field plus a header
       and a footer does not fit above it. */
    .space-assist-prompt__input { min-height: 96px; }
}

/* ===========================================================================
   spaces-target-picker.css — the "which Spaces does this go to?" control

   Split out of spaces-modal.css and RE-SCOPED. These rules were written as
   `.space-post-modal .space-target__*`, which tied a reusable component to one
   dialog: the Repost dialog instantiates the same `SpaceTargetPicker` and got
   a raw checkbox list with bullets and a native-looking select, because not one
   of these rules matched it.

   Scoping is now the component's own `.space-target` block. That still honours
   the project rule — `.space-target__*` is a Spaces-specific prefix, not a bare
   `.chip` or `.popover` that could collide with the admin panel or the widget —
   while letting any dialog mount the picker and have it look like the picker.
   =========================================================================== */

/* ── The multi-destination picker ───────────────────────────────────────────
   Chips for what is chosen, a popover of checkboxes for changing it.

   The chip row is the load-bearing part: a control whose mistake is "you posted
   to the wrong team" has to show its whole answer without being opened. */

.space-target { position: relative; }

.space-target__control {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    box-sizing: border-box;
    min-height: 42px;
    padding: 7px 12px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-real-white, #fff);
    color: var(--sp-text);
    font: inherit;
    text-align: left;
    cursor: pointer;
    /* The control is a focusable div, not a <button> — chips carry their own ×
       buttons and a nested <button> breaks the parser (see the picker's own
       comment). A div has none of a button's implicit UI behaviour, so restore
       the one that is visible here: dragging across it must not select the chip
       labels as text. */
    user-select: none;
    -webkit-user-select: none;
}

.space-target__control:focus-visible {
    outline: none;
    border-color: var(--sp-accent);
    box-shadow: 0 0 0 3px var(--tm-accent-subtle);
}

.space-target__chips {
    display: flex;
    flex: 1 1 auto;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
}

.space-target__placeholder {
    color: var(--tm-text-3);
    font-size: 14px;
    line-height: 26px;
}

.space-target__chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    padding: 3px 4px 3px 10px;
    border-radius: 999px;
    font-size: 13px;
    font-weight: 600;
    background: var(--tm-accent);
    border-color: var(--tm-accent);
    color: #fff;
}

/* The company-wide destination is the outlier in the set and reads as one.
   Same shape as its siblings so it is still obviously one of them. */
.space-target__chip--global {
    background: var(--tm-accent-subtle);
    border-color: var(--sp-accent);
    color: var(--sp-accent);
}

.space-target__chip-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.space-target__chip-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    opacity: 0.6;
}
.space-target__chip-x:hover { opacity: 1; background: var(--sp-card-border); }

.space-target__caret {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--tm-text-3);
}

/* FIXED, not absolute, and positioned from JS (SpaceTargetPicker._positionPopover).
   The picker is mounted in dialogs whose body is `overflow-y: auto`, and a
   scroll container clips an absolutely-positioned descendant: the space list
   was cut off at the dialog's edge and slid under its footer. Fixed positioning
   takes the popover out of that clip and lets it sit OVER the dialog.

   z-index clears the Teams modal, which is 10000 (teams-modals.css) — the same
   reason the shared agent combobox there is raised to 10050.

   `top`/`left`/`width`/`max-height` are deliberately absent: fixed coordinates
   cannot be derived from the anchor in CSS, so JS writes all four. Keep it that
   way — a static `top` here would silently win on the first paint. */
.space-target__popover {
    position: fixed;
    z-index: 10060;
    overflow-y: auto;
    padding: 6px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-real-white, #fff);
    box-shadow: 0 12px 32px rgb(0 0 0 / 18%);
}

.space-target__search {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    margin-bottom: 4px;
    border-radius: var(--tm-r, 8px);
    background: var(--sp-hover);
    color: var(--tm-text-3);
}
.space-target__search input {
    flex: 1 1 auto;
    min-width: 0;
    border: 0;
    background: transparent;
    color: var(--sp-text);
    font: inherit;
    font-size: 14px;
}
.space-target__search input:focus { outline: none; }

.space-target__list { margin: 0; padding: 0; list-style: none; }

.space-target__option {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 8px 10px;
    border-radius: var(--tm-r, 8px);
    cursor: pointer;
    font-size: 14px;
    color: var(--sp-text);
}
.space-target__option:hover { background: var(--sp-hover); }
.space-target__option input { margin-top: 2px; flex: 0 0 auto; }
.space-target__option-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.space-target__option-label { font-weight: 600; }
.space-target__option-hint { color: var(--tm-text-3); font-size: 12px; }

.space-target__option--global .space-target__option-label {
    color: var(--sp-accent);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-size: 13px;
}

.space-target__divider {
    height: 1px;
    margin: 6px 4px;
    background: var(--sp-card-border);
}

.space-target__empty,
.space-target__foot {
    padding: 8px 10px;
    color: var(--tm-text-3);
    font-size: 12px;
}
.space-target__foot { border-top: 1px solid var(--sp-card-border); }

/* ===========================================================================
   spaces-composer-attachments.css — the composer's attachment surfaces

   Split out of spaces-modal.css, which had reached the 1000-line ceiling
   (project rule 3). This is a self-contained unit: the dedicated gallery drop
   field, the chips, the in-flight upload progress, the "waiting for a
   destination" notice and the dialog-wide drop veil. Nothing outside it styles
   these, and it styles nothing else.

   Every selector stays under `.space-post-modal`, exactly as it was — the chat
   page loads many stylesheets at once, and a bare `.attachments` here would
   collide with the admin panel and the widget.
   =========================================================================== */

/* ── Attachments ────────────────────────────────────────────────────────── */

/* The dedicated "attach images to post" target. A button element, not a bare
   div, so it is reachable and operable from the keyboard — dropping is a mouse
   gesture, and browsing must not be mouse-only. */
.space-post-modal__gallery-drop {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    width: 100%;
    box-sizing: border-box;
    padding: 14px 16px;
    border: 1px dashed var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: transparent;
    color: var(--sp-text-muted);
    font: inherit;
    font-size: 13px;
    text-align: center;
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.space-post-modal__gallery-drop:hover,
.space-post-modal__gallery-drop:focus-visible,
.space-post-modal__gallery-drop.is-dragover {
    border-color: var(--tm-accent, #2563eb);
    background: var(--sp-hover, rgba(37, 99, 235, 0.06));
}

.space-post-modal__gallery-drop i { font-size: 16px; }

.space-post-modal__gallery-drop small {
    font-size: 11.5px;
    line-height: 1.45;
    opacity: 0.75;
    max-width: 46ch;
}

.space-post-modal__attachments {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.space-post-modal__attachments:not([hidden]) { margin-top: 4px; }

.space-attach-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    max-width: 240px;
    padding: 5px 8px 5px 6px;
    border: 1px solid var(--sp-card-border);
    border-radius: 999px;
    background: var(--sp-hover);
    font-size: 12.5px;
    color: var(--sp-text);
}

.space-attach-chip img {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
}

.space-attach-chip > i { color: var(--sp-text-muted); padding-left: 4px; }

.space-attach-chip__name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.space-attach-chip__x {
    border: 0;
    background: transparent;
    color: var(--sp-text-muted);
    cursor: pointer;
    font-size: 16px;
    line-height: 1;
    padding: 0 2px;
}

.space-attach-chip__x:hover { color: var(--sp-danger); }

/* ── In-flight attachment ───────────────────────────────────────────────────
   A chip appears the moment a file is chosen, not when its upload finishes. A
   100 MB video takes about a minute to reach production, and the only previous
   feedback was the submit button's label — which nobody watches, so the
   composer read as frozen.

   Squared off rather than a pill: it holds a progress bar, and a bar inside a
   999px radius looks like a mistake. */
.space-attach-chip--pending {
    align-items: flex-start;
    max-width: 300px;
    padding: 7px 8px 7px 10px;
    border-radius: 10px;
    border-style: dashed;
}

.space-attach-chip--pending > i { padding-left: 0; margin-top: 2px; }

.space-attach-chip__body {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
    flex: 1 1 auto;
}

.space-attach-chip__status {
    font-size: 11px;
    font-variant-numeric: tabular-nums;   /* the % must not jitter as it counts */
    color: var(--sp-text-muted);
}

.space-attach-chip__bar {
    display: block;
    height: 3px;
    border-radius: 999px;
    background: var(--sp-card-border);
    overflow: hidden;
}

.space-attach-chip__fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--sp-accent);
    transition: width 0.2s ease;
}

/* Bytes are all out of the browser; the server is pushing them to S3 and we
   cannot measure that. An indeterminate sweep is honest where a bar parked at
   100% for another twenty seconds is not. */
.space-attach-chip--pending.is-processing .space-attach-chip__fill {
    width: 40% !important;
    animation: space-attach-sweep 1.1s ease-in-out infinite;
}

@keyframes space-attach-sweep {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}

.space-attach-chip--pending.is-error {
    border-color: var(--sp-danger);
    border-style: solid;
}
.space-attach-chip--pending.is-error > i,
.space-attach-chip--pending.is-error .space-attach-chip__status {
    color: var(--sp-danger);
}
.space-attach-chip--pending.is-error .space-attach-chip__bar { display: none; }

@media (prefers-reduced-motion: reduce) {
    .space-attach-chip__fill { transition: none; }
    .space-attach-chip--pending.is-processing .space-attach-chip__fill {
        animation: none;
        width: 100% !important;
    }
}

/* ── Drop veil ──────────────────────────────────────────────────────────────
   Covers the whole body rather than a strip, so the target is the obvious one:
   anywhere in the dialog. */

.space-post-modal__dropveil {
    position: absolute;
    inset: 0;
    z-index: 3;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    border: 2px dashed var(--sp-accent);
    border-radius: var(--tm-r-lg, 12px);
    background: var(--sp-card-bg);
    color: var(--sp-accent);
    font-size: 14px;
    font-weight: 600;
    pointer-events: none;   /* never swallow the drop it is advertising */
}

.space-post-modal__dropveil i { font-size: 26px; }

/* ===========================================================================
   spaces-repost.css — everything Repost wears: the dialog, and the banner

   ONE file for the feature rather than rules scattered into the two files that
   happen to host them. Both spaces-modal.css and spaces-post.css were at the
   1000-line ceiling (project rule 3), and neither is the natural home for this:
   the Repost dialog is deliberately NOT the composer (the composer is where
   text is written, and a repost may never offer that — its credit is only
   honest because the content cannot be edited on the way through), and the
   banner is not a property of an ordinary post card.

   Two blocks below:
     1. .space-repost-modal__*  — the dialog
     2. .space-post__repost*    — the banner on a reposted card
   =========================================================================== */
.space-repost-modal__dialog { max-width: 520px; width: 100%; }

.space-repost-modal__body {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding: 18px 20px;
    overflow-y: auto;
}

.space-repost-modal__field { display: flex; flex-direction: column; gap: 7px; }

.space-repost-modal__label {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--sp-text-muted);
}

.space-repost-modal__label em {
    font-style: normal;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    opacity: 0.7;
}

/* Same `box-sizing` reasoning as the composer's controls above: there is no
   global border-box reset, and without this the field is 26px wider than its
   column and scrolls the dialog sideways on a phone. */
.space-repost-modal__textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 10px 12px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-real-white, #fff);
    color: var(--sp-text);
    font: inherit;
    font-size: 14px;
    resize: vertical;
}

.space-repost-modal__textarea::placeholder { color: var(--tm-text-3); }

.space-repost-modal__textarea:focus {
    outline: none;
    border-color: var(--sp-accent);
    box-shadow: 0 0 0 3px var(--tm-accent-subtle);
}

/* The promise about the original, stated where the decision is made. */
.space-repost-modal__note {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    margin: 0;
    padding: 10px 12px;
    border-radius: var(--tm-r, 8px);
    background: var(--sp-surface-muted, transparent);
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--sp-text-secondary, inherit);
}

.space-repost-modal__note i { margin-top: 2px; opacity: 0.7; }

.space-repost-modal__error {
    font-size: 13px;
    color: var(--sp-danger);
    padding: 9px 11px;
    border: 1px solid var(--sp-danger);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-danger-bg);
}


/* ── The banner on a reposted card ─────────────────────────────────────── */

/* The repost banner.

   A BAND across the top of the card, not a line of small print above the
   avatar. The negative margins cancel `.space-post`'s 14px/16px padding so it
   spans the full width, and the bottom border closes it — the eye reads "this
   frame is the reposter's, everything below belongs to somebody else" before it
   reads a word. As an inline caption it sat close enough to the byline to look
   like part of it, which is the one thing it must not look like.

   Tinted rather than bordered-only: on a feed of white cards a hairline reads
   as a divider inside one post, while a tinted band reads as a wrapper around
   one. */
.space-post__repost {
    margin: -14px -16px 12px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--sp-card-border);
    border-radius: var(--sp-radius) var(--sp-radius) 0 0;
    background: var(--tm-accent-subtle);
}

.space-post__repost-credit {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--sp-text-secondary, inherit);
}

.space-post__repost-credit i { font-size: 11px; opacity: 0.75; }

.space-post__repost-credit strong { font-weight: 600; color: var(--sp-text); }

/* The reposter's own sentence. Larger and darker than the credit line above it
   because it is the thing they actually wrote — the credit is provenance, this
   is a message. Indented to the credit's text, past its icon, so the two read
   as one block rather than two stacked notices. */
.space-post__repost-note {
    margin: 6px 0 0 19px;
    font-size: 13.5px;
    line-height: 1.45;
    color: var(--sp-text);
}

/* ===========================================================================
   spaces-post-update.css — "Update post": the two bands and the dialog note

   Everything the feature wears, in one file (spaces-post.css and
   spaces-modal.css are both at the 1000-line ceiling).

     1. .space-post__update-band*       — the band on an outdated post and on
                                          the update that replaced it
     2. .space-post-modal__update-note  — the line in the composer explaining
                                          what posting an update does

   Same geometry as the repost banner: a full-width band across the top of the
   card (negative margins cancel `.space-post`'s 14px/16px padding), so it reads
   as a wrapper around the post rather than a line inside it.
   =========================================================================== */

.space-post .space-post__update-band {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: -14px -16px 12px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--sp-card-border);
    border-radius: var(--sp-radius) var(--sp-radius) 0 0;
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--sp-text-secondary, inherit);
}

/* Two bands on one card (a middle version of a chain) stack flush: only the
   first keeps the card-corner radius and the top bleed. */
.space-post .space-post__update-band + .space-post__update-band {
    margin-top: -12px;
    border-radius: 0;
}

.space-post .space-post__update-band i { font-size: 12px; opacity: 0.85; }

/* OUTDATED: warm, because it is the one a reader must not act on. */
.space-post .space-post__update-band--outdated {
    background: var(--tm-warning-bg, rgba(245, 158, 11, 0.14));
    color: var(--sp-text);
}

.space-post .space-post__update-band--outdated i { color: var(--tm-warning, #b45309); }

/* THE UPDATE: the same quiet tint the repost band uses. */
.space-post .space-post__update-band--update {
    background: var(--tm-accent-subtle);
}

.space-post .space-post__update-link {
    margin-left: auto;
    padding: 2px 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 600;
    color: var(--sp-accent);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.space-post .space-post__update-link:hover { filter: brightness(0.9); }

.space-post .space-post__update-link:focus-visible {
    outline: 2px solid var(--sp-accent);
    outline-offset: 2px;
    border-radius: 3px;
}

/* ── The explanation inside the composer ───────────────────────────────── */

.space-post-modal .space-post-modal__update-note {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    margin: 0 0 12px;
    padding: 10px 12px;
    border-radius: var(--tm-r, 8px);
    background: var(--tm-accent-subtle);
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--sp-text-secondary, inherit);
}

.space-post-modal .space-post-modal__update-note i {
    margin-top: 2px;
    opacity: 0.75;
}

@media (max-width: 500px) {
    .space-post .space-post__update-band {
        display: block;
    }
}

/* =============================================================================
   Team Spaces — the feed filter
   -----------------------------------------------------------------------------
   Its own part of the spaces.css manifest rather than living in
   spaces-modal.css: it is a control in the PANEL HEADER, not in a dialog, and
   the modal part was at the 1000-line limit (CLAUDE.md rule 3). Scoping rule 2
   applies as everywhere else here — every selector is nested under
   `.spaces-panel`.
   ============================================================================= */
/* ── The feed filter ────────────────────────────────────────────────────────
   Lives in the Spaces header beside "New post". Scoped under `.spaces-panel`
   so these names cannot reach the admin panel or the widget — the same rule
   the rest of this feature follows.

   The trigger SAYS WHEN IT IS ON. A quietly filtered feed looks like a broken
   one ("where did the announcement go?"), so an active filter carries a count
   and an accent, and the popover always offers a one-click way out. */

/* The wrapper is an ANCHOR, not a second control: `position: relative` is the
   containing block the popover below is positioned against, and it must stay
   on the base rule so the component keeps working wherever it is mounted. The
   button SURFACE — border, background, hover, active — belongs to the trigger
   and is defined once for every header control in teams-header-actions.css.
   Painting it here too gave the filter two nested button faces. */
.spaces-panel .space-filter {
    position: relative;
    display: inline-flex;
}

.spaces-panel .space-filter__trigger {
    width: 34px;
    height: 34px;
    border-radius: var(--tm-r, 8px);
    color: var(--sp-text-muted);
    font-size: 14px;
    cursor: pointer;
    background: transparent;
}

.spaces-panel .space-filter__trigger.is-active {
    background: var(--tm-accent-subtle);
    border-color: var(--sp-accent);
    color: var(--sp-accent);
}

.spaces-panel .space-filter__count {
    position: absolute;
    top: 1px;
    right: 1px;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    border-radius: 999px;
    background: var(--sp-accent);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 15px;
}

.spaces-panel .space-filter__popover {
    position: absolute;
    z-index: 30;
    top: calc(100% + 6px);
    right: 0;
    width: 280px;
    max-height: 70vh;
    overflow-y: auto;
    padding: 6px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-real-white, #fff);
    box-shadow: 0 14px 36px rgb(0 0 0 / 20%);
    text-align: left;
}

.spaces-panel .space-filter__section { padding: 4px 2px 8px; }
.spaces-panel .space-filter__section + .space-filter__section {
    border-top: 1px solid var(--sp-card-border);
    padding-top: 8px;
}

.spaces-panel .space-filter__heading {
    display: block;
    padding: 2px 8px 6px;
    color: var(--tm-text-3);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.spaces-panel .space-filter__list { margin: 0; padding: 0; list-style: none; }
.spaces-panel .space-filter__list--people { max-height: 220px; overflow-y: auto; }

.spaces-panel .space-filter__option {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 8px;
    border-radius: var(--tm-r, 8px);
    color: var(--sp-text);
    font-size: 13.5px;
    cursor: pointer;
}
.spaces-panel .space-filter__option:hover { background: var(--sp-hover); }
.spaces-panel .space-filter__option i { width: 14px; color: var(--tm-text-3); }

.spaces-panel .space-filter__person {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: var(--tm-r, 8px);
    background: transparent;
    color: var(--sp-text);
    font: inherit;
    font-size: 13.5px;
    text-align: left;
    cursor: pointer;
}
.spaces-panel .space-filter__person:hover { background: var(--sp-hover); }
.spaces-panel .space-filter__person.is-on {
    background: var(--tm-accent-subtle);
    color: var(--sp-accent);
    font-weight: 650;
}
.spaces-panel .space-filter__person-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.spaces-panel .space-filter__person-count {
    flex: 0 0 auto;
    color: var(--tm-text-3);
    font-size: 11.5px;
}

.spaces-panel .space-filter__empty {
    padding: 10px 8px;
    color: var(--tm-text-3);
    font-size: 12.5px;
}

.spaces-panel .space-filter__foot {
    padding: 6px 2px 2px;
    border-top: 1px solid var(--sp-card-border);
}
.spaces-panel .space-filter__clear {
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: var(--tm-r, 8px);
    background: transparent;
    color: var(--sp-accent);
    font: inherit;
    font-size: 13px;
    font-weight: 650;
    cursor: pointer;
}
.spaces-panel .space-filter__clear:hover:not(:disabled) { background: var(--sp-hover); }
.spaces-panel .space-filter__clear:disabled {
    color: var(--tm-text-3);
    cursor: default;
}

/* =============================================================================
   Team Spaces — the tag chip input and its autocomplete
   -----------------------------------------------------------------------------
   Used in two places that must look identical: the New post composer and the
   inline post editor. If they diverge, so do the tags people write in them.

   Every selector is scoped to a container (CLAUDE.md rule 2). `.space-tag-input`
   is mounted inside `.space-post-modal` (a body-appended overlay) AND inside a
   `.space-post` card in the feed, so it is scoped to its OWN block class rather
   than to a panel: an overlay appended to <body> is outside `.spaces-panel`, and
   scoping it there is exactly how a rule silently stops applying.
   ============================================================================= */

.space-tag-input {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* The box LOOKS like one input and behaves like one — clicking anywhere in it
   focuses the field. The chips live inside the same border for that reason. */
.space-tag-input .space-tag-input__box {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    min-height: 40px;
    padding: 6px 8px;
    border: 1px solid var(--sp-card-border, #d8d3c8);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-bg, #fff);
    cursor: text;
}

.space-tag-input .space-tag-input__box:focus-within {
    border-color: var(--sp-accent, #2d5f84);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--sp-accent, #2d5f84) 14%, transparent);
}

.space-tag-input .space-tag-input__chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 4px 3px 10px;
    border-radius: 999px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--sp-accent, #2d5f84);
    background: color-mix(in srgb, var(--sp-accent, #2d5f84) 10%, transparent);
}

.space-tag-input .space-tag-input__chip-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    font-size: 11px;
    cursor: pointer;
    opacity: 0.65;
}
.space-tag-input .space-tag-input__chip-remove:hover { opacity: 1; }

.space-tag-input .space-tag-input__field {
    flex: 1 1 120px;
    min-width: 120px;
    border: 0;
    outline: none;
    background: transparent;
    color: var(--sp-text, #1f1e1d);
    font: inherit;
    font-size: 13.5px;
    padding: 2px 0;
}

.space-tag-input .space-tag-input__hint {
    font-size: 11.5px;
    color: var(--sp-text-muted, #6f6e6d);
}

.space-tag-input.is-full .space-tag-input__remaining {
    color: var(--sp-danger, #c0392b);
    font-weight: 650;
}

/* ── Suggestions ──────────────────────────────────────────────────────────
   Absolutely positioned over whatever follows the field. In the composer that
   is the "important" checkbox and the footer; a menu that pushed them down
   would move the Post button under the pointer mid-selection. */
.space-tag-input .space-tag-input__menu {
    position: absolute;
    z-index: 30;
    top: calc(100% - 14px);
    left: 0;
    right: 0;
    max-height: 220px;
    overflow-y: auto;
    margin: 0;
    padding: 4px;
    list-style: none;
    border: 1px solid var(--sp-card-border, #d8d3c8);
    border-radius: var(--tm-r, 8px);
    background: var(--tm-bg, #fff);
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);
}

.space-tag-input .space-tag-input__suggestion {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    padding: 7px 9px;
    border: 0;
    border-radius: var(--tm-r-sm, 6px);
    background: transparent;
    color: var(--sp-text, #1f1e1d);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}

.space-tag-input .space-tag-input__suggestion:hover,
.space-tag-input .space-tag-input__suggestion.is-active {
    background: var(--sp-hover, #f0ede6);
}

.space-tag-input .space-tag-input__suggestion-name {
    font-weight: 600;
    color: var(--sp-accent, #2d5f84);
}

.space-tag-input .space-tag-input__suggestion-count {
    font-size: 11.5px;
    color: var(--sp-text-muted, #6f6e6d);
    font-variant-numeric: tabular-nums;
}

/* ── The active-tag chip beside the filter trigger ───────────────────────── */

.spaces-panel .space-filter__active-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 180px;
    margin-right: 6px;
    padding: 4px 8px 4px 10px;
    border: 1px solid color-mix(in srgb, var(--sp-accent) 35%, transparent);
    border-radius: 999px;
    background: color-mix(in srgb, var(--sp-accent) 12%, transparent);
    color: var(--sp-accent);
    font: inherit;
    font-size: 12.5px;
    font-weight: 650;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
}
.spaces-panel .space-filter__active-tag:hover {
    background: color-mix(in srgb, var(--sp-accent) 20%, transparent);
}
.spaces-panel .space-filter__active-tag span {
    overflow: hidden;
    text-overflow: ellipsis;
}

.spaces-panel .space-filter__tag {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 7px 9px;
    border: 0;
    border-radius: var(--tm-r-sm, 6px);
    background: var(--sp-hover);
    color: var(--sp-accent);
    font: inherit;
    font-size: 13px;
    font-weight: 650;
    cursor: pointer;
}
.spaces-panel .space-filter__tag:hover {
    background: color-mix(in srgb, var(--sp-accent) 14%, transparent);
}

/* The grid comes first: both gutter components and the intro are placed by it,
   and each of the three below styles only its own inside. */
/* ==========================================================================
   Team Spaces — the feed's three-column stage
   --------------------------------------------------------------------------
   gutter · feed · gutter, inside the scroller. The context column occupies the
   left gutter, the rail the right, and the intro block spans the first two.

   These rules were previously in spaces-rail.css, where they lived while the
   rail was the only tenant of the gutters. They moved when the context column
   arrived: a layout owned by one of two occupants is a layout the other cannot
   reason about, and the class that drives it is now decided by
   js/spaces-stage.js rather than by either component.

   Scoping (CLAUDE.md rule 2): everything is under `.spaces-panel`.

   EXPLICIT PLACEMENT ON EVERY ITEM. Nothing here relies on grid auto-placement.
   The intro spans columns 1–2 in row 1, which leaves row 1 column 3 free, and
   auto-placement will happily drop the next item into it — producing a layout
   that looks plausible and is wrong (the context blocks land top-right and the
   posts get pushed down-left). Every child states its own column.

   Created: 2026-08-15
   See: tasks/features/SPACES_INTRO_BLOCK_AND_LEFT_COLUMN.md
   ========================================================================== */

/* Narrow (the default): one column, which is byte-identical in effect to the
   plain block flow the scroller had before any gutter existed. */
.spaces-panel .space-feed__scroller {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
}

/* `.space-feed__list` centres itself with `margin: 0 auto`, which means one
   thing to a BLOCK child and another to a GRID item: auto margins in the inline
   axis are resolved before stretching, so the list became shrink-to-fit and
   every post card collapsed to the width of its own text. Measured at 90px on a
   1500px pane. An explicit width restores "fill the column, capped at 720,
   centred" — the behaviour `margin: 0 auto` had before this element's parent
   became a grid. */
.spaces-panel .space-feed__scroller > .space-feed__list,
.spaces-panel .space-feed__scroller > .space-feed__spacer,
.spaces-panel .space-feed__scroller > .space-feed__loading {
    width: 100%;
}

/* Both gutters are hidden until the stage says otherwise. */
.spaces-panel .space-rail,
.spaces-panel .space-context { display: none; }

/* On a narrow pane the intro keeps its place at the top of the feed and simply
   takes the single column's width — it was never sticky, so it has nothing to
   give up. The context column follows it, stacked, above the posts: content
   that scrolls away can degrade by stacking, which is precisely why the
   scroll-along blocks are the ones on the left. */
.spaces-panel .space-feed__scroller > .space-intro { width: 100%; }

/* Wide: gutter · feed · gutter.
   The FEED TRACK IS FIXED at its own cap and the gutters are flexible-but-
   capped, which is what keeps the posts both centred and full width at every
   size: below the cap the two gutters split whatever is left (equally, so the
   centre never moves); above it they stop growing at 340px and
   `justify-content: center` centres the whole block, so the gutters stay beside
   the feed instead of drifting to the edges of a 27" monitor. */
.spaces-panel .space-feed__scroller--wide {
    grid-template-columns:
        minmax(0, var(--sp-rail-max, 400px))
        720px
        minmax(0, var(--sp-rail-max, 400px));
    justify-content: center;
    /* ROW 4 ABSORBS THE SIDE COLUMNS' HEIGHT. The rail (rows 1..-1) and the
       context column (rows 2..4) are taller than the feed on a short or empty
       space. With four `auto` rows the grid spreads that shortfall across every
       spanned row — including row 1, so a feed with no intro block (row empty or
       holding only the one-line invitation) opened a tall blank band above the
       posts. A flexible last row takes the whole shortfall instead, so rows 1-3
       stay exactly as tall as their content. */
    /* Row 2 is My Feed's row of compact widgets (spaces-widgets/), empty elsewhere. */
    grid-template-rows: auto auto auto auto minmax(0, 1fr);
}

/* The intro spans the left gutter AND the feed — the composition the block was
   designed for, with the context column sitting underneath its left half.
   Explicitly in row 1 so the items below can state their own row. */
.spaces-panel .space-feed__scroller--wide > .space-intro {
    grid-column: 1 / 3;
    grid-row: 1;
    /* Size to its own content, never to the row. A grid item defaults to
       `stretch`, so anything that sizes row 1 to zero takes the block with it —
       which is precisely what happened when the container still carried
       `overflow: hidden` (see the note in spaces-intro.css). `start` makes the
       block's height its content's height and nothing else. */
    align-self: start;
    /* Line the block up with the context column beneath it rather than with the
       edge of the gutter, so the two read as one stack. */
    margin-right: 16px;
}

/* Everything the feed owns stays in the middle column — including the
   virtualization spacer, whose height IS the scroll length. Leaving it in
   column 1 would scroll the feed against a gutter. */
.spaces-panel .space-feed__scroller--wide > .space-feed__spacer {
    grid-column: 2;
    grid-row: 3;
}

/* The virtual spacer and rendered cards must occupy successive rows. Putting
   both in row 2 made the spacer overlap the list once it grew, which produced
   a blank feed and a scrollbar that appeared to skip a large batch of posts. */
.spaces-panel .space-feed__scroller--wide > .space-feed__list {
    grid-column: 2;
    grid-row: 4;
}

.spaces-panel .space-feed__scroller--wide > .space-feed__loading {
    grid-column: 2;
    grid-row: 5;
}

/* My Feed's compact widgets: under the intro, spanning the left gutter and the
   feed like it, so the columns below start where they always did when the row
   is empty (a hidden element takes no row height). */
.spaces-panel .space-feed__scroller--wide > .space-top-widgets {
    grid-column: 1 / 3;
    grid-row: 2;
    align-self: start;
    margin-right: 16px;
}

/* The context column: left gutter, row 2 — under the intro, beside the posts.
   The intro is deliberately the only left-side block that scrolls away. Once
   this context reaches the top of the feed viewport it remains available, just
   like the right rail. */
.spaces-panel .space-feed__scroller--wide > .space-context {
    grid-column: 1;
    grid-row: 3 / 6;
    display: block;
    align-self: start;
    position: sticky;
    top: 0;
    max-height: calc(100vh - 120px);
    overflow-y: auto;
    overscroll-behavior: contain;
    min-width: 0;
    padding-right: 16px;
}

.spaces-panel .space-feed__scroller--wide > .space-rail {
    grid-column: 3;
    /* Spanning every row is what gives sticky somewhere to travel: the rail's
       grid area becomes the full height of the feed, and the box then sticks
       inside it. `align-self: start` keeps the box its own height rather than
       stretching to that area. */
    grid-row: 1 / -1;
    align-self: start;
    position: sticky;
    top: 0;
    display: block;
    min-width: 0;
    /* Breathing room on BOTH sides so the card reads as a floating block rather
       than something bolted to the edge of the window: 16px of separation from
       the feed, 20px of air before the pane ends. */
    padding-left: 16px;
    padding-right: 20px;
    /* A safety valve, not the normal case: every block is capped small (10
       tags, 5 spaces, 3 drafts, 4 pins), so this only engages on a rail taller
       than the window, which would otherwise have no way to reach its end. */
    max-height: calc(100vh - 120px);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-bottom: 50px;
}

/* Nothing to say — hide the column. The GRID is not collapsed here: the stage
   decides that, because the opposite gutter may still have content and
   collapsing it from one side would move the feed out from under the other. */
.spaces-panel .space-rail.space-rail--empty,
.spaces-panel .space-context.space-context--empty { display: none; }
.spaces-panel .space-rail[hidden],
.spaces-panel .space-context[hidden] { display: none; }

/* =============================================================================
   Team Spaces — the placeholders the feed and both gutters paint while loading
   -----------------------------------------------------------------------------
   ITS OWN FILE, not part of spaces-stage.css, for one specific reason: that
   file is guaranteed to contain NO media query at all (the stage is measured in
   JS because a viewport query cannot see the collapsible Teams sidebar, and a
   test enforces it). The skeleton needs a `prefers-reduced-motion` query, which
   is not a breakpoint but would have broken that guarantee anyway.
   It belongs to neither column: one shimmer, one timing and one shape
   vocabulary for the two gutters that share the grid (spaces-gutter-skeleton.js).

   NO SURFACE OF ITS OWN. The skeleton card carries the host column's real card
   class as well as this one — the rail is painted in `--tm-real-white` and the
   context column in `--sp-card-bg` on purpose — so all that is added here is
   the bars inside it.

   THE FEED'S POST PLACEHOLDERS (`.space-skeleton`) LIVE HERE TOO, with the
   `space-pulse` keyframes all of them share, as does the Teams transcript's
   `teams-skeleton-msg`: one loading language across the page, not three.

   SIZES ARE REPLAYED, NOT GUESSED. Every placeholder that can takes the height
   its real card had last time (spaces-layout-memory.js), so nothing moves when
   the data lands; the natural-height shapes are only for a first visit.

   All selectors scoped to `.spaces-panel` (CLAUDE.md rule 2).
   ------------------------------------------------------------------------- */
@keyframes space-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }

/* ── Post placeholders (the feed column) ─────────────────────────────────────
   Shaped like a post — author row, title, body, action bar — on the post's own
   surface and padding (spaces-post.css), so a placeholder and the post that
   replaces it are the same card. `--sized` replays the real post's height. */
.spaces-panel .space-skeleton {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: 9px;
    overflow: hidden;
    background: var(--tm-real-white);
    border: 1px solid var(--sp-card-border);
    border-radius: var(--sp-radius);
    padding: 14px 16px;
}

.spaces-panel .space-skeleton__author {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 6px;
}

.spaces-panel .space-skeleton__avatar {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--sp-hover);
    animation: space-pulse 1.4s ease-in-out infinite;
}

.spaces-panel .space-skeleton__who {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.spaces-panel .space-skeleton__line {
    height: 10px;
    border-radius: 5px;
    background: var(--sp-hover);
    animation: space-pulse 1.4s ease-in-out infinite;
}

.spaces-panel .space-skeleton__line--name  { width: 28%; }
.spaces-panel .space-skeleton__line--time  { width: 16%; height: 8px; }
.spaces-panel .space-skeleton__line--title { width: 55%; height: 13px; margin-bottom: 3px; }
.spaces-panel .space-skeleton__line--long  { width: 92%; }
.spaces-panel .space-skeleton__line--short { width: 40%; }

/* The action bar sits at the bottom of a sized placeholder, as it does on a post. */
.spaces-panel .space-skeleton__actions {
    display: flex;
    gap: 24px;
    margin-top: auto;
    padding-top: 12px;
}

.spaces-panel .space-skeleton__actions .space-skeleton__line { width: 64px; }

/* ── Gutter placeholders (context column and rail) ─────────────────────────── */
.spaces-panel .space-gutter-skeleton__card {
    /* No height of its own: either the bars give the card its height (first
       visit), or `--sized` replays the real card's measured height
       (spaces-layout-memory.js) — never a guess taller than what arrives. */
    display: flex;
    flex-direction: column;
    gap: 9px;
}

/* The measured height is the real card's border box, bars and all. */
.spaces-panel .space-gutter-skeleton__card--sized {
    box-sizing: border-box;
    overflow: hidden;
}

/* The context column's profile card: a photo with centred lines under it,
   the shape `space-context__card--center` renders. */
.spaces-panel .space-gutter-skeleton__card--profile {
    align-items: center;
}

.spaces-panel .space-gutter-skeleton__avatar {
    width: 56px;
    height: 56px;
    flex-shrink: 0;
    border-radius: 50%;
    margin-bottom: 4px;
    background: var(--sp-hover);
    animation: space-pulse 1.4s ease-in-out infinite;
}

.spaces-panel .space-gutter-skeleton__bar--name { width: 46%; height: 12px; }
.spaces-panel .space-gutter-skeleton__bar--sub  { width: 34%; }

.spaces-panel .space-gutter-skeleton__bar {
    height: 10px;
    border-radius: 5px;
    background: var(--sp-hover);
    animation: space-pulse 1.4s ease-in-out infinite;
}

/* The heading bar is short and sits slightly apart, so the shape reads as
   "a card with a title and rows" rather than an undifferentiated stack. */
.spaces-panel .space-gutter-skeleton__bar--heading {
    width: 52%;
    height: 9px;
    margin-bottom: 3px;
    opacity: 0.75;
}

/* Ragged row widths. Rows of identical length read as a progress bar that has
   stopped; uneven ones read as text. */
.spaces-panel .space-gutter-skeleton__bar--row0 { width: 92%; }
.spaces-panel .space-gutter-skeleton__bar--row1 { width: 68%; }
.spaces-panel .space-gutter-skeleton__bar--row2 { width: 81%; }

@media (prefers-reduced-motion: reduce) {
    .spaces-panel .space-gutter-skeleton__bar,
    .spaces-panel .space-gutter-skeleton__avatar,
    .spaces-panel .space-skeleton__line,
    .spaces-panel .space-skeleton__avatar,
    .spaces-panel .space-intro--skeleton { animation: none; }
}

/* The intro band's placeholder, at the height the band had last time. A plain
   pulsing surface: the band's colours belong to its preset, which is not known
   until page one lands. */
.spaces-panel .space-intro.space-intro--skeleton {
    box-sizing: border-box;
    border-radius: var(--sp-radius);
    background: var(--tm-real-white);
    border: 1px solid var(--sp-card-border);
    animation: space-pulse 1.4s ease-in-out infinite;
}

/* =============================================================================
   Team Spaces — the wide-screen side rail
   -----------------------------------------------------------------------------
   THE RAIL IS INSIDE THE FEED'S SCROLLER, not beside it. Putting it beside the
   scroller (the first attempt) shrank the scroll container to the middle
   column: the wheel did nothing over the right third of the pane and the
   scrollbar moved in from the edge. The feed's scroll area is the whole pane
   again, and the rail simply sticks within it.

   THE FEED DOES NOT MOVE WHEN THE RAIL APPEARS. The scroller becomes a
   SYMMETRIC three-column grid — gutter · feed · gutter — with the rail in the
   right gutter. A two-column layout (feed then rail) was the second attempt and
   it was wrong for a different reason: the 720px post list centres itself
   inside whatever column it is given, so taking 320px off the right shifted
   every post 160px left of where it had always been. With equal gutters the
   list lands at `(W - 720) / 2` either way — exactly where it was with no rail
   at all. The left gutter stays empty on purpose; that is its whole job.

   THERE IS NO BREAKPOINT IN THIS FILE. The rail appears when the SCROLLER — not
   the viewport — is wide enough, because the Teams sidebar collapses and a
   viewport query cannot see that: at 1400px the pane is ~1070px with the
   sidebar open and ~1390px with it shut, and only one of those fits a rail.
   `spaces-rail.js` measures the scroller and toggles
   `.space-feed__scroller--wide`, so the threshold lives in exactly one place.

   All selectors scoped to `.spaces-panel` (CLAUDE.md rule 2).
   ============================================================================= */

/* THE GRID ITSELF LIVES IN spaces-stage.css. It was here while the rail was the
   only tenant of the gutters; the context column now shares them, and a layout
   owned by one of two occupants is a layout the other cannot reason about.
   What remains below is the rail's own appearance inside its column. */

.spaces-panel .space-rail.space-rail--empty { display: none; }
.spaces-panel .space-rail[hidden] { display: none; }

/* Plain flow: the STICKY element is the rail itself now, and a second sticky
   nested inside a sticky box that is also its own scroll container pins to a
   container that is already pinned — it simply never moves. */
.spaces-panel .space-rail__inner {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* THE SAME SURFACE AS A POST CARD, deliberately: `--tm-real-white`, not
   `--sp-card-bg`. The latter resolves to `--tm-surface`, the tone the feed
   itself is painted in — which is exactly the reason post cards stopped using
   it (see spaces-post.css). A rail card in a different tone from the posts
   beside it reads as a different app pane rather than part of the same feed,
   and it is theme-aware in both directions (#fff light, #363f4a dark). */
.spaces-panel .space-rail__card {
    padding: 12px 14px 14px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--sp-radius);
    background: var(--tm-real-white);
}

.spaces-panel .space-rail__heading {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0 0 10px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--sp-text-muted);
}

.spaces-panel .space-rail__card--needs-you .space-rail__heading {
    color: var(--sp-danger);
}

.spaces-panel .space-rail__list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.spaces-panel .space-rail__row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: var(--tm-r-sm, 6px);
    background: transparent;
    color: var(--sp-text);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}

.spaces-panel .space-rail__row:hover { background: var(--sp-hover); }

/* Channel discovery is ONE row per channel: identity on the left, a single
   compact follow control on the right. The rail column is narrow, so the
   action is an icon button with an accessible name — spelling out "Follow"
   here either wrapped the row onto a second line or ate the channel name,
   which is the only part of the row that says what it is.
   Keeping the controls local prevents global button styles from falling back
   to browser defaults in the feed rail. */
.spaces-panel .space-rail__row--channel {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    padding: 0;
    cursor: default;
}

.spaces-panel .space-rail__row--channel:hover { background: transparent; }

.spaces-panel .space-rail__channel-open,
.spaces-panel .space-rail__channel-follow {
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r-sm, 6px);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}

.spaces-panel .space-rail__channel-open {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    /* min-width:0 is what actually lets the name ellipsis instead of forcing
       the row wider than the rail and pushing the follow button out of view. */
    flex: 1 1 auto;
    min-width: 0;
    padding: 7px 8px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    font-weight: 600;
}

.spaces-panel .space-rail__channel-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The follower count yields before the name does. `.space-rail__row-sub` is
   `flex: 0 0 auto` for the rows it was written for; here the name is the part
   that identifies the row, so the count is the one allowed to shrink. */
.spaces-panel .space-rail__row--channel .space-rail__row-sub {
    flex: 0 1 auto;
    min-width: 0;
}

.spaces-panel .space-rail__channel-open:hover,
.spaces-panel .space-rail__channel-follow:hover:not(:disabled) {
    border-color: var(--sp-accent);
    background: var(--sp-hover);
}

.spaces-panel .space-rail__channel-follow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* A 28px square keeps the icon target usable without stealing width the
       channel name needs. */
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    padding: 0;
    background: var(--sp-accent);
    border-color: var(--sp-accent);
    color: var(--tm-real-white);
    font-size: 12px;
}

.spaces-panel .space-rail__channel-follow:hover:not(:disabled) {
    color: var(--sp-accent);
}

/* Already following: a quiet confirmation that is still a live unfollow
   control, not a dead disabled badge. */
.spaces-panel .space-rail__channel-follow.is-following {
    background: transparent;
    border-color: var(--sp-card-border);
    color: var(--sp-accent);
}

.spaces-panel .space-rail__channel-follow:disabled {
    cursor: default;
    opacity: 0.6;
}

.spaces-panel .space-rail__more {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 10px;
    padding: 6px 9px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r-sm, 6px);
    background: transparent;
    color: var(--sp-accent);
    font: 600 12px/1 var(--tm-font-ui);
    cursor: pointer;
}

.spaces-panel .space-rail__more:hover {
    border-color: var(--sp-accent);
    background: var(--sp-hover);
}

.spaces-panel .space-rail__empty {
    margin: 0;
    color: var(--sp-text-muted);
    font-size: 12px;
    line-height: 1.45;
}

.spaces-panel .space-rail__row-main {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.spaces-panel .space-rail__row-sub {
    flex: 0 0 auto;
    max-width: 40%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11.5px;
    color: var(--sp-text-muted);
}

.spaces-panel .space-rail__row--urgent {
    color: var(--sp-danger);
    font-weight: 600;
}

.spaces-panel .space-rail__count {
    flex: 0 0 auto;
    min-width: 20px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--sp-accent);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.spaces-panel .space-rail__tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.spaces-panel .space-rail__tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border: 1px solid color-mix(in srgb, var(--sp-accent) 20%, transparent);
    border-radius: 999px;
    background: color-mix(in srgb, var(--sp-accent) 8%, transparent);
    color: var(--sp-accent);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}

.spaces-panel .space-rail__tag:hover {
    background: color-mix(in srgb, var(--sp-accent) 18%, transparent);
}

/* THE ACTIVE TAG IS FILLED, NOT MERELY TINTED. Without this the only record of
   which tag you clicked was your own memory of clicking it, and clearing the
   filter meant guessing. Solid accent + an × is the strongest "this is on, and
   this is how it goes off" a chip can carry. */
.spaces-panel .space-rail__tag.is-active {
    background: var(--sp-accent);
    border-color: var(--sp-accent);
    color: #fff;
}

.spaces-panel .space-rail__tag.is-active:hover {
    background: var(--sp-accent-hover);
}

.spaces-panel .space-rail__tag.is-active .space-rail__tag-count { opacity: 0.85; }

.spaces-panel .space-rail__tag-count {
    font-size: 11px;
    font-weight: 500;
    opacity: 0.7;
    font-variant-numeric: tabular-nums;
}

/* One tag at a time, so the block says so in words rather than leaving the
   reader to infer it from a chip that turned off when another turned on. */
.spaces-panel .space-rail__note {
    margin: 9px 0 0;
    font-size: 11.5px;
    color: var(--sp-text-muted);
}

.spaces-panel .space-rail__note button {
    padding: 0;
    border: 0;
    background: none;
    color: var(--sp-accent);
    font: inherit;
    font-size: inherit;
    font-weight: 650;
    cursor: pointer;
    text-decoration: underline;
}

/* ── "Your activity" ──────────────────────────────────────────────────────
   A single number, big enough to read without looking for it, and a button
   because it filters the feed. The count and the filter are the same fact
   stated twice — you wrote 42 posts, here they are — so they are one control
   rather than a number with a link beside it. */
.spaces-panel .space-rail__stat {
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    padding: 8px 10px;
    border: 1px solid transparent;
    border-radius: var(--tm-r, 8px);
    background: var(--sp-hover);
    color: var(--sp-text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.spaces-panel .space-rail__stat:hover {
    border-color: color-mix(in srgb, var(--sp-accent) 35%, transparent);
}

.spaces-panel .space-rail__stat.is-active {
    background: var(--sp-accent);
    border-color: var(--sp-accent);
    color: #fff;
}

.spaces-panel .space-rail__stat.is-active:hover { background: var(--sp-accent-hover); }

.spaces-panel .space-rail__stat-value {
    font-size: 20px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.spaces-panel .space-rail__stat-label {
    flex: 1 1 auto;
    font-size: 12.5px;
}

/* ── "Popular this fortnight" ─────────────────────────────────────────────
   Two lines per row: what it is, then why it is here. The counts sit UNDER the
   title rather than beside it because a title truncated to make room for two
   badges stops being recognisable, which is the only job it has. */
.spaces-panel .space-rail__row--stacked {
    flex-direction: column;
    align-items: stretch;
    gap: 3px;
}

.spaces-panel .space-rail__metrics {
    display: flex;
    gap: 12px;
    font-size: 11.5px;
    color: var(--sp-text-muted);
    font-variant-numeric: tabular-nums;
}

.spaces-panel .space-rail__metrics i { margin-right: 3px; }

/* The emoji breakdown on a popular row. Each glyph carries its own count, so
   the group wraps rather than truncates — a row that hides the reaction people
   actually left is the thing this replaced. */
.spaces-panel .space-rail__emojis {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.spaces-panel .space-rail__emoji {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    line-height: 1;
}

/* Emoji render in the system's colour font, which ignores `color` and is
   metrically taller than the surrounding 11.5px text — sized down here so the
   glyph sits on the same baseline as its count. "Twemoji Mozilla" is
   self-hosted (frontend/assets/fonts/twemoji/) as a last resort for machines
   with no OS colour-emoji font at all — see that dir's twemoji-font.css. */
.spaces-panel .space-rail__emoji-glyph {
    font-size: 12.5px;
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
        "Twemoji Mozilla", sans-serif;
    line-height: 1;
}

.spaces-panel .space-rail__emoji--more { opacity: 0.75; }

/* A channel the reader has a role in has no follow control (it is always in
   their feed), so the row names the role instead of showing nothing. */
.spaces-panel .space-rail__channel-role {
    flex: 0 0 auto;
    align-self: center;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--tm-accent-subtle);
    color: var(--tm-text-2);
    font-size: 11px;
    font-weight: 600;
    cursor: help;
}

/* ==========================================================================
   Team Spaces — the context column (left gutter)
   --------------------------------------------------------------------------
   Placement lives in spaces-stage.css, which owns the grid both gutters share.
   This file styles only what is inside the column.

   Scoping (CLAUDE.md rule 2): everything is under `.spaces-panel .space-context`.

   Deliberately quieter than the rail opposite. The rail carries obligations and
   has to draw the eye; this side is reference material that should be
   available, not demanding.

   Created: 2026-08-15
   ========================================================================== */

.spaces-panel .space-context__inner {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding-top: 0;
}

.spaces-panel .space-context__card {
    background: var(--sp-card-bg, #f8f5f0);
    border: 0.5px solid var(--sp-card-border, #d8d3c8);
    border-radius: var(--sp-radius, 12px);
    padding: 12px 14px;
}
.spaces-panel .space-context__card--center { text-align: center; }

.spaces-panel .space-context__heading {
    margin: 0 0 10px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--sp-text-muted, #6f6e6d);
    display: flex;
    align-items: center;
    gap: 7px;
}

/* ── Identity ──────────────────────────────────────────────────────────── */

/* `paintAvatar` writes an INITIAL as this element's text and, once a photo
   loads, replaces it with an `<img class="avatar-image">` and adds
   `.avatar--has-image`. So the circle has to centre a glyph AND clip an image —
   it cannot assume either. An inner span for the initial does not survive that
   swap, which is why there is none. */
.spaces-panel .space-context__photo {
    position: relative;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    border: none;
    padding: 0;
    margin: 2px auto 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: visible;
    cursor: pointer;
    background: var(--sp-accent, #2d5f84);
    color: #ffffff;
    font-size: 20px;
    font-weight: 600;
    line-height: 1;
}
/* The photo itself fills the circle; the badge below must stay outside the clip,
   so the ROUNDING is on the image rather than `overflow: hidden` on the button. */
.spaces-panel .space-context__photo .avatar-image,
.spaces-panel .space-context__face .avatar-image {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}
.spaces-panel .space-context__photo.avatar--has-image {
    background: transparent;
}
/* The camera badge only announces that the photo is editable — it is not the
   hit target. The whole circle is, which is the same affordance the header
   avatar already has. */
.spaces-panel .space-context__photo-badge {
    position: absolute;
    right: -2px;
    bottom: -2px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--sp-card-bg, #f8f5f0);
    border: 0.5px solid var(--sp-card-border, #d8d3c8);
    color: var(--sp-text-muted, #6f6e6d);
    font-size: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.spaces-panel .space-context__name {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--sp-text, #1f1e1d);
    overflow-wrap: anywhere;
}
.spaces-panel .space-context__where {
    margin: 3px 0 0;
    font-size: 12px;
    line-height: 1.5;
    color: var(--sp-text-muted, #6f6e6d);
    overflow-wrap: anywhere;
}

.spaces-panel .space-context__stat {
    margin: 10px 0 0;
    padding: 9px 0 0;
    width: 100%;
    border: none;
    border-top: 0.5px solid var(--sp-card-border, #d8d3c8);
    background: none;
    cursor: pointer;
    display: block;
    border-radius: 0;
    color: inherit;
}
.spaces-panel .space-context__stat-value {
    display: block;
    font-size: 20px;
    font-weight: 600;
    color: var(--sp-text, #1f1e1d);
}
.spaces-panel .space-context__stat-label {
    display: block;
    font-size: 11.5px;
    color: var(--sp-text-muted, #6f6e6d);
}
.spaces-panel .space-context__stat.is-active .space-context__stat-value,
.spaces-panel .space-context__stat:hover .space-context__stat-value {
    color: var(--sp-accent, #2d5f84);
}

/* ── People ────────────────────────────────────────────────────────────── */

.spaces-panel .space-context__faces {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(35px, 1fr));
    gap: 5px;
    cursor: pointer;
}
.spaces-panel .space-context__face {
    aspect-ratio: 1;
    border-radius: 50%;
    background: var(--sp-hover, #f0ede6);
    border: 0.5px solid var(--sp-card-border, #d8d3c8);
    color: var(--sp-text-muted, #6f6e6d);
    font-size: 11px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}
.spaces-panel .space-context__face.avatar--has-image {
    background: transparent;
    border-color: transparent;
}
.spaces-panel .space-context__face--more {
    background: transparent;
    font-size: 10px;
}

/* ── Agenda ────────────────────────────────────────────────────────────── */

.spaces-panel .space-context__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.spaces-panel .space-context__event {
    font-size: 12px;
    line-height: 1.45;
    color: var(--sp-text-muted, #6f6e6d);
    display: flex;
    gap: 7px;
}
.spaces-panel .space-context__event-time {
    color: var(--sp-text, #1f1e1d);
    font-variant-numeric: tabular-nums;
    flex: 0 0 auto;
}
.spaces-panel .space-context__event-title { overflow-wrap: anywhere; }

/* WHAT HAS ALREADY HAPPENED RECEDES — the whole row, time included. Dimming
   only the title left a column of bright timestamps marching down the card,
   which is precisely the part a reader scans, so the finished half of the day
   went on competing with the half that still needs them.

   `opacity` rather than a paler colour: the row can also be a button with its
   own hover and focus paint, and two colour rules for one row is how the
   hovered state of a past event ends up looking active again. */
.spaces-panel .space-context__event.is-past { opacity: 0.5; }
.spaces-panel .space-context__event.is-past .space-context__event-time {
    font-weight: 400;
}

/* ── The row body ────────────────────────────────────────────────────────
   EVERY row gets the same box, openable or not. The openable ones are real
   <button>s — keyboard reachable, announced as controls — and the rest are
   <span>s wearing the identical padding. That padding is the whole point:
   while only some rows had it, a hoverable row sat a few pixels wider and
   further left than its neighbours, and the column's times stopped lining up
   into the vertical rule a scanner reads down. The affordance is the pointer
   and the hover, never a second geometry for half the list. */
.spaces-panel .space-context__event { padding: 0; }

.spaces-panel .space-context__event-open {
    display: flex;
    gap: 7px;
    align-items: baseline;
    width: 100%;
    margin: 0;
    padding: 2px 4px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    font: inherit;
    color: inherit;
    text-align: left;
    transition: background-color 0.12s ease;
}

.spaces-panel .space-context__event--openable .space-context__event-open {
    cursor: pointer;
}

/* Hover lifts the row back to full strength even when it is in the past: the
   reader is pointing at it, so it is the thing they are reading now. */
.spaces-panel .space-context__event--openable:hover { opacity: 1; }
.spaces-panel .space-context__event--openable .space-context__event-open:hover,
.spaces-panel .space-context__event-open:focus-visible {
    background: var(--sp-hover, rgba(0, 0, 0, 0.05));
}
.spaces-panel .space-context__event-open:focus-visible {
    outline: 2px solid var(--sp-accent, #2f6feb);
    outline-offset: 1px;
}
.spaces-panel .space-context__event-open[disabled] { cursor: progress; }

/* Manual "Today" reload — pushed to the far end of the heading row by
   `margin-left: auto` rather than a second flex container, since the
   heading is already `display: flex` (see `.space-context__heading` above). */
.spaces-panel .space-context__agenda-refresh {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--sp-text-muted, #6f6e6d);
    cursor: pointer;
    transition: background-color 0.12s ease, color 0.12s ease;
    opacity: 0.5;
}
.spaces-panel .space-context__agenda-refresh:hover,
.spaces-panel .space-context__agenda-refresh:focus-visible {
    background: var(--sp-hover, rgba(0, 0, 0, 0.05));
    color: var(--sp-text, #1f1e1d);
    opacity: 1;
}
.spaces-panel .space-context__agenda-refresh:focus-visible {
    outline: 2px solid var(--sp-accent, #2f6feb);
    outline-offset: 1px;
}
.spaces-panel .space-context__agenda-refresh[disabled] { cursor: progress; }
.spaces-panel .space-context__agenda-refresh.is-loading i { animation: sp-context-spin 0.8s linear infinite; }
@keyframes sp-context-spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   Team Spaces — the intro block
   --------------------------------------------------------------------------
   The short, opt-in card at the top of a feed. It SCROLLS AWAY with the posts
   rather than sitting in the header chrome: a permanent block costs its height
   on every screen forever, which is the whole reason the LinkedIn-style intro
   this replaces was rejected as a model.

   Scoping (CLAUDE.md rule 2): every selector is prefixed `.space-intro`.

   ── WHY THE PRESET TOKENS ARE DECLARED ON THE BLOCK ITSELF ────────────────
   `.spaces-panel` declares the `--sp-*` palette, and elements appended to
   <body> — the editor dialog and its live preview — inherit NOTHING from it.
   That has already caused one invisible control in this feature area (see the
   header of spaces-foundation.css). So the preset variables below hang off
   `[data-preset]` on the card itself, which resolves identically inside the
   feed, inside a body-appended dialog, and anywhere else a preview is mounted.

   ── WHY POLARITY IS A CONSTANT AND NOT A COMPUTATION ──────────────────────
   Text sits ON the background, so it needs a foreground that is readable
   against it. Deriving that from the colour at runtime means re-deciding
   contrast forever and getting it silently wrong on the awkward cases. Each
   preset instead ships a fixed foreground pair, and every one of the 20
   combinations below (10 presets x 2 themes) was measured against its own
   WORST stop — the lightest for white text, the darkest for dark text — and
   passes WCAG AA at 4.5:1 for BOTH the primary and the secondary line.

   Changing any colour here means re-running that check. Contrast that is only
   true at the moment it was written is contrast nobody can rely on.

   ── DARK THEME ───────────────────────────────────────────────────────────
   The theme is `body.dark-theme`, never `prefers-color-scheme` — this app does
   not use the media query. Dark presets lift slightly so they stay distinct
   from the page behind them; light presets deepen so they stop glaring. Both
   keep their polarity, which is what makes the fixed foreground safe.

   Created: 2026-08-15
   See: tasks/features/SPACES_INTRO_BLOCK_AND_LEFT_COLUMN.md
   ========================================================================== */

/* --------------------------------------------------------------------------
   Foreground pairs. Two only: one for backgrounds that need light text, one
   for backgrounds that need dark text.
   -------------------------------------------------------------------------- */
.space-intro {
    --si-fg-light: #ffffff;
    --si-fg-light-2: #f2f6f9;
    --si-fg-dark: #1f1e1d;
    --si-fg-dark-2: #3a3733;

    /* Height is set here rather than by an aspect ratio. The block spans the
       left column AND the feed, so it is ~1020px wide on a wide screen and
       ~720px on a narrow one — a fixed ratio would make it grow on exactly the
       monitors that already have the most to show, which is how the "too tall"
       problem comes back. */
    --si-band-h: 180px;
}

/* --------------------------------------------------------------------------
   The ten presets.
   Seven carry light text, three carry dark. The split is deliberate: a wall of
   pale cards reads as unfinished, and a wall of dark ones reads as heavy.
   -------------------------------------------------------------------------- */

/* slate — the default. The product's own accent, so an intro that nobody
   styles still looks like it belongs to this application. */
.space-intro[data-preset="slate"] {
    --si-from: #2d5f84; --si-to: #1b3d57;
    --si-fg: var(--si-fg-light); --si-fg-2: var(--si-fg-light-2);
}
.space-intro[data-preset="dusk"] {
    --si-from: #4a3f8f; --si-to: #241d52;
    --si-fg: var(--si-fg-light); --si-fg-2: var(--si-fg-light-2);
}
.space-intro[data-preset="ocean"] {
    --si-from: #0f6e70; --si-to: #073f4a;
    --si-fg: var(--si-fg-light); --si-fg-2: var(--si-fg-light-2);
}
.space-intro[data-preset="forest"] {
    --si-from: #2f6b3f; --si-to: #163a23;
    --si-fg: var(--si-fg-light); --si-fg-2: var(--si-fg-light-2);
}
.space-intro[data-preset="ember"] {
    --si-from: #a8442a; --si-to: #6b2415;
    --si-fg: var(--si-fg-light); --si-fg-2: var(--si-fg-light-2);
}
.space-intro[data-preset="plum"] {
    --si-from: #7d2f5c; --si-to: #471634;
    --si-fg: var(--si-fg-light); --si-fg-2: var(--si-fg-light-2);
}
.space-intro[data-preset="graphite"] {
    --si-from: #3a3a38; --si-to: #1c1c1b;
    --si-fg: var(--si-fg-light); --si-fg-2: var(--si-fg-light-2);
}
.space-intro[data-preset="sand"] {
    --si-from: #f0e2c8; --si-to: #dcc9a4;
    --si-fg: var(--si-fg-dark); --si-fg-2: var(--si-fg-dark-2);
}
.space-intro[data-preset="mint"] {
    --si-from: #d8f0e2; --si-to: #b0dcc6;
    --si-fg: var(--si-fg-dark); --si-fg-2: var(--si-fg-dark-2);
}
.space-intro[data-preset="clay"] {
    --si-from: #f3ddd2; --si-to: #e2bfae;
    --si-fg: var(--si-fg-dark); --si-fg-2: var(--si-fg-dark-2);
}

/* Dark theme: same polarity, adjusted stops. Only the two colour stops move —
   the foregrounds do not, which is what keeps the measured pairs valid. */
body.dark-theme .space-intro[data-preset="slate"]    { --si-from: #356f99; --si-to: #1f4763; }
body.dark-theme .space-intro[data-preset="dusk"]     { --si-from: #564aa3; --si-to: #2a2260; }
body.dark-theme .space-intro[data-preset="ocean"]    { --si-from: #0f7275; --si-to: #094a56; }
body.dark-theme .space-intro[data-preset="forest"]   { --si-from: #316d41; --si-to: #1a4429; }
body.dark-theme .space-intro[data-preset="ember"]    { --si-from: #ab4629; --si-to: #7a2a19; }
body.dark-theme .space-intro[data-preset="plum"]     { --si-from: #8f3669; --si-to: #521a3c; }
body.dark-theme .space-intro[data-preset="graphite"] { --si-from: #474745; --si-to: #232322; }
body.dark-theme .space-intro[data-preset="sand"]     { --si-from: #dccbab; --si-to: #c6b087; }
body.dark-theme .space-intro[data-preset="mint"]     { --si-from: #bcd9c9; --si-to: #9ac7b1; }
body.dark-theme .space-intro[data-preset="clay"]     { --si-from: #dfc7ba; --si-to: #cbaa9a; }

/* --------------------------------------------------------------------------
   Uploaded background images.

   AN ADDITION TO THE PRESETS, NOT A REPLACEMENT. A block with no image is
   painted by its preset exactly as before, and removing an image falls straight
   back to whichever preset the author last chose — which is why the preset is
   never cleared while an image is set.

   ── THE SCRIM IS NOT OPTIONAL ─────────────────────────────────────────────
   Everything above this point gets its contrast by CONSTRUCTION: ten known
   gradients, twenty measured combinations. A photograph offers no such
   guarantee — any font colour can be defeated by some region of some image.
   So a wash is ALWAYS painted under the text, and only its DIRECTION varies.

   That direction is `data-scrim`, computed once at transcode time from the
   luminance of the TEXT REGION of the finished crop (the left ~72% of the
   band), never from the whole image and never in the browser. An image that is
   half white and half black averages to "medium", and both font colours then
   fail — see `_scrim_polarity` in app/utils/avatars/transcode.py, which also
   explains why the statistic is a median rather than a mean.

   The foreground pairs below are the SAME two the presets use, so a photograph
   can never introduce a third text colour into this block.

   ── POSITION ──────────────────────────────────────────────────────────────
   The server already cropped to the band's ratio, so `cover` normally has
   nothing left to trim. `--si-focal` is applied anyway: on a narrow pane the
   box is proportionally taller than the crop, and honouring the author's focal
   point means the strip they chose is the strip that survives twice over.
   -------------------------------------------------------------------------- */
.space-intro[data-scrim="dark"] {
    --si-fg: var(--si-fg-light);
    --si-fg-2: var(--si-fg-light-2);
    /* Strongest where the text starts, gone by the right edge, so the image is
       given away only where it would cost legibility. */
    --si-scrim: linear-gradient(
        90deg,
        rgba(0, 0, 0, 0.74) 0%,
        rgba(0, 0, 0, 0.62) 45%,
        rgba(0, 0, 0, 0.24) 82%,
        rgba(0, 0, 0, 0.12) 100%);
}
.space-intro[data-scrim="light"] {
    --si-fg: var(--si-fg-dark);
    --si-fg-2: var(--si-fg-dark-2);
    --si-scrim: linear-gradient(
        90deg,
        rgba(255, 255, 255, 0.80) 0%,
        rgba(255, 255, 255, 0.68) 45%,
        rgba(255, 255, 255, 0.28) 82%,
        rgba(255, 255, 255, 0.14) 100%);
}

.space-intro[data-scrim] .space-intro__band {
    background-image: var(--si-scrim), var(--si-image);
    background-size: cover, cover;
    background-position: center, 50% var(--si-focal, 50%);
    background-repeat: no-repeat, no-repeat;
}

/* The narrow variant is half the pixels and all a small pane can show. */
@media (max-width: 900px) {
    .space-intro[data-scrim] .space-intro__band {
        background-image: var(--si-scrim), var(--si-image-narrow, var(--si-image));
    }
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */
/* NO `overflow: hidden` HERE, and that is load-bearing.
   ------------------------------------------------------------------------
   It was here to clip the gradient band to the rounded corners, and it
   collapsed the whole block to a 1px line in the merged feed. A grid item's
   automatic minimum size (`min-height: auto`) applies ONLY while `overflow` is
   `visible`; with `hidden` the minimum becomes 0, so the row sized to 0, the
   item stretched to 0, and the clip then hid a perfectly-rendered 42px band.
   Measured in dev: container content box 0px, band inside it 42.19px.

   The corners are rounded on the CHILDREN instead — first child gets the top
   pair, last child the bottom pair — which clips the gradient exactly as well
   and leaves the container's height free to follow its content. */
.space-intro {
    border: 0.5px solid var(--sp-card-border, #d8d3c8);
    border-radius: var(--sp-radius, 12px);
    margin: 0 0 var(--sp-gap, 16px);
    background: var(--sp-card-bg, #f8f5f0);
}
.space-intro > :first-child {
    border-top-left-radius: inherit;
    border-top-right-radius: inherit;
}
.space-intro > :last-child {
    border-bottom-left-radius: inherit;
    border-bottom-right-radius: inherit;
}

.space-intro .space-intro__band {
    min-height: var(--si-band-h);
    /* BORDER-BOX so `--si-band-h` is the height the band actually renders at.
       With the default content-box the padding was added on top of it, so the
       token said 180px and the screen showed 208 — and the stored image is cut
       to exactly this height, so the two must be the same number. */
    box-sizing: border-box;
    padding: 14px 18px;
    display: flex;
    align-items: center;
    gap: 12px;
    background: linear-gradient(135deg, var(--si-from), var(--si-to));
    color: var(--si-fg);
    justify-self: normal;
}

.space-intro__text { min-width: 0; flex: 1 1 auto; }

/* Text position (headline + one-liner only; the goal row is a separate surface).
   Defaults — left, vertically centred — are what the band did before the
   setting existed, so an unstamped block is unchanged. */
.space-intro[data-align-h="center"] .space-intro__text { text-align: center; }
.space-intro[data-align-h="right"] .space-intro__text { text-align: right; }
.space-intro[data-align-v="top"] .space-intro__text { align-self: flex-start; }
.space-intro[data-align-v="bottom"] .space-intro__text { align-self: flex-end; }

.space-intro__headline {
    margin: 0;
    /* A little larger now that no avatar is competing for the row. The block
       has exactly one job — say what this place is — so the sentence that does
       it should be the biggest thing in the band. */
    font-size: 25px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--si-fg);
    overflow-wrap: anywhere;
}

.space-intro__body {
    margin: 4px 0 0;
    font-size: 14px;
    line-height: 1.45;
    color: var(--si-fg-2);
    overflow-wrap: anywhere;
}

/* The edit control. On the band, so it inherits the preset's foreground rather
   than a panel token that would be invisible on half the presets. */
.space-intro__edit {
    flex: 0 0 auto;
    background: rgba(255, 255, 255, 0.18);
    border: none;
    border-radius: 999px;
    width: 30px;
    height: 30px;
    cursor: pointer;
    color: var(--si-fg);
    display: flex;
    align-items: center;
    justify-content: center;
    align-self: flex-start;
    transition: background 0.15s ease;
}
.space-intro__edit:hover { background: rgba(255, 255, 255, 0.32); }
.space-intro[data-preset="sand"] .space-intro__edit,
.space-intro[data-preset="mint"] .space-intro__edit,
.space-intro[data-preset="clay"] .space-intro__edit {
    background: rgba(0, 0, 0, 0.10);
}
.space-intro[data-preset="sand"] .space-intro__edit:hover,
.space-intro[data-preset="mint"] .space-intro__edit:hover,
.space-intro[data-preset="clay"] .space-intro__edit:hover {
    background: rgba(0, 0, 0, 0.20);
}

/* WITH AN IMAGE, THE CONTROL FOLLOWS THE SCRIM, NOT THE PRESET — and these
   rules sit AFTER the preset ones on purpose: the two selectors have identical
   specificity, so on a light-preset space wearing a dark image the last one
   written is the one that decides. Over a light wash a white pill disappears,
   which is how a control ends up invisible on exactly the bright images
   somebody chose because they liked them. */
.space-intro[data-scrim="light"] .space-intro__edit {
    background: rgba(0, 0, 0, 0.10);
}
.space-intro[data-scrim="light"] .space-intro__edit:hover {
    background: rgba(0, 0, 0, 0.20);
}
.space-intro[data-scrim="dark"] .space-intro__edit {
    background: rgba(255, 255, 255, 0.18);
}
.space-intro[data-scrim="dark"] .space-intro__edit:hover {
    background: rgba(255, 255, 255, 0.32);
}

/* --------------------------------------------------------------------------
   The goal row — BELOW the band, on a solid surface.
   That placement is the point: long-lived text that must stay readable never
   sits on the decorative background, so it inherits no contrast problem from
   whichever preset the author picked (or from an uploaded image later).
   -------------------------------------------------------------------------- */
.space-intro__goal {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 18px;
    border-top: 0.5px solid var(--sp-card-border, #d8d3c8);
    font-size: 13.5px;
    color: var(--sp-text, #1f1e1d);
}
.space-intro__goal-icon { color: var(--sp-accent, #2d5f84); flex: 0 0 auto; }
.space-intro__goal-text { 
    flex: 1 1 auto; 
    min-width: 0; 
    overflow-wrap: anywhere; 
    font-weight: 600;
}

.space-intro__goal-age {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--sp-text-muted, #6f6e6d);
    white-space: nowrap;
}

/* Curated image picker: thumbnails show the actual asset rather than an
   invented label, while the radio semantics keep the choice usable by a
   keyboard and screen reader. */
.space-intro-modal__library { margin: 12px 0; }
.space-intro-modal__library > strong { display: block; margin-bottom: 6px; font-size: 13px; }
.space-intro-modal__library-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.space-intro-modal__library-image {
    width: 78px; height: 40px; border: 2px solid transparent; border-radius: 6px;
    background-position: center; background-size: cover; cursor: pointer;
}
.space-intro-modal__library-image:hover,
.space-intro-modal__library-image:focus-visible,
.space-intro-modal__library-image.is-selected {
    border-color: var(--sp-accent, #2d5f84); outline: none;
}

/* The empty state an editor sees on a scope nobody has configured. Muted and
   quiet: it is an invitation, not a card pretending to have content. */
.space-intro--empty .space-intro__band {
    background: var(--sp-hover, #f0ede6);
    color: var(--sp-text-muted, #6f6e6d);
    min-height: 0;
    padding: 12px 18px;
    align-items: center;
    cursor: pointer;
}
.space-intro--empty .space-intro__headline {
    font-size: 14px;
    font-weight: 500;
    color: var(--sp-text-muted, #6f6e6d);
}

/* --------------------------------------------------------------------------
   Narrow panes. Below the rail's threshold there are no gutters at all, so the
   block simply becomes feed-width. Nothing else changes — it was never sticky,
   so it has nothing to give up.
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
    .space-intro { --si-band-h: 150px; }
    .space-intro__headline { font-size: 16px; }
}

/* ==========================================================================
   The editor dialog
   --------------------------------------------------------------------------
   Lives in THIS file, not spaces-modal.css, and that is deliberate: the dialog
   is appended to <body> and loads its own leaf stylesheets before mounting (see
   ensureStylesheet). Keeping its rules beside the block it edits means one
   awaited file covers both the dialog and the live preview inside it — and the
   preview reuses the `.space-intro` markup above, so it is painted by exactly
   the rules that will paint the feed.

   The tokens below fall back to literals because nothing here inherits from
   `.spaces-panel`.
   ========================================================================== */

.space-intro-modal {
    position: fixed;
    inset: 0;
    z-index: 10050;
    background: rgba(0, 0, 0, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
}

.space-intro-modal__dialog {
    background: var(--tm-bg, #ffffff);
    color: var(--tm-msg-text, #1f1e1d);
    border-radius: var(--tm-r-lg, 12px);
    width: min(600px, 100%);
    max-height: 90vh;
    overflow-y: auto;
    font-family: var(--tm-font-ui, system-ui, sans-serif);
}

.space-intro-modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 0.5px solid var(--tm-border, #d8d3c8);
}
.space-intro-modal__head h2 { margin: 0; font-size: 16px; font-weight: 600; }
.space-intro-modal__close {
    background: none;
    border: none;
    cursor: pointer;
    font-size: 16px;
    color: var(--tm-text-1, #6f6e6d);
    padding: 4px 8px;
}

/* The preview sits on the panel background rather than the dialog's, so the
   card reads as a card instead of merging into the sheet behind it. */
.space-intro-modal__preview {
    padding: 16px 18px;
    background: var(--tm-bg-alt, #f0ede6);
}
.space-intro-modal__preview .space-intro { margin: 0; }

.space-intro-modal__body { padding: 16px 18px; display: grid; gap: 14px; }

.space-intro-modal__field { display: block; }
.space-intro-modal__field > span {
    display: block;
    font-size: 12px;
    font-weight: 600;
    margin-bottom: 5px;
    color: var(--tm-text-1, #6f6e6d);
}
.space-intro-modal__field input[type="text"],
.space-intro-modal__field textarea {
    width: 100%;
    padding: 8px 10px;
    font: inherit;
    font-size: 14px;
    color: inherit;
    background: var(--tm-surface, #f8f5f0);
    border: 0.5px solid var(--tm-border, #d8d3c8);
    border-radius: var(--tm-r-sm, 8px);
    resize: vertical;
}

.space-intro-modal__presets {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(46px, 1fr));
    gap: 8px;
}
/* Each swatch IS a `.space-intro[data-preset]`, so it paints itself from the
   same gradient the block will use — a swatch list with its own colour table
   is a second palette that drifts from the first. */
.space-intro-modal__preset {
    height: 34px;
    border-radius: var(--tm-r-sm, 8px);
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
    background: linear-gradient(135deg, var(--si-from), var(--si-to));
    overflow: visible;
}
.space-intro-modal__preset[aria-checked="true"] {
    border-color: var(--tm-accent, #2d5f84);
}

/* Colours | Image. Both panes' styles live in THIS file, which is the one
   stylesheet the dialog awaits before it mounts — a tab styled from a sheet
   the dialog does not load is the timing bug `ensureStylesheet` exists to
   prevent, and it would show up as an unstyled row of buttons only on slow
   connections and partner domains. */
.space-intro-modal__tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 10px;
}
.space-intro-modal__tab {
    font: inherit;
    font-size: 13px;
    padding: 5px 12px;
    border-radius: 999px;
    border: 0.5px solid var(--tm-border, #d8d3c8);
    background: transparent;
    color: inherit;
    cursor: pointer;
}
.space-intro-modal__tab[aria-selected="true"] {
    background: var(--tm-accent, #2d5f84);
    border-color: var(--tm-accent, #2d5f84);
    color: #ffffff;
}

/* `[hidden]` IS ONLY A UA DEFAULT OF `display: none`, and any `display` in a
   stylesheet outranks it — so the swatch grid below stayed on screen with the
   Image tab selected, showing both panes at once. Found by opening the dialog;
   no assertion about the JS could have caught it, because the JS was right. */
.space-intro-modal__presets[hidden],
.space-intro-modal [data-pane][hidden] { display: none; }

.space-intro-modal__image { display: grid; gap: 10px; }
.space-intro-modal__image-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.space-intro-modal__hint {
    margin: 0;
    font-size: 12px;
    line-height: 1.45;
    color: var(--tm-text-1, #6f6e6d);
}

.space-intro-modal__toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    font-size: 13.5px;
    cursor: pointer;
}

/* The app's on/off switch, restated under this dialog's own class: the dialog
   loads one stylesheet and must not depend on the Settings page's. */
.space-intro-modal .space-intro-modal__switch {
    position: relative;
    width: 44px;
    height: 25px;
    flex-shrink: 0;
}

.space-intro-modal .space-intro-modal__switch input {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
    z-index: 1;
}

.space-intro-modal .space-intro-modal__switch-slider {
    position: absolute;
    inset: 0;
    border-radius: 999px;
    background: var(--tm-border-strong, #b0ab9f);
    transition: background 0.18s ease;
}

.space-intro-modal .space-intro-modal__switch-slider::before {
    content: "";
    position: absolute;
    width: 19px;
    height: 19px;
    left: 3px;
    top: 3px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.16);
    transition: transform 0.18s ease;
}

.space-intro-modal .space-intro-modal__switch input:checked + .space-intro-modal__switch-slider {
    background: var(--tm-accent, #2c6b9a);
}

.space-intro-modal .space-intro-modal__switch input:checked + .space-intro-modal__switch-slider::before {
    transform: translateX(19px);
}

.space-intro-modal .space-intro-modal__switch input:focus-visible + .space-intro-modal__switch-slider {
    outline: 2px solid var(--tm-accent, #2c6b9a);
    outline-offset: 2px;
}

.space-intro-modal__foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 14px 18px;
    border-top: 0.5px solid var(--tm-border, #d8d3c8);
}

body.dark-theme .space-intro-modal__dialog {
    background: var(--tm-bg, #1f1e1d);
    color: var(--tm-msg-text, #f0ede6);
}

/* The dialog's own buttons. Deliberately NOT the shared `teams-btn`: this
   dialog awaits exactly one stylesheet before it mounts, and a class borrowed
   from a file it does not load is the timing dependency that awaiting was
   meant to remove. */
.space-intro-modal__btn {
    font: inherit;
    font-size: 13.5px;
    padding: 8px 16px;
    border-radius: var(--tm-r-sm, 8px);
    border: 0.5px solid var(--tm-border, #d8d3c8);
    background: transparent;
    color: inherit;
    cursor: pointer;
}
.space-intro-modal__btn:hover { background: var(--tm-bg-alt, #f0ede6); }
.space-intro-modal__btn--primary {
    background: var(--tm-accent, #2d5f84);
    border-color: var(--tm-accent, #2d5f84);
    color: #ffffff;
}
.space-intro-modal__btn--primary:hover { background: var(--tm-accent-hover, #245070); }
.space-intro-modal__btn[disabled] { opacity: 0.6; cursor: default; }

/* THE PENCIL SHOWS ON HOVER. A permanent edit button on the banner competed
   with the headline for attention on every visit, for an action taken once in
   a while. It appears while the pointer is over the banner, and stays reachable
   by keyboard (focus reveals it). Touch screens have no hover, so there it is
   always shown — hiding it would make the banner uneditable on a phone. */
@media (hover: hover) {
    .space-intro .space-intro__band .space-intro__edit {
        opacity: 0;
        transition: opacity 0.15s ease, background 0.15s ease;
    }
    .space-intro .space-intro__band:hover .space-intro__edit,
    .space-intro .space-intro__band:focus-within .space-intro__edit {
        opacity: 1;
    }
}

/* Editor: the 3x3 text-position picker. */
.space-intro-modal__align {
    display: grid;
    grid-template-columns: repeat(3, 34px);
    gap: 6px;
}
.space-intro-modal__align-cell {
    width: 34px;
    height: 26px;
    padding: 0;
    border: 1px solid var(--sp-card-border, #d8d3c8);
    border-radius: 6px;
    background: var(--sp-card-bg, #f8f5f0);
    cursor: pointer;
    display: flex;
    /* The dot sits where the text will, so the grid reads as a tiny map of the band. */
    align-items: var(--cell-v, center);
    justify-content: var(--cell-h, center);
    box-sizing: border-box;
}
.space-intro-modal__align-cell::after {
    content: "";
    width: 12px;
    height: 4px;
    margin: 4px;
    border-radius: 2px;
    background: var(--sp-text-muted, #6f6e6d);
}
.space-intro-modal__align-cell[aria-checked="true"] {
    border-color: var(--sp-accent, #2d5f84);
}
.space-intro-modal__align-cell[aria-checked="true"]::after {
    background: var(--sp-accent, #2d5f84);
}
.space-intro-modal__align-cell:focus-visible {
    outline: 2px solid var(--sp-accent, #2d5f84);
    outline-offset: 1px;
}

/* My Feed widgets: the board's wrappers and Customize mode (after the cards). */
/* ==========================================================================
   My Feed widgets — the board's wrappers and Customize My Feed mode
   --------------------------------------------------------------------------
   js/spaces-widgets/board.js wraps every side-column card in `.mf-widget`;
   js/spaces-widgets/customize.js adds the edit chrome, the "Add widget" slots,
   the bar and the gallery. The cards themselves keep their own styles
   (spaces-rail.css, spaces-context.css) — a widget is the same card it was.

   Scoping (CLAUDE.md rule 2): everything is under `.spaces-panel`.
   Task: tasks/features/FEATURES_MY_FEED_WIDGETS.md
   ========================================================================== */

/* A widget wraps one card (or, for "Needs you", a stack of them) without
   changing its spacing: the stack inside keeps the column's own 12px gap. */
.spaces-panel .mf-widget { min-width: 0; }
.spaces-panel .mf-widget__body {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

/* Height presets are MAX heights: shorter content stays short, longer content
   scrolls inside its own widget instead of pushing the column. */
.spaces-panel .mf-widget--h-s .mf-widget__body,
.spaces-panel .mf-widget--h-m .mf-widget__body,
.spaces-panel .mf-widget--h-l .mf-widget__body {
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: var(--sp-radius, 12px);
}
.spaces-panel .mf-widget--h-s .mf-widget__body { max-height: 200px; }
.spaces-panel .mf-widget--h-m .mf-widget__body { max-height: 360px; }
.spaces-panel .mf-widget--h-l .mf-widget__body { max-height: 520px; }

/* ── The header button ──────────────────────────────────────────────────── */
.spaces-panel .teams-conversation__customize {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: 1px solid var(--tm-border, var(--sp-card-border));
    border-radius: 8px;
    background: var(--sp-card-bg);
    color: var(--tm-text-2);
    cursor: pointer;
    font-size: 14px;
}
.spaces-panel .teams-conversation__customize[hidden] { display: none; }
.spaces-panel .teams-conversation__customize:hover,
.spaces-panel .teams-conversation__customize.is-active {
    background: var(--sp-accent);
    border-color: var(--sp-accent);
    color: #fff;
}

/* ── Customize mode ─────────────────────────────────────────────────────── */

/* The posts step back and stop taking clicks: this mode edits the page around
   them, and a stray click on a card must not open a post mid-edit. */
.spaces-panel .mf-customizing .space-feed__list,
.spaces-panel .mf-customizing .space-feed__loading {
    opacity: 0.35;
    pointer-events: none;
    filter: grayscale(0.4);
    transition: opacity 0.15s ease;
}

.spaces-panel .mf-board--editing .mf-widget {
    position: relative;
    border: 1.5px dashed var(--sp-accent);
    border-radius: calc(var(--sp-radius, 12px) + 4px);
    padding: 4px;
    background: color-mix(in srgb, var(--sp-accent) 5%, transparent);
}
.spaces-panel .mf-board--editing .mf-widget[draggable="true"] { cursor: grab; }
.spaces-panel .mf-board--editing .mf-widget.is-dragging {
    opacity: 0.55;
    border-style: solid;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
}
.spaces-panel .mf-zone.is-drop-target {
    outline: 2px solid var(--sp-accent);
    outline-offset: 4px;
    border-radius: var(--sp-radius, 12px);
}
/* Inert content while editing: links and buttons inside a card would navigate
   away from an unsaved layout. The chrome sits above and stays live. */
.spaces-panel .mf-board--editing .mf-widget__body { pointer-events: none; }
.spaces-panel .mf-widget__placeholder { opacity: 0.85; }

.spaces-panel .mf-widget__chrome {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 2px 2px 6px 4px;
    font-size: 12px;
    color: var(--sp-text-muted);
}
.spaces-panel .mf-widget__chrome--fixed { padding-bottom: 6px; }
.spaces-panel .mf-widget__title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
    color: var(--sp-text);
}
.spaces-panel .mf-widget__chrome--fixed .mf-widget__title { font-weight: 500; color: var(--sp-text-muted); }
.spaces-panel .mf-widget__chrome--fixed .mf-widget__title i { margin-right: 6px; opacity: 0.6; }
/* Spacer that keeps the controls right-aligned; the name is for screen readers. */
.spaces-panel .mf-widget__title--sr {
    color: transparent;
    font-size: 0;
}
.spaces-panel .mf-widget__handle { cursor: grab; padding: 0 4px; opacity: 0.7; }
.spaces-panel .mf-widget__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    min-width: 24px;
    height: 24px;
    padding: 0 4px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--sp-text-muted);
    cursor: pointer;
    font: inherit;
}
.spaces-panel .mf-widget__btn:hover,
.spaces-panel .mf-widget__btn:focus-visible {
    border-color: var(--sp-card-border);
    background: var(--tm-real-white);
    color: var(--sp-text);
}
.spaces-panel .mf-widget__btn[data-mf-action="remove"]:hover,
.spaces-panel .mf-widget__btn[data-mf-intro-remove]:hover { color: var(--tm-danger, #b42318); }
.spaces-panel .mf-widget__btn--text { padding: 0 8px; }
.spaces-panel .mf-widget__height {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.spaces-panel .mf-widget__height select {
    height: 24px;
    max-width: 82px;
    border: 1px solid var(--sp-card-border);
    border-radius: 6px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    font: inherit;
    font-size: 11px;
}

.spaces-panel .mf-add-slot {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    min-height: 64px;
    border: 1.5px dashed var(--sp-card-border);
    border-radius: var(--sp-radius, 12px);
    background: transparent;
    color: var(--sp-text-muted);
    cursor: pointer;
    font: inherit;
    font-weight: 600;
}
.spaces-panel .mf-add-slot:hover,
.spaces-panel .mf-add-slot:focus-visible {
    border-color: var(--sp-accent);
    color: var(--sp-accent);
    background: color-mix(in srgb, var(--sp-accent) 6%, transparent);
}

/* The top zone (the intro's place above the posts). */
.spaces-panel .space-intro.mf-top--editing {
    position: relative;
    outline: 1.5px dashed var(--sp-accent);
    outline-offset: 3px;
}
.spaces-panel .mf-top-chrome {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 6px 4px 10px;
    border: 1px solid var(--sp-card-border);
    border-radius: 10px;
    background: var(--tm-real-white);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
    font-size: 12px;
}
.spaces-panel .mf-top-chrome .mf-widget__title { flex: 0 1 auto; margin-right: 6px; }

/* The bar: floats at the BOTTOM of the feed pane while the mode is on — at
   the top it sat over the intro's own Edit / Remove controls. */
.spaces-panel .mf-customize-bar {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 8px 8px 16px;
    border: 1px solid var(--sp-card-border);
    border-radius: 999px;
    background: var(--tm-real-white);
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.16);
    white-space: nowrap;
}
.spaces-panel .mf-customize-bar__title {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-right: 6px;
    font-weight: 600;
    color: var(--sp-text);
}
.spaces-panel .mf-customize-bar__title i { color: var(--sp-accent); }
.spaces-panel .mf-customize-bar__btn {
    height: 32px;
    padding: 0 14px;
    border: 1px solid var(--sp-card-border);
    border-radius: 999px;
    background: transparent;
    color: var(--sp-text);
    cursor: pointer;
    font: inherit;
    font-size: 13px;
}
.spaces-panel .mf-customize-bar__btn:hover:not(:disabled) { border-color: var(--sp-accent); color: var(--sp-accent); }
.spaces-panel .mf-customize-bar__btn--primary {
    border-color: var(--sp-accent);
    background: var(--sp-accent);
    color: #fff;
}
.spaces-panel .mf-customize-bar__btn--primary:hover:not(:disabled) { background: var(--sp-accent-hover); color: #fff; }
.spaces-panel .mf-customize-bar__btn:disabled { opacity: 0.6; cursor: progress; }

/* ── The gallery ────────────────────────────────────────────────────────── */
.spaces-panel .mf-gallery {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(15, 18, 24, 0.45);
}
.spaces-panel .mf-gallery__dialog {
    width: min(520px, 100%);
    max-height: min(640px, 90vh);
    display: flex;
    flex-direction: column;
    border-radius: 14px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
    overflow: hidden;
}
.spaces-panel .mf-gallery__head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 16px 18px;
    border-bottom: 1px solid var(--sp-card-border);
}
.spaces-panel .mf-gallery__head h2 { margin: 0; font-size: 16px; }
.spaces-panel .mf-gallery__zone {
    flex: 1 1 auto;
    font-size: 12px;
    color: var(--sp-text-muted);
}
.spaces-panel .mf-gallery__close {
    width: 32px;
    height: 32px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--sp-text-muted);
    cursor: pointer;
}
.spaces-panel .mf-gallery__close:hover { background: var(--sp-card-bg); }
.spaces-panel .mf-gallery__list {
    margin: 0;
    padding: 8px;
    list-style: none;
    overflow-y: auto;
}
.spaces-panel .mf-gallery__item {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 12px;
    border: 1px solid transparent;
    border-radius: 10px;
    background: transparent;
    color: inherit;
    text-align: left;
    cursor: pointer;
    font: inherit;
}
.spaces-panel .mf-gallery__item:hover:not(:disabled),
.spaces-panel .mf-gallery__item:focus-visible {
    border-color: var(--sp-accent);
    background: color-mix(in srgb, var(--sp-accent) 5%, transparent);
}
.spaces-panel .mf-gallery__item:disabled { cursor: default; opacity: 0.6; }
.spaces-panel .mf-gallery__icon {
    width: 36px;
    height: 36px;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    border-radius: 10px;
    background: var(--sp-card-bg);
    color: var(--sp-accent);
}
.spaces-panel .mf-gallery__text {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.spaces-panel .mf-gallery__text span { font-size: 12px; color: var(--sp-text-muted); }
.spaces-panel .mf-gallery__state { font-size: 12px; color: var(--sp-text-muted); white-space: nowrap; }
.spaces-panel .mf-gallery__none { padding: 16px; color: var(--sp-text-muted); }

/* ── The row of compact widgets above the posts (top zone) ───────────────── */
.spaces-panel .space-top-widgets[hidden] { display: none; }
.spaces-panel .space-top-widgets { margin: 0 0 var(--sp-gap, 16px); min-width: 0; }
.spaces-panel .mf-zone--strip {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
}
/* Narrow panes and phones: one row, scrolled sideways. */
.spaces-panel .space-feed__scroller:not(.space-feed__scroller--wide) .mf-zone--strip {
    display: flex;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    padding-bottom: 4px;
}
.spaces-panel .space-feed__scroller:not(.space-feed__scroller--wide) .mf-zone--strip > * {
    flex: 0 0 min(280px, 82%);
    scroll-snap-align: start;
}
.spaces-panel .mf-zone--strip .mf-add-slot { min-height: 96px; }
/* Every widget in the row is as tall as the tallest: the grid stretches the
   widget, and its body and card fill it. */
.spaces-panel .mf-zone--strip > .mf-widget { display: flex; flex-direction: column; }
.spaces-panel .mf-zone--strip > .mf-widget > .mf-widget__body { flex: 1 1 auto; }
.spaces-panel .mf-zone--strip > .mf-widget > .mf-widget__body > .mf-tile { flex: 1 1 auto; height: auto; }
.spaces-panel .mf-tile {
    display: flex;
    flex-direction: column;
    gap: 8px;
    height: 100%;
    min-height: 96px;
    box-sizing: border-box;
    padding: 12px 14px;
    /* The row starts above the left column, so its tiles wear that column's tone. */
    border: 0.5px solid var(--sp-card-border, #d8d3c8);
    border-radius: var(--sp-radius, 12px);
    background: var(--sp-card-bg, #f8f5f0);
    color: var(--sp-text);
    cursor: pointer;
}
.spaces-panel .mf-tile:hover,
.spaces-panel .mf-tile:focus-visible { border-color: var(--sp-accent); outline: none; }
.spaces-panel .mf-tile__head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--sp-text-muted);
}
/* The title takes the free space; the hover controls stay at the far right
   (a bare `> span` also stretched the controls span, centring the pencil). */
.spaces-panel .mf-tile__head > span:not(.mf-brief__controls) { flex: 1 1 auto; min-width: 0; }
.spaces-panel .mf-tile__stats { display: flex; gap: 16px; font-size: 13px; color: var(--sp-text-muted); }
.spaces-panel .mf-tile__stat strong { font-size: 20px; color: var(--sp-text); margin-right: 2px; }
.spaces-panel .mf-tile__stat.is-overdue strong { color: var(--tm-danger, #b42318); }
.spaces-panel .mf-tile__line {
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
}

/* ── Action items widget ───────────────────────────────────────────────── */
.spaces-panel .mf-ai .space-rail__heading { position: relative; }
.spaces-panel .mf-ai__expand {
    margin-left: auto;
    opacity: 0;
    transition: opacity 0.12s ease;
    width: 24px;
    height: 24px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--sp-text-muted);
    cursor: pointer;
}
.spaces-panel .mf-ai__expand:hover { background: var(--sp-card-bg); color: var(--sp-accent); }
.spaces-panel .mf-ai__section {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 8px 0 4px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--sp-text-muted);
}
.spaces-panel .mf-ai__section.is-overdue,
.spaces-panel .mf-ai__row.is-overdue .mf-ai__due { color: var(--tm-danger, #b42318); }

/* "@N" on a chat row in "Needs you": a mention, not an unread count. */
.spaces-panel .space-rail__count--mention { font-weight: 700; }

/* One column (narrow pane): the row fills the width, like the intro above it. */
.spaces-panel .space-feed__scroller > .space-top-widgets { width: 100%; }

/* ── One tone per column (2026-11-07) ──────────────────────────────────────
   The left column has always been the beige `--sp-card-bg` surface and the
   right one the post-card white. A widget keeps the tone of the column it is
   PLACED in, not the column it came from — a rail card moved left otherwise
   reads as a foreign object (and vice versa). */
.spaces-panel .space-context.mf-board .space-rail__card {
    background: var(--sp-card-bg, #f8f5f0);
    border: 0.5px solid var(--sp-card-border, #d8d3c8);
}
.spaces-panel .space-rail.mf-board .space-context__card {
    background: var(--tm-real-white);
    border: 1px solid var(--sp-card-border);
}

/* The expand control is an affordance, not content: it appears on hover or
   keyboard focus of its widget — and always on touch screens, which cannot hover. */
.spaces-panel .mf-ai:hover .mf-ai__expand,
.spaces-panel .mf-ai:focus-within .mf-ai__expand,
.spaces-panel .mf-ai__expand:focus-visible { opacity: 1; }
@media (hover: none) {
    .spaces-panel .mf-ai__expand { opacity: 1; }
}

/* Action items rows: the title may take two lines and the due date / chat line
   is never cut to 40% of the row — the column has the room. */
.spaces-panel .mf-ai__row .space-rail__row-main {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    white-space: normal;
}
.spaces-panel .mf-ai__row .space-rail__row-sub {
    max-width: none;
    white-space: normal;
}

/* ── Customize mode over a custom feed background ──────────────────────────
   With "use my Teams background in Spaces" on, the dashed frames and their
   controls sit on a photo and nearly disappear. There — and only there, so the
   default background keeps the airy look — each editable frame, slot and the
   fixed-widget label get a frosted surface of the theme's own card colour. */
.spaces-panel:where(.teams-panel--has-chat-background.teams-panel--background-spaces *) .mf-board--editing .mf-widget,
.spaces-panel:where(.teams-panel--has-chat-background.teams-panel--background-spaces *) .mf-add-slot {
    background: color-mix(in srgb, var(--tm-real-white) 80%, transparent);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border-color: var(--sp-accent);
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
}
.spaces-panel:where(.teams-panel--has-chat-background.teams-panel--background-spaces *) .mf-widget__chrome,
.spaces-panel:where(.teams-panel--has-chat-background.teams-panel--background-spaces *) .mf-add-slot,
.spaces-panel:where(.teams-panel--has-chat-background.teams-panel--background-spaces *) .mf-widget__chrome--fixed .mf-widget__title {
    color: var(--sp-text);
}

/* ── Daily AI Brief widget (brief-widget.js) ─────────────────────────────── */
.spaces-panel .mf-brief__text {
    position: relative;
    max-height: 13.5em;            /* about nine lines, then a fade */
    overflow: hidden;
    font-size: 13px;
    line-height: 1.5;
    color: var(--sp-text);
    cursor: pointer;
}
.spaces-panel .mf-brief__text::after {
    content: '';
    position: absolute;
    inset: auto 0 0 0;
    height: 2.5em;
    background: linear-gradient(transparent, var(--mf-fade, var(--tm-real-white)));
    pointer-events: none;
}
.spaces-panel .space-context.mf-board .mf-brief__text,
.spaces-panel .mf-tile .mf-brief__text { --mf-fade: var(--sp-card-bg, #f8f5f0); }
.spaces-panel .mf-brief__text--compact { max-height: 4.5em; }
.spaces-panel .mf-brief__text p { margin: 0 0 6px; }
.spaces-panel .mf-brief__text ul,
.spaces-panel .mf-brief__text ol { margin: 0 0 6px; padding-left: 18px; }
.spaces-panel .mf-brief__meta { margin: 6px 0 0; font-size: 11.5px; color: var(--sp-text-muted); }
.spaces-panel .mf-brief__warn { margin: 6px 0 0; font-size: 12px; color: var(--tm-danger, #b42318); }
.spaces-panel .mf-brief__state { margin: 0; font-size: 13px; color: var(--sp-text-muted); }
.spaces-panel .mf-brief__controls { display: inline-flex; gap: 2px; margin-left: auto; }
.spaces-panel .mf-brief__controls .mf-ai__expand { margin-left: 0; }

/* Setup dialog (reuses the gallery overlay). */
.spaces-panel .mf-brief-modal__body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 16px 18px;
    overflow-y: auto;
}
.spaces-panel .mf-brief-modal__label { font-size: 12px; font-weight: 600; color: var(--sp-text-muted); }
.spaces-panel .mf-brief-modal__text,
.spaces-panel .mf-brief-modal__time {
    box-sizing: border-box;
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--sp-card-border);
    border-radius: 8px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    font: inherit;
    font-size: 13px;
    resize: vertical;
}
.spaces-panel .mf-brief-modal__time { width: 130px; }
.spaces-panel .mf-brief-modal__agent {
    width: 100%;
    height: 36px;
    padding: 0 10px;
    border: 1px solid var(--sp-card-border);
    border-radius: 8px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    font: inherit;
    font-size: 13px;
}
.spaces-panel .mf-brief-modal__hint--tight { margin: -2px 0 4px; }
.spaces-panel .mf-brief-modal__time-row { display: flex; align-items: center; gap: 10px; }
.spaces-panel .mf-brief-modal__tz { font-size: 12px; color: var(--sp-text-muted); }
.spaces-panel .mf-brief-modal__days { display: flex; flex-wrap: wrap; gap: 6px; }
.spaces-panel .mf-brief-modal__day {
    min-width: 46px;
    height: 32px;
    border: 1px solid var(--sp-card-border);
    border-radius: 999px;
    background: transparent;
    color: var(--sp-text);
    cursor: pointer;
    font: inherit;
    font-size: 12px;
}
.spaces-panel .mf-brief-modal__day.is-on { background: var(--sp-accent); border-color: var(--sp-accent); color: #fff; }
.spaces-panel .mf-brief-modal__hint { margin: 4px 0 0; font-size: 12px; color: var(--sp-text-muted); }
.spaces-panel .mf-brief-modal__foot {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 18px;
    border-top: 1px solid var(--sp-card-border);
}
.spaces-panel .mf-brief-modal__spacer { flex: 1 1 auto; }
.spaces-panel .mf-brief-modal__delete { color: var(--tm-danger, #b42318); }

/* The drawer: the full brief and its recent runs. Below the Teams thread
   panels (1000) and the artifact drawer (999), like every side panel here. */
/* Same floating geometry as the Action list drawer (teams-reminders.css), so the
   two read as one family and both clear the app header. */
.spaces-panel .mf-drawer {
    position: fixed;
    top: 80px;
    right: 10px;
    bottom: 10px;
    z-index: 990;
    display: flex;
    flex-direction: column;
    width: min(520px, calc(100vw - 22px));
    overflow: hidden;
    border: 1px solid var(--sp-card-border);
    border-radius: 14px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
}
@media (max-width: 768px) {
    .spaces-panel .mf-drawer { inset: 0; width: 100vw; border-radius: 0; }
}
.spaces-panel .mf-drawer__head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 16px 18px;
    border-bottom: 1px solid var(--sp-card-border);
}
.spaces-panel .mf-drawer__head i { color: var(--sp-accent); }
.spaces-panel .mf-drawer__head h2 { flex: 1 1 auto; margin: 0; font-size: 16px; }
.spaces-panel .mf-drawer__actions { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 18px; }
.spaces-panel .mf-drawer__link { display: inline-flex; align-items: center; text-decoration: none; }
.spaces-panel .mf-drawer__body { flex: 1 1 auto; overflow-y: auto; padding: 0 18px 24px; }
.spaces-panel .mf-brief__text--full { max-height: none; overflow: visible; cursor: auto; font-size: 14px; }
.spaces-panel .mf-brief__text--full::after { display: none; }
.spaces-panel .mf-drawer__subhead {
    margin: 24px 0 8px;
    font-size: 12px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--sp-text-muted);
}
.spaces-panel .mf-drawer__runs { margin: 0; padding: 0; list-style: none; }
.spaces-panel .mf-drawer__run {
    display: flex;
    justify-content: space-between;
    width: 100%;
    padding: 8px 10px;
    border: 1px solid transparent;
    border-radius: 8px;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font: inherit;
    font-size: 13px;
}
.spaces-panel .mf-drawer__run:hover:not(:disabled),
.spaces-panel .mf-drawer__run.is-active { border-color: var(--sp-card-border); background: var(--sp-card-bg); }
.spaces-panel .mf-drawer__run:disabled { opacity: 0.6; cursor: default; }
.spaces-panel .mf-drawer__run-status { color: var(--sp-text-muted); }
.spaces-panel .mf-drawer__run-status.is-failed { color: var(--tm-danger, #b42318); }

/* ── Looker tiles widget (looker-tiles-widget.js) ──────────────────────────
   Each link is one tile; numbers and comparison text arrive ready-made from
   the server. Colours are theme tokens only — a comparison is good / bad /
   neutral, never Looker's own hex. The widget wears its column's tone; the
   tiles inside it are a faint step off that tone so they read as tiles. */
.spaces-panel .mf-lk__grid { display: flex; flex-direction: column; gap: 8px; }
/* Above the posts an instance's tiles sit side by side and wrap in a narrow slot. */
.spaces-panel .mf-lk__grid--row { flex-flow: row wrap; flex: 1 1 auto; }
.spaces-panel .mf-lk__grid--row { align-items: stretch; }
.spaces-panel .mf-lk__grid--row > .mf-lk__tile { flex: 1 1 96px; min-width: 0; }
/* Colour presets (store.COLORS): the reader picks a NAME, the theme picks the
   shade — a light-theme ink and a dark-theme ink, both readable on the card. */
.spaces-panel {
    --mf-lk-green: #15803d; --mf-lk-teal: #0f766e; --mf-lk-blue: #1d4ed8; --mf-lk-purple: #7e22ce;
    --mf-lk-pink: #be185d; --mf-lk-red: #b91c1c; --mf-lk-orange: #c2410c; --mf-lk-yellow: #a16207;
    --mf-lk-olive: #4d7c0f; --mf-lk-gray: #475569;
}
body.dark-theme .spaces-panel {
    --mf-lk-green: #4ade80; --mf-lk-teal: #2dd4bf; --mf-lk-blue: #60a5fa; --mf-lk-purple: #c084fc;
    --mf-lk-pink: #f472b6; --mf-lk-red: #f87171; --mf-lk-orange: #fb923c; --mf-lk-yellow: #facc15;
    --mf-lk-olive: #a3e635; --mf-lk-gray: #94a3b8;
}
.spaces-panel .mf-lk--c-green { --mf-lk-accent: var(--mf-lk-green); }
.spaces-panel .mf-lk--c-teal { --mf-lk-accent: var(--mf-lk-teal); }
.spaces-panel .mf-lk--c-blue { --mf-lk-accent: var(--mf-lk-blue); }
.spaces-panel .mf-lk--c-purple { --mf-lk-accent: var(--mf-lk-purple); }
.spaces-panel .mf-lk--c-pink { --mf-lk-accent: var(--mf-lk-pink); }
.spaces-panel .mf-lk--c-red { --mf-lk-accent: var(--mf-lk-red); }
.spaces-panel .mf-lk--c-orange { --mf-lk-accent: var(--mf-lk-orange); }
.spaces-panel .mf-lk--c-yellow { --mf-lk-accent: var(--mf-lk-yellow); }
.spaces-panel .mf-lk--c-olive { --mf-lk-accent: var(--mf-lk-olive); }
.spaces-panel .mf-lk--c-gray { --mf-lk-accent: var(--mf-lk-gray); }

.spaces-panel .mf-lk__tile {
    --mf-lk-tint: color-mix(in srgb, var(--mf-lk-accent, var(--sp-accent)) 16%, transparent);
    position: relative;
    display: flex;
    border: 0.5px solid var(--sp-card-border, #d8d3c8);
    border-radius: 10px;
    background: color-mix(in srgb, var(--sp-text) 3%, transparent);
    color: var(--sp-text);
}
/* A coloured tile without a progress comparison is tinted all over. */
.spaces-panel .mf-lk__tile[class*="mf-lk--c-"] { background: color-mix(in srgb, var(--mf-lk-accent) 8%, transparent); }
/* Progress comparisons fill the background from the left, as Looker does:
   100% and over is a full tile, 67% two thirds. */
.spaces-panel .mf-lk__tile.has-fill {
    background:
        linear-gradient(to right, var(--mf-lk-tint) var(--mf-lk-fill), transparent var(--mf-lk-fill)),
        color-mix(in srgb, var(--sp-text) 3%, transparent);
}
.spaces-panel .mf-lk__tile:hover,
.spaces-panel .mf-lk__tile:focus-within { border-color: var(--mf-lk-accent, var(--sp-accent)); }
.spaces-panel .mf-lk__link {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
    padding: 10px 12px;
    border-radius: inherit;
    color: inherit;
    text-decoration: none;
    min-height: 55px;
}
.spaces-panel .mf-lk__link:focus-visible { outline: 2px solid var(--sp-accent); outline-offset: -2px; }
.spaces-panel .mf-lk__refresh {
    position: absolute;
    top: 6px;
    right: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 6px;
    background: var(--sp-card-bg);
    color: var(--sp-text-muted);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s ease;
}
.spaces-panel .mf-lk__tile:hover .mf-lk__refresh,
.spaces-panel .mf-lk__refresh:focus-visible { opacity: 1; }
.spaces-panel .mf-lk__refresh:hover { color: var(--sp-accent); }
@media (hover: none) { .spaces-panel .mf-lk__refresh { opacity: 1; } }
.spaces-panel .mf-lk__tile.is-refreshing .mf-lk__refresh { opacity: 1; }
.spaces-panel .mf-lk__tile.is-refreshing .mf-lk__refresh i { animation: fa-spin 1s linear infinite; }
.spaces-panel .mf-lk__value { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.spaces-panel .mf-lk__label {
    padding-right: 24px;            /* clear of the hover refresh button */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--sp-text-muted);
}
.spaces-panel .mf-lk__number {
    color: var(--mf-lk-accent, var(--sp-text));
    font-size: 24px;
    font-weight: 700;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}
.spaces-panel .mf-lk__grid--row .mf-lk__number { 
    font-size: 24px; 
}

.spaces-panel .mf-lk__cmp {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px;
    font-size: 12px;
    color: var(--sp-text-muted);
}
.spaces-panel .mf-lk__cmp.is-pos { color: var(--tm-success, #15803d); }
.spaces-panel .mf-lk__cmp.is-neg { color: var(--tm-danger, #b42318); }
.spaces-panel .mf-lk__cmp-label { color: var(--sp-text-muted); }
/* A coloured tile wears its colour on the title and the number (Looker's look). */
.spaces-panel [class*="mf-lk--c-"] .mf-lk__label { color: var(--mf-lk-accent); }
.spaces-panel .mf-lk__state { font-size: 12px; color: var(--sp-text-muted); }
.spaces-panel .mf-lk__state--error { color: var(--tm-danger, #b42318); }
.spaces-panel .mf-lk__stale {
    position: absolute;
    top: 12px;
    right: 34px;
    font-size: 11px;
    color: var(--sp-text-muted);
}
/* Above the posts the widget is a container of links, not one big button. */
.spaces-panel .mf-tile.mf-lk--strip { cursor: default; }
.spaces-panel .mf-tile.mf-lk--strip:hover { border-color: var(--sp-card-border, #d8d3c8); }
.spaces-panel .mf-lk-modal__input {
    box-sizing: border-box;
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--sp-card-border);
    border-radius: 8px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    font: inherit;
    font-size: 13px;
}
.spaces-panel .mf-lk-modal__input:focus-visible { outline: 2px solid var(--sp-accent); outline-offset: 1px; }
.spaces-panel .mf-lk-modal__status { min-height: 16px; margin: 0; font-size: 12px; color: var(--sp-text-muted); }
.spaces-panel .mf-lk-modal__status.is-ok { color: var(--tm-success, #15803d); }
.spaces-panel .mf-lk-modal__status.is-error { color: var(--tm-danger, #b42318); }
.spaces-panel .mf-lk-modal__options {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
    font-size: 12px;
    color: var(--sp-text-muted);
}
.spaces-panel .mf-lk-modal__check { display: inline-flex; align-items: center; gap: 6px; }
.spaces-panel .mf-lk-modal__swatches { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.spaces-panel .mf-lk-modal__swatch {
    width: 18px;
    height: 18px;
    padding: 0;
    border: 1px solid var(--sp-card-border);
    border-radius: 50%;
    background: var(--mf-lk-accent, transparent);
    cursor: pointer;
}
/* "No colour": an outlined circle with a slash. */
.spaces-panel .mf-lk-modal__swatch:not([class*="mf-lk--c-"]) {
    background: linear-gradient(135deg, transparent 45%, var(--sp-text-muted) 45% 55%, transparent 55%);
}
.spaces-panel .mf-lk-modal__swatch.is-on { outline: 2px solid var(--sp-accent); outline-offset: 2px; }

/* ── My notes widget + drawer (notes-widget.js, notes-drawer.js) ───────────── */
.spaces-panel .mf-notes__item {
    display: block;
    width: 100%;
    margin: 0 0 6px;
    padding: 8px 10px;
    border: 1px solid var(--sp-card-border);
    border-radius: 8px;
    background: color-mix(in srgb, var(--sp-accent) 4%, transparent);
    color: var(--sp-text);
    text-align: left;
    font: inherit;
    cursor: pointer;
}
.spaces-panel .mf-notes__item:hover,
.spaces-panel .mf-notes__item:focus-visible { border-color: var(--sp-accent); }
.spaces-panel .mf-notes__title { display: block; font-size: 13px; margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spaces-panel .mf-notes__lines { margin: 0; padding: 0; list-style: none; }
.spaces-panel .mf-notes__line {
    display: flex;
    gap: 6px;
    font-size: 12px;
    line-height: 1.4;
    color: var(--sp-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.spaces-panel .mf-notes__line > span { overflow: hidden; text-overflow: ellipsis; }
.spaces-panel .mf-notes__line--bullet::before { content: "•"; flex: none; }
.spaces-panel .mf-notes__line--check i { flex: none; margin-top: 2px; }
.spaces-panel .mf-notes__line.is-done > span { text-decoration: line-through; opacity: 0.7; }
.spaces-panel .mf-notes__all { margin: 0; padding: 0; list-style: none; }
.spaces-panel .mf-notes--tile .mf-notes__item { margin-bottom: 0; }

.spaces-panel .mf-notes-drawer .mf-notes__bar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 10px 18px;
    border-bottom: 1px solid var(--sp-card-border);
}
.spaces-panel .mf-notes__status { flex: 1 1 auto; text-align: right; margin: 0 8px; }
.spaces-panel .mf-notes__conflict {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 10px 18px;
    background: color-mix(in srgb, var(--tm-danger, #b42318) 10%, transparent);
    font-size: 13px;
}
.spaces-panel .mf-notes__conflict[hidden] { display: none; }
.spaces-panel .mf-notes__editor { padding-top: 14px; }
.spaces-panel .mf-notes__title-input {
    box-sizing: border-box;
    width: 100%;
    margin-bottom: 10px;
    padding: 6px 0;
    border: none;
    border-bottom: 1px solid var(--sp-card-border);
    background: transparent;
    color: var(--sp-text);
    font: inherit;
    font-size: 18px;
    font-weight: 600;
    outline: none;
}
.spaces-panel .mf-notes__title-input:focus { border-bottom-color: var(--sp-accent); }
.spaces-panel .mf-notes__row { display: flex; align-items: flex-start; gap: 8px; }
.spaces-panel .mf-notes__box { flex: none; margin: 7px 0 0; accent-color: var(--sp-accent); }
.spaces-panel .mf-notes__dot { flex: none; padding-top: 4px; color: var(--sp-text-muted); }
.spaces-panel .mf-notes__text {
    flex: 1 1 auto;
    box-sizing: border-box;
    min-height: 28px;
    padding: 4px 2px;
    border: none;
    background: transparent;
    color: var(--sp-text);
    font: inherit;
    font-size: 14px;
    line-height: 1.45;
    resize: none;
    overflow: hidden;
    outline: none;
}
.spaces-panel .mf-notes__text.is-done { text-decoration: line-through; opacity: 0.6; }

/* ── My performance / Team performance (performance-widget.js) ─────────────── */
.spaces-panel .mf-perf__tabs { display: flex; gap: 4px; margin-bottom: 8px; border-bottom: 1px solid var(--sp-card-border); }
.spaces-panel .mf-perf__tab {
    padding: 4px 10px;
    border: none;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: var(--sp-text-muted);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}
.spaces-panel .mf-perf__tab.is-on { color: var(--sp-text); border-bottom-color: var(--sp-accent); }
.spaces-panel .mf-perf__who { margin: 0 0 8px; font-size: 12px; color: var(--sp-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spaces-panel .mf-perf__kpis { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.spaces-panel .mf-perf__kpi {
    display: flex;
    flex-direction: column;
    min-width: 0;
    padding: 6px 8px;
    border: 1px solid var(--sp-card-border);
    border-radius: 8px;
    gap: 3px;
    background: color-mix(in srgb, var(--sp-accent) 4%, transparent);
}
.spaces-panel .mf-perf__label { 
    font-size: 9px; 
    font-weight: 500; 
    letter-spacing: 0.04em; 
    text-transform: uppercase; 
    color: var(--sp-text-muted); 
}

.spaces-panel .mf-perf__value { font-size: 17px; color: var(--sp-text); overflow: hidden; text-overflow: ellipsis; }
.spaces-panel .mf-perf__of { font-size: 12px; font-weight: 400; color: var(--sp-text-muted); }
.spaces-panel .mf-perf__sub { display: block; font-size: 11px; font-weight: 400; color: var(--sp-text-muted); }
.spaces-panel .mf-perf__pick { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; padding-top: 8px; border-top: 1px dashed var(--sp-card-border); }
.spaces-panel .mf-perf__pick select {
    max-width: 100%;
    padding: 3px 6px;
    border: 1px solid var(--sp-card-border);
    border-radius: 6px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    font: inherit;
    font-size: 12px;
}
.spaces-panel .mf-perf__notes { margin-top: 8px; }
.spaces-panel .mf-perf__notes .mf-perf__sub { margin: 0; }
.spaces-panel .mf-perf__pick input {
    flex: 1 1 140px;
    min-width: 0;
    padding: 3px 6px;
    border: 1px solid var(--sp-card-border);
    border-radius: 6px;
    background: var(--tm-real-white);
    color: var(--sp-text);
    font: inherit;
    font-size: 12px;
}

/* Artifact cards under a brief's answer (card and drawer) */
.spaces-panel .mf-brief__artifacts { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.spaces-panel .mf-brief__artifacts:empty { display: none; }

/* =============================================================================
   Team Spaces — post galleries and file cards
   -----------------------------------------------------------------------------
   Part of the spaces.css manifest. Scoping (CLAUDE.md rule 2): every selector is
   prefixed `.space-` or nested under `.spaces-panel` / `.space-post-modal`.
   ============================================================================= */

/* ── Post attachments ────────────────────────────────────────────────────────
   A photo collage, not a row of thumbnails. A feed post with photos reads as
   ONE visual unit, and the arrangement people already know from every social
   feed is a 1/2/3/4-up grid with the overflow counted on the last tile.
   `data-count` is capped at 4 by the renderer, so five photos and fifty photos
   both land on the four-tile layout. */

.space-gallery {
    display: grid;
    gap: 3px;
    margin-top: 10px;
    border-radius: var(--sp-radius);
    overflow: hidden;
    background: var(--sp-card-border);   /* becomes the hairline between tiles */
}

.space-gallery__tile {
    position: relative;
    display: block;
    overflow: hidden;
    background: var(--sp-hover);
    min-height: 0;
    cursor: zoom-in;
    appearance: none;
    border: 0;
    padding: 0;
    width: 100%;
    text-align: inherit;
}

.space-gallery__tile:focus-visible { outline: 3px solid var(--sp-accent); outline-offset: -3px; z-index: 1; }

/* Multi-image tiles FILL their grid cell: `width: 100%` with `object-fit: cover` is
   what makes a 2-4 photo grid read as a grid. Sizing by intrinsic ratio instead
   (`width: auto`) leaves a portrait shot narrower than its cell, with a gap beside it. */
.space-gallery__tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* One photo keeps its shape rather than being cropped to a band — a single
   image IS the post's content, and a portrait shot cropped to 16:9 loses it.
   It fills the tile's width (the base rule above) and takes its own height up to
   the cap, `contain`ed so nothing is cut. */
.space-gallery[data-count="1"] .space-gallery__tile img {
    height: auto;
    max-height: 400px;
    object-fit: contain;
    background: var(--tm-real-white);
    margin: auto;
}

.space-gallery[data-count="2"] { grid-template-columns: 1fr 1fr; aspect-ratio: 2 / 1; }

/* Three and four both lead with one large tile: the eye needs somewhere to
   land, and four equal squares gives it nowhere. */
.space-gallery[data-count="3"] {
    grid-template-columns: 1.5fr 1fr;
    grid-template-rows: 1fr 1fr;
    aspect-ratio: 3 / 2;
}
.space-gallery[data-count="3"] .space-gallery__tile:first-child { grid-row: 1 / -1; }

.space-gallery[data-count="4"] {
    grid-template-columns: 1.5fr 1fr;
    grid-template-rows: repeat(3, 1fr);
    aspect-ratio: 3 / 2;
}
.space-gallery[data-count="4"] .space-gallery__tile:first-child { grid-row: 1 / -1; }

/* "+6" on the last tile. Scrim rather than a flat block so the photo underneath
   still reads as a photo — it is a count, not a cover. */
.space-gallery__more {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.52);
    color: #fff;
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* Non-image attachments keep the download-card treatment chat messages use —
   a PDF in a grid cell is just a grey rectangle. */
.space-post__files {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 10px;
}

.space-post__file {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--sp-card-border);
    border-radius: var(--tm-r, 8px);
    background: var(--sp-hover);
    color: var(--sp-text);
    text-decoration: none;
    font-size: 13.5px;
}

.space-post__file:hover { border-color: var(--sp-accent); }
.space-post__file > i { color: var(--sp-text-muted); font-size: 16px; }

.space-post__file-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.space-post__file-open {
    flex: 0 0 auto;
    font-weight: 600;
    color: var(--sp-accent);
}

.space-post__video {
    width: 100%;
    max-height: 480px;
    border-radius: var(--sp-radius);
    background: #000;
}

@media (max-width: 640px) {
    /* Narrow: the large-tile arrangement stops earning itself, and a tall
       collage pushes the post's text off screen. */
    .space-gallery[data-count="3"],
    .space-gallery[data-count="4"] {
        grid-template-columns: 1fr 1fr;
        grid-template-rows: 1fr 1fr;
        aspect-ratio: 1 / 1;
    }
    .space-gallery[data-count="3"] .space-gallery__tile:first-child,
    .space-gallery[data-count="4"] .space-gallery__tile:first-child {
        grid-row: auto;
        grid-column: 1 / -1;
    }

    .space-gallery[data-count="1"] .space-gallery__tile img {
        width: 100%;
    }
}

.space-post .space-post__video[data-layout-fixed] {
    height: auto;
    object-fit: contain;
}
.space-post .space-post__body img[width][height] {
    height: auto;
    max-width: 100%;
}
.space-post .space-post__body img:not([width][height]) {
    width: 100%;
    object-fit: contain;
    background: var(--tm-real-white);
    max-height: 400px;
}
/* While an image is still loading the browser draws its alt text ("image.jpg")
   in the reserved box; the empty box is the honest placeholder. */
.space-post .space-post__body img {
    color: transparent;
    overflow: hidden;
}

/* =============================================================================
   Team Spaces — gallery viewer
   ============================================================================= */

.space-media-viewer {
    position: fixed;
    z-index: 12000;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 28px;
    background: rgba(11, 18, 31, 0.78);
    backdrop-filter: blur(4px);
}

.space-media-viewer__dialog {
    display: grid;
    grid-template-columns: minmax(0, 1.7fr) minmax(300px, 0.72fr);
    width: min(1280px, 100%);
    height: min(820px, 90vh);
    overflow: hidden;
    border-radius: 14px;
    background: var(--sp-card-bg);
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35);
}

.space-media-viewer__stage {
    position: relative;
    display: grid;
    place-items: center;
    min-width: 0;
    min-height: 0;
    background: #111827;
}

.space-media-viewer__image {
    display: block;
    max-width: 100%;
    max-height: 100%;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.space-media-viewer__dialog .space-media-viewer__content .space-media-viewer__body img {
    width: 100%;
    height: auto;
}

.space-media-viewer__nav,
.space-media-viewer__close,
.space-media-viewer__copy-link {
    appearance: none;
    border: 0;
    cursor: pointer;
}

.space-media-viewer__nav {
    position: absolute;
    top: 50%;
    z-index: 1;
    transform: translateY(-50%);
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.56);
    color: #fff;
    font-size: 16px;
}
.space-media-viewer__nav:hover { background: rgba(0, 0, 0, 0.8); }
.space-media-viewer__nav:disabled { visibility: hidden; }
.space-media-viewer__nav--previous { left: 16px; }
.space-media-viewer__nav--next { right: 16px; }

.space-media-viewer__count {
    position: absolute;
    right: 16px;
    bottom: 14px;
    padding: 5px 9px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 12px;
    font-weight: 700;
}

.space-media-viewer__context {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0;
    min-width: 0;
    padding: 20px;
    color: var(--sp-text);
    background: var(--sp-card-bg);
    overflow: hidden;
}
.space-media-viewer__topline { position: absolute; top: 14px; right: 14px; z-index: 1; }
.space-media-viewer__close, .space-media-viewer__copy-link { width: 34px; height: 34px; border-radius: 7px; background: transparent; color: var(--sp-text); font-size: 18px; }
.space-media-viewer__copy-link { margin-right: 4px; }
.space-media-viewer__close:hover, .space-media-viewer__copy-link:hover { background: var(--sp-hover); }

.space-media-viewer__author { display: flex; align-items: center; gap: 10px; }
.space-media-viewer__content { display: flex; flex: 1 1 auto; flex-direction: column; gap: 16px; min-height: 0; overflow-y: auto; padding: 20px; }
.space-media-viewer__avatar { display: grid; place-items: center; width: 36px; height: 36px; overflow: hidden; border-radius: 50%; background: var(--sp-accent); color: #fff; font-size: 13px; font-weight: 700; }
.space-media-viewer__avatar img { width: 100%; height: 100%; object-fit: cover; }
.space-media-viewer__author > div { display: flex; flex-direction: column; gap: 2px; font-size: 13px; }
.space-media-viewer__author span { color: var(--sp-text-muted); font-size: 12px; }
.space-media-viewer__context h3 { margin: 0; font-size: 17px; line-height: 1.35; }
.space-media-viewer__body { min-width: 0; font-size: 14px; line-height: 1.55; }
.space-media-viewer__body > :first-child { margin-top: 0; }
.space-media-viewer__body > :last-child { margin-bottom: 0; }
.space-media-viewer__body p { margin: 0 0 10px; }
.space-media-viewer__body a { color: var(--sp-accent); overflow-wrap: anywhere; }
.space-media-viewer__comments { padding-top: 14px; border-top: 1px solid var(--sp-card-border); }
.space-media-viewer__comments h4 { margin: 0 0 10px; font-size: 14px; }
.space-media-viewer__comments [data-comment-list] { display: flex; flex-direction: column; gap: 10px; }
.space-media-viewer__comments .space-comment { margin: 0; align-items: flex-start; }
.space-media-viewer__comments .space-comment__bubble { box-sizing: border-box; width: 100%; padding: 8px 10px; border: 1px solid var(--sp-card-border); background: var(--sp-hover); }
.space-media-viewer__comment { position: relative; flex: 0 0 auto; padding: 10px 12px 12px; border-top: 1px solid var(--sp-card-border); background: var(--sp-card-bg); }
.space-media-viewer__comment-tools { display: flex; gap: 3px; margin: 0 0 7px; }
.space-media-viewer__comment-tools button { display: grid; place-items: center; width: 28px; height: 26px; border: 0; border-radius: 5px; background: transparent; color: var(--sp-text-muted); cursor: pointer; }
.space-media-viewer__comment-tools button:hover { background: var(--sp-hover); color: var(--sp-accent); }
.space-media-viewer__comment-field { position: relative; }
.space-media-viewer__comment textarea { display: block; box-sizing: border-box; width: 100%; min-height: 72px; resize: none; border: 1px solid var(--sp-card-border); border-radius: 9px; padding: 9px 46px 9px 10px; font: inherit; color: var(--sp-text); background: var(--tm-real-white, #fff); }
.space-media-viewer__comment textarea::placeholder { color: var(--sp-text-muted); }
.space-media-viewer__comment textarea:focus { outline: none; border-color: var(--sp-accent); box-shadow: 0 0 0 3px var(--tm-accent-subtle); }
.space-media-viewer__comment [type="submit"] { position: absolute; right: 7px; bottom: 7px; display: grid; place-items: center; width: 32px; height: 32px; border: 0; border-radius: 7px; background: var(--sp-accent); color: #fff; cursor: pointer; z-index: 2;}
.space-media-viewer__comment [type="submit"]:disabled { opacity: .55; cursor: default; }
.space-media-viewer .mention-input-shell { position: relative; display: grid; width: 100%; }
.space-media-viewer .mention-input-shell > textarea, .space-media-viewer .mention-input-highlight { grid-area: 1 / 1; min-width: 0; }
.space-media-viewer .mention-input-highlight { box-sizing: border-box; min-height: 72px; border: 1px solid transparent; padding: 9px 46px 9px 10px; font: inherit; line-height: inherit; white-space: pre-wrap; word-break: break-word; overflow: hidden; color: transparent; pointer-events: none; z-index: 2; }
.space-media-viewer .mention-input-shell > textarea { position: relative; z-index: 1; -webkit-text-fill-color: currentcolor; caret-color: var(--sp-text); }
.space-media-viewer .mention-input-highlight__mention { color: var(--sp-accent, #2563eb); }

@media (max-width: 760px) {
    .space-media-viewer { padding: 0; }
    /* `grid-template-rows` is the load-bearing addition here. Without it,
       both rows default to `auto` (fit-content) — and CSS Grid does NOT
       expand `auto` tracks to fill a definite-height container the way it
       does for `fr` tracks, so whatever the stage + context don't consume
       between them is left as dead space painted in the DIALOG's own
       background (`--sp-card-bg`, a light cream) — the empty band under a
       short post's comments. Meanwhile the stage, ALSO sized `auto` with no
       row height to resolve `height: 100%` against on its `<img>` (a
       percentage height of an auto-sized ancestor computes as `auto`, a
       classic CSS trap), rendered the image near its own intrinsic size
       inside a `place-items: center` cell — leaving it "floating" with dark
       `#111827` letterboxing above it rather than actually filling its row.
       `minmax(0, 1fr)` makes the stage take exactly the space left after the
       context pane's real content height, so the image scales to fill it and
       the dead band disappears in the same move. */
    .space-media-viewer__dialog {
        grid-template-columns: 1fr;
        grid-template-rows: minmax(0, 1fr) auto;
        width: 100%;
        height: 100%;
        border-radius: 0;
    }
    .space-media-viewer__context { max-height: 45vh; }
    .space-media-viewer__content { padding: 0; }
    .space-media-viewer__stage { min-height: 0; }
    .space-media-viewer__body > :last-child { margin: 0; }
}

.space-media-viewer__locked {
    margin: 10px 0 0;
    font-size: 13px;
    color: var(--sp-text-muted, #6b7280);
}

/* Team Spaces — paged post discussion dialog. */
.space-post-detail__dialog { width: min(760px, calc(100vw - 28px)); max-height: 90vh; display: flex; flex-direction: column; }
.space-post-detail__copy-link { margin-left: auto; width: 34px; height: 34px; border: 0; border-radius: 7px; background: transparent; color: var(--sp-text-muted); cursor: pointer; }
.space-post-detail__copy-link:hover { background: var(--sp-hover); color: var(--sp-text); }
/* This app has no global border-box reset, so a `width: 100%` box that also
   carries padding or a border overflows its own column by exactly that amount.
   The reply field overflowed by 66px and made the whole discussion horizontally
   scrollable — on a phone, the difference between a readable thread and one you
   drag sideways to read. Declared on the specific controls rather than as a
   `.space-post-detail__dialog *` blanket: a subtree rule would also overrule the
   box-sizing that any embedded library (EasyMDE's CodeMirror, as in the New-post
   dialog) sets on its own internals. */
.space-post-detail__dialog { box-sizing: border-box; }
.space-post-detail__body { overflow-y: auto; overflow-x: hidden; padding: 16px; }
/* Wide, unbreakable content (long URLs, code, images) must scroll INSIDE its own
   block rather than widening the dialog. */
.space-post-detail__body img,
.space-post-detail__body video { max-width: 100%; }
.space-post-detail__body pre { max-width: 100%; overflow-x: auto; }
.space-post-detail__body .space-post__body { overflow-wrap: anywhere; }
/* The card keeps its own gutter here. `padding: 0` was written when this card
   was a flat, background-less block inside the dialog; now that it paints a card
   surface, zero padding put the author line, the title and full-bleed images
   hard against the card's own edge — noticeably tighter than the identical card
   in the feed one tap away. The border still goes: the dialog IS the frame. */
.space-post-detail__body > .space-post { border: 0; padding: 14px 16px; }
.space-post-detail__comments { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--sp-card-border); }
.space-post-detail__comments h4 { margin: 0 0 12px; color: var(--sp-text); font-size: 16px; }
.space-post-detail__comments [data-comment-list] { display: flex; flex-direction: column; gap: 12px; }
.space-post-detail__comments .space-comment { margin: 0; align-items: flex-start; }
.space-post-detail__comments .space-comment__bubble {
    width: 100%;
    box-sizing: border-box;
    padding: 10px 12px;
    border: 1px solid var(--sp-card-border);
    background: var(--sp-hover);
    color: var(--sp-text);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
    line-height: normal;
}
.space-post-detail__comments .space-comment__author { color: var(--sp-text); }
.space-post-detail__comments .space-comment__time { color: var(--sp-text-muted); }
.space-post-detail__comments .space-comment__bubble [data-comment-body] { margin-top: 5px; color: var(--sp-text); }
/* Send lives INSIDE the field. Beside it, a 42px icon button on the dialog's
   own background read as page furniture rather than the action that completes
   the form. `resize` is off because the button owns the corner the grabber
   would otherwise sit in. */
.space-post-detail__reply { margin-top: 16px; }
.space-post-detail__reply-field { position: relative; display: block; }
.space-post-detail__reply textarea {
    display: block;
    width: 100%;
    box-sizing: border-box;
    min-height: 84px;
    resize: none;
    border: 1px solid var(--sp-card-border);
    border-radius: 10px;
    padding: 10px 52px 10px 12px;
    font: inherit;
    color: var(--sp-text);
    background: var(--tm-real-white, #fff);
}
.space-post-detail__reply textarea:focus {
    outline: none;
    border-color: var(--sp-accent);
    box-shadow: 0 0 0 3px var(--tm-accent-subtle);
}
.space-post-detail__send {
    position: absolute;
    right: 8px;
    bottom: 8px;
    display: inline-grid;
    place-items: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: none;
    border-radius: 8px;
    background: var(--sp-accent);
    color: #fff;
    font-size: 14px;
    cursor: pointer;
    transition: background var(--tm-t, 0.15s) ease, opacity var(--tm-t, 0.15s) ease;
    z-index: 2;
}

.space-post-detail__send:hover:not(:disabled) { background: var(--sp-accent-hover); }
.space-post-detail__send:disabled { opacity: 0.55; cursor: default; }
.space-post-detail__send:focus-visible { outline: 2px solid var(--sp-accent); outline-offset: 2px; }
.space-post-detail__loading { padding: 32px; text-align: center; color: var(--sp-text-muted); }
.space-post--editing { display: flex; flex-direction: column; gap: 12px; }
.space-post__edit-field { display: flex; flex-direction: column; gap: 5px; font-size: 13px; font-weight: 600; }
.space-post__edit-field input, .space-post__edit-field textarea { border: 1px solid var(--sp-card-border); border-radius: 7px; padding: 8px 10px; color: var(--sp-text); background: var(--sp-card-bg); font: inherit; font-weight: 400; }
.space-post__edit-actions { display: flex; align-items: center; gap: 8px; }
.space-post__edit-actions [data-edit-error] { flex: 1 1 auto; color: var(--sp-danger); font-size: 12px; }
/* No mobile override needed any more: the field is full-width at every size and
   the send button is anchored inside it rather than competing for a row. */

/* ── @mention highlight inside a post discussion ────────────────────────────
   The chat rules for `.teams-mention` are scoped to `.teams-panel`, and this
   dialog is appended to <body>, so it needs its own — but the VALUES are
   deliberately identical: a mention should look the same wherever it is read,
   and a second palette here would be the first step to two products. */
.space-post-detail .teams-mention {
    color: var(--tm-accent);
    font-weight: 600;
    background: var(--tm-accent-subtle);
    border-radius: 4px;
    padding: 0 3px;
}

/* A mention OF the reader stands out in red so they notice they were named —
   the whole reason for highlighting a long thread at all. */
.space-post-detail .teams-mention--self {
    color: #d92d20;
    background: rgb(217 45 32 / 12%);
}

/* ── The @-mention mirror in the reply box ──────────────────────────────────
   `agent-mention-dropdown.js` wraps the textarea in `.mention-input-shell` and
   grid-stacks a `.mention-input-highlight` mirror over it to paint the coloured
   @tokens. Its CSS lives in mention-dropdown.css scoped to `#inputContainer` /
   `#freshChatInputWrapper` — the chat page's two composer hosts — so in THIS
   dialog none of it applied: the shell was a plain div and the mirror rendered
   as an ordinary block of dark text ABOVE the field, which is what "@marg"
   duplicating itself above the input actually was.

   The mirror must reproduce the textarea's box model EXACTLY or the coloured
   token drifts off the raw text underneath it. These values are copied from
   `.space-post-detail__reply textarea` above and must move with it. */

.space-post-detail .mention-input-shell {
    position: relative;
    display: grid;
    width: 100%;
}

.space-post-detail .mention-input-shell > textarea,
.space-post-detail .mention-input-shell > .mention-input-highlight {
    /* Both in the same grid cell = stacked. `min-width: 0` stops the padded
       textarea from widening the track past the shell on a narrow screen. */
    grid-area: 1 / 1;
    min-width: 0;
    font-weight: 600;
}

.space-post-detail .mention-input-highlight {
    box-sizing: border-box;
    /* Matches the textarea, including a TRANSPARENT border: the textarea's 1px
       border insets its text by 1px under border-box, and without the same
       inset here every token sits one pixel off. */
    border: 1px solid transparent;
    padding: 10px 52px 10px 12px;
    min-height: 84px;
    font: inherit;
    line-height: inherit;
    white-space: pre-wrap;
    word-break: break-word;
    overflow: hidden;
    /* The mirror shows ONLY the highlight spans; the readable text is the real
       textarea underneath. Transparent here rather than hidden, because a
       hidden layer cannot paint the tokens either. */
    color: transparent;
    pointer-events: none;
    z-index: 2;
}

.space-post-detail .mention-input-shell > textarea {
    position: relative;
    z-index: 1;
    /* `-webkit-text-fill-color` beats `color` in WebKit, so the mirror's
       transparency would otherwise leak onto the real text. */
    -webkit-text-fill-color: currentcolor;
    caret-color: var(--sp-text);
}

.space-post-detail .mention-input-shell > textarea::placeholder {
    -webkit-text-fill-color: initial;
}

/* COLOUR ONLY. The mirror is stacked on top of the real textarea, character
   for character, so anything that changes text METRICS — font-weight,
   font-size, letter-spacing — makes the mirror's copy of the line a different
   width from the raw text beneath it, and every character after the mention
   drifts. A bold `@Bruce Paulson` is exactly that: the token renders wider than
   the text it is painted over, and the rest of the line no longer lines up.
   (Bolding the textarea to match "fixes" the alignment by bolding everything
   the user types.) Chat's own rule is a bare `color` for the same reason. */
.space-post-detail .mention-input-highlight__mention {
    color: var(--sp-accent, #2563eb);
}

.space-post-detail__comments .space-post-detail__locked {
    margin: 8px 0 0;
    font-size: 0.85rem;
    color: var(--sp-text-muted, #6b7280);
}

.space-post-detail__comments .space-post-detail__locked i {
    margin-right: 4px;
}

/* "Team docs" rail block. Every selector is scoped to .space-team-docs; colours
   come from the --tm-* tokens so dark mode follows automatically. */
.space-team-docs { position: relative; }
.space-team-docs .space-team-docs__list { display: grid; gap: 2px; max-height: min(46vh, 380px); margin: 0; padding: 0 4px 0 0; overflow-y: auto; list-style: none; overscroll-behavior: contain; }
.space-team-docs .space-team-docs__row { display: flex; align-items: center; gap: 10px; width: 100%; padding: 7px 8px; border: 0; border-radius: var(--tm-r); color: var(--tm-msg-text); background: transparent; text-align: left; cursor: pointer; font: inherit; }
.space-team-docs .space-team-docs__row:hover { background: var(--tm-accent-subtle); }
.space-team-docs .docs-badge { display: grid; flex: none; width: 30px; height: 30px; place-items: center; border-radius: 7px; color: var(--tm-accent); background: var(--tm-accent-subtle); font-size: 14px; }
.space-team-docs .docs-badge--pdf { color: #c0392b; background: rgba(192, 57, 43, .12); }
.space-team-docs .docs-badge--sheet { color: #2e7d32; background: rgba(46, 125, 50, .12); }
.space-team-docs .docs-badge--word { color: #2b579a; background: rgba(43, 87, 154, .12); }
.space-team-docs .docs-badge--image { color: #b9770e; background: rgba(185, 119, 14, .14); }
.space-team-docs .docs-badge--video, .space-team-docs .docs-badge--audio { color: #8e44ad; background: rgba(142, 68, 173, .12); }
.space-team-docs .space-team-docs__name { display: grid; min-width: 0; }
.space-team-docs .space-team-docs__name strong, .space-team-docs .space-team-docs__name small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.space-team-docs .space-team-docs__name strong { font-size: 13px; font-weight: 600; }
.space-team-docs .space-team-docs__name small { color: var(--tm-text-1); font-size: 11.5px; }
.space-team-docs .space-team-docs__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--tm-border); }
.space-team-docs .space-team-docs__btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 11px; border: 1px solid var(--tm-border-strong); border-radius: var(--tm-r); color: var(--tm-msg-text); background: var(--tm-real-white); font: 600 12px/1.2 var(--tm-font-ui); cursor: pointer; }
.space-team-docs .space-team-docs__btn:hover { border-color: var(--tm-accent); color: var(--tm-accent); }
.space-team-docs .space-team-docs__link { border: 0; color: var(--tm-accent); background: transparent; font: 600 12px/1.2 var(--tm-font-ui); cursor: pointer; }
.space-team-docs.is-uploading { opacity: .65; pointer-events: none; }
.space-team-docs .space-team-docs__drop { position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; justify-content: center; gap: 8px; border: 2px dashed var(--tm-accent); border-radius: inherit; color: var(--tm-accent); background: var(--tm-accent-subtle); font-weight: 700; backdrop-filter: blur(2px); }
.space-team-docs .space-team-docs__drop[hidden] { display: none; }
.space-team-docs .space-team-docs__more { padding: 10px; color: var(--tm-text-1); font-size: 12px; text-align: center; list-style: none; }

/* Loaded last so the Spaces filter trigger receives the shared header-control
   treatment despite its popover-specific stylesheet above. */
/* ── Shared Teams / Spaces header controls ─────────────────────────────────
   Chats and Spaces render the same header chassis. Keep every action button
   in this one rule so switching between the two views never changes the
   control geometry, surface, border, or interaction treatment. */

.teams-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions),
.teams-panel .teams-conversation__overflow-actions > .teams-conversation__action-list,
.teams-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger,
.spaces-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions),
.spaces-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    margin: 0;
    padding: 0;
    border: 1px solid var(--tm-border);
    border-radius: var(--tm-r);
    background: var(--tm-surface);
    color: var(--tm-text-2);
    font: 14px/1 var(--tm-font-ui);
    cursor: pointer;
    transition:
        background var(--tm-t),
        color var(--tm-t),
        border-color var(--tm-t);
}

/* The filter owns a popover, so its shell remains the positioning anchor;
   visually it is deliberately indistinguishable from the other icon buttons.
   DESCENDANT, not child: the panel mounts the component into a
   `[data-space-filter-slot]` span, so `.space-filter` is a grandchild of the
   actions row. A `>` here silently matched nothing once that slot arrived,
   which is why these declarations were briefly duplicated in
   spaces-filter.css under a slot-specific selector. */
.teams-panel .teams-conversation__header-actions .space-filter,
.spaces-panel .teams-conversation__header-actions .space-filter {
    position: relative;
    box-sizing: border-box;
    width: 34px;
    height: 34px;
    border: 0;
    border-radius: var(--tm-r);
    background: transparent;
    box-shadow: none;
}

.teams-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions):hover:not(:disabled),
.teams-panel .teams-conversation__overflow-actions > .teams-conversation__action-list:hover,
.teams-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger:hover,
.spaces-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions):hover:not(:disabled),
.spaces-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger:hover {
    background: var(--tm-accent);
    color: #fff;
    border-color: var(--tm-accent);
}

.teams-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions):focus-visible,
.teams-panel .teams-conversation__overflow-actions > .teams-conversation__action-list:focus-visible,
.teams-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger:focus-visible,
.spaces-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions):focus-visible,
.spaces-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions):focus-visible {
    outline: 2px solid var(--tm-accent);
    outline-offset: 2px;
}

/* The filter's popover trigger retains pointer focus after it is clicked. An
   outline there reads as a second permanent border, unlike the other actions,
   so its active state is the visual indication instead. */
.teams-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger:focus-visible,
.spaces-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger:focus-visible {
    outline: none;
}

/* Active filters retain their semantic accent at rest, but use the same hover
   treatment as every other header control. */
.teams-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger.is-active,
.spaces-panel .teams-conversation__header-actions .space-filter > .space-filter__trigger.is-active {
    background: var(--tm-accent-subtle);
    color: var(--tm-accent);
    border-color: var(--tm-accent);
}

.teams-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions):disabled,
.spaces-panel .teams-conversation__header-actions > button:not(.teams-conversation__video):not(.teams-conversation__video-select):not(.teams-conversation__more-actions):disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

