/*
 * Announcement cards — the styling that goes with js/components/announcement-card.js.
 *
 * These rules used to live in an inline <style> block in pages/club.html. When
 * the renderer was shared with the dashboard Club Feed (TODO 135) the markup
 * moved and the CSS did not, so the dashboard rendered the right elements with
 * none of the styling — no card, no padding, no accent. A shared renderer needs
 * a shared stylesheet; keeping them apart is how that happens again.
 *
 * The club page's announcement FORM and SIDEBAR styles are deliberately NOT
 * here — they belong to that page's authoring UI, which the feed does not have.
 *
 * Loaded by: index.html (dashboard feed), pages/club.html (announcements tab).
 *
 * ── The scale ─────────────────────────────────────────────────────────────
 * This file was written by five tasks that never saw each other's rendered
 * output, and it showed: 18 spacing values, 8 radii and 15 font sizes, three
 * of them half-pixel. Individually fine, together they read as noise. The
 * tokens below are the one scale everything now snaps to. There is no
 * platform-wide token set to reuse — css/theme.css carries runtime colour
 * only — so these are namespaced `--fd-` (feed/announcement design) rather
 * than claiming a name a future global system would want. The control radius
 * deliberately matches `.btn` in css/components.css (8px), so a button
 * borrowed from the platform sits correctly beside a feed control.
 *
 * Add a rule? Use a token. If none fits, the rule probably wants rethinking
 * rather than a thirteenth value.
 */

:root {
    /* Spacing — 4px base. `2xs` is the one sub-step: it exists for padding
       INSIDE a small control, where 4px would change the control's height. */
    --fd-space-2xs: 2px;
    --fd-space-xs: 4px;
    --fd-space-sm: 8px;
    --fd-space-md: 12px;
    --fd-space-lg: 16px;
    --fd-space-xl: 20px;
    --fd-space-2xl: 24px;

    /* Radius — four roles, not eight values. */
    --fd-radius-pill: 999px;
    --fd-radius-circle: 50%;
    --fd-radius-card: 12px;     /* cards, modal panels, banner images */
    --fd-radius-control: 8px;   /* inputs, buttons, media thumbnails */

    /* The floor for anything you can press, and the one dimension in this file
       that is a REQUIREMENT rather than a taste: members are largely elderly
       and tapping on tablets. Named so a rule that has to reserve room for a
       control (the chips beside the admin's "…") measures the same thing the
       control does, instead of repeating 30 and drifting from it. */
    --fd-tap-min: 30px;

    /* Type — whole pixels only. */
    --fd-fs-2xs: 10px;
    --fd-fs-xs: 11px;
    --fd-fs-sm: 12px;
    --fd-fs-md: 13px;
    --fd-fs-lg: 14px;
    --fd-fs-xl: 16px;
    --fd-fs-glyph: 24px;        /* a drawn "×", not text */

    /* Greys. The feed had two near-identical ramps mixed together (#475467
       beside #475569, #98a2b3 beside #94a3b8) because two tasks reached for
       two palettes. One ramp now, slate, chosen because it was already the
       more complete of the two. */
    --fd-ink: #0f172a;          /* titles, author names */
    --fd-body: #475569;         /* post and comment body */
    --fd-muted: #64748b;        /* secondary labels */
    --fd-faint: #94a3b8;        /* timestamps */
    --fd-ghost: #cbd5e1;        /* a control at rest that is not yet wanted */
    --fd-line: #e2e8f0;         /* borders on controls */
    --fd-hairline: #f1f5f9;     /* dividers between rows */
    --fd-wash: #f8fafc;         /* hover ground */

    /* Indigo: "you did this" / "this is selected". */
    --fd-accent-bg: #eef2ff;
    --fd-accent-line: #a5b4fc;
    --fd-accent-fg: #4338ca;
    --fd-focus: #6366f1;
}

/* ---- The card itself ---------------------------------------------------- *
 *
 * The club page's announcement card (pages/club.html), also reused inside the
 * feed's modal. It is on the scale like everything else: this block held the
 * last off-scale group in the file (17px title, 14px margins, 10px and 6px
 * radii, 5px and 3px gaps) and leaving it out would have meant one file with
 * two spacing systems in it, which is the exact problem this pass exists to
 * remove.
 *
 * Every move here is to the NEAREST rung and none is larger than 2px, so the
 * card keeps its proportions, its colours and its structure — this is the
 * noise coming off, not a restyle. The two that are worth naming:
 *   - `border-radius: 20px` on the type badge becomes the pill token. At
 *     11px/1.6 plus padding the badge is ~25px tall, so the browser was
 *     already clamping 20px to a full pill. Identical pixels.
 *   - `.ann-action-btn` gains a :focus-visible ring, so the club page's edit
 *     and delete buttons say where the keyboard is in the same words every
 *     other control in this file uses. Nothing changes until you Tab to it.
 */

