/**
 * BPMB UI — the shared design system (handle: `bpmb-ui-helpers`).
 *
 * THE SINGLE SOURCE OF TRUTH FOR EVERY `--bpmb-*` TOKEN.
 *
 * Pairs with public/js/bpmb-ui.js. Every other stylesheet in the pair (Free
 * public/editor/shortcodes/single/admin, and all 11 Pro stylesheets) declares
 * `bpmb-ui-helpers` as a dependency and 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.
 */

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

    /* --- Brand / primary ------------------------------------ */
    --bpmb-primary: var(--bx-color-accent, var(--color-theme-primary, var(--reign-colors-theme, var(--wp--preset--color--primary, #2563eb))));
    --bpmb-primary-hover: var(--bx-color-button-bg-hover, var(--reign-accent-hover-color, #1d4ed8));
    --bpmb-primary-dark: #1d4ed8;
    --bpmb-primary-light: #e8f0fb;
    --bpmb-primary-light-hover: #dbeafe;
    --bpmb-primary-light-border: #bfdbfe;
    --bpmb-primary-ultra-light: #eff6ff;

    --bpmb-secondary: #6b7280;
    --bpmb-secondary-hover: #545b62;
    --bpmb-secondary-light: #f8f9fa;

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

    /* --- Semantic status ------------------------------------ */
    --bpmb-success: #16a34a;
    --bpmb-success-bg: #dcfce7;
    --bpmb-success-light: #ebf9eb;
    --bpmb-success-light-2: #d4edda;
    --bpmb-success-text: #155724;

    --bpmb-danger: #dc2626;
    --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)));
    --bpmb-text-tertiary: #94a3b8;
    --bpmb-text-muted: #9ca3af;
    --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-hover: rgba(0, 0, 0, 0.05);
    --bpmb-bg-active: rgba(0, 0, 0, 0.08);
    --bpmb-bg-light: #f8fafc;
    --bpmb-bg-lighter: #f9fafb;
    --bpmb-bg-dark: #f2f4f6;
    --bpmb-bg-white: #fff;
    --bpmb-bg-calendar: #f8fafc;
    --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);
    --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-strong: rgba(0, 0, 0, 0.2);
    --bpmb-border-calendar: #e2e8f0;

    /* --- 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-sheen: #e0e0e0;
    --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-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);
    --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-author-bg: var(--bpmb-primary-light-hover);
    --bpmb-role-author-fg: var(--bpmb-primary-dark);
    --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;

    /*
     * Editor dark palette. A `-dark-` PREFIXED token is an anti-pattern under
     * spec 09 (dark mode should be the same token with a different value), but
     * the Editor.js/Medium chrome consumes these names directly, so they are
     * preserved verbatim until the editor is rewritten in Phase 3c.
     */
    --bpmb-dark-bg-primary: #1e1e1e;
    --bpmb-dark-bg-secondary: #2c3338;
    --bpmb-dark-bg-tertiary: #3c434a;
    --bpmb-dark-text-primary: #c3c4c7;
    --bpmb-dark-text-secondary: #8c8f94;
    --bpmb-dark-text-muted: #50575e;
    --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);

    --bpmb-primary-light: rgba(37, 99, 235, 0.18);
    --bpmb-primary-light-hover: rgba(37, 99, 235, 0.26);
    --bpmb-primary-ultra-light: rgba(37, 99, 235, 0.12);

    /* 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-author-bg: rgba(37, 99, 235, 0.26);
    --bpmb-role-author-fg: #bfdbfe;
    --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);
}

@media (prefers-color-scheme: dark) {
    :root[data-bx-mode="auto"] {
        --bpmb-primary-light: rgba(37, 99, 235, 0.18);
        --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-author-bg: rgba(37, 99, 235, 0.26);
        --bpmb-role-author-fg: #bfdbfe;
        --bpmb-role-coauthor-bg: rgba(124, 58, 237, 0.26);
        --bpmb-role-coauthor-fg: #ddd6fe;
    }
}


/*--------------------------------------------------------------
# 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;
}

body.bpmb-modal-open {
    overflow: hidden;
}

/*--------------------------------------------------------------
# Button
--------------------------------------------------------------*/
.bpmb-btn {
    --bpmb-btn-pad-y: 8px;
    --bpmb-btn-pad-x: 16px;
    --bpmb-btn-radius: 8px;
    --bpmb-btn-bg: var(--bpmb-bg-secondary, #f1f5f9);
    --bpmb-btn-fg: var(--bpmb-text-primary, #111827);
    --bpmb-btn-border: var(--bpmb-border-color, #e2e8f0);

    /*
     * BORDER-BOX, or the tap target below is only honoured on half the elements.
     *
     * `<button>` is `border-box` in the UA stylesheet, `<a>` is `content-box`. Without this,
     * `min-height: 40px` reads as 40px TOTAL on a button and 40px of CONTENT on a link, which then
     * adds 16px of padding and 2px of border - so the same class rendered 40px and 58px side by
     * side. The admin sheet carried the identical bug and it is what "the buttons are too big"
     * turned out to be.
     */
    box-sizing: border-box;

    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;

    /* 40px minimum tap target (spec 09 §4). */
    min-height: 40px;
    padding-block: var(--bpmb-btn-pad-y);
    padding-inline: var(--bpmb-btn-pad-x);

    font-size: 14px;
    font-weight: 500;
    line-height: 1.25;
    text-decoration: none;

    color: var(--bpmb-btn-fg);
    background-color: var(--bpmb-btn-bg);
    border: 1px solid var(--bpmb-btn-border);
    border-radius: var(--bpmb-btn-radius);

    cursor: pointer;
    transition: background-color .2s cubic-bezier(.4, 0, .2, 1),
                border-color .2s cubic-bezier(.4, 0, .2, 1),
                color .2s cubic-bezier(.4, 0, .2, 1);
}

.bpmb-btn:hover {
    filter: brightness(.96);
}

.bpmb-btn:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 3px rgba(37, 99, 235, .35);
}

.bpmb-btn[disabled],
.bpmb-btn[aria-disabled="true"] {
    opacity: .6;
    cursor: not-allowed;
}

.bpmb-btn--primary {
    --bpmb-btn-bg: var(--bpmb-primary, #2563eb);
    --bpmb-btn-fg: #fff;
    --bpmb-btn-border: var(--bpmb-primary, #2563eb);
}

.bpmb-btn--secondary {
    --bpmb-btn-bg: transparent;
    --bpmb-btn-fg: var(--bpmb-text-primary, #111827);
    --bpmb-btn-border: var(--bpmb-border-color, #e2e8f0);
}

.bpmb-btn--danger {
    --bpmb-btn-bg: var(--bpmb-danger, #dc2626);
    --bpmb-btn-fg: #fff;
    --bpmb-btn-border: var(--bpmb-danger, #dc2626);
}

.bpmb-btn--ghost {
    --bpmb-btn-bg: transparent;
    --bpmb-btn-border: transparent;
}

/*--------------------------------------------------------------
# Modal — the generic dialog (bpmbModal)
--------------------------------------------------------------*/
.bpmb-modal {
    --bpmb-modal-width: 520px;
    --bpmb-modal-pad: 20px;
    --bpmb-modal-radius: 12px;
    --bpmb-modal-accent: var(--bpmb-primary, #2563eb);

    position: fixed;
    inset: 0;
    z-index: 100000;

    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;

    opacity: 0;
    visibility: hidden;

    /*
     * Transition opacity ONLY. `visibility` must flip synchronously with
     * .is-active: if it is transitioned it stays `hidden` for the duration of
     * the transition, and a browser refuses to move focus into an invisible
     * element — so the dialog would open with focus stranded on <body>.
     */
    transition: opacity .2s cubic-bezier(.4, 0, .2, 1);
}

.bpmb-modal.is-active {
    opacity: 1;
    visibility: visible;
}

.bpmb-modal--sm { --bpmb-modal-width: 420px; }
.bpmb-modal--lg { --bpmb-modal-width: 720px; }

.bpmb-modal--danger  { --bpmb-modal-accent: var(--bpmb-danger, #dc2626); }
.bpmb-modal--warning { --bpmb-modal-accent: var(--bpmb-warning, #d97706); }
.bpmb-modal--info    { --bpmb-modal-accent: var(--bpmb-info, #2563eb); }

.bpmb-modal__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, .55);
}

.bpmb-modal__panel {
    position: relative;
    z-index: 1;

    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: var(--bpmb-modal-width);
    max-height: calc(100vh - 32px);
    overflow: hidden;

    background: var(--bpmb-bg-primary, #fff);
    color: var(--bpmb-text-primary, #111827);
    border-radius: var(--bpmb-modal-radius);
    box-shadow: 0 20px 25px -5px rgba(0, 0, 0, .18), 0 8px 10px -6px rgba(0, 0, 0, .12);

    transform: translateY(8px) scale(.98);
    transition: transform .2s cubic-bezier(.4, 0, .2, 1);
}

.bpmb-modal.is-active .bpmb-modal__panel {
    transform: translateY(0) scale(1);
}

.bpmb-modal__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;

    padding: var(--bpmb-modal-pad);
    padding-block-end: 0;
}

.bpmb-modal__title {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
    line-height: 1.25;
    color: inherit;
}

/* An empty title still needs the close button to sit correctly. */
.bpmb-modal__title:empty {
    display: none;
}

.bpmb-modal__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;

    /* Icon-only control: keep the 40px target even though the glyph is 18px. */
    width: 40px;
    height: 40px;
    margin-block-start: -8px;
    margin-inline-end: -8px;

    color: var(--bpmb-text-secondary, #6b7280);
    background: none;
    border: 0;
    border-radius: 8px;
    cursor: pointer;
}

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

.bpmb-modal__close:focus-visible {
    outline: 2px solid var(--bpmb-modal-accent);
    outline-offset: 2px;
}

.bpmb-modal__body {
    padding: var(--bpmb-modal-pad);
    overflow-y: auto;
    font-size: 14px;
    line-height: 1.5;
}

.bpmb-modal__message {
    margin: 0;
    color: var(--bpmb-text-secondary, #6b7280);
}

.bpmb-modal__foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;

    padding: var(--bpmb-modal-pad);
    padding-block-start: 0;
}

/*--------------------------------------------------------------
# Confirm — a destructive-action gate rendered inside a modal
--------------------------------------------------------------*/
.bpmb-confirm {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    text-align: center;
}

.bpmb-confirm__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;

    width: 64px;
    height: 64px;
    border-radius: 9999px;

    color: var(--bpmb-modal-accent, #dc2626);
    /* Tint of the accent, with a hex fallback for browsers without color-mix. */
    background: var(--bpmb-danger-light, #fee2e2);
    background: color-mix(in srgb, var(--bpmb-modal-accent, #dc2626) 12%, transparent);
}

.bpmb-confirm__message {
    margin: 0;
    font-size: 15px;
    line-height: 1.5;
    color: var(--bpmb-text-secondary, #6b7280);
}

/*--------------------------------------------------------------
# Toast
--------------------------------------------------------------*/
.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;
}

/*--------------------------------------------------------------
# Skeleton — the LOADING state (spec 09 §3 four-state contract)
--------------------------------------------------------------*/
.bpmb-skeleton-group {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.bpmb-skeleton {
    background: var(--bpmb-skeleton-base, #e9edf2);
    background-image: linear-gradient(
        90deg,
        var(--bpmb-skeleton-base, #e9edf2) 0%,
        var(--bpmb-skeleton-sheen, #f4f6f9) 50%,
        var(--bpmb-skeleton-base, #e9edf2) 100%
    );
    background-size: 200% 100%;
    border-radius: 6px;
    animation: bpmb-skeleton-sheen 1.4s ease-in-out infinite;
}

.bpmb-skeleton--thumb  { width: 100%; aspect-ratio: 16 / 9; border-radius: 10px; }
.bpmb-skeleton--title  { width: 70%;  height: 18px; }
.bpmb-skeleton--meta   { width: 40%;  height: 12px; }
.bpmb-skeleton--text   { width: 100%; height: 12px; }
.bpmb-skeleton--short  { width: 55%; }
.bpmb-skeleton--avatar { width: 40px; height: 40px; border-radius: 9999px; flex: 0 0 auto; }

.bpmb-skeleton-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/*--------------------------------------------------------------
# Busy state
#
# `.bpmb-loading` is applied by ~13 call sites across both plugins while a request
# is in flight, but until now it only existed as a layout rule in the EDITOR
# stylesheet - `display: inline-flex` and a gap, no visual feedback at all, and not
# loaded on any other screen. So on most surfaces the class was applied, the user
# saw nothing change, and the button still looked clickable.
#
# Defining it here, in the shared sheet every surface already depends on, is what
# makes those existing call sites mean something. Works on a button or a container:
# the element dims, stops taking pointer events, and grows a spinner.
--------------------------------------------------------------*/

/*
 * THE busy state. State only - dim, inert, and a progress cursor. It deliberately
 * paints NO spinner, because this codebase already has a spinner primitive
 * (`.bpmb-spinner`, below) that six call sites inject into the markup themselves.
 * A glyph on the base class collided with those and painted two spinners at once.
 *
 * Want a glyph without touching markup? Add `.bpmb-loading--spinner` as well.
 */
.bpmb-loading {
    opacity: 0.6;
    pointer-events: none;
    cursor: progress;
}

/*
 * Opt-in trailing glyph, for callers that have no `.bpmb-spinner` of their own.
 * Sits after the label rather than replacing it: swapping the text out would change
 * the button's width mid-click and shift whatever is beside it.
 */
.bpmb-loading--spinner::after {
    content: "";
    display: inline-block;
    width: 1em;
    height: 1em;
    /*
     * `flex: 0 0 auto` is load-bearing, not defensive. Most of the buttons this lands on are
     * `inline-flex` (.bpmb-btn, .add-bpmb-category), which makes the pseudo-element a flex ITEM -
     * and a flex item shrinks by default. Measured without it: a spinner 4px wide and 13px tall,
     * i.e. a squashed line rather than a circle.
     */
    flex: 0 0 auto;
    margin-inline-start: 0.5em;
    vertical-align: -0.125em;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: bpmb-spin 0.7s linear infinite;
}

/*
 * Honour reduced-motion: keep the affordance, drop the spin. A pulse still says
 * "working" without animating a rotating element for someone who asked us not to.
 */
@media (prefers-reduced-motion: reduce) {
    .bpmb-loading--spinner::after {
        animation: bpmb-busy-pulse 1.2s ease-in-out infinite;
        border-top-color: currentColor;
    }

    @keyframes bpmb-busy-pulse {
        50% { opacity: 0.25; }
    }
}

/*
 * The ONE rotate keyframe for the whole design system. It used to be copy-pasted six
 * times (twice in buddypress-member-blog-public.css alone, plus the editor, admin,
 * Pro co-authors and Pro analytics stylesheets). Every one of those stylesheets
 * depends on `bpmb-ui-helpers` - Pro's reach it transitively through the virtual
 * `bpmbp-frontend-tokens` handle - so they all resolve this definition.
 */
@keyframes bpmb-spin {
    to { transform: rotate(360deg); }
}

.bpmb-skeleton-card__body {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.bpmb-skeleton-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.bpmb-skeleton-row .bpmb-skeleton--text {
    flex: 1 1 auto;
}

@keyframes bpmb-skeleton-sheen {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/*--------------------------------------------------------------
# Badge
--------------------------------------------------------------*/
.bpmb-badge {
    --bpmb-badge-fg: var(--bpmb-text-secondary, #6b7280);
    --bpmb-badge-bg: var(--bpmb-bg-secondary, #f1f5f9);

    display: inline-flex;
    align-items: center;
    gap: 4px;

    padding: 2px 8px;

    font-size: 12px;
    font-weight: 500;
    line-height: 1.5;
    white-space: nowrap;

    color: var(--bpmb-badge-fg);
    background: var(--bpmb-badge-bg);
    border-radius: 6px;
}

.bpmb-badge--success   { --bpmb-badge-fg: var(--bpmb-success, #16a34a); --bpmb-badge-bg: var(--bpmb-success-light, #dcfce7); }
.bpmb-badge--danger    { --bpmb-badge-fg: var(--bpmb-danger, #dc2626);  --bpmb-badge-bg: var(--bpmb-danger-light, #fee2e2); }
.bpmb-badge--warning   { --bpmb-badge-fg: var(--bpmb-warning, #d97706); --bpmb-badge-bg: var(--bpmb-warning-light, #fef3c7); }
.bpmb-badge--info      { --bpmb-badge-fg: var(--bpmb-info, #2563eb);    --bpmb-badge-bg: var(--bpmb-info-light, #dbeafe); }
.bpmb-badge--featured  { --bpmb-badge-fg: #92400e; --bpmb-badge-bg: #fef3c7; }

/*--------------------------------------------------------------
# Avatar
--------------------------------------------------------------*/
.bpmb-avatar {
    --bpmb-avatar-size: 32px;

    display: inline-block;
    flex: 0 0 auto;
    width: var(--bpmb-avatar-size);
    height: var(--bpmb-avatar-size);
    overflow: hidden;
    border-radius: 9999px;
}

.bpmb-avatar img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: inherit;
}

.bpmb-avatar--sm { --bpmb-avatar-size: 24px; }
.bpmb-avatar--lg { --bpmb-avatar-size: 48px; }

/* Stacked group (co-authors). */
.bpmb-avatar-group {
    display: inline-flex;
    align-items: center;
}

.bpmb-avatar-group .bpmb-avatar + .bpmb-avatar {
    margin-inline-start: -8px;
    box-shadow: 0 0 0 2px var(--bpmb-bg-primary, #fff);
}

/*--------------------------------------------------------------
# States — EMPTY / ERROR / SUCCESS (the other three of the four)
--------------------------------------------------------------*/
.bpmb-empty,
.bpmb-error-state,
.bpmb-success-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;

    padding: 40px 20px;
    text-align: center;

    color: var(--bpmb-text-secondary, #6b7280);
    background: var(--bpmb-bg-secondary, #f8fafc);
    border: 1px solid var(--bpmb-border-color, #e2e8f0);
    border-radius: 12px;
}

.bpmb-empty__icon,
.bpmb-error-state__icon,
.bpmb-success-state__icon {
    color: var(--bpmb-text-muted, #9ca3af);
}

/*
 * `--bpmb-danger-text`, not `--bpmb-danger`.
 *
 * `--bpmb-danger` is the SIGNAL colour - for borders, icons and filled buttons, where
 * it sits against a light surface. As body text on the state's own dark surface it
 * measured 3.13:1, under the 4.5:1 AA floor. `--bpmb-danger-text` is the token that
 * flips per mode for exactly this (#721c24 light, #fca5a5 dark).
 */
.bpmb-error-state   { color: var(--bpmb-danger-text, #721c24); }
.bpmb-success-state { color: var(--bpmb-success, #16a34a); }

.bpmb-error-state__icon   { color: currentColor; }
.bpmb-success-state__icon { color: currentColor; }

.bpmb-empty__title,
.bpmb-error-state__title,
.bpmb-success-state__title {
    margin: 0;
    font-size: 16px;
    font-weight: 600;
    color: var(--bpmb-text-primary, #111827);
}

.bpmb-empty__text,
.bpmb-error-state__text,
.bpmb-success-state__text {
    margin: 0;
    font-size: 14px;
    line-height: 1.5;
}

/*--------------------------------------------------------------
# Responsive — spec 09 §4: stack under 480px, full-bleed dialogs
--------------------------------------------------------------*/
@media (max-width: 640px) {
    .bpmb-toast-container {
        inset-block-start: 12px;
        inset-inline: 12px;
        max-width: none;
    }
}

@media (max-width: 480px) {
    .bpmb-modal {
        padding: 0;
        align-items: flex-end;
    }

    .bpmb-modal__panel {
        max-width: none;
        max-height: 90vh;
        border-start-start-radius: var(--bpmb-modal-radius);
        border-start-end-radius: var(--bpmb-modal-radius);
        border-end-start-radius: 0;
        border-end-end-radius: 0;
    }

    .bpmb-modal__foot {
        flex-direction: column-reverse;
    }

    .bpmb-modal__foot .bpmb-btn {
        width: 100%;
    }
}

/*--------------------------------------------------------------
# Reduced motion
--------------------------------------------------------------*/
@media (prefers-reduced-motion: reduce) {
    .bpmb-modal,
    .bpmb-modal__panel,
    .bpmb-toast,
    .bpmb-btn {
        transition: none;
    }

    .bpmb-skeleton {
        animation: 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);

    margin: 0;
    padding: 0;
    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;
}

.bpmb-pagination .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.
   ========================================================================== */

.bpmb-topic-header {
	padding-block-end: var(--bpmb-space-md);
	margin-block-end: var(--bpmb-space-lg);
	border-block-end: 1px solid var(--bpmb-border-color);
}

.bpmb-topic-name {
	margin: 0 0 var(--bpmb-space-sm);
	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: var(--bpmb-space-sm) 0 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-sm);
}

.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) {
	.bpmb-topic-header {
		margin-block-end: var(--bpmb-space-md);
	}
}

/* ==========================================================================
   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;
}

/* 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;
}

.bpmb-follow-btn.is-following:hover {
	color: var(--bpmb-danger) !important;
	border-color: var(--bpmb-danger) !important;
}

/* 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;
}
