/**
 * BPMB Front — tokens + reader components (handle: `bpmb-front`).
 *
 * THE SINGLE SOURCE OF TRUTH FOR EVERY `--bpmb-*` TOKEN, and the only sheet a
 * READING page needs: post lists and grids, pagination, related posts, the
 * writer header, the topic hub, and the clap / bookmark / follow buttons.
 *
 * WHY THIS IS ITS OWN FILE. Until 4.3.0 all of this lived in bpmb-ui.css
 * alongside the app kit (buttons, modal, confirm, toast, skeleton, states).
 * Every other stylesheet depended on that one handle for the tokens, so a
 * logged-out reader on a single post or a topic hub downloaded the whole kit -
 * measured in the browser, 6% of its rules matched anything on a single post.
 * The kit is now `bpmb-ui-helpers` (bpmb-ui.css), which DEPENDS on this sheet,
 * so anything that asks for the full kit still gets exactly what it did before.
 *
 * Rule of thumb when adding CSS: if a logged-out visitor can see it without any
 * script running, it belongs here. If it only exists because a script put it on
 * the page (a toast, a dialog, a busy state), it belongs in bpmb-ui.css.
 *
 * Every other stylesheet in the pair CONSUMES these tokens. None of them may
 * declare a `--bpmb-*` token of its own.
 *
 * Before 4.0.0 the same tokens were re-declared in 15 files: 672 root-level
 * declarations, 164 distinct tokens, 49 exact duplicates and 21 genuine
 * CONFLICTS (the same token with different values in different files, so the
 * winner depended on stylesheet load order). Pro carried a whole parallel
 * `--bpmbp-*` set on top. That is all collapsed here.
 *
 * TOKEN LAYERS (spec 09 §4)
 *   Layer 1  Host theme / WordPress  -> --bx-* (BuddyX), --reign-* (Reign),
 *                                       --global-*, --wp--preset--*
 *   Layer 2  Wbcom semantic          -> --bpmb-*   (this file, :root below)
 *   Layer 3  Component locals        -> --bpmb-<component>-* on the component's
 *                                       own selector (see the component layer)
 *
 * THEME ADOPTION IS DELIBERATE. Spec 09 §4 says to drop the `--reign-*`
 * coupling and honor themes only via `--wp--preset--*`. We do NOT: Reign and
 * BuddyX are Wbcom's own flagship themes and the majority of installs, and
 * dropping adoption would stop the plugin matching its host theme's colours.
 * Instead each semantic colour keeps the full chain
 *      BuddyX -> Reign/global -> --wp--preset--* -> canonical hex
 * and only the TERMINAL fallback is reconciled to the spec 09 §A4 palette
 * (primary #2563eb, success #16a34a, danger #dc2626, warning #d97706). On a
 * themed site the theme still wins; on a standalone site you get the canonical
 * palette. Best of both, and no regression for the flagship themes.
 *
 * SPACING/RADIUS/TYPE keep their shipped values (space-md 16px, not spec 09's
 * 12px). Every existing component was authored against this scale; retuning it
 * re-flows all of them at once. The tighter spec scale lands with the component
 * rewrites (Phase 3c/3d), where each surface can be verified individually.
 *
 * @package BuddyPress_Member_Blog
 * @since   3.4.2
 * @since   4.0.0 Became the single token source; added the component library.
 * @since   4.3.0 Split from bpmb-ui.css: tokens + reader components only.
 */