.ann-card {
    background: #fff;
    border-radius: var(--fd-radius-card);
    border-left: 4px solid #111037;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .06);
    margin-bottom: var(--fd-space-lg);
    overflow: hidden;
    transition: box-shadow .2s;
}
.ann-card:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, .1); }
.ann-pinned { border: 1px solid #ffc107; border-left: 4px solid #ffc107; }

.ann-banner img {
    width: 100%;
    max-height: 220px;
    object-fit: cover;
    display: block;
}

.ann-body { padding: var(--fd-space-xl); }

.ann-top-row {
    display: flex;
    align-items: center;
    gap: var(--fd-space-sm);
    flex-wrap: wrap;
    margin-bottom: var(--fd-space-md);
}

.ann-type-badge {
    color: #fff;
    font-size: var(--fd-fs-xs);
    font-weight: 600;
    padding: var(--fd-space-xs) var(--fd-space-md);
    border-radius: var(--fd-radius-pill);
    display: inline-flex;
    align-items: center;
    gap: var(--fd-space-xs);
}
.ann-pin-badge,
.ann-priority-badge {
    font-size: var(--fd-fs-xs);
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    gap: var(--fd-space-xs);
}
.ann-pin-badge { color: #ffc107; }
.ann-priority-badge { color: #dc3545; }

.ann-actions { margin-left: auto; display: flex; gap: var(--fd-space-xs); }
.ann-action-btn {
    background: none;
    border: 1px solid #dee2e6;
    border-radius: var(--fd-radius-control);
    padding: var(--fd-space-xs) var(--fd-space-sm);
    cursor: pointer;
    color: #6c757d;
    font-size: var(--fd-fs-md);
    transition: all .15s;
}
.ann-action-btn:hover { background: #f8f9fa; color: #333; }
.ann-action-btn:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
.ann-action-delete:hover { background: #dc3545; color: #fff; border-color: #dc3545; }
/* A pinned post's thumbtack reads as ON, so the button says which state you
   are in as well as what pressing it does. Amber matches .ann-pin-badge. */
.ann-action-pinned { color: #ffc107; border-color: #ffc107; }
.ann-action-pinned:hover { background: #ffc107; color: #fff; }

.ann-title { margin: 0 0 var(--fd-space-sm); color: #2c3e50; font-size: var(--fd-fs-xl); font-weight: 600; }
.ann-message {
    margin: 0 0 var(--fd-space-lg);
    color: #555;
    font-size: var(--fd-fs-lg);
    line-height: 1.7;
    white-space: pre-wrap;
}

.ann-footer {
    font-size: var(--fd-fs-sm);
    color: #999;
    display: flex;
    gap: var(--fd-space-lg);
    flex-wrap: wrap;
}
.ann-footer i { margin-right: var(--fd-space-xs); }

.ann-media-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: var(--fd-space-sm);
    margin-bottom: var(--fd-space-lg);
}
.ann-media-item img,
.ann-media-item video {
    width: 100%;
    border-radius: var(--fd-radius-control);
    cursor: pointer;
    max-height: 180px;
    object-fit: cover;
}
/* Read-only surfaces suppress the lightbox click, so don't imply one. */
.club-feed-list .ann-media-item img,
.club-feed-list .ann-media-item video { cursor: default; }

/* ── Audience badge ────────────────────────────────────────────────────────
   Who a targeted post is for. Deliberately quiet: it is context, not a
   warning, and every post that predates the feature renders without one.
   Shared by both compositions, so it is frozen with the card. */
.ann-audience-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--fd-space-xs);
    background: var(--fd-accent-bg);
    color: var(--fd-accent-fg);
    border: 1px solid #c7d2fe;
    padding: var(--fd-space-2xs) var(--fd-space-sm);
    border-radius: var(--fd-radius-pill);
    font-size: var(--fd-fs-xs);
    font-weight: 600;
    white-space: nowrap;
}
.ann-audience-badge i { font-size: var(--fd-fs-2xs); }
/* The sentence a screen reader needs and the eye does not: sighted readers get
   the icon, which says "audience" faster than three words do. Absolutely
   positioned so it is not a flex item and does not earn a `gap` of its own.

   The 1px/-1px/0 below are the visually-hidden idiom, not spacing. They are
   deliberately NOT tokenised: a future edit to the scale must not be able to
   reach in here and make every accessible name visible text. */
.ann-audience-badge .ann-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ---- Compact card, inside the feed column ------------------------------- *
 *
 * INERT TODAY, kept on purpose. `renderClubFeed` only ever puts the `feed`
 * variant into `#clubFeed`, and `openFeedPost` mounts the full card on
 * document.body — so nothing matching `.club-feed-list .ann-card` is currently
 * rendered. These are the sizings a full card WOULD need if one ever landed in
 * the column, and they no longer contradict the live feed-post rules below
 * (which is what made "which value wins?" unanswerable before).
 */

.club-feed-list .ann-card { margin-bottom: var(--fd-space-md); border-radius: var(--fd-radius-card); }
.club-feed-list .ann-body { padding: var(--fd-space-lg); }
.club-feed-list .ann-footer { font-size: var(--fd-fs-xs); gap: var(--fd-space-md); }
/* Release notes are long by nature. The feed variant already renders only the
   summary — it never emits `.ann-release-details` at all — so this is belt and
   braces for a full card in the column, not the mechanism. */
.club-feed-list .ann-release-details { display: none; }

.club-feed-list .no-data {
    color: var(--fd-muted);
    font-size: var(--fd-fs-md);
    text-align: center;
    padding: var(--fd-space-2xl) var(--fd-space-md);
    margin: 0;
}

/* ---- Feed post composition --------------------------------------------- *
 *
 * A post reads: who, when, then what. The left accent bar is dropped — on a
 * card that already leads with a coloured avatar it is a second, weaker signal
 * of the same thing, and a column of stripes is what made this read as a list
 * of filed notices rather than people talking.
 *
 * A feed is one continuous surface with posts separated by a hairline, not a
 * stack of boxed cards. Boxes make each item look like a filed record; a
 * divider makes them look like a conversation running down the page.
 *
 * No horizontal padding. The 4px it used to carry did not shorten the divider
 * — the post's border box already spanned the column's full content width —
 * but it inset every piece of ink 4px from the ends of the rule drawn beneath
 * it, so avatars, titles, reaction pills and the thread toggle all floated
 * just inside a line that ran past them on both sides. Measured at 0 now:
 * avatar, title, first pill and toggle icon share one left edge, and the
 * column's own 20px of `.card-content` padding is the only inset.
 */
.ann-feed-post {
    display: block;
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--fd-hairline);
    border-radius: 0;
    box-shadow: none;
    margin: 0;
    padding: var(--fd-space-lg) 0;
    cursor: pointer;
    transition: background .12s;
}
.ann-feed-post:last-child { border-bottom: 0; }
.ann-feed-post:hover { background: var(--fd-wash); }
/* Inset, unlike every other focus ring here: the post runs the full width of a
   scrolling column, so an outset ring would be clipped by the overflow. */
.ann-feed-post:focus-visible {
    outline: 2px solid var(--fd-focus);
    outline-offset: -2px;
}
.ann-feed-post.ann-pinned { background: #fffdf3; }
.ann-feed-post.ann-pinned:hover { background: #fffaea; }

.ann-post-head {
    display: flex;
    align-items: center;
    gap: var(--fd-space-md);
    margin-bottom: var(--fd-space-sm);
}
/* THE POST LEADS, HERE TOO. The byline was a 36px avatar beside a 13px name in
   the same ink as the title, directly above a 13px body — three things of
   roughly equal weight, and the one that matters least was the biggest. In the
   feed the byline is now plainly subordinate: a smaller face, a smaller name,
   and the title left as the heaviest thing in the card. The club page's card
   and the modal keep the larger treatment, which is right where a single
   announcement is the whole surface. */
.ann-feed-post .ann-avatar {
    width: 32px;
    height: 32px;
    font-size: var(--fd-fs-sm);
}
.ann-feed-post .ann-author { font-size: var(--fd-fs-sm); }

.ann-avatar {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: var(--fd-radius-circle);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: var(--fd-fs-md);
    font-weight: 700;
    letter-spacing: .3px;
    /* A soft ring lifts the avatar off the card without a drop shadow. The 3px
       spread is optical, not spacing — it is the width of the halo. */
    box-shadow: 0 0 0 3px rgba(255, 255, 255, .9), 0 1px 3px rgba(15, 23, 42, .12);
}

.ann-post-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.3;
}
.ann-author {
    font-weight: 600;
    font-size: var(--fd-fs-md);
    color: var(--fd-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ann-when { font-size: var(--fd-fs-xs); color: var(--fd-faint); }

.ann-post-chips {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--fd-space-xs);
    flex-shrink: 0;
}
/* The admin's "…" is drawn in the post's top-right corner (see the overflow
   menu below), which is exactly where the chips end — so an audience badge or
   an "Important" flag would sit underneath a transparent button. The corner is
   reserved only on a post that actually has a menu: a member's post keeps its
   full width, and `:has()` is already how this file draws a checked chip. */
.ann-feed-post:has(.feed-post-menu) .ann-post-chips {
    padding-right: calc(var(--fd-tap-min) + var(--fd-space-xs));
}
.ann-feed-post .ann-type-badge { font-size: var(--fd-fs-2xs); padding: var(--fd-space-xs) var(--fd-space-sm); }
.ann-feed-post .ann-release-tag {
    background: var(--fd-ink);
    color: #fff;
    padding: var(--fd-space-2xs) var(--fd-space-sm);
    border-radius: var(--fd-radius-control);
    font-size: var(--fd-fs-2xs);
    font-family: ui-monospace, monospace;
    font-weight: 700;
}

/* The title opens the post rather than heading it: same size as the body,
   heavier, sitting directly above it.
   It is `display: block`, not `inline`. The message is a `-webkit-box` for the
   line clamp, which is block-level, so the title never shared its line no
   matter what it was told — it just carried a right margin that could not do
   anything. Blocked and given a real 4px gap, which it did not have. */
.ann-post-content { font-size: var(--fd-fs-md); line-height: 1.55; color: var(--fd-body); }
.ann-feed-post .ann-title {
    display: block;
    font-size: var(--fd-fs-lg);
    line-height: 1.35;
    letter-spacing: -.01em;
    font-weight: 700;
    color: var(--fd-ink);
    margin: 0 0 var(--fd-space-xs);
}
.ann-feed-post .ann-message {
    /* Clamp rather than truncate with an ellipsis span: the full post lives in
       the modal, and a feed's job is to tell you something happened. */
    display: -webkit-box;
    -webkit-line-clamp: 5;
    line-clamp: 5;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin: 0 0 var(--fd-space-sm);
    font-size: var(--fd-fs-md);
    line-height: 1.55;
    color: var(--fd-body);
    white-space: pre-wrap;
}

/* The media is the post.
   Was `max-height: 120px`, which put a 3:4 portrait through a ~3:1 strip —
   a thin band however it is positioned. `height: auto` lets the image keep
   its OWN shape, so nothing is cropped at all until it would take over the
   screen; the cap is what stops a very tall photo doing that, and `contain`
   means even then it letterboxes rather than slicing faces off. */
/* Full-bleed, and ABOVE the text now rather than under it. */
.ann-feed-post .ann-banner { margin-top: 0; margin-bottom: var(--fd-space-sm); }
.ann-feed-post .ann-banner img {
    border-radius: var(--fd-radius-card);
    height: auto;
    max-height: min(62vh, 460px);
    object-fit: contain;
    background: var(--fd-wash);
}
/* One image or one video fills the width like a banner rather than sitting in
   a 110px thumbnail tile — a single photo is the post, not an attachment. */
.ann-feed-post .ann-media-grid:has(.ann-media-item:only-child) {
    grid-template-columns: 1fr;
}
.ann-feed-post .ann-media-grid .ann-media-item:only-child img,
.ann-feed-post .ann-media-grid .ann-media-item:only-child video {
    width: 100%;
    height: auto;
    max-height: min(62vh, 460px);
    object-fit: contain;
    background: var(--fd-wash);
}
/* With media leading, the copy is the caption. The full post is one tap away
   in the modal, so the feed shows the headline and an opening line rather
   than five lines competing with the picture above them. */
.ann-feed-post.ann-has-media .ann-message {
    -webkit-line-clamp: 2;
    line-clamp: 2;
}
.ann-feed-post .ann-media-grid {
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: var(--fd-space-xs);
    margin: var(--fd-space-sm) 0;
}
.ann-feed-post .ann-media-item img,
.ann-feed-post .ann-media-item video { border-radius: var(--fd-radius-control); max-height: 110px; }

/* ---- One post, opened in full ------------------------------------------ *
 *
 * The feed clamps a long body, which is right for scanning and useless for
 * reading. Opening in place beats linking away to the club page: the reader
 * keeps their position in the feed.
 */

.ann-modal {
    position: fixed;
    inset: 0;
    z-index: 10050;
    background: rgba(15, 23, 42, .55);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--fd-space-2xl);
    overflow-y: auto;
}
.ann-modal-panel {
    position: relative;
    width: 100%;
    max-width: 640px;
    max-height: calc(100vh - 48px);   /* twice the overlay's padding */
    overflow-y: auto;
    background: #fff;
    border-radius: var(--fd-radius-card);
    box-shadow: 0 20px 48px rgba(15, 23, 42, .28);
}
.ann-modal-close {
    position: absolute;
    top: var(--fd-space-md);
    right: var(--fd-space-md);
    z-index: 1;
    width: 32px;
    height: 32px;
    border: 0;
    border-radius: var(--fd-radius-circle);
    background: rgba(255, 255, 255, .92);
    color: var(--fd-body);
    font-size: var(--fd-fs-glyph);
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(15, 23, 42, .18);
}
.ann-modal-close:hover { background: #fff; color: var(--fd-ink); }
.ann-modal-close:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
/* Inside the modal the card is the whole surface, so it drops its own frame. */
.ann-modal-body .ann-card {
    margin: 0;
    border: 0;
    border-radius: var(--fd-radius-card);
    box-shadow: none;
}
.ann-modal-body .ann-message {
    -webkit-line-clamp: none;
    line-clamp: none;
    overflow: visible;
    display: block;
}

/* ── Opi's avatar ──────────────────────────────────────────────────────────
   The mascot rather than "OA" in a circle: initials are not an identity, and
   two letters cannot tell a member that the platform wrote this post.

   No background and no ring: the drawing IS the avatar, and .ann-avatar's
   white halo drawn behind a transparent square reads as a ghost circle
   floating beside the byline rather than as a lift off the card. */
.ann-avatar-opi {
    background: transparent !important;
    box-shadow: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}
/* The mascot is sized entirely by its container — opi-mascot.js ships a
   viewBox with no width/height precisely so one string serves this avatar and
   the larger bubble mascot and stays crisp on a 3x screen. */
.ann-avatar-opi .ann-avatar-face { width: 100%; height: 100%; }

/* ── The social row ────────────────────────────────────────────────────────
   Inside a post, but outside the click target that opens it: the dashboard
   checks for [data-feed-social] before treating a click as "open this post".

   THE POST LEADS. Five always-visible emoji pills per post made the reactions
   the loudest thing in a narrow column stacked eight posts deep — the owner's
   words were "the emojis are a bit overpowering". Everything below is now
   deliberately quieter than the title and body above it: the two action
   controls are grey text on no ground until you touch them, the tally is a
   sentence rather than a button, and the five faces exist only while somebody
   has asked for them.

   Every control in this row and the thread below it shares one hover and one
   focus language: a 2px --fd-focus ring at 2px offset, and a hover that
   changes ground for a filled control or ink for a bare one.

   30px is the floor for anything you can press. Members are largely elderly
   and tapping on tablets, so the action controls are 34px and the picker's
   pills 36px — err larger, never smaller. */
.feed-social { margin-top: var(--fd-space-sm); }

/* What everyone else chose, above the action row. Display only — it is a
   sentence, not a sixth button. Absent entirely when nobody has reacted. */
.feed-react-tally {
    display: flex;
    align-items: center;
    gap: var(--fd-space-xs);
    padding: var(--fd-space-2xs) 0 var(--fd-space-sm);
}
.feed-tally-faces { display: inline-flex; align-items: center; }
/* The faces overlap slightly, the way a stacked cluster does, and each sits on
   the card's own ground so the overlap reads as depth rather than as a
   collision. The first one cancels the pull so the cluster starts on the same
   left edge as the avatar and title above it. */
.feed-tally-emoji {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    margin-left: calc(-1 * var(--fd-space-xs));
    border-radius: var(--fd-radius-circle);
    background: #fff;
    box-shadow: 0 0 0 1px var(--fd-line);
    font-size: var(--fd-fs-xs);
    line-height: 1;
}
.feed-tally-emoji:first-child { margin-left: 0; }
.feed-tally-count { font-size: var(--fd-fs-sm); color: var(--fd-muted); }

/* The two ways into a post, side by side at the post's own left edge. */
/* No rule above it. The post already ends in a hairline (.ann-feed-post's
   bottom border), and a second line forty pixels above the first is a divider
   doing work the whitespace was already doing — eight posts deep that reads as
   ruled paper. The gap separates them instead. */
.feed-actions {
    display: flex;
    align-items: stretch;
    gap: var(--fd-space-sm);
    padding-top: var(--fd-space-2xs);
    /* Pulled back by exactly one control's left padding, so the first icon
       lands on the post's own left edge rather than one padding-step inside
       it. The hover ground keeps its padding and overhangs into the column's
       20px of `.card-content` inset, which is what a Facebook-style bare
       control does: the INK aligns, the ground breathes. */
    margin-left: calc(-1 * var(--fd-space-sm));
}
.feed-react-trigger,
.feed-thread-toggle {
    /* Natural width, packed from the left — NOT `flex: 1 1 0`. Two halves each
       centring its own label put "React" and "Comment" in the middle of
       nothing: the owner's report was that the action row lined up with none
       of the avatar, title, body or tally above it, all of which sit at 0, and
       the wider the column got the further out they drifted (52px adrift at a
       1280px viewport, 100px at 2000px). A row of actions reads as a row of
       actions; it does not need half the card each. */
    flex: 0 1 auto;
    min-width: 0;
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
    gap: var(--fd-space-xs);
    background: none;
    border: 0;
    border-radius: var(--fd-radius-control);
    padding: var(--fd-space-xs) var(--fd-space-sm);
    min-height: 34px;
    font: inherit;
    font-size: var(--fd-fs-sm);
    font-weight: 600;
    color: var(--fd-muted);
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}
.feed-react-trigger:hover,
.feed-thread-toggle:hover { background: var(--fd-wash); color: var(--fd-ink); }
.feed-react-trigger:focus-visible,
.feed-thread-toggle:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
/* Once you have reacted the control wears your own emoji and reads as chosen —
   the same indigo "you did this" the picked pill uses. */
.feed-react-trigger--mine { color: var(--fd-accent-fg); }
.feed-react-trigger--mine:hover { background: var(--fd-accent-bg); color: var(--fd-accent-fg); }
.feed-react-trigger .feed-react-emoji { font-size: var(--fd-fs-lg); line-height: 1; }
.feed-react-label,
.feed-thread-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The five, revealed. Rendered only while open, so a closed picker is not a
   hidden row of buttons a Tab could still walk into. */
.feed-react-picker {
    display: flex;
    flex-wrap: wrap;
    gap: var(--fd-space-xs);
    margin-top: var(--fd-space-xs);
    padding: var(--fd-space-xs);
    background: var(--fd-wash);
    border: 1px solid var(--fd-line);
    border-radius: var(--fd-radius-pill);
}
.feed-react {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--fd-space-xs);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--fd-radius-pill);
    /* The pill keeps its full size when the count is hidden — a reaction
       nobody has picked must stay as easy to hit as the popular one, and
       min-height keeps it a comfortable target on a touch screen. 36px is a
       floor, not a target to trim towards: these are tapped on tablets by
       members who are largely elderly. */
    padding: var(--fd-space-xs) var(--fd-space-sm);
    min-width: 36px;
    min-height: 36px;
    font-size: var(--fd-fs-xl);
    line-height: 1.2;
    cursor: pointer;
    transition: background .12s ease, border-color .12s ease, transform .12s ease;
}
.feed-react:hover { background: var(--fd-line); transform: translateY(-1px); }
.feed-react--mine { background: var(--fd-accent-bg); border-color: var(--fd-accent-line); }
.feed-react-count { font-size: var(--fd-fs-sm); font-weight: 700; color: var(--fd-body); }
.feed-react--mine .feed-react-count { color: var(--fd-accent-fg); }
.feed-react:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
/* The words behind the picture. An emoji announces unpredictably and a border
   tint announces not at all, so every control here carries its name, its count
   and "you reacted" as real text — visible only to a screen reader.
   Not display:none and not visibility:hidden: both remove it from the
   accessibility tree, which is the one place it exists to be.

   As with .ann-sr-only above, the 1px/-1px/0 here are the visually-hidden
   idiom and are deliberately left untokenised. */
.feed-react .feed-sr-only,
.feed-social .feed-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── The comment thread ────────────────────────────────────────────────────
   Collapsed behind a count until asked for: the feed panel shows about six
   posts, and six open threads is a scroll rather than a feed.

   Drawn as a conversation, not as a form. Every comment is an avatar and a
   bubble; the time and the delete sit underneath in a quiet foot rather than
   competing with the author's name on one baseline; and the composer wears the
   reader's own face so the box reads as their seat at the table. */
.feed-thread-body { padding: var(--fd-space-sm) 0 var(--fd-space-xs); }
.feed-comment-list { list-style: none; margin: 0 0 var(--fd-space-sm); padding: 0; }
.feed-comment {
    display: flex;
    align-items: flex-start;
    gap: var(--fd-space-sm);
    padding: var(--fd-space-2xs) 0 var(--fd-space-sm);
}

/* The platform's avatar, at conversation scale. .ann-avatar draws the circle,
   the ring and the weight; this only makes it smaller, so a member's face is
   one thing on this page rather than two. */
.feed-avatar {
    width: 28px;
    height: 28px;
    font-size: var(--fd-fs-xs);
    box-shadow: none;
}

.feed-comment-main { display: block; min-width: 0; flex: 1 1 auto; }
/* The bubble. Its ground is what separates one member's words from the next's
   — the hairline rules between comments were doing that job and made a
   conversation look like a table. */
.feed-comment-bubble {
    display: inline-block;
    max-width: 100%;
    padding: var(--fd-space-sm) var(--fd-space-md);
    background: var(--fd-hairline);
    border-radius: var(--fd-radius-card);
    border-top-left-radius: var(--fd-space-xs);
}
.feed-comment-author {
    display: block;
    font-size: var(--fd-fs-sm);
    font-weight: 700;
    color: var(--fd-ink);
}
.feed-comment-body {
    display: block;
    font-size: var(--fd-fs-md);
    line-height: 1.5;
    color: var(--fd-body);
    /* A member typed this. Newlines are theirs to keep, and a 400-character
       word must wrap rather than push the whole card sideways. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.feed-comment-foot {
    display: flex;
    align-items: center;
    gap: var(--fd-space-sm);
    padding-left: var(--fd-space-md);
}
.feed-comment-when { font-size: var(--fd-fs-xs); color: var(--fd-faint); }
.feed-comment-del {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--fd-tap-min);
    min-height: var(--fd-tap-min);
    background: none;
    border: 0;
    border-radius: var(--fd-radius-circle);
    color: var(--fd-ghost);
    cursor: pointer;
    padding: var(--fd-space-2xs) var(--fd-space-xs);
}
.feed-comment-del:hover { color: #dc3545; background: var(--fd-wash); }
/* A control that only appears on hover of a mouse is unreachable by keyboard;
   this one is always there, so it only needs to SAY where the focus is. */
.feed-comment-del:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }

.feed-composer { display: flex; align-items: flex-start; gap: var(--fd-space-sm); }
.feed-composer-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--fd-space-sm);
    align-items: flex-start;
}
.feed-comment-box {
    flex: 1 1 160px;
    min-width: 0;
    resize: vertical;
    font: inherit;
    font-size: var(--fd-fs-md);
    padding: var(--fd-space-sm) var(--fd-space-md);
    border: 1px solid var(--fd-line);
    /* A rounded box beside a round avatar: the composer is a message being
       written, drawn like the messages above it. */
    border-radius: var(--fd-radius-card);
    background: var(--fd-wash);
}
/* Was a bare border-colour change with `outline: none` — the one control in
   the row whose focus you had to hunt for. Now it says the same thing as every
   button beside it, drawn as a ring rather than an outline so the border and
   the indicator do not fight. */
.feed-comment-box:focus {
    outline: none;
    background: #fff;
    border-color: var(--fd-focus);
    box-shadow: 0 0 0 3px rgba(99, 102, 241, .18);
}
/* .btn-small (css/components.css) is a 4px radius, which put a square-ish
   button against a rounded textarea. Matched here rather than globally. */
.feed-comment-send { border-radius: var(--fd-radius-control); min-height: var(--fd-tap-min); }
.feed-comment-send:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
/* Opi's bubble takes the full width beneath the box rather than squeezing it. */
.feed-opi-host { flex: 1 0 100%; }
/* Empty until Opi has something to say — no reserved gap under every composer. */
.feed-opi-host:empty { display: none; }
/* The send button while the comment is being screened. Visibly out of action
   rather than merely inert — a member who cannot see why nothing happened taps
   again, which is the double-post the disable exists to prevent. */
.feed-comment-send[disabled] { opacity: .55; cursor: default; }

/* ── The club admin's overflow menu ────────────────────────────────────────
   Edit and Delete used to be two labelled buttons under every post, which is
   what a CMS looks like and not what anything social does. They now live
   behind a "…" in the post's top-right corner.

   Absolutely positioned against .ann-feed-post, which is the post's own box —
   so the glyph sits level with the byline while the markup stays inside
   [data-feed-social], where the open-the-post delegation already steps over
   it. The alternative was a second social slot in AnnouncementCard, which
   would put half of "what a post's chrome is" in the shared renderer that the
   club page also uses. */
.ann-feed-post { position: relative; }
.feed-post-menu { position: absolute; top: var(--fd-space-md); right: 0; }
.feed-post-menu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--fd-tap-min);
    height: var(--fd-tap-min);
    background: none;
    border: 0;
    border-radius: var(--fd-radius-circle);
    color: var(--fd-faint);
    font-size: var(--fd-fs-lg);
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}
.feed-post-menu-btn:hover { background: var(--fd-hairline); color: var(--fd-ink); }
.feed-post-menu-btn:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
.feed-post-menu-btn[aria-expanded="true"] { background: var(--fd-hairline); color: var(--fd-ink); }
.feed-post-menu-list {
    position: absolute;
    top: calc(100% + var(--fd-space-xs));
    right: 0;
    z-index: 20;
    min-width: 148px;
    display: flex;
    flex-direction: column;
    padding: var(--fd-space-xs);
    background: #fff;
    border: 1px solid var(--fd-line);
    border-radius: var(--fd-radius-control);
    box-shadow: 0 8px 24px rgba(15, 23, 42, .16);
}
.feed-post-act {
    display: flex;
    align-items: center;
    gap: var(--fd-space-sm);
    width: 100%;
    min-height: 34px;
    background: none;
    border: 0;
    border-radius: var(--fd-radius-control);
    padding: var(--fd-space-xs) var(--fd-space-sm);
    font: inherit;
    font-size: var(--fd-fs-md);
    font-weight: 600;
    color: var(--fd-body);
    text-align: left;
    cursor: pointer;
}
.feed-post-act:hover { background: var(--fd-wash); color: var(--fd-ink); }
.feed-post-act--danger { color: #b91c1c; }
.feed-post-act--danger:hover { background: #fef2f2; color: #991b1b; }
.feed-post-act:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }

/* ── Held-comment review ───────────────────────────────────────────────────
   The club admin's queue of comments the guardrail declined. A modal, not a
   permanent section: most days it is empty, and a panel of nothing on the
   busiest screen in the app teaches everyone to ignore it. */

/* The way in, in the feed's card header. Muted, not alarming: it is a routine
   errand that is usually empty, and a red badge on an empty queue is a false
   alarm every single day. Sized to the same 30px floor as everything else you
   can press here.

   ═══ BACK ON A PALE GROUND ═══
   This was first written as a light pill, then redrawn as a control ON DARK
   because dashboard-module.js injects it into `.card-header`, which at the
   time was a navy slab. The slab is gone: `.card-header` is now a pale band
   with a hairline (css/layout.css), so the dark treatment would be a white
   lozenge on near-white. It is a light control again — and deliberately the
   same object as `.card-header .btn` next door, 30px tall on an 8px radius,
   because the owner's complaint was that the two headers' right-hand controls
   did not look like they belonged to the same app. A pill beside a rounded
   rectangle is exactly that mismatch, so the pill radius is gone too.

   The one token it borrows from outside this file is `--ch-ink` (css/layout.css),
   the club colour mixed down to a guaranteed 4.5:1. It is NOT `--club-primary`:
   a club can pick pale yellow from the picker, and this label has to stay
   readable when it does. The fallback is the platform navy, so a page that
   somehow loads this sheet without layout.css still gets a legible control. */
.feed-held-btn {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--fd-space-sm);
    justify-content: center;
    background: #fff;
    color: var(--ch-ink, #111037);
    border: 1px solid var(--ch-control-line, #6b7280);
    border-radius: var(--fd-radius-control);
    padding: 0 var(--fd-space-md);
    min-height: var(--fd-tap-min);
    font-size: var(--fd-fs-md);
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition: background .12s ease, border-color .12s ease;
}
.feed-held-btn:hover {
    background: var(--ch-ground, var(--fd-wash));
    border-color: var(--ch-ink, #111037);
    color: var(--ch-ink, #111037);
}
.feed-held-btn:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
/* css/theme.css pads every Font Awesome glyph 10px each side with !important
   and paints it in the theme accent; inside a control that is a shield sitting
   a third of the way into its own button. */
.feed-held-btn > i {
    padding: 0 !important;
    color: inherit !important;
    font-size: var(--fd-fs-md);
}
/* The count appears only when there is one, so it is allowed to be emphatic.
   On the old navy header the emphatic version was the light one; on a pale
   control it is the solid one. */
.feed-held-count {
    background: #b91c1c;
    color: #fff;
    border-radius: var(--fd-radius-pill);
    padding: 0 var(--fd-space-sm);
    font-size: var(--fd-fs-xs);
    font-weight: 700;
    line-height: 18px;
}

.held-panel-overlay {
    position: fixed;
    inset: 0;
    z-index: 10020;
    background: rgba(15, 23, 42, .55);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 6vh var(--fd-space-lg);
    overflow-y: auto;
}
.held-panel {
    background: #fff;
    border-radius: var(--fd-radius-card);
    width: min(640px, 100%);
    box-shadow: 0 20px 50px rgba(15, 23, 42, .25);
}
.held-panel-head {
    display: flex;
    align-items: center;
    gap: var(--fd-space-md);
    padding: var(--fd-space-lg) var(--fd-space-xl);
    border-bottom: 1px solid var(--fd-line);
}
.held-panel-head h3 { margin: 0; font-size: var(--fd-fs-xl); }
.held-panel-close {
    margin-left: auto;
    background: none;
    border: 0;
    font-size: var(--fd-fs-glyph);
    line-height: 1;
    color: var(--fd-faint);
    cursor: pointer;
}
.held-panel-close:hover { color: var(--fd-ink); }
.held-panel-close:focus-visible { outline: 2px solid var(--fd-focus); outline-offset: 2px; }
.held-panel-body { padding: var(--fd-space-md) var(--fd-space-xl) var(--fd-space-xl); }
/* An empty queue is a GOOD state and is drawn as one — a green tick, not a
   grey dash and not an error. */
.held-empty { color: var(--fd-body); }
.held-empty .fa-check-circle { color: #16a34a; }

.held-list { list-style: none; margin: 0; padding: 0; }
.held-row { padding: var(--fd-space-md) 0; border-bottom: 1px solid var(--fd-hairline); }
.held-row:last-child { border-bottom: 0; }
.held-row-head { display: flex; align-items: baseline; gap: var(--fd-space-md); flex-wrap: wrap; }
.held-author { font-size: var(--fd-fs-md); font-weight: 700; color: var(--fd-ink); }
.held-when { font-size: var(--fd-fs-xs); color: var(--fd-faint); }
/* The platform's judgement of a member, so it is a small label beside the
   decision rather than a headline over their words — but it is printed,
   because an admin approving the same category over and over is the only
   signal a mis-tuned filter ever gives. */
.held-category {
    margin-left: auto;
    font-size: var(--fd-fs-2xs);
    font-weight: 700;
    letter-spacing: .04em;
    background: #fef2f2;
    color: #991b1b;
    border: 1px solid #fecaca;
    padding: var(--fd-space-2xs) var(--fd-space-sm);
    border-radius: var(--fd-radius-pill);
}
/* Quoted, not styled as a comment: the panel is asking for a decision about
   this text, not presenting it as part of the club's conversation. */
.held-message {
    margin: var(--fd-space-sm) 0;
    padding: var(--fd-space-sm) var(--fd-space-md);
    border-left: 3px solid var(--fd-line);
    background: var(--fd-wash);
    font-size: var(--fd-fs-md);
    color: var(--fd-body);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.held-row-actions { display: flex; gap: var(--fd-space-sm); }

/* ── Audience picker (the poll admin's, in the announcement form) ─────── */
.aud-picker { margin-top: var(--fd-space-md); }
.aud-hint { display: block; color: var(--fd-muted); font-size: var(--fd-fs-xs); margin-bottom: var(--fd-space-xs); }
/* Who this post is about to reach, in the same words the badge will use.
   Prominent on purpose: an admin who ignores the picker must be able to SEE
   that they did not narrow anything. */
.aud-summary {
    display: block;
    margin: var(--fd-space-2xs) 0 var(--fd-space-sm);
    padding: var(--fd-space-xs) var(--fd-space-md);
    background: var(--fd-accent-bg);
    border: 1px solid var(--fd-accent-line);
    border-radius: var(--fd-radius-control);
    font-size: var(--fd-fs-sm);
    font-weight: 700;
    color: var(--fd-accent-fg);
}
.aud-section { margin-top: var(--fd-space-md); }
.aud-section-title { font-size: var(--fd-fs-sm); font-weight: 700; color: var(--fd-body); margin-bottom: var(--fd-space-xs); }
.aud-section-title small { font-weight: 500; color: var(--fd-faint); }
.aud-chip-row { display: flex; flex-wrap: wrap; gap: var(--fd-space-xs); }
.aud-chip {
    display: inline-flex; align-items: center; gap: var(--fd-space-xs);
    background: var(--fd-wash); border: 1px solid var(--fd-line); border-radius: var(--fd-radius-pill);
    padding: var(--fd-space-xs) var(--fd-space-md); font-size: var(--fd-fs-sm); cursor: pointer;
}
.aud-chip:has(input:checked) { background: var(--fd-accent-bg); border-color: var(--fd-accent-line); color: var(--fd-accent-fg); }