/*--------------------------------------------------------------
# Layer 2 — semantic tokens (declared ONCE, here)
--------------------------------------------------------------*/
:root {

    /*
     * --- Brand / primary ------------------------------------
     *
     * THE SEAM. These eight tokens (primary, primary-hover, text-primary,
     * text-secondary, bg-primary, bg-secondary, border-color, border-color-dark)
     * are the ONLY ones a theme is expected to set. Everything tinted or shaded
     * below derives from them with color-mix, so one override recolours the ramp
     * in-hue instead of leaving a coral button on a blue tint.
     *
     * Semantic colours (success/danger/warning/info/scheduled) and the Google SERP
     * preview colours deliberately do NOT derive from the seam and are not
     * overridable — a "delete" button that is not red is a safety defect, and a
     * recoloured search preview is a wrong preview. See bpmb_color_tokens().
     *
     * `--wp--preset--color--accent` comes before `--primary`: TT4 and TT5 ship
     * `accent` and have no `primary` slug at all, so the old chain fell straight
     * through to the hex on the two most common block themes.
     */
    --bpmb-primary: var(--bx-color-accent, var(--color-theme-primary, var(--reign-colors-theme, var(--wp--preset--color--accent, var(--wp--preset--color--primary, #2563eb)))));
    /*
     * THE HOVER FILL — and its last resort has to follow the accent, not a blue we chose.
     *
     * The chain is right: a theme that states its own button-hover colour should win, that is
     * what the seam is for. What was wrong is where the chain ENDED. `#1d4ed8` is our default
     * blue, so on any theme that sets neither variable — a block theme, most generic themes —
     * a red accent button turned BLUE under the cursor. Exactly the "coral button on a blue
     * tint" the comment above this block warns about, three lines under the warning.
     *
     * The fallback is kept in its own token so the dark block can flip its direction without
     * disturbing the theme chain: hovering DARKENS on a light page and LIGHTENS on a dark one,
     * which is what themes themselves do (BuddyX Pro's own hover is #ff8989 against a #ff6b6b
     * accent). Overriding `--bpmb-primary-hover` directly in the dark block would have thrown
     * the theme's own value away.
     */
    --bpmb-primary-hover-fallback: #1d4ed8;
    --bpmb-primary-hover-fallback: color-mix(in srgb, var(--bpmb-primary) 85%, #000);
    --bpmb-primary-hover: var(--bx-color-button-bg-hover, var(--reign-accent-hover-color, var(--bpmb-primary-hover-fallback)));

    /*
     * Derived from the seam. Each token keeps its old literal on the line above as
     * the pre-color-mix fallback — the same two-declaration pattern already used by
     * --bpmb-primary-filled below.
     */
    --bpmb-primary-dark: #1d4ed8;
    --bpmb-primary-dark: color-mix(in srgb, var(--bpmb-primary) 82%, #000);
    --bpmb-primary-light: #e8f0fb;
    --bpmb-primary-light: color-mix(in srgb, var(--bpmb-primary) 12%, var(--bpmb-bg-primary));
    --bpmb-primary-light-hover: #dbeafe;
    --bpmb-primary-light-hover: color-mix(in srgb, var(--bpmb-primary) 18%, var(--bpmb-bg-primary));
    --bpmb-primary-light-border: #bfdbfe;
    --bpmb-primary-light-border: color-mix(in srgb, var(--bpmb-primary) 32%, var(--bpmb-bg-primary));
    --bpmb-primary-ultra-light: #eff6ff;
    --bpmb-primary-ultra-light: color-mix(in srgb, var(--bpmb-primary) 7%, var(--bpmb-bg-primary));

    --bpmb-secondary: #6b7280;
    --bpmb-secondary-hover: #545b62;
    --bpmb-secondary-light: #f8f9fa;
    --bpmb-secondary-light: color-mix(in srgb, var(--bpmb-text-primary) 3%, var(--bpmb-bg-primary));

    --bpmb-accent: var(--bpmb-primary);
    --bpmb-link-color: var(--bpmb-primary);

    /* --- Semantic status ------------------------------------ */
    --bpmb-success: #16a34a;

    /* TEXT ON A SUCCESS-COLOURED SURFACE — the same problem as `--bpmb-on-danger` below, failing in
     * the opposite mode, which is why neither was noticed.
     *
     * White on this green is 3.3:1. Near-black on it is 6.01:1. `--bpmb-text-light` is white in
     * light mode and (via BuddyX Pro's mapping) near-black in dark, so every filled success pill was
     * FAILING IN LIGHT MODE and passing in dark by accident — while the danger pills next to them
     * did exactly the reverse. A theme's mapping was the only thing deciding either.
     *
     * Fixed by darkening the green for FILLED use, the same move `--bpmb-primary-filled` already
     * makes for the accent and for the same reason, so a white label is safe in both modes and the
     * Accept/Decline pair reads as one control set instead of one white label and one black one.
     * White on the darkened green is 5.1:1. `--bpmb-success` itself is untouched: it is still the
     * right colour for a success ICON or BORDER, where the 3:1 non-text bar applies. */
    --bpmb-on-success: #fff;
    --bpmb-success-filled: #117c38;
    --bpmb-success-filled: color-mix(in srgb, var(--bpmb-success) 78%, #000);

    --bpmb-success-bg: #dcfce7;
    --bpmb-success-light: #ebf9eb;
    --bpmb-success-light-2: #d4edda;
    --bpmb-success-text: #155724;

    --bpmb-danger: #dc2626;

    /* TEXT ON A DANGER-COLOURED SURFACE — and it must NOT be `--bpmb-text-light`.
     *
     * `--bpmb-danger` is a fixed dark red in BOTH modes, so its foreground has to be fixed light in
     * both modes. `--bpmb-text-light` is not: **BuddyX Pro maps it at `:root`** in
     * `bp-plugin-support.css` (`--bpmb-text-light: var(--color-theme-white, #ecf0f1)`), and that
     * theme's "white" resolves to #0a0a0a in dark mode. So the delete button rendered near-black
     * text on the red — measured 4.1:1 against AA's 4.5.
     *
     * The theme's mapping is not wrong for what it is aiming at; most `--bpmb-text-light` uses sit
     * on `--bpmb-primary`, which DOES go pale in dark mode, and there dark text is correct. It only
     * breaks where the background is fixed. Hence a separate token rather than fighting the theme:
     * a name it does not map, for the one case where the foreground must not follow the mode.
     *
     * `--bpmb-on-primary` is the equivalent for the accent, and deliberately flips where this
     * deliberately does not. And per the same reasoning, the darkened hover below keeps the button
     * FILLED: the old hover inverted it to danger-red-on-danger-tint, which measured 2.67:1 in dark
     * and 4.35:1 in light — a hover state less readable than the resting one, in both modes. */
    --bpmb-on-danger: #fff;
    --bpmb-danger-strong: #b91c1c;
    --bpmb-danger-strong: color-mix(in srgb, var(--bpmb-danger) 85%, #000);

    --bpmb-danger-bg: #fee2e2;
    --bpmb-danger-light: #fce8ea;
    --bpmb-danger-light-hover: #fee2e2;
    --bpmb-danger-light-border: #fecaca;
    --bpmb-danger-ultra-light: #fef2f2;
    --bpmb-danger-text: #721c24;

    --bpmb-warning: #d97706;
    --bpmb-warning-bg: #fef3c7;
    --bpmb-warning-light: #fff8e1;
    --bpmb-warning-light-2: #fff3cd;
    --bpmb-warning-text: #92400e;

    --bpmb-info: #2563eb;
    --bpmb-info-bg: #dbeafe;
    --bpmb-info-light: #e7f6f8;
    --bpmb-info-light-2: #d1ecf1;
    --bpmb-info-text: #0c5460;

    --bpmb-scheduled: #6f42c1;
    --bpmb-scheduled-light: #e2d9f3;
    --bpmb-scheduled-light-2: #f3e8ff;
    --bpmb-scheduled-text: #4a2c8a;

    --bpmb-featured: #f59e0b;
    --bpmb-unlimited: #059669;

    /* --- Text ------------------------------------------------ */
    --bpmb-text-primary: var(--bx-color-fg, var(--reign-site-body-text-color, var(--global-font-color, var(--wp--preset--color--contrast, #111827))));
    --bpmb-text-secondary: var(--bx-color-fg-muted, var(--reign-site-alternate-text-color, var(--global-font-color, #6b7280)));
    /*
     * Derived, and a contrast fix at the same time. As literals these measured
     * 2.56:1 and 2.54:1 on white — both below WCAG AA — and they assumed a white
     * surface, so on a dark theme they were computed against the wrong ground.
     * Mixing toward the actual surface tracks both modes.
     */
    /*
     * 85%, not 78%. At 78% this resolved to rgb(119,119,119) on white — 4.48:1, missing AA by
     * two hundredths, which is a fail with no margin for a theme whose secondary text sits a
     * shade lighter than BuddyX's. Measured on the composer's upload hint, the smallest real
     * use of it. 85% gives 5.41:1 and is still clearly a step lighter than `--bpmb-text-secondary`
     * (8.06:1), which is the only job this token has.
     */
    --bpmb-text-tertiary: #767676;
    --bpmb-text-tertiary: color-mix(in srgb, var(--bpmb-text-secondary) 85%, var(--bpmb-bg-primary));
    --bpmb-text-muted: #9ca3af;
    --bpmb-text-muted: color-mix(in srgb, var(--bpmb-text-secondary) 85%, var(--bpmb-bg-primary));
    --bpmb-text-light: #fff;
    --bpmb-text-dark: #111827;
    --bpmb-text-color: var(--bpmb-text-primary);

    /* --- Surfaces -------------------------------------------- */
    --bpmb-bg-primary: var(--bx-color-bg-elevated, var(--color-theme-white-box, var(--reign-site-sections-bg-color, var(--wp--preset--color--base, #fff))));
    --bpmb-bg-secondary: var(--bx-color-bg-muted, var(--reign-site-secondary-bg-color, var(--global-body-lightcolor, #f8f9fa)));
    --bpmb-bg-tertiary: #f2f2f2;
    --bpmb-bg-tertiary: color-mix(in srgb, var(--bpmb-text-primary) 6%, var(--bpmb-bg-primary));
    --bpmb-bg-hover: rgba(0, 0, 0, 0.05);
    --bpmb-bg-active: rgba(0, 0, 0, 0.08);
    --bpmb-bg-light: #f8fafc;
    --bpmb-bg-light: color-mix(in srgb, var(--bpmb-text-primary) 3%, var(--bpmb-bg-primary));
    --bpmb-bg-lighter: #f9fafb;
    --bpmb-bg-lighter: color-mix(in srgb, var(--bpmb-text-primary) 2%, var(--bpmb-bg-primary));
    --bpmb-bg-dark: #f2f4f6;
    --bpmb-bg-dark: color-mix(in srgb, var(--bpmb-text-primary) 7%, var(--bpmb-bg-primary));
    --bpmb-bg-white: #fff;
    --bpmb-bg-calendar: #f8fafc;
    --bpmb-bg-calendar: color-mix(in srgb, var(--bpmb-text-primary) 3%, var(--bpmb-bg-primary));
    --bpmb-white: #fff;

    /* TEXT ON A PRIMARY-COLOURED SURFACE.
     *
     * NOT `--bpmb-white`, and not a raw `#fff`. In dark mode `--bpmb-primary` becomes a LIGHT
     * accent (Reign resolves it to a pale blue), so "white on primary" inverts into white on
     * near-white. Measured on the follow button with Reign's toggle set to dark: 1.89:1, against a
     * WCAG AA requirement of 4.5:1 — effectively unreadable.
     *
     * So the foreground has to flip with the mode, and this token is what does it. Anything sitting
     * on `--bpmb-primary` uses this, never `#fff`. */
    --bpmb-on-primary: #fff;

    /* THE FILLED-BUTTON BACKGROUND — and it is NOT simply `--bpmb-primary`.
     *
     * `--bpmb-primary` is the THEME's accent, whatever the theme happens to pick, and a theme is
     * free to pick an accent that white text cannot sit on. Measured: Reign's blue (#1d76da) carries
     * white at 4.51:1 — just passes. BuddyX's coral (#ef5455) carries it at 3.46:1 — fails, and the
     * follow button has a text label, so it needs 4.5.
     *
     * And there is no single foreground that works everywhere: white fails on the coral, dark text
     * fails on the blue. The luminance of the accent decides, and CSS cannot branch on luminance
     * (`color-contrast()` is not shipped).
     *
     * So darken the accent instead, just enough that white is always safe. `color-mix()` keeps the
     * theme's HUE — a site that picked coral still gets coral, a shade deeper — rather than
     * abandoning the theme for a colour of our own. The plain `--bpmb-primary` line above it is the
     * fallback for a browser without color-mix.
     *
     * Not needed in dark mode: there the accent goes pale and `--bpmb-on-primary` goes dark, which
     * already measures 7.13:1 on BuddyX and 8.66:1 on Reign. */
    --bpmb-primary-filled: var(--bpmb-primary);
    --bpmb-primary-filled: color-mix(in srgb, var(--bpmb-primary) 78%, #000);

    /* THE FILLED BUTTON'S HOVER — a shade of the fill, never the theme's hover.
     *
     * `--bpmb-primary-hover` is the THEME's hover accent, and Reign's is LIGHTER than its accent
     * (#3c8ce6 behind #1d76da). A filled button that hovered to it put its white label on 3.45:1,
     * so the text faded exactly when the member reached for it. The hover of a filled button has
     * to move AWAY from the label: darker here, lighter in dark mode (see the dark block). */
    --bpmb-primary-filled-hover: var(--bpmb-primary-hover);
    --bpmb-primary-filled-hover: color-mix(in srgb, var(--bpmb-primary-filled) 85%, #000);

    /* THE ACCENT AS INK — accent-coloured TEXT on a page surface.
     *
     * The mirror image of `--bpmb-primary-filled` above, and it needs its own token for the same
     * reason: the theme's raw accent is chosen to be seen, not to be read. Measured as text on the
     * card surface (#f7f7f9): BuddyX Pro's red 3.62:1, BuddyX's coral 3.23:1 — both under the 4.5:1
     * AA needs for body-size text. Reign's blue and our own default clear it, so the failure depends
     * entirely on which accent the site picked, which is exactly what a token is for.
     *
     * Darkened the same way, keeping the theme's hue. Worst case across the accents we ship against
     * — BuddyX, BuddyX Pro, Reign, our default, a Twenty-x green — is coral at 4.97:1.
     *
     * IT LANDS ON THE SAME 78% AS `--bpmb-primary-filled` BY COINCIDENCE, NOT BY SHARED MEANING.
     * That one asks "how dark before WHITE is safe ON it", this one asks "how dark before IT is safe
     * on white". Two questions, two answers that currently agree. Keep them as two declarations so
     * that retuning one cannot silently retune the other.
     *
     * Dark mode resets it to the plain accent (see the dark block): there the accent is already pale
     * against a dark surface — the context link measures 7.13:1 untouched — and darkening it would
     * push it back towards the background. */
    --bpmb-primary-ink: var(--bpmb-primary);
    --bpmb-primary-ink: color-mix(in srgb, var(--bpmb-primary) 78%, #000);
    --bpmb-black: #000;
    --bpmb-btn-white: #fff;

    /* --- Borders --------------------------------------------- */
    --bpmb-border-color: var(--bx-color-border, var(--global-border-color, var(--reign-site-border-color, #e2e8f0)));
    --bpmb-border-color-dark: var(--bx-color-border-strong, #ccc);
    --bpmb-border-color-light: rgba(0, 0, 0, 0.1);
    --bpmb-border-color-input: #d0d5dd;
    --bpmb-border-color-input: color-mix(in srgb, var(--bpmb-border-color) 70%, var(--bpmb-text-primary));
    --bpmb-border-strong: rgba(0, 0, 0, 0.2);
    --bpmb-border-calendar: #e2e8f0;
    --bpmb-border-calendar: var(--bpmb-border-color);

    /* --- Grays (neutral ramp) -------------------------------- */
    --bpmb-gray-50: #f9fafb;
    --bpmb-gray-100: #f8fafc;
    --bpmb-gray-200: #e5e7eb;
    --bpmb-gray-300: #e2e8f0;
    --bpmb-gray-400: #d1d5db;
    --bpmb-gray-500: #9ca3af;
    --bpmb-gray-600: #6b7280;
    --bpmb-gray-700: #374151;
    --bpmb-gray-800: #1f2937;
    --bpmb-gray-900: #334155;
    --bpmb-gray-950: #667085;

    /* --- Overlays -------------------------------------------- */
    --bpmb-overlay-bg-rgb: 255, 255, 255;
    --bpmb-overlay-lightest: rgba(0, 0, 0, 0.03);
    --bpmb-overlay-lighter: rgba(0, 0, 0, 0.05);
    --bpmb-overlay-hover: rgba(0, 0, 0, 0.1);
    --bpmb-overlay-dashed: rgba(0, 0, 0, 0.2);
    --bpmb-overlay-light: rgba(0, 0, 0, 0.3);
    --bpmb-overlay-medium: rgba(0, 0, 0, 0.5);
    --bpmb-overlay-dark: rgba(0, 0, 0, 0.6);
    --bpmb-overlay-heavy: rgba(0, 0, 0, 0.75);

    /* --- Spacing (shipped scale — see header note) ----------- */
    --bpmb-space-xs: 4px;
    --bpmb-space-sm: 8px;
    --bpmb-space-md: 16px;
    --bpmb-space-lg: 24px;
    --bpmb-space-xl: 32px;
    --bpmb-space-2xl: 48px;

    /* --- Radius ---------------------------------------------- */
    --bpmb-radius-sm: 4px;
    --bpmb-radius-md: 6px;
    --bpmb-radius-lg: 10px;
    --bpmb-radius-xl: 16px;
    --bpmb-radius-full: 100px;
    /* Aliases used by the Pro skins — same scale, older names. */
    --bpmb-border-radius-sm: 4px;
    --bpmb-border-radius: 6px;
    --bpmb-border-radius-md: 8px;
    --bpmb-border-radius-lg: 12px;
    --bpmb-border-radius-xl: 20px;

    /* --- Typography ------------------------------------------ */
    --bpmb-font-size-xs: 12px;
    --bpmb-font-size-sm: 13px;
    --bpmb-font-size-md: 14px;
    --bpmb-font-size-base: 15px;
    --bpmb-font-size-lg: 16px;
    --bpmb-font-size-xl: 18px;
    --bpmb-font-size-2xl: 20px;
    --bpmb-font-size-3xl: 22px;

    --bpmb-font-weight-normal: 400;
    --bpmb-font-weight-medium: 500;
    --bpmb-font-weight-semibold: 600;
    --bpmb-font-weight-bold: 700;

    --bpmb-line-height-tight: 1.2;
    --bpmb-line-height-base: 1.5;
    --bpmb-line-height-relaxed: 1.7;

    /* --- Shadows --------------------------------------------- */
    --bpmb-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
    --bpmb-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
    --bpmb-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
    --bpmb-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.08);
    --bpmb-shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.2);
    --bpmb-shadow-calendar: 0 10px 40px rgba(0, 0, 0, 0.15);
    --bpmb-shadow-overlay: rgba(0, 0, 0, 0.5);
    --bpmb-shadow-focus: 0 0 0 3px rgba(37, 99, 235, 0.25);

    /* --- Motion ---------------------------------------------- */
    --bpmb-transition-fast: 150ms ease;
    --bpmb-transition-base: 200ms ease;
    --bpmb-transition-normal: 200ms ease;
    --bpmb-transition-slow: 300ms ease;
    --bpmb-transition-slowest: 500ms ease;

    /* --- Z-index --------------------------------------------- */
    --bpmb-z-dropdown: 100;
    --bpmb-z-modal: 1000;
    --bpmb-z-tooltip: 1100;
    --bpmb-z-overlay: 99999;

    /* --- Component-ish singletons used across files ---------- */
    --bpmb-skeleton-base: #f0f0f0;
    --bpmb-skeleton-base: color-mix(in srgb, var(--bpmb-text-primary) 8%, var(--bpmb-bg-primary));
    --bpmb-skeleton-sheen: #e0e0e0;
    --bpmb-skeleton-sheen: color-mix(in srgb, var(--bpmb-text-primary) 14%, var(--bpmb-bg-primary));
    --bpmb-spinner-track: rgba(0, 0, 0, 0.15);
    --bpmb-progress-track: rgba(0, 0, 0, 0.08);

    --bpmb-input-bg: var(--bpmb-bg-primary);
    --bpmb-input-border-color: var(--bpmb-border-color-input);
    --bpmb-input-border-radius: var(--bpmb-radius-md);
    --bpmb-input-focus-border: var(--bpmb-primary);
    --bpmb-input-focus-shadow: var(--bpmb-shadow-focus);
    --bpmb-input-font-size: var(--bpmb-font-size-md);
    --bpmb-input-height: 40px;
    --bpmb-input-padding-x: 12px;
    --bpmb-input-padding-y: 8px;

    /* Google SERP preview (SEO panel) — intentionally literal brand colours. */
    --bpmb-google-title-color: #1a0dab;
    --bpmb-google-url-color: #006621;
    --bpmb-google-desc-color: #545454;
    --bpmb-google-desc-muted: #888;

    /* Calendar + series derived tokens (Pro consumes these). */
    --bpmb-text-calendar: #64748b;
    --bpmb-text-calendar: color-mix(in srgb, var(--bpmb-text-secondary) 92%, var(--bpmb-bg-primary));
    --bpmb-cal-published: var(--bpmb-success);
    --bpmb-cal-published-soft: var(--bpmb-success-bg);
    --bpmb-cal-scheduled: var(--bpmb-scheduled);
    --bpmb-cal-scheduled-soft: var(--bpmb-scheduled-light-2);

    /* THE SAME TWO STATUS COLOURS, AS INK.
     *
     * `--bpmb-cal-published` and `--bpmb-cal-scheduled` are the FULL-STRENGTH status colours: right
     * for a dot, a chip fill or a border, where the bar is 3:1. Used as TEXT they are not: measured
     * in dark mode, the day-add hover put the green on its own 18% tint at 3.89:1, and "Schedule a
     * post" put the violet on the panel at 2.54:1.
     *
     * `--bpmb-success-text` and `--bpmb-scheduled-text` already existed and already flip with the
     * mode - they were simply never wired to the calendar, so the calendar reached for the fill
     * colour and used it as a foreground. These two aliases close that, and keep Pro reading only
     * `--bpmb-cal-*` rather than having to know which underlying ramp each one comes from. */
    --bpmb-cal-published-ink: var(--bpmb-success-text);
    --bpmb-cal-scheduled-ink: var(--bpmb-scheduled-text);
    --bpmb-cal-draft: var(--bpmb-text-muted);
    --bpmb-cal-draft-soft: var(--bpmb-bg-secondary);

    /* Role badges (Pro co-authors). The author badge reuses the primary ramp;
       co-author gets its own violet so the two roles are distinguishable at a
       glance without relying on the label alone. */
    --bpmb-role-coauthor-bg: #f3e8ff;
    --bpmb-role-coauthor-fg: #7c3aed;

    --bpmb-series-primary: var(--bpmb-primary);
    --bpmb-series-primary-hover: var(--bpmb-primary-hover);
    --bpmb-series-success: var(--bpmb-success);
    --bpmb-series-bg: var(--bpmb-bg-primary);
    --bpmb-series-bg-light: var(--bpmb-bg-secondary);
    --bpmb-series-border: var(--bpmb-border-color);
    --bpmb-series-text: var(--bpmb-text-primary);
    --bpmb-series-text-muted: var(--bpmb-text-secondary);
    --bpmb-series-radius: var(--bpmb-radius-lg);
    --bpmb-series-shadow: var(--bpmb-shadow-sm);

    /* --- Icons (Lucide) -------------------------------------- */
    --bpmb-icon-stroke: 1.75;
    --bpmb-icon-xs: 12px;
    --bpmb-icon-sm: 14px;
    --bpmb-icon-md: 16px;
    --bpmb-icon-lg: 20px;
    --bpmb-icon-xl: 24px;

    /* --- Admin surface set (wp-admin only) ------------------- */
    --bpmb-admin-accent: var(--bpmb-primary);
    --bpmb-admin-accent-hover: var(--bpmb-primary-hover);
    --bpmb-admin-accent-light: #eff6ff;
    --bpmb-admin-success: var(--bpmb-success);
    --bpmb-admin-success-light: var(--bpmb-success-bg);
    --bpmb-admin-warn: var(--bpmb-warning);
    --bpmb-admin-warn-light: var(--bpmb-warning-bg);
    --bpmb-admin-danger: var(--bpmb-danger);
    --bpmb-admin-white: #fff;
    --bpmb-admin-surface-1: #fff;
    --bpmb-admin-surface-2: #f8fafc;
    --bpmb-admin-text: #1e293b;
    --bpmb-admin-text-2: #475569;
    --bpmb-admin-text-3: #64748b;
    --bpmb-admin-text-4: #334155;
    --bpmb-admin-text-muted: #94a3b8;
    --bpmb-admin-border: #e2e8f0;
    --bpmb-admin-border-strong: #cbd5e1;
    /*
     * Aliases consumed by Pro's admin stylesheet. These were NEVER declared
     * anywhere and carry no hex fallback, so every rule using them resolved to
     * an invalid value and was dropped by the browser — Pro's admin styling has
     * been silently partly-dead. Defining them here fixes that; they alias the
     * canonical set above rather than introducing new values.
     */
    --bpmb-admin-primary: var(--bpmb-admin-accent);
    --bpmb-admin-primary-dark: var(--bpmb-primary-dark);
    --bpmb-admin-primary-bg: var(--bpmb-admin-accent-light);
    --bpmb-admin-primary-border: var(--bpmb-primary-light-border);
    --bpmb-admin-primary-legacy: var(--bpmb-admin-accent);
    --bpmb-admin-accent-bg: var(--bpmb-admin-accent-light);

    --bpmb-admin-error: var(--bpmb-danger);
    --bpmb-admin-error-dark: #b91c1c;
    --bpmb-admin-error-darker: #991b1b;
    --bpmb-admin-error-bg: var(--bpmb-danger-bg);
    --bpmb-admin-error-border: var(--bpmb-danger-light-border);

    --bpmb-admin-success-bg: var(--bpmb-success-bg);
    --bpmb-admin-success-dark: #15803d;

    --bpmb-admin-warning-bg: var(--bpmb-warning-bg);
    --bpmb-admin-warning-border: #fcd34d;
    --bpmb-admin-warning-icon: var(--bpmb-warning);
    --bpmb-admin-warning-text: var(--bpmb-warning-text);

    --bpmb-admin-info: var(--bpmb-info);
    --bpmb-admin-info-bg: var(--bpmb-info-bg);
    --bpmb-admin-info-text: #1e40af;

    --bpmb-admin-surface: var(--bpmb-admin-surface-1);
    --bpmb-admin-surface-alt: var(--bpmb-admin-surface-2);
    --bpmb-admin-surface-subtle: #f8fafc;
    --bpmb-admin-surface-muted: #f1f5f9;
    --bpmb-admin-surface-hover: #f1f5f9;
    --bpmb-admin-surface-info: var(--bpmb-info-bg);

    --bpmb-admin-text-primary: var(--bpmb-admin-text);
    --bpmb-admin-text-secondary: var(--bpmb-admin-text-2);
    --bpmb-admin-text-dark: #0f172a;
    --bpmb-admin-text-medium: var(--bpmb-admin-text-4);
    --bpmb-admin-text-subtle: var(--bpmb-admin-text-muted);

    --bpmb-admin-border-light: #eef2f6;
    --bpmb-admin-border-muted: #f1f5f9;
    --bpmb-admin-disabled: #cbd5e1;
    --bpmb-admin-conditional-bg: #f8fafc;
    --bpmb-admin-conditional-text: var(--bpmb-admin-text-3);

    --bpmb-admin-danger-dark: #b91c1c;
    --bpmb-admin-danger-light: #fee2e2;
    --bpmb-admin-radius: 10px;
    --bpmb-admin-radius-sm: 6px;
    --bpmb-admin-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
    --bpmb-admin-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.08);
    --bpmb-admin-gap-sm: 8px;
    --bpmb-admin-gap-md: 16px;
    --bpmb-admin-gap-lg: 24px;
    --bpmb-admin-gap-xl: 32px;
    --bpmb-admin-tap: 40px;

    /*
     * Dark palette for the WordPress media modal (and one Editor.js popover
     * rule). A `-dark-` PREFIXED token is an anti-pattern under spec 09 - dark
     * mode should be the same token with a different value - and these hold
     * DARK values unconditionally, so every rule reading them MUST be scoped to
     * `body.bpmb-dark` itself. The media block was not, which is how 4.0.1
     * shipped a dark modal onto light-mode sites.
     *
     * The earlier note here claimed "the Editor.js/Medium chrome consumes these
     * names directly". It does not: every consumer is in the media-modal block
     * of bp-member-blog-editor.css, plus a single `.ce-popover` background.
     * `--bpmb-dark-text-muted` had no consumer at all and is gone.
     */
    --bpmb-dark-bg-primary: #1e1e1e;
    --bpmb-dark-bg-secondary: #2c3338;
    --bpmb-dark-bg-tertiary: #3c434a;
    --bpmb-dark-text-primary: #c3c4c7;

    /* 4.5:1 minimum on --bpmb-dark-bg-secondary, where the sidebar's 12px field
       labels sit. The previous #8c8f94 measured 3.95:1; this is 5.49:1. */
    --bpmb-dark-text-secondary: #a7aaad;
    --bpmb-dark-border-color: #3c434a;
    --bpmb-dark-border-color-light: #50575e;
    --bpmb-dark-accent: #72aee6;
    --bpmb-dark-accent-hover: #a3c9e6;
    --bpmb-dark-success: #46b450;
    --bpmb-dark-shadow: rgba(0, 0, 0, .15);
}

/*--------------------------------------------------------------
# Dark mode — root-level token overrides ONLY (spec 09 §4)
--------------------------------------------------------------
Never a per-component dark rule: dark mode is the SAME token with a different
value. Consolidated here from 491 scattered declarations across 15 files.
Selector list covers Reign (data-bx-mode), BuddyX, and generic conventions. */
:root[data-bx-mode="dark"],
:root[data-theme="dark"],
html.dark,
html.dark-mode,
body.dark-mode,
body.dark-scheme,
.bpmb-dark {
    /* Primary is a LIGHT accent in dark mode, so text on it must be DARK. Measured against Reign's
       dark palette: #1a2028 on the pale-blue accent is ~8.9:1, comfortably AA. */
    --bpmb-on-primary: var(--bpmb-bg-primary, #1a2028);

    /* In dark mode the accent is already pale and the label is dark, so leave the fill alone —
       darkening it here would only muddy it. Measured: 8.66:1 on Reign, 7.13:1 on BuddyX. */
    --bpmb-primary-filled: var(--bpmb-primary);

    /* The label is dark here, so the filled hover LIGHTENS - which is what the theme's hover
       already does in dark mode (below). */
    --bpmb-primary-filled-hover: var(--bpmb-primary-hover);

    /* Same for the accent used as ink: the pale accent on a dark surface already passes (7.13:1 on
       BuddyX Pro), and the light-mode darkening would push it back towards the background. */
    --bpmb-primary-ink: var(--bpmb-primary);

    /*
     * Hover LIGHTENS on a dark page, where light mode darkens.
     *
     * THE WHOLE CHAIN IS REPEATED HERE, and it has to be. Redefining only
     * `--bpmb-primary-hover-fallback` in this block does nothing: `--bpmb-primary-hover` is
     * DECLARED at `:root`, and a custom property's `var()`s are substituted where the property
     * is declared, not where it is read. The fallback token would be re-read at `:root` and this
     * override never consulted — measured, and the dark hover kept darkening.
     *
     * So the theme chain is written out again with the other tail. The theme still wins in both
     * modes; only the last resort differs. Two declarations as everywhere else in this file: the
     * first is what a browser without color-mix gets.
     */
    --bpmb-primary-hover: var(--bx-color-button-bg-hover, var(--reign-accent-hover-color, #60a5fa));
    --bpmb-primary-hover: var(--bx-color-button-bg-hover, var(--reign-accent-hover-color, color-mix(in srgb, var(--bpmb-primary) 85%, #fff)));

    /* THE ACCENT TINTS, AT AN ALPHA RATHER THAN MIXED INTO THE PAGE.
     *
     * Light mode mixes these into `--bpmb-bg-primary` to get an opaque pastel; dark mode wants an
     * alpha instead, so the panel underneath keeps showing through and the tint stays a tint rather
     * than a flat block that fights the surface it sits on.
     *
     * WHAT THEY MUST NOT DO — and did — IS FORGET THE THEME. These three were the plugin's own
     * default blue, hardcoded, so on a red-accent theme a chip rendered the theme's red text on a
     * BLUE tint. Visible on the series landing page in dark mode: the "Up next" chip. Light mode
     * derived them from the accent all along, so it was dark mode alone that dropped the theme.
     *
     * The literal rgba line above each one is the fallback for a browser without color-mix; it
     * keeps the old (blue) behaviour there rather than losing the tint entirely. */
    --bpmb-primary-light: rgba(37, 99, 235, 0.18);
    --bpmb-primary-light: color-mix(in srgb, var(--bpmb-primary) 18%, transparent);
    --bpmb-primary-light-hover: rgba(37, 99, 235, 0.26);
    --bpmb-primary-light-hover: color-mix(in srgb, var(--bpmb-primary) 26%, transparent);
    --bpmb-primary-ultra-light: rgba(37, 99, 235, 0.12);
    --bpmb-primary-ultra-light: color-mix(in srgb, var(--bpmb-primary) 12%, transparent);

    /* Role badges: the light-mode pastels go muddy on a dark surface — tint the
       surface instead, and lift the text to a shade that still passes contrast. */
    --bpmb-role-coauthor-bg: rgba(124, 58, 237, 0.26);
    --bpmb-role-coauthor-fg: #ddd6fe;

    --bpmb-success-light: rgba(22, 163, 74, 0.18);
    --bpmb-success-light-2: rgba(22, 163, 74, 0.24);
    --bpmb-success-bg: rgba(22, 163, 74, 0.18);
    --bpmb-success-text: #86efac;

    --bpmb-danger-light: rgba(220, 38, 38, 0.18);
    --bpmb-danger-light-hover: rgba(220, 38, 38, 0.26);
    --bpmb-danger-ultra-light: rgba(220, 38, 38, 0.12);
    --bpmb-danger-bg: rgba(220, 38, 38, 0.18);
    --bpmb-danger-text: #fca5a5;

    --bpmb-warning-light: rgba(217, 119, 6, 0.18);
    --bpmb-warning-light-2: rgba(217, 119, 6, 0.24);
    --bpmb-warning-bg: rgba(217, 119, 6, 0.18);
    --bpmb-warning-text: #fcd34d;

    --bpmb-info-light: rgba(37, 99, 235, 0.18);
    --bpmb-info-light-2: rgba(37, 99, 235, 0.24);
    --bpmb-info-bg: rgba(37, 99, 235, 0.18);
    --bpmb-info-text: #93c5fd;

    --bpmb-scheduled-light: rgba(111, 66, 193, 0.2);
    --bpmb-scheduled-light-2: rgba(111, 66, 193, 0.26);
    --bpmb-scheduled-text: #c4b5fd;

    --bpmb-text-tertiary: #94a3b8;
    --bpmb-text-muted: #9ca3af;
    --bpmb-text-dark: #e5e7eb;

    --bpmb-bg-tertiary: rgba(255, 255, 255, 0.08);
    --bpmb-bg-hover: rgba(255, 255, 255, 0.06);
    --bpmb-bg-active: rgba(255, 255, 255, 0.1);
    --bpmb-bg-light: rgba(255, 255, 255, 0.04);
    --bpmb-bg-lighter: rgba(255, 255, 255, 0.03);
    --bpmb-bg-dark: rgba(255, 255, 255, 0.06);
    --bpmb-bg-calendar: rgba(255, 255, 255, 0.04);

    --bpmb-border-color-light: rgba(255, 255, 255, 0.12);
    --bpmb-border-color-input: rgba(255, 255, 255, 0.2);
    --bpmb-border-strong: rgba(255, 255, 255, 0.28);
    --bpmb-border-calendar: rgba(255, 255, 255, 0.12);

    --bpmb-overlay-bg-rgb: 26, 26, 26;
    --bpmb-overlay-lightest: rgba(255, 255, 255, 0.03);
    --bpmb-overlay-lighter: rgba(255, 255, 255, 0.05);
    --bpmb-overlay-hover: rgba(255, 255, 255, 0.1);
    --bpmb-overlay-dashed: rgba(255, 255, 255, 0.2);

    --bpmb-skeleton-base: rgba(255, 255, 255, 0.08);
    --bpmb-skeleton-sheen: rgba(255, 255, 255, 0.16);
    --bpmb-spinner-track: rgba(255, 255, 255, 0.2);
    --bpmb-progress-track: rgba(255, 255, 255, 0.12);

    --bpmb-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --bpmb-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.45);
    --bpmb-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5);
    --bpmb-shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.45);

    --bpmb-google-title-color: #8ab4f8;
    --bpmb-google-url-color: #81c995;
    --bpmb-google-desc-color: #bdc1c6;
    --bpmb-google-desc-muted: #9aa0a6;

    --bpmb-text-calendar: #94a3b8;

    --bpmb-admin-surface-1: #0f172a;
    --bpmb-admin-surface-2: #1e293b;
    --bpmb-admin-text: #f1f5f9;
    --bpmb-admin-text-2: #cbd5e1;
    --bpmb-admin-text-3: #94a3b8;
    --bpmb-admin-text-4: #e2e8f0;
    --bpmb-admin-border: #334155;
    --bpmb-admin-border-strong: #475569;
    --bpmb-admin-accent-light: rgba(37, 99, 235, 0.16);
}

/*--------------------------------------------------------------
# Dark mode — the SEAM tokens, split by who owns the dark palette
--------------------------------------------------------------

The block above overrides 70 tokens and, until 4.0.2, not one of the eight seam
tokens. That was survivable on Reign and BuddyX, where `--bx-color-*` flips
underneath so the seam moved without anything here saying so. It was NOT
survivable anywhere else: a theme that only sets `html.dark` / `body.dark-mode`
(Blocksy, Kadence, GeneratePress and Astra dark add-ons all do) flipped those 70
tokens while the surfaces and text stayed LIGHT. The shipped result was a mixed
state — status text at 1.4-1.9:1 on white, and the translucent-white surfaces
above compositing over white into invisible panels and invisible borders.

It cannot be fixed with one rule, because the two families need opposite
treatment:

  - Reign/BuddyX set their OWN variables and flip them. Chain to those, so the
    plugin keeps the theme's exact dark shades.
  - The generic conventions do not expose a variable we can read. Chaining is
    actively wrong there: `--bx-color-bg-elevated` is still defined at `:root`
    with its LIGHT value, so `var(--bx-color-bg-elevated, #1a2028)` resolves to
    WHITE and the dark fallback never fires. They need literals.

Specificity settles the overlap: `[data-bx-mode="dark"]` is (0,2,0) and beats
`html.dark` at (0,1,1), so a Reign site that also carries a generic class still
gets the theme-aware values regardless of source order. */
:root[data-bx-mode="dark"],
:root[data-theme="dark"] {
    --bpmb-bg-primary: var(--bx-color-bg-elevated, var(--color-theme-white-box, var(--reign-site-sections-bg-color, #1a2028)));
    --bpmb-bg-secondary: var(--bx-color-bg-muted, var(--reign-site-secondary-bg-color, #232b36));
    --bpmb-text-primary: var(--bx-color-fg, var(--reign-site-body-text-color, #e6eaf2));
    --bpmb-text-secondary: var(--bx-color-fg-muted, var(--reign-site-alternate-text-color, #a9b3c4));
    --bpmb-border-color: var(--bx-color-border, var(--reign-site-border-color, #2f3945));
    --bpmb-border-color-dark: var(--bx-color-border-strong, #3d4757);
}

html.dark,
html.dark-mode,
body.dark-mode,
body.dark-scheme,
.bpmb-dark {
    --bpmb-bg-primary: #1a2028;
    --bpmb-bg-secondary: #232b36;
    --bpmb-text-primary: #e6eaf2;
    --bpmb-text-secondary: #a9b3c4;
    --bpmb-border-color: #2f3945;
    --bpmb-border-color-dark: #3d4757;
}

@media (prefers-color-scheme: dark) {
    :root[data-bx-mode="auto"] {
        /* Accent-derived, for the reason spelled out on the `.bpmb-dark` copy above: the status
           tints below are meant to be fixed colours, this one is meant to be the theme's. */
        --bpmb-primary-light: rgba(37, 99, 235, 0.18);
        --bpmb-primary-light: color-mix(in srgb, var(--bpmb-primary) 18%, transparent);
        --bpmb-success-light: rgba(22, 163, 74, 0.18);
        --bpmb-success-bg: rgba(22, 163, 74, 0.18);
        --bpmb-danger-light: rgba(220, 38, 38, 0.18);
        --bpmb-danger-bg: rgba(220, 38, 38, 0.18);
        --bpmb-warning-light: rgba(217, 119, 6, 0.18);
        --bpmb-warning-bg: rgba(217, 119, 6, 0.18);
        --bpmb-info-light: rgba(37, 99, 235, 0.18);
        --bpmb-info-bg: rgba(37, 99, 235, 0.18);
        --bpmb-text-muted: #9ca3af;
        --bpmb-bg-tertiary: rgba(255, 255, 255, 0.08);
        --bpmb-bg-hover: rgba(255, 255, 255, 0.06);
        --bpmb-skeleton-base: rgba(255, 255, 255, 0.08);
        --bpmb-skeleton-sheen: rgba(255, 255, 255, 0.16);
        --bpmb-overlay-bg-rgb: 26, 26, 26;
        --bpmb-role-coauthor-bg: rgba(124, 58, 237, 0.26);
        --bpmb-role-coauthor-fg: #ddd6fe;
    }
}


/* Native controls follow the theme's dark mode on every Member Blog page: an unchecked checkbox,
   a radio, a date or file picker drew as a bright white square on the dark surface otherwise. */
body.bpmb-dark {
    color-scheme: dark;
}

/*--------------------------------------------------------------
# Utilities
--------------------------------------------------------------*/
.bpmb-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.bpmb-icon {
    display: inline-block;
    flex: 0 0 auto;
    vertical-align: middle;
}

/*--------------------------------------------------------------
# Toast
--------------------------------------------------------------
Lives with the reader components, not the app kit: the clap / bookmark / follow
buttons report a failure with bpmbToast, and those buttons are on reading pages.
Keeping it in the kit made a signed-in reader load the whole kit for this.
--------------------------------------------------------------*/
.bpmb-toast-container {
    --bpmb-toast-offset: 24px;

    position: fixed;
    inset-block-start: var(--bpmb-toast-offset);
    inset-inline-end: 24px;
    z-index: 100001;

    display: flex;
    flex-direction: column;
    gap: 8px;

    max-width: min(380px, calc(100vw - 32px));
    pointer-events: none;
}

/* Clear the WordPress admin bar so toasts never render underneath it. */
body.admin-bar .bpmb-toast-container {
    --bpmb-toast-offset: 56px;
}

@media screen and (max-width: 782px) {
    body.admin-bar .bpmb-toast-container {
        --bpmb-toast-offset: 70px;
    }
}

.bpmb-toast {
    --bpmb-toast-accent: var(--bpmb-info, #2563eb);

    display: flex;
    align-items: flex-start;
    gap: 10px;

    padding: 12px 14px;
    pointer-events: auto;

    font-size: 14px;
    line-height: 1.45;
    color: var(--bpmb-text-primary, #111827);

    background: var(--bpmb-bg-primary, #fff);
    border: 1px solid var(--bpmb-border-color, #e2e8f0);
    border-inline-start: 3px solid var(--bpmb-toast-accent);
    border-radius: 10px;
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, .12), 0 4px 6px -4px rgba(0, 0, 0, .08);

    opacity: 0;
    transform: translateY(-6px);
    transition: opacity .2s cubic-bezier(.4, 0, .2, 1),
                transform .2s cubic-bezier(.4, 0, .2, 1);
}

.bpmb-toast.is-active {
    opacity: 1;
    transform: translateY(0);
}

.bpmb-toast.is-leaving {
    opacity: 0;
    transform: translateY(-6px);
}

.bpmb-toast--success { --bpmb-toast-accent: var(--bpmb-success, #16a34a); }
.bpmb-toast--error   { --bpmb-toast-accent: var(--bpmb-danger, #dc2626); }
.bpmb-toast--warning { --bpmb-toast-accent: var(--bpmb-warning, #d97706); }
.bpmb-toast--info    { --bpmb-toast-accent: var(--bpmb-info, #2563eb); }

.bpmb-toast__icon {
    display: inline-flex;
    flex: 0 0 auto;
    color: var(--bpmb-toast-accent);
}

.bpmb-toast__content {
    flex: 1 1 auto;
    min-width: 0;
}

.bpmb-toast__title {
    font-weight: 600;
}

.bpmb-toast__message {
    overflow-wrap: anywhere;
}

.bpmb-toast__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;

    width: 24px;
    height: 24px;

    color: var(--bpmb-text-muted, #9ca3af);
    background: none;
    border: 0;
    border-radius: 6px;
    cursor: pointer;
}

.bpmb-toast__close:hover {
    color: var(--bpmb-text-primary, #111827);
    background: var(--bpmb-bg-hover, rgba(0, 0, 0, .05));
}

.bpmb-toast__close:focus-visible {
    outline: 2px solid var(--bpmb-toast-accent);
    outline-offset: 1px;
}

@media (max-width: 640px) {
    .bpmb-toast-container {
        inset-block-start: 12px;
        inset-inline: 12px;
        max-width: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .bpmb-toast {
        transition: none;
    }
}

/*--------------------------------------------------------------
# Post list / grid  — FIXES A SHIPPED LAYOUT BUG
--------------------------------------------------------------
The shortcodes put `bpmb-columns-{n}` on the WRAPPER and then nest the cards
inside `<ul class="bpmb-post-list">`. The wrapper's grid therefore had exactly
ONE child (the <ul>), so `columns=3` never produced 3 columns — every post grid
rendered as a single stacked column. `.bpmb-post-list` and `.bpmb-pagination`
also had ZERO rules anywhere in the plugin, so the <li>s showed raw bullets and
pagination rendered as a bulleted list.

Fix: the <ul> inherits the wrapper's column template, so the columns apply to
the CARDS. Works whether `bpmb-columns-{n}` sits on the wrapper (today) or on
the <ul> itself (spec 02 §91, where the Renderer emits it). */
.bpmb-post-list {
    /*
     * The wrapper is the declared grid (`bpmb-columns-{n}`) but the <ul> is its
     * ONLY child, so the <ul> would otherwise sit in column 1 and subdivide just
     * that column — 3 cards squeezed into a third of the row. Span the whole row
     * first, then adopt the wrapper's column template so the CARDS get it.
     */
    grid-column: 1 / -1;

    display: grid;
    grid-template-columns: inherit;
    gap: var(--bpmb-space-lg);

    /* Component geometry, so !important (see the contract above the clap button): a theme's
       `.entry-content ul` (BuddyX: margin-left 1.05em) out-specifies one class and indented
       every card past the toolbar above it. */
    margin: 0 !important;
    padding: 0 !important;
    list-style: none;
}

.bpmb-post-list > li {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* A grid container that directly holds cards (authors) keeps working as-is. */
.bpmb-columns-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.bpmb-columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.bpmb-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.bpmb-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/*
 * The user's `columns` must actually take effect on the discovery lists.
 *
 * The recent / popular / list shortcodes set `bpmb-columns-{n}` on the WRAPPER,
 * but the grid <ul> carries its own `bpmb-columns-3` (grid()'s default, emitted
 * for the direct-call profile / topic-hub templates that have no wrapper). At
 * equal specificity the <ul>'s own class beat the inherited wrapper template, so
 * every list rendered 3 columns and any other `columns` value — including List's
 * default of 1 — was silently ignored.
 *
 * This descendant selector (two classes) outranks the <ul>'s single-class
 * `bpmb-columns-3`, so when a grid <ul> sits inside a columns wrapper the
 * WRAPPER's declared count wins. Direct-call templates have no columns wrapper,
 * so their <ul> keeps its own default untouched.
 */
.bpmb-columns-1 > .bpmb-post-list { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.bpmb-columns-2 > .bpmb-post-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.bpmb-columns-3 > .bpmb-post-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.bpmb-columns-4 > .bpmb-post-list { grid-template-columns: repeat(4, minmax(0, 1fr)); }

@media (max-width: 1024px) {
    .bpmb-columns-3,
    .bpmb-columns-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    .bpmb-columns-3 > .bpmb-post-list,
    .bpmb-columns-4 > .bpmb-post-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
    .bpmb-columns-2,
    .bpmb-columns-3,
    .bpmb-columns-4 { grid-template-columns: minmax(0, 1fr); }

    .bpmb-columns-2 > .bpmb-post-list,
    .bpmb-columns-3 > .bpmb-post-list,
    .bpmb-columns-4 > .bpmb-post-list { grid-template-columns: minmax(0, 1fr); }
}

/*--------------------------------------------------------------
# Pagination
--------------------------------------------------------------
paginate_links( type => 'list' ) emits <ul class="page-numbers">, which had no
styling at all — it rendered as a bulleted list. */
.bpmb-pagination {
    /*
     * Popular / member-list render the pager INSIDE the `bpmb-columns-{n}` grid
     * wrapper (Recent puts it outside). As a grid child it would be trapped in
     * column 1 and wrap onto two lines. Span the full row wherever it lands.
     */
    grid-column: 1 / -1;

    margin-block-start: var(--bpmb-space-lg);
}

.bpmb-pagination ul.page-numbers {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--bpmb-space-xs);

    margin: 0;
    padding: 0;
    list-style: none;
}

.bpmb-pagination ul.page-numbers > li {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The buttons only. `paginate_links( type => list )` also puts `page-numbers` on the
   <ul>, and a bare `.page-numbers` painted the whole list as one wide button. */
.bpmb-pagination :is(a, span).page-numbers {
    display: inline-flex;
    align-items: center;
    justify-content: center;

    /* 40px tap target (spec 09 §4). */
    min-width: 40px;
    min-height: 40px;
    padding-inline: var(--bpmb-space-sm);

    font-size: var(--bpmb-font-size-md);
    line-height: 1;
    text-decoration: none;

    color: var(--bpmb-text-primary);
    background: var(--bpmb-bg-primary);
    border: 1px solid var(--bpmb-border-color);
    border-radius: var(--bpmb-radius-md);

    transition: background-color var(--bpmb-transition-fast),
                border-color var(--bpmb-transition-fast),
                color var(--bpmb-transition-fast);
}

.bpmb-pagination a.page-numbers:hover {
    background: var(--bpmb-bg-secondary);
    border-color: var(--bpmb-border-color-dark);
}

.bpmb-pagination a.page-numbers:focus-visible {
    outline: 2px solid var(--bpmb-primary);
    outline-offset: 2px;
}

.bpmb-pagination .page-numbers.current {
    color: var(--bpmb-text-light);
    background: var(--bpmb-primary);
    border-color: var(--bpmb-primary);
    font-weight: var(--bpmb-font-weight-semibold);
}

.bpmb-pagination .page-numbers.dots {
    border-color: transparent;
    background: transparent;
}

/*--------------------------------------------------------------
# Related posts
--------------------------------------------------------------
An <aside> under an article. The GRID is not defined here: it reuses the shared
`.bpmb-columns-{n}` + `.bpmb-post-list` system above, which already collapses to
one column at 640px. A bespoke breakpoint here would be a second answer to a
question that is already answered. */
.bpmb-related-posts {
    margin-block-start: var(--bpmb-space-2xl);
    padding-block-start: var(--bpmb-space-xl);
    border-block-start: 1px solid var(--bpmb-border-color);
}

.bpmb-related-posts-title {
    margin-block-end: var(--bpmb-space-lg);
    color: var(--bpmb-text-primary);
    font-size: var(--bpmb-font-size-lg);
    font-weight: 600;
}

/* ==========================================================================
   Writer profile header

   LIVES HERE, not in the shortcodes stylesheet, because it is the one component
   rendered on TWO surfaces: `/author/{nick}/` (a template takeover) and the
   BuddyPress member "blog" tab (a BP screen). Those two surfaces load different
   stylesheets — the BP profile does not load the shortcodes CSS — and this file is
   the only one BOTH of them load.

   Measured: with the rules in the shortcodes stylesheet, the header rendered
   `display: block` on the BP tab. Avatar, name, stats and follow button all stacked
   into an unstyled column, on the surface a member sees most.
   ========================================================================== */

.bpmb-writer-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--bpmb-space-lg);
	flex-wrap: wrap;
	padding: var(--bpmb-space-lg);
	margin-block-end: var(--bpmb-space-lg);
	background: var(--bpmb-bg-secondary);
	border: 1px solid var(--bpmb-border-color);
	border-radius: var(--bpmb-radius-lg);
}

.bpmb-writer-identity {
	display: flex;
	align-items: center;
	gap: var(--bpmb-space-md);
	min-width: 0; /* Lets a long display name ellipsize instead of shoving the follow button off. */
}

.bpmb-writer-avatar img {
	display: block;
	width: 96px;
	height: 96px;
	border-radius: 50%;
	object-fit: cover;
}

.bpmb-writer-meta {
	min-width: 0;
}

.bpmb-writer-name {
	margin: 0 0 var(--bpmb-space-xs);
	color: var(--bpmb-text-primary);
	line-height: 1.2;
}

.bpmb-writer-bio {
	margin: 0 0 var(--bpmb-space-sm);
	color: var(--bpmb-text-secondary);
	/* A bio is user input with no length limit. Two lines, then clamp — an essay in the header
	   would push the post grid below the fold on every profile. */
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
	max-width: 60ch;
}

/* posts · views · followers */
.bpmb-writer-stats {
	display: flex;
	gap: var(--bpmb-space-lg);
	margin: 0;
}

.bpmb-writer-stat {
	display: flex;
	align-items: baseline;
	gap: var(--bpmb-space-xs);
}

.bpmb-writer-stats .bpmb-stat-value {
	margin: 0;
	font-weight: 600;
	color: var(--bpmb-text-primary);
}

.bpmb-writer-stats .bpmb-stat-label {
	margin: 0; /* <dd> has a default margin-inline-start of 40px. */
	color: var(--bpmb-text-secondary);
	font-size: var(--bpmb-font-size-sm);
}

.bpmb-writer-actions {
	flex-shrink: 0;
}

/* Under 640px the header stacks and centres: side-by-side, the avatar plus a two-line bio plus
   three stats leaves the follow button about 60px of width. */
@media screen and (max-width: 640px) {
	.bpmb-writer-header {
		flex-direction: column;
		align-items: center;
		text-align: center;
		padding: var(--bpmb-space-md);
	}

	.bpmb-writer-identity {
		flex-direction: column;
		align-items: center;
	}

	.bpmb-writer-avatar img {
		width: 72px;
		height: 72px;
	}

	.bpmb-writer-stats {
		justify-content: center;
		flex-wrap: wrap;
		gap: var(--bpmb-space-md);
	}

	.bpmb-writer-actions {
		width: 100%;
	}

	.bpmb-writer-actions .bpmb-follow-btn {
		width: 100%;
		justify-content: center;
	}
}

/* ==========================================================================
   Engagement buttons inside a BuddyPress wrapper

   BuddyPress Nouveau resets EVERY button inside `.buddypress-wrap`:

       .bp-nouveau .buddypress-wrap ... button { display: inline-block; padding: 10px 20px; }

   Specificity 0,2,1 — which beats a plain `.bpmb-clap-btn` (0,1,0). So on the member's blog tab
   the clap and bookmark buttons came out `display: block`, 62x48, with 20px of side padding:
   the min-width/min-height got through and nothing else did. Not a theme bug — it is BuddyPress's
   own reset, and it lands on any plugin that renders a button inside its wrapper.

   Scoped high enough to win (0,3,0) and no higher. `!important` would win too, and would also
   stop a site owner restyling these buttons at all.
   ========================================================================== */

.buddypress-wrap .bpmb-post-actions-inline .bpmb-clap-btn,
.buddypress-wrap .bpmb-post-actions-inline .bpmb-bookmark-btn,
.buddypress-wrap .bpmb-writer-actions .bpmb-follow-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--bpmb-space-xs);
	width: auto;
	height: auto;
	min-width: 40px;
	min-height: 40px;
	padding: 0 var(--bpmb-space-sm);
	line-height: 1;
	background: transparent;
	color: var(--bpmb-text-secondary);
	border-radius: 999px;
	cursor: pointer;
}

.buddypress-wrap .bpmb-writer-actions .bpmb-follow-btn {
	/* The follow button is a primary action and reads as one — the clap and bookmark are quiet. */
	padding: 0 var(--bpmb-space-md);
	background: var(--bpmb-primary);
	/* `--bpmb-on-primary`: white in light mode, DARK in dark mode — because `--bpmb-primary` itself
	   becomes a pale accent there and white-on-pale measured 1.89:1. Not `--bpmb-btn-fg`, which is
	   scoped inside `.bpmb-btn` and resolves to nothing out here. */
	color: var(--bpmb-on-primary, #fff);
	border: 0;
}

.buddypress-wrap .bpmb-writer-actions .bpmb-follow-btn.is-following {
	background: transparent;
	color: var(--bpmb-text-primary);
	border: 1px solid var(--bpmb-border-color);
}

/* ==========================================================================
   Topic hub
   Lives beside the writer header, for the same reason: the hub renders on a term-archive
   TAKEOVER and via a shortcode on any page, and those two do not load the same stylesheets.
   ========================================================================== */

/*
 * ONE HEADER BLOCK: the topic name with its Follow button on the same line, the description and
 * the count under the name. The four used to stack flush-left with no air between them, the count
 * in 11px type, so the header read as leftovers above the "Writers on this topic" grid.
 */
.bpmb-topic-header {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	column-gap: var(--bpmb-space-lg);
	row-gap: var(--bpmb-space-sm);
	align-items: center;
	padding-block: var(--bpmb-space-lg);
	margin-block-end: var(--bpmb-space-xl);
	border-block-end: 1px solid var(--bpmb-border-color);
}

.bpmb-topic-header > :not(.bpmb-follow-btn) {
	grid-column: 1;
}

.bpmb-topic-header > .bpmb-follow-btn {
	grid-column: 2;
	grid-row: 1;
}

.bpmb-topic-name {
	margin: 0;
	color: var(--bpmb-text-primary);
}

.bpmb-topic-description {
	max-width: 65ch; /* Bringhurst: past ~75 characters the eye loses the line on the return sweep. */
	color: var(--bpmb-text-secondary);
}

.bpmb-topic-description p:last-child {
	margin-block-end: 0;
}

.bpmb-topic-count {
	margin: 0;
	/* `--bpmb-text-secondary`, NOT `--bpmb-text-light`.
	 *
	 * `--bpmb-text-light` means "light-COLOURED text" — for dark backgrounds — not "text for light
	 * mode". Used here it put near-white text on a white page: measured 1.04:1, which is invisible.
	 * A token named for its colour rather than its role is a trap, and this is what falling into it
	 * looks like. */
	color: var(--bpmb-text-secondary);
	font-size: var(--bpmb-font-size-base);
	font-weight: 500;
}

.bpmb-topic-writers {
	margin-block-end: var(--bpmb-space-lg);
}

.bpmb-topic-writers-title {
	margin: 0 0 var(--bpmb-space-md);
	color: var(--bpmb-text-primary);
	font-size: 1.15rem;
}

@media screen and (max-width: 640px) {
	/* One column: the Follow button drops under the count, full width, a thumb's reach. */
	.bpmb-topic-header {
		grid-template-columns: minmax(0, 1fr);
		padding-block: var(--bpmb-space-md);
		margin-block-end: var(--bpmb-space-lg);
	}

	.bpmb-topic-header > .bpmb-follow-btn {
		grid-column: 1;
		grid-row: auto;
		width: 100% !important;
	}
}

/* ==========================================================================
   COMPONENT ARMOUR — our components are OURS, on any theme.

   THE PROBLEM. Themes ship blanket rules for bare elements, and they out-specify a plain class by a
   mile. BuddyX's is real, and it is not subtle:

       button:not([class*="wp-"]):not([class*="customize-"]):not([class*="wc-block-"])
             :not([class*="bn-"]):not([class*="mvs-"]):not(.bx-color-mode-toggle__btn)
             :not(.menu-toggle):not(.menu-close):not(.datepicker button):not([class*="tribe-events"])

   TEN :not()s — specificity (0,10,1). BuddyPress Nouveau has its own. So the SAME clap button
   rendered 40x40 on a shortcode page, 62x48 inside a BuddyPress wrap, and 22px tall on the author
   archive. Three looks for one component, none of them ours, and one of them below the WCAG minimum
   tap target.

   WHY !important, AND WHY IT IS NOT LAZINESS. I first tried to win on specificity by repeating the
   class — (0,4,0). It lost to (0,10,1), and it would lose again the day a theme adds an eleventh
   :not(). Counting selectors is an arms race against every theme that will ever exist, and the
   plugin loses it by default. Short of Shadow DOM, `!important` is the only DETERMINISTIC way for a
   component to keep its own geometry.

   THE CONTRACT THIS CREATES, and it is the important part:

       THE TOKENS ARE THE API. The RULES are ours.

   A site owner restyles these components by setting `--bpmb-primary`, `--bpmb-radius-*`,
   `--bpmb-space-*` — not by out-specifying our CSS. That is a better seam than the one it replaces:
   it is documented, it is stable across releases, and it cannot be broken by us renaming a class.

   AND WE STILL DO NOT TAKE THE PALETTE. `--bpmb-primary` maps to the theme's accent, the FONT is
   inherited (`all: unset` is inherit-for-inherited), and the radius and spacing come from tokens a
   theme can set. The button is the site's colour and the site's typeface, in our shape, at a
   contrast we can actually guarantee. Premium, and still unmistakably part of the site.
   ========================================================================== */

.bpmb-follow-btn,
.bpmb-clap-btn,
.bpmb-bookmark-btn {
	all: unset !important;

	box-sizing: border-box !important;
	display: inline-flex !important;
	align-items: center !important;
	justify-content: center !important;
	gap: var(--bpmb-space-xs) !important;

	/* 40px: a real tap target. WCAG 2.5.8 asks 24 for a link; 40 is the figure for a CONTROL, and
	   these are controls. It is also what the theme kept taking away. */
	min-width: 40px !important;
	min-height: 40px !important;
	padding: 0 var(--bpmb-space-sm) !important;

	font-family: inherit !important;   /* the SITE's typeface — we are not taking that */
	font-size: var(--bpmb-font-size-sm) !important;
	font-weight: 500 !important;
	line-height: 1 !important;
	text-decoration: none !important;
	white-space: nowrap !important;

	border: 1px solid transparent !important;
	border-radius: 999px !important;
	cursor: pointer !important;
}

/* Quiet by default — a clap and a save are small gestures, not calls to action. */
.bpmb-clap-btn,
.bpmb-bookmark-btn {
	color: var(--bpmb-text-secondary) !important;
	background-color: transparent !important;
	border-color: var(--bpmb-border-color) !important;
}

.bpmb-clap-btn:hover,
.bpmb-bookmark-btn:hover {
	color: var(--bpmb-text-primary) !important;
	background-color: var(--bpmb-bg-secondary) !important;
}

/*
 * Clapped reads at a glance, in dark mode too, where the accent is a pale blue on grey: accent
 * border and colour here (at this block's weight, or the reset above erases them), and the icon
 * fills - bp-member-blog-shortcodes.css, beside the rest of the clap icon rules.
 */
.bpmb-clap-btn.has-clapped,
.bpmb-clap-btn.has-clapped:hover {
	color: var(--bpmb-primary-ink, var(--bpmb-primary)) !important;
	border-color: var(--bpmb-primary) !important;
}

/* Follow IS a call to action, so it is filled — with the THEME's accent, darkened just enough that
   its label is readable on any of them (see --bpmb-primary-filled). */
.bpmb-follow-btn {
	padding: 0 var(--bpmb-space-md) !important;
	color: var(--bpmb-on-primary) !important;
	background-color: var(--bpmb-primary-filled) !important;
	border-color: var(--bpmb-primary-filled) !important;
}

/* Already following = a state you can undo, not a thing to press. It steps back. */
.bpmb-follow-btn.is-following {
	color: var(--bpmb-text-primary) !important;
	background-color: transparent !important;
	border-color: var(--bpmb-border-color) !important;
}

/*
 * Pointing at (or tabbing to) "Following" previews the undo: red, and it says "Unfollow".
 * Not straight after the click that followed - the pointer is still on the button, and
 * "Unfollow" in red is the wrong reply to "Follow". `is-settling` holds until it leaves.
 */
@media (hover: hover) {
	.bpmb-follow-btn.is-following:not(.is-settling):hover {
		color: var(--bpmb-danger) !important;
		border-color: var(--bpmb-danger) !important;
	}

	.bpmb-follow-btn.is-following:not(.is-settling):hover .bpmb-follow-text--following {
		display: none;
	}

	.bpmb-follow-btn.is-following:not(.is-settling):hover .bpmb-follow-text--unfollow {
		display: inline;
	}
}

.bpmb-follow-btn.is-following:not(.is-settling):focus-visible {
	color: var(--bpmb-danger) !important;
	border-color: var(--bpmb-danger) !important;
}

.bpmb-follow-btn.is-following:not(.is-settling):focus-visible .bpmb-follow-text--following {
	display: none;
}

.bpmb-follow-btn.is-following:not(.is-settling):focus-visible .bpmb-follow-text--unfollow {
	display: inline;
}

/* A visible focus ring. `all: unset` removes the browser's, and a keyboard user who cannot see
   where they are is a keyboard user who cannot use this. */
.bpmb-follow-btn:focus-visible,
.bpmb-clap-btn:focus-visible,
.bpmb-bookmark-btn:focus-visible {
	outline: 2px solid var(--bpmb-primary) !important;
	outline-offset: 2px !important;
}

.bpmb-clap-btn:disabled {
	opacity: .6 !important;
	cursor: default !important;
}

/* The icon keeps its size whatever the theme does to svg. */
.bpmb-follow-btn svg,
.bpmb-clap-btn svg,
.bpmb-bookmark-btn svg {
	width: 18px !important;
	height: 18px !important;
	flex: 0 0 auto !important;
}

/*
 * BOOKMARK STATE SWAP — lives here because this sheet loads everywhere.
 *
 * The button ships BOTH icons and lets CSS pick one, so the JS only has to flip a class
 * and can never leave the button rendering both states or neither. The rules used to sit
 * in bp-member-blog-shortcodes.css, which the single-post branch does not enqueue — so on
 * a single post both the outline and the filled bookmark rendered side by side and the
 * control read as two separate save buttons. It looked intermittent because three
 * unrelated things (related-posts auto-insert, a listing shortcode in the body, the
 * bpmb_enqueue_card_styles filter) pull that sheet in and accidentally fixed it.
 */
.bpmb-bookmark-btn .bpmb-icon-saved,
.bpmb-bookmark-btn.is-saved .bpmb-icon-save {
	display: none;
}

.bpmb-bookmark-btn.is-saved .bpmb-icon-saved {
	display: block;
}

/*
 * FOLLOW LABEL SWAP + COUNT — here for the same reason as the bookmark swap above. The follow
 * button now also renders in the author box under a single post, where the card stylesheet is
 * not loaded; with the swap rules there, the button read "Follow Following".
 * One label is shown, the other hidden, so the JS only flips a class.
 */
.bpmb-follow-btn .bpmb-follow-text--following,
.bpmb-follow-btn .bpmb-follow-text--unfollow,
.bpmb-follow-btn.is-following .bpmb-follow-text--follow {
	display: none;
}

.bpmb-follow-btn.is-following .bpmb-follow-text--following {
	display: inline;
}

.bpmb-follow-count {
	padding-inline-start: var(--bpmb-space-xs);
	border-inline-start: 1px solid currentColor;
	margin-inline-start: var(--bpmb-space-xs);
	font-variant-numeric: tabular-nums;
	opacity: 0.85;
}

/* -------------------------------------------------------------------------
 * Load more (4.0.1)
 *
 * Sits under a listing, in place of the numbered pager, which the script
 * hides once it takes over. Everything here is token-driven, so it inherits
 * the dark-mode values the rest of this sheet already defines; there is no
 * raw colour and no physical margin, so RTL needs no override.
 * ---------------------------------------------------------------------- */
.bpmb-load-more {
    /*
     * SPAN THE WHOLE GRID, exactly as .bpmb-pagination already does.
     *
     * Half the listing shortcodes put their footer INSIDE the grid wrapper
     * (Popular, List) and half put it outside (Recent, Following). Inside a
     * `display: grid` wrapper an un-spanned child becomes a single CELL — the
     * button rendered 254px wide in the first column of a 3-column grid,
     * sitting where a card should be. `1 / -1` is inert in the block layouts,
     * so one rule covers both placements.
     */
    grid-column: 1 / -1;

    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--bpmb-space-sm, 8px);
    margin-block-start: var(--bpmb-space-lg, 24px);
    text-align: center;
}

/*
 * `hidden` is how the control ships from the server, and a `display: flex`
 * above would beat the UA's `display: none` on the attribute — the button
 * would be visible for every reader whose JavaScript never arrives, which is
 * precisely the reader this feature is not supposed to strand.
 */
.bpmb-load-more[hidden] {
    display: none;
}

.bpmb-load-more__button {
    min-width: 180px;
}

.bpmb-load-more__button.is-loading {
    cursor: progress;
}

.bpmb-load-more__status {
    margin: 0;
    min-height: 1.25em; /* Reserve the line so announcing a message shifts nothing. */
    font-size: var(--bpmb-font-size-sm, 13px);
    color: var(--bpmb-text-muted, #6b7280);
}

@media (max-width: 480px) {
    .bpmb-load-more__button {
        width: 100%;
        min-width: 0;
    }
}

/*--------------------------------------------------------------
# The post card (4.3.0) - ONE card for every post list
--------------------------------------------------------------
PostCardRenderer::card(). The same markup is a grid TILE inside `.bpmb-columns-{n}` (image on
top at a fixed ratio) and a list ROW inside `.bpmb-post-list--rows` (a small fixed thumbnail on
the inline-start side). Lives here, in the sheet every list surface loads, so the dashboard, the
profile Blog tab, the hubs and every shortcode draw the same card.
*/

/* Every tile in a grid row is the row's height - a tall image card no longer leaves a hole
   under its shorter neighbours. */
.bpmb-post-list > li {
    display: flex;
    min-width: 0;
}

.bpmb-post-item {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: var(--bpmb-bg-primary);
    border: 1px solid var(--bpmb-border-color);
    border-radius: var(--bpmb-radius-lg);
    transition: box-shadow var(--bpmb-transition-fast, 150ms ease);
}

.bpmb-post-item:hover {
    box-shadow: var(--bpmb-shadow-md);
}

/* The card is not `overflow: hidden` (its action menu must escape it), so the image clips itself. */
.bpmb-post-thumbnail {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    border-start-start-radius: var(--bpmb-radius-lg);
    border-start-end-radius: var(--bpmb-radius-lg);
    background: var(--bpmb-bg-secondary);
}

.bpmb-post-thumbnail a {
    display: block;
    height: 100%;
}

.bpmb-post-thumbnail img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.bpmb-post-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: var(--bpmb-space-sm);
    min-width: 0;
    padding: var(--bpmb-space-md);
}

.bpmb-post-title {
    /* Flex, so a badge (Pro's "Co-author") sits on the title's line instead of above it. */
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: var(--bpmb-space-xs);
    margin: 0;
    font-size: var(--bpmb-font-size-lg, 1.125rem);
    font-weight: 600;
    line-height: 1.35;
    overflow-wrap: anywhere;
}

/* Grows to the row, so the title's hit area spans the card (WCAG 2.5.8 wants 24px). */
.bpmb-post-title a {
    display: block;
    flex: 1 1 10rem;
    min-width: 0;
    padding-block: 2px;
    color: var(--bpmb-text-primary);
    text-decoration: none;
}

.bpmb-post-title a:hover,
.bpmb-post-title a:focus-visible {
    color: var(--bpmb-primary-ink, var(--bpmb-primary));
}

/* A sent-back draft on its owner's list: the badge, then the reviewer's reason in one line. */
.bpmb-post-sent-back {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--bpmb-space-xs) var(--bpmb-space-sm);
	margin: var(--bpmb-space-xs) 0 0;
	font-size: var(--bpmb-font-size-sm);
	color: var(--bpmb-text-primary);
}

/* ONE meta line: status, date, category, views (author on reader lists). Wraps on narrow cards;
   separation is the gap's job, so no separator glyph can orphan itself at a line start. */
.bpmb-post-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--bpmb-space-xs) var(--bpmb-space-md);
    font-size: var(--bpmb-font-size-sm);
    color: var(--bpmb-text-secondary);
}

.bpmb-post-meta > span {
    display: inline-flex;
    align-items: center;
    gap: var(--bpmb-space-xs);
}

.bpmb-post-meta a {
    color: inherit;
    text-decoration: none;
}

.bpmb-post-meta a:hover,
.bpmb-post-meta a:focus-visible {
    color: var(--bpmb-primary-ink, var(--bpmb-primary));
    text-decoration: underline;
}

.bpmb-post-author .bpmb-author-avatar img {
    display: block;
    border-radius: 50%;
}

.bpmb-post-excerpt {
    font-size: var(--bpmb-font-size-base, 0.9375rem);
    line-height: 1.6;
    color: var(--bpmb-text-primary);
}

.bpmb-post-excerpt p {
    margin: 0;
}

.bpmb-read-more {
    align-self: flex-start;
    margin-block-start: auto;
    font-size: var(--bpmb-font-size-sm);
    font-weight: 600;
    color: var(--bpmb-primary-ink, var(--bpmb-primary));
    text-decoration: none;
}

.bpmb-read-more:hover,
.bpmb-read-more:focus-visible {
    text-decoration: underline;
}

/* Status pill and the co-author badge: one pill component. */
.bpmb-post-status,
.bpmb-role-badge {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--bpmb-space-sm);
    border-radius: var(--bpmb-radius-full);
    font-size: var(--bpmb-font-size-xs);
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
    background: var(--bpmb-bg-secondary);
    color: var(--bpmb-text-secondary);
}

.bpmb-post-status--draft   { background: var(--bpmb-warning-bg); color: var(--bpmb-warning-text); }
.bpmb-post-status--pending { background: var(--bpmb-info-bg); color: var(--bpmb-info-text); }
.bpmb-post-status--future  { background: var(--bpmb-scheduled-light); color: var(--bpmb-scheduled-text); }

.bpmb-role-badge {
    background: var(--bpmb-role-coauthor-bg);
    color: var(--bpmb-role-coauthor-fg);
}

/* Pro: a post the group featured (group Blog tab only). */
.bpmb-role-featured {
    background: var(--bpmb-warning-bg);
    color: var(--bpmb-warning-text);
}

/* Reader actions (clap, save) sit at the foot of the card. */
.bpmb-post-actions-inline {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--bpmb-space-sm);
    margin-block-start: auto;
}

/* Owner actions: Edit in plain sight, the rest in "More actions". */
.bpmb-card-actions {
    display: flex;
    align-items: center;
    gap: var(--bpmb-space-xs);
    margin-block-start: auto;
}

.bpmb-card-edit,
.bpmb-card-menu__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--bpmb-space-xs);
    min-height: 40px;
    margin: 0;
    border: 1px solid var(--bpmb-border-color);
    border-radius: var(--bpmb-radius-md);
    background: var(--bpmb-bg-primary);
    color: var(--bpmb-text-primary);
    font: inherit;
    font-size: var(--bpmb-font-size-sm);
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    box-shadow: none;
    cursor: pointer;
}

.bpmb-card-edit {
    padding-inline: var(--bpmb-space-md);
}

.bpmb-card-menu__toggle {
    width: 40px;
    padding: 0;
    color: var(--bpmb-text-secondary);
}

.bpmb-card-edit:hover,
.bpmb-card-menu__toggle:hover,
.bpmb-card-menu.is-open .bpmb-card-menu__toggle {
    border-color: var(--bpmb-border-strong, var(--bpmb-border-color));
    background: var(--bpmb-bg-hover, var(--bpmb-bg-secondary));
    color: var(--bpmb-text-primary);
}

.bpmb-card-edit:focus-visible,
.bpmb-card-menu__toggle:focus-visible,
.bpmb-card-menu__item:focus-visible {
    outline: 2px solid var(--bpmb-primary);
    outline-offset: 2px;
}

.bpmb-card-menu {
    position: relative;
}

.bpmb-card-menu__list {
    position: absolute;
    inset-block-start: calc(100% + var(--bpmb-space-xs));
    inset-inline-end: 0;
    z-index: var(--bpmb-z-dropdown, 100);
    min-width: 12rem;
    margin: 0;
    padding: var(--bpmb-space-xs);
    list-style: none;
    background: var(--bpmb-bg-primary);
    border: 1px solid var(--bpmb-border-color);
    border-radius: var(--bpmb-radius-md);
    box-shadow: var(--bpmb-shadow-lg);
}

.bpmb-card-menu__list[hidden] {
    display: none;
}

.bpmb-card-menu__list > li {
    margin: 0;
    padding: 0;
    list-style: none;
}

.bpmb-card-menu__item {
    display: flex;
    align-items: center;
    gap: var(--bpmb-space-sm);
    width: 100%;
    min-height: 40px;
    margin: 0;
    padding: 0 var(--bpmb-space-sm);
    border: 0;
    border-radius: var(--bpmb-radius-sm);
    background: transparent;
    color: var(--bpmb-text-primary);
    font: inherit;
    font-size: var(--bpmb-font-size-sm);
    font-weight: 500;
    text-align: start;
    text-decoration: none;
    box-shadow: none;
    cursor: pointer;
}

.bpmb-card-menu__item:hover,
.bpmb-card-menu__item:focus-visible {
    background: var(--bpmb-bg-secondary);
    color: var(--bpmb-text-primary);
}

.bpmb-card-menu__item--danger,
.bpmb-card-menu__item--danger:hover,
.bpmb-card-menu__item--danger:focus-visible {
    color: var(--bpmb-danger-text, var(--bpmb-danger));
}

/*
 * BuddyPress Nouveau paints every `button` and heading inside `.buddypress-wrap` at a higher
 * specificity (`.buddypress .buddypress-wrap button` - a filled blue button; `body.buddypress
 * .buddypress-wrap h3` - a 17px margin). Inside the card our component wins.
 */
body.buddypress .buddypress-wrap .bpmb-post-item .bpmb-post-title {
    margin: 0;
    font-size: var(--bpmb-font-size-lg, 1.125rem);
}

.buddypress .buddypress-wrap .bpmb-card-actions .bpmb-card-menu__toggle {
    width: 40px;
    min-height: 40px;
    padding: 0;
    border: 1px solid var(--bpmb-border-color);
    border-radius: var(--bpmb-radius-md);
    background: var(--bpmb-bg-primary);
    color: var(--bpmb-text-secondary);
    box-shadow: none;
}

.buddypress .buddypress-wrap .bpmb-card-actions .bpmb-card-menu__toggle:hover,
.buddypress .buddypress-wrap .bpmb-card-actions .bpmb-card-menu.is-open .bpmb-card-menu__toggle {
    background: var(--bpmb-bg-hover, var(--bpmb-bg-secondary));
    color: var(--bpmb-text-primary);
}

.buddypress .buddypress-wrap .bpmb-card-menu__list .bpmb-card-menu__item {
    min-height: 40px;
    padding: 0 var(--bpmb-space-sm);
    border: 0;
    background: transparent;
    color: var(--bpmb-text-primary);
    box-shadow: none;
}

.buddypress .buddypress-wrap .bpmb-card-menu__list .bpmb-card-menu__item:hover,
.buddypress .buddypress-wrap .bpmb-card-menu__list .bpmb-card-menu__item:focus-visible {
    background: var(--bpmb-bg-secondary);
}

.buddypress .buddypress-wrap .bpmb-card-menu__list .bpmb-card-menu__item--danger {
    color: var(--bpmb-danger-text, var(--bpmb-danger));
}

/* Popular: rank badge on the top three. */
.bpmb-post-rank {
    position: absolute;
    inset-block-start: var(--bpmb-space-sm);
    inset-inline-start: var(--bpmb-space-sm);
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    font-size: var(--bpmb-font-size-xs);
    font-weight: 700;
    color: var(--bpmb-on-primary, #fff);
    background: var(--bpmb-primary-filled, var(--bpmb-primary));
    box-shadow: var(--bpmb-shadow-sm);
}

.bpmb-post-item:not(.bpmb-post-item--has-thumb) .bpmb-post-rank {
    position: static;
    margin: var(--bpmb-space-md) var(--bpmb-space-md) 0;
}

/* Medal colours are fixed artwork, not theme colours. */
.bpmb-rank-1 .bpmb-post-rank { background: linear-gradient(135deg, #ffd700, #ffa500); color: #212529; }
.bpmb-rank-2 .bpmb-post-rank { background: linear-gradient(135deg, #c0c0c0, #8a8a8a); }
.bpmb-rank-3 .bpmb-post-rank { background: linear-gradient(135deg, #cd7f32, #a0522d); }

/*
 * THE ROW. The member's own lists (`.bpmb-post-list--rows`) and any one-column list: a small
 * fixed thumbnail on the inline-start side when there is one (no giant banner, and no empty slot
 * when there is none), title and meta beside it, the actions at the inline end.
 * The dashboard's grid toggle adds `.bpmb-grid-view` to the container and the row becomes a tile.
 */
:not(.bpmb-grid-view) > .bpmb-post-list--rows,
ul.bpmb-post-list.bpmb-columns-1,
.bpmb-columns-1 > ul.bpmb-post-list {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--bpmb-space-sm);
}

:is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-item {
    flex-direction: row;
    align-items: flex-start;
    gap: var(--bpmb-space-md);
    padding: var(--bpmb-space-md);
}

:is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-thumbnail {
    flex: 0 0 96px;
    width: 96px;
    aspect-ratio: 4 / 3;
    border-radius: var(--bpmb-radius-md);
}

:is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-content {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--bpmb-space-md);
    row-gap: var(--bpmb-space-xs);
    align-items: center;
    padding: 0;
}

:is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-content > * {
    grid-column: 1;
}

:is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-content > :is(.bpmb-card-actions, .bpmb-post-actions-inline) {
    grid-column: 2;
    grid-row: 1 / span 3;
    margin: 0;
}

:is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-title {
    font-size: var(--bpmb-font-size-md, 1rem);
}

/* The dashboard / profile grid toggle: the same cards as tiles. */
.bpmb-grid-view > .bpmb-post-list--rows {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr));
    gap: var(--bpmb-space-lg);
}

@media (max-width: 640px) {
    /* Rows: the actions drop under the meta so the title keeps the width. */
    :is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-item {
        gap: var(--bpmb-space-sm);
        padding: var(--bpmb-space-sm);
    }

    :is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-thumbnail {
        flex-basis: 72px;
        width: 72px;
    }

    :is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-content {
        grid-template-columns: minmax(0, 1fr);
    }

    :is(:not(.bpmb-grid-view) > .bpmb-post-list--rows, ul.bpmb-post-list.bpmb-columns-1, .bpmb-columns-1 > ul.bpmb-post-list) .bpmb-post-content > :is(.bpmb-card-actions, .bpmb-post-actions-inline) {
        grid-column: 1;
        grid-row: auto;
        justify-content: flex-start;
    }

    .bpmb-post-title {
        font-size: var(--bpmb-font-size-md, 1rem);
    }
}

@media (prefers-reduced-motion: reduce) {
    .bpmb-post-item {
        transition: none;
    }
}

