/**
 * WPMediaVerse — Frontend Styles
 *
 * Base styles for templates, shortcodes, and unstyled blocks.
 * Mobile-first. RTL-ready via [dir="rtl"] overrides.
 * Dark mode via BuddyNext [data-theme="dark"] token cascade.
 *
 * @package WPMediaVerse
 */

/* ==========================================================================
   0. Design Tokens
   ========================================================================== */

:root {
    /* Typography — BuddyNext first, then system stack fallback */
    --mvs-font: var(--font-body, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
    --mvs-font-heading: var(--font-display, var(--mvs-font));
    /* Font-size scale — single source of truth for every font-xs/sm/md/lg
       callsite (137 of them, Free + Pro + admin). Was only in the never-enqueued
       src/shared/design-tokens.css, so bare uses silently fell back to inherited size. */
    --mvs-font-xs: 12px;
    --mvs-font-sm: 14px;
    --mvs-font-md: 16px;
    --mvs-font-lg: 18px;

    /* Brand — prefer theme BUTTON tokens first so the plugin's primary
       buttons (Follow / Post / Add Media (+) / .mvs-btn--primary / .mvs-fab)
       match the theme's own button palette. Fall back to the theme's
       primary/link color, then to the plugin's hard-coded brand color.
         BuddyX, BuddyX Pro       → --button-background-color
         BuddyBoss                → --bb-primary-button-background
         Reign + generic themes   → --color-theme-primary
       Without this cascade, Follow/Post/+ buttons were always the
       brand/link color regardless of how the theme styled its own
       buttons — visibly off-palette next to native theme buttons.
       --mvs-on-primary picks up the theme's button text color via the
       same chain. */
    --mvs-primary: var(--reign-site-button-bg-color, var(--button-background-color, var(--bb-primary-button-background, var(--color-theme-primary, var(--brand, #0073aa)))));
    --mvs-primary-hover: var(--reign-site-button-bg-hover-color, var(--button-background-hover-color, var(--bb-primary-button-background-regular-hover, var(--color-theme-primary, var(--brand-hover, #005a87)))));
    --mvs-on-primary: var(--reign-site-button-text-color, var(--button-text-color, var(--bb-primary-button-text-regular, #ffffff)));

    /* ── A CONTRAST FLOOR WE OWN ──────────────────────────────────────────
     *
     * Everything above derives from the theme, which means our AA compliance
     * was decided by whichever theme the buyer installed. Measured: the primary
     * call-to-action passes on Reign (4.51) and FAILS on Astra (4.29). Both
     * halves of that pair come from the theme, so when a theme's own button
     * colours do not meet AA, we inherited the failure (Basecamp 10252222099).
     *
     * `--mvs-primary-fill` nudges the accent 10% toward black. That alone does
     * NOT guarantee the floor and was never going to: measured across candidate
     * brand colours, a 15% darken still leaves white text at 2.00 on yellow and
     * 2.88 on lime. Darkening enough to fix those would change the buyer's
     * brand colour, which is the one thing they would notice and did not ask
     * for.
     *
     * `--mvs-on-primary-auto` is what actually guarantees it: relative colour
     * syntax reads the fill's OKLCH lightness and returns white below the
     * threshold, black above it. The hue is never touched — only the text
     * flips. Measured on the same candidates, fill + auto foreground:
     *
     *   Reign 5.38 · Astra 6.15 · yellow 11.78 · lime 8.09 · pink 5.19
     *   navy 17.09 · grey 6.41          (minimum 5.19, all comfortably AA)
     *
     * The 10% nudge stays because it buys headroom on the dark accents that
     * were merely scraping past — Reign goes 4.51 -> 5.38.
     *
     * Fallback: browsers without relative colour syntax keep the theme's own
     * button text colour, i.e. exactly today's behaviour. Nobody is worse off
     * than before; supporting browsers are better off.
     */
    --mvs-primary-fill: color-mix(in srgb, var(--mvs-primary) 90%, #000);
    /*
     * Declared straight, with NO @supports guard, and that is deliberate.
     * Chrome discards `@supports (color: oklch(from red l c h))` at the at-rule
     * parser while `CSS.supports()` returns true for the same string — measured,
     * the guarded rule never reached the CSSOM at all. The guard is also
     * unnecessary: a custom property a browser cannot parse becomes
     * guaranteed-invalid, so every consumer's own `var(..., fallback)` takes
     * over. Old browsers therefore keep exactly today's behaviour for free.
     */
    --mvs-on-primary-auto: oklch(from var(--mvs-primary-fill) clamp(0, (0.62 - l) * 1000, 1) 0 0);

    /* The brand colour AS TEXT, dark enough to pass AA.
     *
     * --mvs-primary is tuned to be a button BACKGROUND, where white sits on it
     * and passes easily. The same value used as text on a light page does not:
     * measured 4.36:1 on the dashboard rail link and the active tab label,
     * under the 4.5 floor for normal text (Basecamp 10253005724).
     *
     * Clamping lightness keeps the theme's hue - a site's brand still reads as
     * its brand - while guaranteeing the contrast. Text only; the button
     * background keeps the unmodified colour, which is why this is a separate
     * token rather than a change to --mvs-primary.
     *
     * The dark block below sets it back to --mvs-primary: a theme lightens its
     * brand for dark mode already (measured 7.98:1 there), so darkening again
     * would make it worse in exactly the mode that was fine. */
    --mvs-primary-text: oklch(from var(--mvs-primary) min(l, 0.48) c h);

    /*
     * The primary button's colours, resolved ONCE.
     *
     * `.mvs-btn--primary` is declared in four stylesheets across the two
     * plugins — here, shared-ui-frame.css (modals), bp-integration.css
     * (#buddypress, where a class-only rule loses to the theme's
     * `#buddypress button`) and Pro's gamification.css (which wins on the
     * competition pages by load order). The three extra copies exist for
     * specificity and load order, which are real problems; restating the
     * COLOUR in each of them was not. It meant the contrast-floor fix had to
     * be made four times, and whichever copy someone forgets is the one that
     * ships broken — which is exactly how a `color: var(--mvs-surface)` (a
     * page-surface token used as button text, black-on-brand in dark mode)
     * survived in shared-ui-frame.css long after the same mistake was found
     * and commented elsewhere.
     *
     * The other three now reference these four tokens and declare no colour of
     * their own, so the formula lives here and changing it changes every
     * surface. They keep a plain `var(--mvs-primary)` fallback so a context
     * that somehow loads without this file degrades to the theme colour rather
     * than to nothing — a fallback, deliberately not a second copy of the
     * formula. Basecamp 10252281364.
     */
    --mvs-btn-primary-bg: var(--mvs-primary-fill, var(--mvs-primary));
    --mvs-btn-primary-fg: var(--mvs-on-primary-auto, var(--mvs-on-primary, #ffffff));
    --mvs-btn-primary-bg-hover: var(--mvs-primary-hover);
    --mvs-btn-primary-fg-hover: var(--mvs-on-primary, #ffffff);

    --mvs-primary-rgb: 0, 115, 170;
    --mvs-danger: var(--red, #d63638);
    --mvs-danger-rgb: 214, 54, 56;
    --mvs-success: var(--green, #44b700);
    --mvs-success-rgb: 68, 183, 0;
    --mvs-warning: #b58105;
    --mvs-warning-rgb: 181, 129, 5;

    /*
     * Text-only variants of the semantic colours, same split as
     * --mvs-primary-text above and for the same reason: each is used BOTH as a
     * fill (light text on top) and as text on its own tint, and those two jobs
     * want opposite adjustments.
     *
     * Measured on the drive chips against the COMPOSITED tint — the number that
     * matters, since the fill is an alpha over the row, not an opaque colour:
     *
     *   before      light    dark
     *   PDF         3.94     2.78
     *   DIR/word    3.90     6.82
     *
     * So light needed darkening and dark needed lightening; the dark block
     * below does the second half. Changing --mvs-danger itself was the tempting
     * one-liner and is wrong in both directions: it fills the Delete button,
     * where white sits on it at 4.73:1, and moving it either way breaks that.
     * (Basecamp 10253005724.)
     */
    --mvs-danger-text: oklch(from var(--mvs-danger) min(l, 0.45) c h);
    --mvs-success-text: oklch(from var(--mvs-success) min(l, 0.45) c h);

    /* Semantic backgrounds — derived tints (work in light + dark via alpha) */
    --mvs-info-bg: rgba(var(--mvs-primary-rgb), 0.08);
    --mvs-info-bg-light: rgba(var(--mvs-primary-rgb), 0.04);
    --mvs-info-border: rgba(var(--mvs-primary-rgb), 0.30);
    --mvs-success-bg: rgba(var(--mvs-success-rgb), 0.10);
    --mvs-warning-bg: rgba(var(--mvs-warning-rgb), 0.12);
    --mvs-danger-bg: rgba(var(--mvs-danger-rgb), 0.10);

    /* Extended semantic tokens — promoted from former PHANTOM uses (used via
       var() across the plugin but never defined, so they silently froze on a
       light hardcoded fallback and broke in dark mode). Defined here = single
       source of truth, dark-mode-safe. */
    --mvs-bg-subtle: var(--mvs-surface-2);     /* subtle panel fill */
    --mvs-on-danger: #ffffff;                  /* text on danger (red both modes) */
    /* Emphasis tier — see the border contract below. Mixing toward --mvs-text
       means this strengthens in BOTH modes off one declaration: dark redefines
       --mvs-border and --mvs-text, so the mix re-resolves lighter there. */
    --mvs-border-strong: color-mix(in oklab, var(--mvs-border), var(--mvs-text) 30%);
    --mvs-accent: #6366f1;                      /* indigo accent (boosts/highlights) */
    --mvs-accent-2: #ec4899;                     /* secondary accent (stories-ring gradient end) */
    --mvs-accent-soft: rgba(99, 102, 241, 0.12); /* translucent → works light + dark */

    /* ── Theme palette mapping (THE single source of truth) ──────────────────
       Each --mvs-* token inherits from the active theme's own variable so the
       plugin matches the theme in BOTH light and dark mode (the theme flips its
       own vars). Chain order = Reign → BuddyX / BuddyX Pro (--global-* /
       --color-theme-*) → BuddyNext (--text-* / --bg-*) → hardcoded fallback.
       Only the active theme's var resolves; the rest are absent. To support a
       new theme, add its var name to these chains here — nowhere else. The dark
       block below repeats the SAME chains with dark hardcoded fallbacks (only
       hit by generic themes that signal dark but don't theme their own vars). */

    /* Surfaces */
    --mvs-bg: var(--reign-site-body-bg-color, var(--color-theme-white-box, var(--bg, #ffffff)));
    --mvs-surface: var(--reign-site-body-bg-color, var(--global-body-lightcolor, var(--bg-subtle, #f6f7f7)));
    --mvs-surface-2: var(--reign-site-body-bg-color, var(--global-body-lightcolor, var(--bg-hover, #eeeeee)));
    --mvs-overlay: rgba(0, 0, 0, 0.72);
    --mvs-bg-muted: var(--reign-site-body-bg-color, var(--global-body-lightcolor, #f9fafb));

    /* Text */
    --mvs-text: var(--reign-site-body-text-color, var(--global-title-color, var(--text-1, #1e1e1e)));
    --mvs-text-secondary: var(--reign-site-alternate-text-color, var(--global-font-color, var(--text-2, #646970)));
    --mvs-text-muted: var(--reign-site-alternate-text-color, var(--color-meta, var(--text-3, #a7aaad)));

    /* ── Borders: THREE TIERS, ONE JOB EACH ─────────────────────────────────
       Before 2.4.0 every box drew the same line: a text input, the card holding
       it, and the panel holding that were all `1px solid var(--mvs-border)`. So
       nesting produced three identical edges and nothing receded — the member
       counted boxes instead of reading content. Pick by JOB, never by taste:

         --mvs-border-light   STRUCTURE — cards, panels, sections, rows,
                              dividers. Neutral and quiet; it separates, it does
                              not ask to be looked at.
         --mvs-border         CONTROLS  — input, select, textarea, button, chip,
                              dropzone. Theme-tinted: a visible edge here means
                              "you can interact with me".
         --mvs-border-strong  EMPHASIS  — active/selected state, table head rule.

       Two hard rules that keep this from drifting back:
       1. A bordered box MUST NOT contain another bordered box. Where nesting is
          real, the OUTER one drops its border and expresses depth with
          background + spacing instead.
       2. Semantic borders (success/warning/danger, brand) are NOT part of this
          ladder — never swap one for a neutral tier, it destroys the meaning. */
    --mvs-border: var(--reign-site-border-color, var(--global-border-color, var(--border, #dcdcde)));
    --mvs-border-light: var(--border-soft, #e9e9ea);
    /* Elevation — BuddyNext-first (soft, layered --bn-shadow-*), with a matching
       standalone fallback. Shared Wbcom Community-OS standard: components pick a
       token, never a literal box-shadow, so elevation is uniform across the suite. */
    --mvs-shadow-sm: var(--bn-shadow-sm, 0 1px 2px rgba(16, 24, 40, 0.04), 0 2px 4px rgba(16, 24, 40, 0.04));
    --mvs-shadow-md: var(--bn-shadow-md, 0 2px 4px rgba(16, 24, 40, 0.04), 0 8px 16px rgba(16, 24, 40, 0.06));
    --mvs-shadow-lg: var(--bn-shadow-lg, 0 8px 16px rgba(16, 24, 40, 0.06), 0 24px 48px rgba(16, 24, 40, 0.08));

    /* Radii — BuddyNext first, then hardcoded fallback */
    --mvs-radius-sm: var(--r-sm, 6px);
    --mvs-radius-md: var(--r-md, 8px);
    --mvs-radius-lg: var(--r-lg, 12px);
    --mvs-radius-pill: var(--r-full, 100px);

    /*
     * Breakpoints — DOCUMENTATION ONLY. A @media query cannot read a custom
     * property, so nothing consumes these; they exist so that "the tablet
     * breakpoint" means one thing to the next person.
     *
     * They previously did not. This block said tablet=768 / mobile=480 while
     * src/shared/design-tokens.css said tablet=1024 / mobile=767 — the same two
     * names, different numbers, and whichever file you happened to read decided
     * what you wrote. That is how this codebase ended up with rules at both 767
     * and 768, and at both 1023 and 1024 (Basecamp 10252222205). Both blocks now
     * carry the list below.
     *
     *   480px  small phone                       (13 rules)
     *   640px  phone — the most-used breakpoint  (24 Free / 38 Pro)
     *   768px  tablet boundary; below it is 767  (17 rules)
     *  1024px  top of tablet; desktop is 1025+   (5 rules)
     *   782px  WordPress admin's own breakpoint — core's number, not ours
     *
     * The contract asks for three and there are five. Collapsing them is a
     * deliberate redesign, not a tidy-up, and is NOT what this card did — the
     * fix here was to stop the same name meaning two things.
     */
    --mvs-bp-phone-sm: 480px;
    --mvs-bp-phone: 640px;
    --mvs-bp-tablet: 768px;
    --mvs-bp-desktop: 1025px;

    /* ─── Mobile UX guideline tokens (see docs/MOBILE_UX_GUIDELINE.md §2.2) ── */

    /* Touch target floor — Apple HIG / WCAG 2.5.5 (AAA). Every interactive
       element below the desktop breakpoint must clear this in both axes. */
    --mvs-touch-min: 44px;

    /* Spacing scale — mirrors theme.json so PHP and CSS share one ramp. */
    --mvs-space-1: 0.25rem;  /*  4px */
    --mvs-space-2: 0.5rem;   /*  8px */
    --mvs-space-3: 1rem;     /* 16px — base gutter */
    --mvs-space-4: 1.5rem;   /* 24px */
    --mvs-space-5: 2rem;     /* 32px */
    --mvs-space-6: 3rem;     /* 48px */
    --mvs-space-7: 4rem;     /* 64px */

    /* Z-index layers — never use bare integers in plugin CSS.
       The overlay-and-above band sits in the 9990s: theme sticky headers run
       high (Reign #masthead is 999/1000, BuddyBoss ~991), and at the old
       200–500 an MVS dropdown/modal/toast painted BEHIND the pinned header
       (Basecamp 10240335491). The band clears common theme headers while
       staying under the WP admin bar (99999), which must remain on top.
       Ceiling note: a theme with a fixed header at 99999 (e.g. Divi) would
       still win; that is a deliberate stop short of the admin bar rather than
       an oversight. Page chrome that legitimately sits below a theme header
       (base/sticky/fab) stays low. */
    --mvs-z-base:    1;
    --mvs-z-sticky:  50;
    --mvs-z-fab:     100;
    --mvs-z-overlay: 9990;
    --mvs-z-modal:   9991;
    --mvs-z-toast:   9992;
    --mvs-z-tooltip: 9993;

    /* Motion */
    --mvs-duration-fast: 120ms;
    --mvs-duration-base: 200ms;
    --mvs-duration-slow: 320ms;
    --mvs-easing: cubic-bezier(0.2, 0, 0, 1);

    /* FAB sizing + safe area so floating elements never overlap content. */
    --mvs-fab-size:   48px;
    --mvs-fab-offset: 16px;
    --mvs-fab-safe:   calc(var(--mvs-fab-size) + var(--mvs-fab-offset) * 2);
}

/* Dark mode: theme-controlled only (no prefers-color-scheme).
   Honors every Wbcom Community-OS dark signal:
   - [data-theme="dark"] on <html> (generic themes)
   - [data-bn-theme="dark"] on <html> (BuddyNext)
   - [data-bx-mode="dark"] on <html> (Reign / BuddyX / BuddyX Pro 5.1+)
   - .dark-mode on <html> or <body> (legacy BuddyX)
   When a parent theme is active its own --color-* vars cascade through the
   :root mapping above, so we only set hardcoded values for sites
   without a dark-mode-aware parent theme. */
[data-theme="dark"],
[data-bn-theme="dark"],
[data-bx-mode="dark"],
html.dark-mode,
body.dark-mode {
    /* Same Reign chain as :root, but with DARK hardcoded fallbacks. Reign flips
       its own --reign-site-* vars for dark, so those still track the theme.

       The generic --global-* / --color-* fallbacks are deliberately NOT repeated
       in this block. Themes such as BuddyX declare them once at :root with
       light-mode values (--global-font-color: #0f172a, --global-body-lightcolor:
       #f1f5f9 -- note the name) and never flip them for dark, so chaining
       through them meant the dark fallbacks below were never reached: dark mode
       resolved to near-black text on a dark surface (~1.3:1) on every
       MediaVerse surface. A theme that genuinely themes dark flips
       --reign-site-* or restyles directly; a readable default beats an
       unreadable "themed" one. */
    --mvs-bg: var(--reign-site-body-bg-color, #121212);
    --mvs-surface: var(--reign-site-body-bg-color, #1e1e1e);
    --mvs-surface-2: var(--reign-site-body-bg-color, #2a2a2a);
    --mvs-bg-muted: var(--reign-site-body-bg-color, #2a2a2a);
    --mvs-text: var(--reign-site-body-text-color, #f0f0f0);
    --mvs-text-secondary: var(--reign-site-alternate-text-color, #b0b0b0);
    --mvs-text-muted: var(--reign-site-alternate-text-color, #888888);
    --mvs-border: var(--reign-site-border-color, #363636);
    --mvs-border-light: #2e2e2e;
    /* Dark mode: a theme already lightens its brand here (measured 7.98:1 on
       the same rail link that fails at 4.36:1 in light), so the light-mode
       darkening must NOT apply or it would break the mode that was correct. */
    --mvs-primary-text: var(--mvs-primary);
    --mvs-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.35);
    --mvs-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.45);
    --mvs-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.55);
    --mvs-warning: #f0b73a;
    /* Danger and success were never lightened for dark mode the way warning
       was, so a PDF chip painted --mvs-danger on a --mvs-danger-bg tint scored
       2.78:1 against the dark surface. The fill tokens stay put; only the text
       variants lift. */
    --mvs-danger-text: oklch(from var(--mvs-danger) max(l, 0.72) c h);
    --mvs-success-text: oklch(from var(--mvs-success) max(l, 0.72) c h);
    --mvs-info-bg: rgba(var(--mvs-primary-rgb), 0.18);
    --mvs-info-bg-light: rgba(var(--mvs-primary-rgb), 0.10);
    --mvs-success-bg: rgba(var(--mvs-success-rgb), 0.18);
    --mvs-warning-bg: rgba(240, 183, 58, 0.15);
    --mvs-danger-bg: rgba(var(--mvs-danger-rgb), 0.18);
}

[data-theme="dark"] .mvs-album-badge,
[data-bn-theme="dark"] .mvs-album-badge,
[data-bx-mode="dark"] .mvs-album-badge,
.dark-mode .mvs-album-badge,
[data-theme="dark"] .mvs-album-badge .dashicons,
[data-bn-theme="dark"] .mvs-album-badge .dashicons,
[data-bx-mode="dark"] .mvs-album-badge .dashicons,
.dark-mode .mvs-album-badge .dashicons,
[data-theme="dark"] .mvs-grid-item-overlay,
[data-bn-theme="dark"] .mvs-grid-item-overlay,
[data-bx-mode="dark"] .mvs-grid-item-overlay,
.dark-mode .mvs-grid-item-overlay {
    color: var(--mvs-text);
}

[data-theme="dark"] .mvs-empty-state .dashicons,
[data-bn-theme="dark"] .mvs-empty-state .dashicons,
[data-bx-mode="dark"] .mvs-empty-state .dashicons,
.dark-mode .mvs-empty-state .dashicons {
    color: var(--mvs-text-muted, #6c7781);
}

/* ==========================================================================
   0a. Type Reset — lock down all typography inside MVS templates.
       Follows BuddyNext type scale. Works with or without BuddyNext.
   ========================================================================== */

/* WordPress defers global-styles-inline-css to <body> when Interactivity API
   script modules are used. This overrides theme body font-size rules that loaded
   in <head>. We reset body font-size on MVS pages to the theme's own CSS variable
   (--font-size-regular) if available, falling back to 1rem. This ensures consistent
   typography regardless of stylesheet load order. */
body.mvs-page {
    font-size: var(--font-size-regular, 1rem);
}

/* Scope: all MVS template wrappers use these classes.
   font-family set here so BuddyNext's Inter/Plus Jakarta Sans carries through. */
.mvs-single-media,
.mvs-single-album,
.mvs-single-collection,
.mvs-explore,
.mvs-dashboard,
.mvs-profile-edit {
    font-family: var(--mvs-font);
    font-size: 0.9375rem;
    line-height: 1.6;
    color: var(--mvs-text);
}

/* Private-media gate shown when the viewer can't see the requested media. */
.mvs-privacy-blocked {
    text-align: center;
    padding: var(--mvs-space-7) var(--mvs-space-4);
}

.mvs-privacy-blocked__icon {
    font-size: 48px;
}

/* Heading reset within any MVS template — uses display font for headings */
.mvs-single-media h1,
.mvs-single-album h1,
.mvs-single-collection h1,
.mvs-explore h1,
.mvs-dashboard h1,
.mvs-profile-edit h1 {
    font-family: var(--mvs-font-heading);
    font-size: 1.375rem;
    font-weight: 700;
    line-height: 1.3;
    margin: 0 0 0.5em;
}

.mvs-single-media h2,
.mvs-single-album h2,
.mvs-single-collection h2,
.mvs-explore h2,
.mvs-dashboard h2,
.mvs-profile-edit h2 {
    font-family: var(--mvs-font-heading);
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.3;
    margin: 0 0 0.5em;
}

.mvs-single-media h3,
.mvs-single-album h3,
.mvs-single-collection h3,
.mvs-explore h3,
.mvs-dashboard h3,
.mvs-profile-edit h3 {
    font-family: var(--mvs-font-heading);
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.35;
    margin: 0 0 0.5em;
}

.mvs-single-media h4,
.mvs-single-album h4,
.mvs-single-collection h4,
.mvs-explore h4,
.mvs-dashboard h4,
.mvs-profile-edit h4 {
    font-size: 0.875rem;
    font-weight: 600;
    margin: 0 0 0.5em;
}

/* Form elements inherit font from wrapper */
.mvs-single-media button,
.mvs-single-media input,
.mvs-single-media select,
.mvs-single-media textarea,
.mvs-dashboard button,
.mvs-dashboard input,
.mvs-dashboard select,
.mvs-dashboard textarea,
.mvs-profile-edit button,
.mvs-profile-edit input,
.mvs-profile-edit select,
.mvs-profile-edit textarea {
    font-family: inherit;
    font-size: inherit;
}

/* ==========================================================================
   0c. Mobile UX base layer
       Defined by docs/MOBILE_UX_GUIDELINE.md. Lives here so every template
       inherits Apple-quality defaults without per-page CSS overrides.
   ========================================================================== */

/* §3.1 — Universal touch-target floor below the desktop breakpoint.
   Every interactive element clears 44×44 in both axes, expanding via padding
   when the visible chip is smaller.

   1024, not 1023. Desktop starts at 1025 (src/shared/base.css), tablet is
   768-1024, and the block CSS generator emits `max-width: 1024px` for its
   tablet tier — so at exactly 1024px, which is iPad landscape, this floor was
   the ONLY tablet rule in the plugin that switched itself off. The three other
   tablet blocks in this file already say 1024; this one was the outlier
   (Basecamp 10252222205). */
@media (max-width: 1024px) {
    .mvs-page .mvs-btn,
    .mvs-page .mvs-icon-btn,
    .mvs-page .mvs-fab,
    .mvs-page .mvs-tab,
    .mvs-page .mvs-action-icon,
    .mvs-page .mvs-toast-close,
    .mvs-page .mvs-modal-close,
    .mvs-page .mvs-back-link,
    .mvs-page .mvs-tag-pill,
    .mvs-page .mvs-tag-cloud-item,
    .mvs-page .mvs-bulk-check,
    .mvs-single-media .mvs-btn,
    .mvs-single-album .mvs-btn,
    .mvs-profile-edit .mvs-btn,
    .mvs-dashboard .mvs-btn {
        min-height: var(--mvs-touch-min);
        min-width: var(--mvs-touch-min);
    }
    /* Load More is a full-width-ish button whose own padding left it at 40px
       tall. Height only — its width is already well past the floor and forcing
       min-width would fight the centred layout. */
    .mvs-page .mvs-load-more-btn {
        min-height: var(--mvs-touch-min);
    }
    /* Icon-only link wrapping a 40px avatar. Grow the HIT AREA to the floor and
       centre the avatar inside it, rather than scaling the image up — the
       picture should stay 40px, the target should not. Same technique as the
       chips above. Found by the tap-target assertion in
       audit/journeys/customer/08-explore-browse-mobile.md. */
    .mvs-page .mvs-dashboard-rail-head__avatar-link {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: var(--mvs-touch-min);
        min-width: var(--mvs-touch-min);
    }
    /* Small chips/tags expand their hit area via padding instead of growing
       the visible bubble. Tag-cloud filter chips were 36px before this floor. */
    .mvs-page .mvs-tag-pill,
    .mvs-page .mvs-tag-cloud-item {
        display: inline-flex;
        align-items: center;
    }
    /* Upload privacy select sat at 35px — give it the touch-target height
       without forcing the 44px min-width a full-width control would break on. */
    .mvs-page .mvs-upload-privacy {
        min-height: var(--mvs-touch-min);
    }
}

/* §6 — Back link component for every detail page header. */
.mvs-back-link {
    display: inline-flex;
    align-items: center;
    gap: var(--mvs-space-2);
    color: var(--mvs-text-secondary);
    text-decoration: none;
    font-size: 0.875rem;
    line-height: 1;
    padding: var(--mvs-space-2) var(--mvs-space-3) var(--mvs-space-2) var(--mvs-space-2);
    margin-bottom: var(--mvs-space-3);
    border-radius: var(--mvs-radius-md);
    transition: color var(--mvs-duration-fast) var(--mvs-easing),
                background var(--mvs-duration-fast) var(--mvs-easing);
}

.mvs-back-link:hover,
.mvs-back-link:focus-visible {
    color: var(--mvs-primary);
    background: var(--mvs-info-bg-light);
}

.mvs-back-link i,
.mvs-back-link svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    /* Hide textual label — keep the chevron only.
       The screen-reader label stays via aria-label on the anchor. */
    .mvs-back-link__label {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
    .mvs-back-link {
        padding: var(--mvs-space-2);
    }
}

/* §7 — FAB safe area: every page that hosts a FAB pads its bottom so content
   never sits behind it. iOS notch supported via env(safe-area-inset-bottom). */
.mvs-page,
.mvs-single-media,
.mvs-single-album,
.mvs-single-collection,
.mvs-profile-edit,
.mvs-explore,
.mvs-dashboard {
    padding-bottom: calc(var(--mvs-fab-safe) + env(safe-area-inset-bottom, 0px));
}

/* §4.1/§4.2 — Icon-collapse buttons: text-and-icon control that collapses to
   icon-only below the tablet breakpoint and exposes a CSS-only tooltip. */
.mvs-btn--icon-collapse {
    display: inline-flex;
    align-items: center;
    gap: var(--mvs-space-2);
    position: relative;
}

.mvs-btn--icon-collapse i,
.mvs-btn--icon-collapse svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    .mvs-btn--icon-collapse .mvs-btn__label {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
    .mvs-btn--icon-collapse {
        padding-inline: var(--mvs-space-2);
        gap: 0;
        justify-content: center;
    }
}

/* CSS-only tooltip: shows on :hover (mouse) and :focus-visible (touch + kb).
   Pages set data-mvs-tooltip="Label" on any element to opt in. */
[data-mvs-tooltip] {
    position: relative;
}

[data-mvs-tooltip]::after {
    content: attr(data-mvs-tooltip);
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%) translateY(4px);
    background: var(--mvs-text);
    color: var(--mvs-bg);
    font-size: 0.75rem;
    line-height: 1;
    padding: var(--mvs-space-2) var(--mvs-space-2);
    border-radius: var(--mvs-radius-sm);
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--mvs-duration-fast) var(--mvs-easing),
                transform var(--mvs-duration-fast) var(--mvs-easing);
    z-index: var(--mvs-z-tooltip);
}

[data-mvs-tooltip]:hover::after,
[data-mvs-tooltip]:focus-visible::after {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

@media (prefers-reduced-motion: reduce) {
    [data-mvs-tooltip]::after { transition: none; }
}

/* §-skeleton — Loading shimmer placeholders.
   Drop-in component for any "waiting on REST" surface. Pages add the
   .mvs-skeleton class to a sized box; the shimmer animation pulses across.
   Reduced motion swaps to a static muted background. */
.mvs-skeleton {
    position: relative;
    overflow: hidden;
    background: var(--mvs-surface);
    border-radius: var(--mvs-radius-md);
    isolation: isolate;
}

.mvs-skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        90deg,
        transparent 0,
        rgba(255, 255, 255, 0.4) 50%,
        transparent 100%
    );
    transform: translateX(-100%);
    animation: mvs-skeleton-shimmer 1.4s var(--mvs-easing) infinite;
}

[data-theme="dark"] .mvs-skeleton::after,
[data-bx-mode="dark"] .mvs-skeleton::after,
html.dark-mode .mvs-skeleton::after,
body.dark-mode .mvs-skeleton::after {
    background: linear-gradient(
        90deg,
        transparent 0,
        rgba(255, 255, 255, 0.06) 50%,
        transparent 100%
    );
}

@keyframes mvs-skeleton-shimmer {
    100% {
        transform: translateX(100%);
    }
}

@media (prefers-reduced-motion: reduce) {
    .mvs-skeleton::after {
        animation: none;
    }
}

.mvs-skeleton--text {
    height: 0.875rem;
    margin-bottom: 0.5rem;
    border-radius: var(--mvs-radius-sm);
}

.mvs-skeleton--text:last-child {
    margin-bottom: 0;
    width: 60%;
}

.mvs-skeleton--avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
}

.mvs-skeleton--thumb {
    aspect-ratio: 1 / 1;
    border-radius: var(--mvs-radius-md);
}

.mvs-skeleton-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--mvs-space-2);
}

@media (min-width: 768px) /* --mvs-bp-tablet and up */ {
    .mvs-skeleton-grid {
        grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
        gap: var(--mvs-space-3);
    }
}

/* §5 — Tab strip pattern: horizontal scroll with edge-fade mask + scroll-snap.
   Components opt in by adding the .mvs-tabs-strip class. */
.mvs-tabs-strip {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
    /* Edge-fade telegraphs that more tabs exist beyond the right edge. */
    -webkit-mask-image: linear-gradient(to right, black calc(100% - 32px), transparent);
    mask-image: linear-gradient(to right, black calc(100% - 32px), transparent);
}

.mvs-tabs-strip::-webkit-scrollbar { display: none; }

.mvs-tabs-strip > * {
    scroll-snap-align: start;
    flex: 0 0 auto;
}

/* When the strip fits without scrolling, drop the mask so the right edge is solid. */
@supports selector(:has(*)) {
    .mvs-tabs-strip:not(:hover):not(:focus-within):where(:not([data-overflow="true"])) {
        /* Re-enabled by JS via [data-overflow="true"] when scrollWidth > clientWidth. */
    }
}

/* ==========================================================================
   0b. Profile Header Card (explore grid filtered by user)
   ========================================================================== */

.mvs-profile-header-card {
    display: flex;
    align-items: flex-start;
    gap: 32px;
    padding: 32px;
    margin-bottom: 24px;
    background: var(--mvs-surface);
    /* Pair the foreground with the background this component sets; without it
       the text inherited the ambient theme colour and went unreadable in dark
       mode (measured 1.07-1.38:1). */
    color: var(--mvs-text);
    border-radius: 12px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}

.mvs-profile-header-avatar {
    width: 120px;
    height: 120px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    border: 3px solid var(--mvs-surface-2);
}

.mvs-profile-header-info {
    flex: 1;
    min-width: 0;
}

.mvs-profile-header-top {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
    flex-wrap: wrap;
}

.mvs-profile-header-name {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 700;
    /* No color → inherits theme heading color, washes out on the dark surface
       card in dark mode (same bug class as the compete-hub headings). */
    color: var(--mvs-text);
}

.mvs-follow-toggle {
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.mvs-follow-toggle--following {
    background: var(--mvs-surface-2);
    color: var(--mvs-text);
    border: 1px solid var(--mvs-border);
}

.mvs-follow-toggle--following:hover {
    background: var(--mvs-danger-bg);
    color: var(--mvs-danger);
    border-color: var(--mvs-danger);
}

.mvs-follow-toggle:disabled {
    opacity: 0.6;
    cursor: wait;
}

.mvs-message-btn {
    transition: background 0.15s;
}

.mvs-profile-header-stats {
    display: flex;
    gap: 28px;
    font-size: 0.9375rem;
    color: var(--mvs-text-secondary);
    margin-bottom: 12px;
}

.mvs-profile-header-stats span {
    display: flex;
    align-items: baseline;
    gap: 4px;
}

.mvs-profile-header-stats strong {
    color: var(--mvs-text);
    font-size: 1.125rem;
    font-weight: 700;
}

.mvs-profile-header-bio {
    margin: 0;
    color: var(--mvs-text);
    font-size: 0.9rem;
    line-height: 1.6;
    max-width: 600px;
}

@media (max-width: 640px) {
    .mvs-profile-header-card {
        flex-direction: column;
        align-items: center;
        text-align: center;
        gap: 16px;
        padding: 24px 16px;
    }

    .mvs-profile-header-avatar {
        width: 96px;
        height: 96px;
    }

    .mvs-profile-header-top {
        justify-content: center;
    }

    .mvs-profile-header-stats {
        justify-content: center;
    }

    .mvs-profile-header-bio {
        max-width: 100%;
    }
}

/* ==========================================================================
   1. Single Media Template
   ========================================================================== */

.mvs-single-media {
    max-width: 680px;
    margin: 0 auto;
    padding: 20px 16px;
}

.mvs-media-article {
    background: var(--mvs-bg);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.mvs-media-header {
    padding: 16px var(--mvs-space-4);
}

.mvs-media-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.mvs-media-author-info {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.mvs-media-author-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}

.mvs-media-author-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.mvs-media-author-name {
    font-weight: 600;
    font-size: 0.9375rem;
    color: var(--mvs-text);
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mvs-media-author-name:hover {
    color: var(--mvs-primary);
}

.mvs-media-date {
    font-size: 0.8125rem;
    color: var(--mvs-text-muted);
}

.mvs-media-sep {
    margin: 0 2px;
}

.mvs-media-title {
    margin: 0;
    font-size: 1.5em;
    line-height: 1.3;
}

/* ==========================================================================
   Document identity line — type, size, privacy, location.
   The drive row already showed all four; this page showed none, so opening a
   document from the list LOST information. Privacy is the one that matters:
   without it a member cannot tell a private contract from a public one, on the
   same screen as a button that can mint a link anyone can open.
   ========================================================================== */

.mvs-media-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--mvs-space-2);
    margin: var(--mvs-space-2) 0 0;
    color: var(--mvs-text-muted);
    font-size: var(--mvs-font-sm);
    line-height: 1.4;
}

/* Separator is drawn, not typed, so the parts stay independently omittable —
   any of type / size / privacy / location can be absent without leaving a
   stranded bullet behind. */
.mvs-media-meta__item + .mvs-media-meta__item::before,
.mvs-media-meta__type + .mvs-media-meta__item::before,
.mvs-media-meta__privacy + .mvs-media-meta__item::before {
    content: "·";
    margin-inline-end: var(--mvs-space-2);
    opacity: 0.55;
}

.mvs-media-meta__item {
    font-variant-numeric: tabular-nums;
}

/* The type chip is a LABEL, not a status, so it stays neutral.
   It deliberately does NOT copy the drive's `--mvs-info-bg` + `--mvs-primary`
   pairing: on this theme `--mvs-primary` is red, so that pair renders red text
   on a blue wash. A neutral chip also lets the privacy chip beside it be the
   only coloured thing on the line, which is the one worth noticing. */
.mvs-media-meta__type {
    display: inline-flex;
    align-items: center;
    padding: 2px 6px;
    border-radius: var(--mvs-radius-sm);
    background: var(--mvs-bg-muted);
    color: var(--mvs-text-secondary);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* Amber wash carries the "restricted" signal; the TEXT is the normal ink so it
   stays legible. `--mvs-warning` on `--mvs-warning-bg` is the same hue at 15%
   alpha — amber on amber, which measured at almost no contrast in either
   theme. */
.mvs-media-meta__privacy {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: var(--mvs-radius-pill);
    background: var(--mvs-warning-bg);
    color: var(--mvs-text);
    font-size: var(--mvs-font-sm);
    font-weight: 600;
}

/* Sits beside the privacy chip and borrows its shape. Deliberately NOT the
   privacy chip's colour: this is not a restriction, it is a fact about reach,
   and colouring it like a lock would read as a second privacy state. */
.mvs-media-meta__shared {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: var(--mvs-surface-2);
    color: var(--mvs-text-secondary);
    border-radius: var(--mvs-radius-pill, 999px);
    padding: 2px var(--mvs-space-sm, 8px);
    font-size: 0.8125rem;
}

.mvs-media-meta__shared svg,
.mvs-media-meta__shared [data-lucide] {
    width: 14px;
    height: 14px;
    color: inherit;
    flex-shrink: 0;
}

.mvs-media-meta__privacy i,
.mvs-media-meta__privacy svg {
    color: var(--mvs-warning);
}

.mvs-media-meta__privacy i,
.mvs-media-meta__privacy svg {
    width: 12px;
    height: 12px;
}

/* The document block lines up with the title above it.
   `.mvs-media-content` is deliberately flush at 0 so a PHOTO can run edge to
   edge in its card — that is right for an image and wrong for a page of text,
   which sat 24px to the left of its own heading. Inset the document wrapper
   only, rather than padding the shared content area and boxing in every image
   on the site. */
.mvs-media-document {
    padding-inline: var(--mvs-space-4);
}

@media (max-width: 640px) {

    .mvs-media-document {
        padding-inline: var(--mvs-space-3);
    }

}

/* Follow steps back on a document page so Download is the only primary. */
.mvs-btn.mvs-follow-btn--quiet {
    background: transparent;
    border-color: var(--mvs-border);
    color: var(--mvs-text-secondary);
    font-weight: 500;
}

.mvs-btn.mvs-follow-btn--quiet:hover,
.mvs-btn.mvs-follow-btn--quiet:focus-visible {
    border-color: var(--mvs-primary);
    color: var(--mvs-primary);
}

/* Media Content */
.mvs-media-content {
    padding: 0;
}

.mvs-media-image {
    width: 100%;
    background: var(--mvs-surface-2);
}

.mvs-media-image img {
    display: block;
    width: 100%;
    height: auto;
}

.mvs-media-video {
    width: 100%;
    background: var(--mvs-overlay);
    /* aspect-ratio is set inline from the media's stored width/height so
     * the frame is reserved on first paint. Without it, the box reflows
     * when metadata/poster/playback transitions happen. */
}

.mvs-media-video video {
    display: block;
    width: 100%;
    height: 100%;
    /* Letterbox the source inside the locked frame instead of stretching
     * — keeps poster and playback at identical dimensions. */
    object-fit: contain;
    background: #000;
}

.mvs-media-audio {
    padding: 16px 24px;
}

.mvs-media-audio audio {
    width: 100%;
}

.mvs-media-description {
    padding: 12px var(--mvs-space-4) 0;
    line-height: 1.6;
    color: var(--mvs-text);
    font-size: 0.9375rem;
}

.mvs-media-description p:first-child {
    margin-top: 0;
}

/* Footer & Tags */

/*
 * ONE HORIZONTAL RHYTHM ON THE MEDIA CARD.
 *
 * The card's four rows each chose their own inline padding — header 24px,
 * content 0 (its own children re-inset to 24), social wrapper 0, footer and
 * comments 16px — so the comment box and the description sat 8px closer to the
 * card's edge than the title, meta, preview and actions above them. Reported as
 * "why is it touching the border": nothing was overflowing, the rows simply
 * disagreed about where the edge is.
 *
 * 24px desktop / 16px mobile, on every row, so a vertical line drawn down the
 * card touches the start of all of them.
 */
.mvs-media-footer {
    padding: 8px var(--mvs-space-4) var(--mvs-space-4);
}

.mvs-media-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.mvs-tag {
    display: inline-block;
    padding: 4px 12px;
    background: var(--mvs-info-bg);
    border-radius: 16px;
    font-size: 0.8125rem;
    color: var(--mvs-primary);
    text-decoration: none;
    transition: all 0.2s;
}

.mvs-tag:hover {
    background: var(--mvs-primary);
    color: var(--mvs-bg);
}

/* ==========================================================================
   2. Album Template
   ========================================================================== */

.mvs-single-album {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

.mvs-album-article {
    background: var(--mvs-bg);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    padding: 24px;
}

.mvs-album-header {
    margin-bottom: 24px;
}

.mvs-album-title {
    margin: 0 0 8px;
    font-size: 1.75em;
}

.mvs-album-description {
    color: var(--mvs-text-secondary);
    margin-bottom: 12px;
    line-height: 1.6;
}

.mvs-album-count {
    display: inline-block;
    padding: 4px 12px;
    background: var(--mvs-surface-2);
    border-radius: 16px;
    font-size: 0.85em;
    color: var(--mvs-text-secondary);
}

/* ==========================================================================
   3. Explore / Archive Template
   ========================================================================== */

.mvs-explore-page {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
    box-sizing: border-box;
    width: 100%;
}

.mvs-explore-header {
    margin-bottom: 24px;
}

.mvs-explore-header h1 {
    margin: 0;
    font-size: 1.75em;
    /* Sits directly on the THEME's page background, not on a MediaVerse
       surface, so it follows the theme's own text colour. Using --mvs-text made
       it vanish on a theme that signals dark (so --mvs-text goes light) while
       keeping the page background white - BuddyX only darkens its footer. */
    color: inherit;
}

/* ==========================================================================
   4. Media Grid (shared by explore, album, shortcodes)
   ========================================================================== */

.mvs-media-grid {
    display: grid;
    gap: 16px;
    max-width: 100%;
    overflow: hidden;
    box-sizing: border-box;
}

.mvs-cols-2 {
    grid-template-columns: repeat(2, 1fr);
}

.mvs-cols-3 {
    grid-template-columns: repeat(3, 1fr);
}

.mvs-cols-4 {
    grid-template-columns: repeat(4, 1fr);
}

.mvs-cols-5 {
    grid-template-columns: repeat(5, 1fr);
}

@media (max-width: 1024px) {

    .mvs-cols-5 {
        grid-template-columns: repeat(4, 1fr);
    }
}

@media (max-width: 767px) /* below --mvs-bp-tablet */ {

    .mvs-cols-3,
    .mvs-cols-4,
    .mvs-cols-5 {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 480px) {

    .mvs-cols-2,
    .mvs-cols-3,
    .mvs-cols-4,
    .mvs-cols-5 {
        grid-template-columns: 1fr;
    }
}

.mvs-grid-item {
    position: relative;
    overflow: hidden;
    border-radius: 8px;
    background: var(--mvs-surface-2);
    /* Pair the foreground with the background this component sets; without it
       the text inherited the ambient theme colour and went unreadable in dark
       mode (measured 1.07-1.38:1). */
    color: var(--mvs-text);
    aspect-ratio: 1;
    min-width: 0;
}

.mvs-grid-item a {
    display: block;
    width: 100%;
    height: 100%;
}

.mvs-grid-item img,
.mvs-grid-item .mvs-grid-video-preview {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.3s;
    background: #0f1014;
}

.mvs-grid-item:hover img,
.mvs-grid-item:hover .mvs-grid-video-preview {
    transform: scale(1.05);
}

.mvs-grid-item .mvs-grid-video-preview {
    pointer-events: none;
}

.mvs-grid--original .mvs-grid-item {
    aspect-ratio: auto;
}

.mvs-grid--original .mvs-grid-item img {
    height: auto;
}

.mvs-grid--original .mvs-dashboard-card-thumb {
    aspect-ratio: auto;
}

.mvs-grid--original .mvs-dashboard-card-thumb img {
    height: auto;
    object-fit: contain;
}

/* Original mode = true masonry (Pinterest / Unsplash): every image keeps its
   native aspect ratio (no square crop) and tiles pack with no gaps. CSS
   multi-column is a no-JS masonry — it works with Load More (appended cards
   flow into the columns) and preserves DOM order so the lightbox prev/next
   stays correct. This overrides .mvs-media-grid's `display:grid`, whose uniform
   rows left large gaps under short/wide images. (Aspect-ratio request.) */
.mvs-media-grid.mvs-grid--original {
    display: block;
    column-gap: 16px;
}

.mvs-grid--original.mvs-cols-2 {
    column-count: 2;
}

.mvs-grid--original.mvs-cols-3 {
    column-count: 3;
}

.mvs-grid--original.mvs-cols-4 {
    column-count: 4;
}

.mvs-grid--original.mvs-cols-5 {
    column-count: 5;
}

.mvs-grid--original .mvs-grid-item {
    -webkit-column-break-inside: avoid;
    break-inside: avoid;
    margin-bottom: 16px;
    width: 100%;
}

/* The card's <a> wrapper is forced square by `.mvs-feed .mvs-grid-item-link`
   (a later, equal-specificity rule). In masonry that square wrapper re-introduces
   the gap even though the <img> is natural height. Beat it with a compound
   selector so the link hugs the image and the card hugs the link. */
.mvs-media-grid.mvs-grid--original .mvs-grid-item-link,
.mvs-media-grid.mvs-grid--original .mvs-grid-item a {
    aspect-ratio: auto;
    height: auto;
}

@media (max-width: 1024px) {

    .mvs-grid--original.mvs-cols-5 {
        column-count: 4;
    }
}

@media (max-width: 767px) /* below --mvs-bp-tablet */ {

    .mvs-grid--original.mvs-cols-3,
    .mvs-grid--original.mvs-cols-4,
    .mvs-grid--original.mvs-cols-5 {
        column-count: 2;
    }
}

@media (max-width: 480px) {

    .mvs-grid--original.mvs-cols-2,
    .mvs-grid--original.mvs-cols-3,
    .mvs-grid--original.mvs-cols-4,
    .mvs-grid--original.mvs-cols-5 {
        column-count: 1;
    }
}

.mvs-grid-item-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: var(--mvs-text-muted);
}

.mvs-grid-item-placeholder .dashicons {
    font-size: 48px;
    width: 48px;
    height: 48px;
}

.mvs-grid-item-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.4);
    color: var(--mvs-bg);
    font-size: 0.9em;
    opacity: 0;
    transition: opacity 0.3s;
}

.mvs-grid-item:hover .mvs-grid-item-overlay,
.mvs-grid-item-link:hover .mvs-grid-item-overlay {
    opacity: 1;
}

.mvs-grid-item-stats {
    display: flex;
    gap: 16px;
    font-size: 1.1em;
    font-weight: 700;
}

.mvs-grid-stat {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* Lucide's default <svg> is 24x24, which towers over a two-digit count in the
   thumbnail overlay. Same cap the button rule applies, at the smaller size this
   context wants. `currentColor` is what makes these follow the token palette —
   the thing the emoji they replaced could never do. */
/* Cover placeholders for albums and collections. Same reason as the grid stats:
   a Lucide <svg> ignores the `font-size` these were sized by, so it needs real
   dimensions to keep the presence the placeholder was laid out around. */
.mvs-grid-collection-icon svg,
.mvs-grid-collection-icon [data-lucide],
.mvs-grid-album-icon svg,
.mvs-grid-album-icon [data-lucide] {
    width: 40px;
    height: 40px;
    stroke-width: 1.5;
    color: inherit;
}

.mvs-grid-stat svg,
.mvs-grid-stat [data-lucide] {
    width: 14px;
    height: 14px;
    stroke-width: 2;
    flex-shrink: 0;
    color: inherit;
}

.mvs-grid-item-title {
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Instagram-like feed: author row below each card */
.mvs-feed {
    gap: 6px;
}

.mvs-feed .mvs-grid-item {
    aspect-ratio: auto;
    border-radius: 4px;
    overflow: hidden;
    background: var(--mvs-bg);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.mvs-feed .mvs-grid-item-link {
    display: block;
    aspect-ratio: 1;
    overflow: hidden;
    position: relative;
}

.mvs-feed .mvs-grid-item-overlay {
    pointer-events: none;
}

.mvs-grid-item-info {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    background: var(--mvs-bg);
    font-size: 0.85em;
}

.mvs-grid-avatar {
    border-radius: 50%;
    width: 24px !important;
    height: 24px !important;
}

.mvs-grid-item-author {
    font-weight: 600;
    color: var(--mvs-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Avatar + name link to the uploader's profile (card #9962508646). Reset the
   default link chrome so it reads like the prior plain text; the name
   underlines + tints on hover/focus as the affordance. min-width:0 keeps the
   inner ellipsis working inside the flex link. */
.mvs-grid-item-author-link {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    text-decoration: none;
    color: inherit;
}

.mvs-grid-item-author-link:visited {
    color: inherit;
}

.mvs-grid-item-author-link:hover .mvs-grid-item-author,
.mvs-grid-item-author-link:focus-visible .mvs-grid-item-author {
    color: var(--mvs-primary, var(--mvs-text));
    text-decoration: underline;
}

.mvs-grid-item-author-link:focus-visible {
    outline: 2px solid var(--mvs-primary, currentColor);
    outline-offset: 2px;
    border-radius: 4px;
}

/* -- Album badge (carousel indicator) in feed */
.mvs-album-badge {
    position: absolute;
    top: 8px;
    right: 8px;
    display: flex;
    align-items: center;
    gap: 2px;
    background: rgba(0, 0, 0, 0.6);
    color: var(--mvs-bg);
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 0.8em;
    font-weight: 600;
    pointer-events: none;
    z-index: 2;
}

.mvs-album-badge .dashicons {
    font-size: 16px;
    width: 16px;
    height: 16px;
    color: var(--mvs-bg);
}

/* -- Gallery badge (multi-image group indicator) */
.mvs-gallery-badge {
    position: absolute;
    top: 8px;
    right: 8px;
    display: flex;
    align-items: center;
    gap: 2px;
    background: rgba(0, 0, 0, 0.6);
    color: var(--mvs-surface);
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 0.8em;
    font-weight: 600;
    pointer-events: none;
    z-index: 2;
}

.mvs-gallery-badge .dashicons {
    font-size: 16px;
    width: 16px;
    height: 16px;
    color: var(--mvs-surface);
}

/* ==========================================================================
   5. Pagination
   ========================================================================== */

.mvs-pagination {
    margin-top: 32px;
    text-align: center;
}

.mvs-pagination .nav-links {
    display: flex;
    justify-content: center;
    gap: 8px;
    flex-wrap: wrap;
}

.mvs-pagination .page-numbers {
    display: inline-block;
    padding: 8px 14px;
    background: var(--mvs-surface-2);
    border-radius: 4px;
    text-decoration: none;
    color: var(--mvs-text);
    font-size: 0.9em;
    transition: background 0.2s;
}

.mvs-pagination .page-numbers.current,
.mvs-pagination .page-numbers:hover {
    background: var(--mvs-primary);
    color: var(--mvs-bg);
}

/* ==========================================================================
   6. Empty State
   ========================================================================== */

.mvs-no-media {
    text-align: center;
    padding: 48px 24px;
    color: var(--mvs-text-muted);
    font-size: 1.1em;
}

/* ==========================================================================
   9. Story Viewer
   ========================================================================== */

.mvs-story-viewer {
    position: relative;
    max-width: 400px;
    margin: 0 auto;
    background: var(--mvs-overlay);
    border-radius: 12px;
    overflow: hidden;
    aspect-ratio: 9/16;
}

.mvs-story-viewer img,
.mvs-story-viewer video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.mvs-story-progress {
    display: flex;
    gap: 4px;
    padding: 8px 12px;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 2;
}

.mvs-story-progress-bar {
    flex: 1;
    height: 3px;
    background: rgba(255, 255, 255, 0.4);
    border-radius: 2px;
    overflow: hidden;
}

.mvs-story-progress-fill {
    height: 100%;
    background: var(--mvs-bg);
    width: 0;
    transition: width linear;
}

.mvs-story-header {
    position: absolute;
    top: 24px;
    left: 12px;
    right: 12px;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--mvs-bg);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.mvs-story-nav {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 50%;
    z-index: 1;
    cursor: pointer;
    background: none;
    border: none;
}

.mvs-story-nav--prev {
    left: 0;
}

.mvs-story-nav--next {
    right: 0;
}

/* ==========================================================================
   10. Stats Cards
   ========================================================================== */

.mvs-stats-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 16px;
}

.mvs-stat-card {
    background: var(--mvs-bg);
    border-radius: 8px;
    padding: 20px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    text-align: center;
}

.mvs-stat-value {
    display: block;
    font-size: 2em;
    font-weight: 700;
    color: var(--mvs-primary);
    line-height: 1.2;
}

.mvs-stat-label {
    display: block;
    margin-top: 4px;
    font-size: 0.9em;
    color: var(--mvs-text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* ==========================================================================
   11. Lock Overlay (Paywall)
   ========================================================================== */

.mvs-lock-overlay {
    position: relative;
    border-radius: 8px;
    overflow: hidden;
}

.mvs-lock-preview {
    filter: blur(8px);
    pointer-events: none;
}

.mvs-lock-prompt {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    background: rgba(255, 255, 255, 0.95);
    padding: 32px 40px;
    border-radius: 12px;
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
    z-index: 2;
    max-width: 360px;
    width: 90%;
}

.mvs-lock-icon {
    font-size: 36px;
    color: var(--mvs-primary);
    margin-bottom: 12px;
}

.mvs-lock-title {
    font-size: 1.2em;
    font-weight: 600;
    margin-bottom: 8px;
}

.mvs-lock-message {
    color: var(--mvs-text-secondary);
    font-size: 0.9em;
    margin-bottom: 16px;
    line-height: 1.5;
}

.mvs-lock-button {
    display: inline-block;
    padding: 10px 24px;
    background: var(--mvs-primary);
    color: var(--mvs-bg);
    border: none;
    border-radius: 6px;
    font-size: 1em;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.2s;
}

.mvs-lock-button:hover {
    background: var(--mvs-primary-hover);
    color: var(--mvs-bg);
}

/* ==========================================================================
   12. Social Interactions Bar (for Step 47d)
   ========================================================================== */

.mvs-reactions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.mvs-social-bar button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    background: none;
    border: 1px solid var(--mvs-border);
    border-radius: 20px;
    cursor: pointer;
    font-size: 0.9em;
    color: var(--mvs-text-secondary);
    transition: all 0.2s;
}

.mvs-social-bar button:hover {
    background: none;
    border: 1px solid var(--mvs-primary);
    color: var(--mvs-primary);
}

.mvs-social-bar__actions {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.mvs-social-bar button.active {
    background: var(--mvs-primary);
    border-color: var(--mvs-primary);
    color: var(--mvs-bg);
}

.mvs-social-bar .mvs-count {
    font-weight: 600;
}

.mvs-social-bar button.mvs-view-count {
    color: var(--mvs-text-muted);
    font-size: 0.85em;
}

.mvs-social-actions {
    display: flex;
    align-items: center;
    padding: 8px 24px 12px;
}

.mvs-social-actions-left {
    display: flex;
    align-items: center;
    gap: 8px;
}

.mvs-social-actions .mvs-view-count {
    flex: 1;
    text-align: center;
}

.mvs-social-actions-right {
    display: flex;
    align-items: center;
    gap: 6px;
}

.mvs-social-actions button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    background: none;
    border: 1px solid var(--mvs-border);
    border-radius: 20px;
    cursor: pointer;
    font-size: 0.9em;
    color: var(--mvs-text-secondary);
    transition: all 0.2s;
}

.mvs-social-actions button:hover,
.mvs-social-actions button:focus {
    background: none;
    color: var(--mvs-primary);
    border: 1px solid var(--mvs-primary);
}

.mvs-social-actions button.active {
    background: var(--mvs-primary);
    border-color: var(--mvs-primary);
    color: var(--mvs-bg);
}

@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    /* Hide the view count on mobile to reclaim space — it's a passive metric. */
    .mvs-single-media .mvs-social-actions .mvs-view-count {
        display: none;
    }
}

/* ==========================================================================
   13. Comments Section (for Step 47d)
   ========================================================================== */

.mvs-comments-section {
    padding: 12px var(--mvs-space-4) var(--mvs-space-4);
    border-top: 1px solid var(--mvs-border-light);
}

.mvs-comments-title {
    font-size: 0.9375rem;
    font-weight: 600;
    margin: 0 0 12px;
    color: var(--mvs-text);
}

.mvs-comment-form {
    display: flex;
    gap: 8px;
    margin-bottom: 16px;
}

.mvs-comment-form textarea {
    flex: 1;
    padding: 10px 12px;
    background-color: var(--mvs-bg);
    border: 1px solid var(--mvs-border);
    border-radius: 8px;
    resize: none;
    min-height: 40px;
    font-family: inherit;
    font-size: 0.875rem;
}

.mvs-comment-form button {
    padding: 10px 16px;
    background: var(--mvs-primary);
    color: #fff;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-size: 0.9em;
    align-self: flex-end;
    transition: background 0.2s;
}

.mvs-comment-form button:hover {
    background: var(--mvs-primary-hover);
}

.mvs-comment-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.mvs-comment-item {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 0;
    border-bottom: 1px solid var(--mvs-border-light, #e5e7eb);
}

.mvs-comment-item:last-child {
    border-bottom: none;
}

.mvs-comment-avatar-link {
    flex-shrink: 0;
    display: block;
    line-height: 0;
    text-decoration: none;
}

.mvs-comment-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

.mvs-comment-body-wrap {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.mvs-comment-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    flex-wrap: wrap;
}

.mvs-comment-author-link {
    text-decoration: none;
    color: inherit;
}

.mvs-comment-author-link:hover .mvs-comment-author {
    text-decoration: underline;
}

.mvs-comment-author {
    font-weight: 600;
    font-size: 14px;
    color: var(--mvs-text, #111827);
}

.mvs-comment-date {
    font-size: 12px;
    color: var(--mvs-text-muted, #6b7280);
    white-space: nowrap;
    flex-shrink: 0;
}

.mvs-comment-text {
    font-size: 14px;
    line-height: 1.55;
    color: var(--mvs-text, #111827);
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

.mvs-comment-actions {
    display: flex;
    gap: 6px;
    margin-top: 4px;
}

.mvs-comment-actions .mvs-btn--small {
    padding: 4px 10px;
    font-size: 12px;
}

/* ==========================================================================
   14. Dashboard
   ========================================================================== */

.mvs-dashboard {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
}

.mvs-dashboard-header h1 {
    margin: 0 0 20px;
    font-size: 1.75em;
}

.mvs-dashboard-tabs {
    display: flex;
    gap: 0;
    border-bottom: 2px solid var(--mvs-border-light);
    margin-bottom: 24px;
}

.mvs-dashboard-tab {
    /* 12px padding on a 0.95em label lands at ~37px, under the 40px contract.
       The earlier tap-target sweep missed these because it measured buttons and
       .mvs-btn, and a tab is neither - it is an <a class="mvs-dashboard-tab">
       (QA re-verify, Basecamp 10252222115: 7 tabs measured 36.6-37.6px).
       min-height enforces the floor without changing the visual padding on
       surfaces where the label already pushes it past 40px. */
    min-height: var(--mvs-touch-min, 40px);
    display: inline-flex;
    align-items: center;
    padding: 12px 24px;
    background: none;
    border: none;
    border-radius: 0;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    cursor: pointer;
    font-size: 0.95em;
    font-weight: 600;
    color: var(--mvs-text);
    opacity: 0.75;
    transition: all 0.2s;
}

.mvs-dashboard-tab:hover,
.mvs-dashboard-tab:focus,
.mvs-dashboard-tab.active {
    opacity: 1;
}

.mvs-dashboard-tab:hover,
.mvs-dashboard-tab:focus {
    color: var(--mvs-primary-text, var(--mvs-primary));
    background: none;
    border: none;
    border-bottom: 2px solid var(--mvs-primary);
    border-radius: 0;
}

.mvs-dashboard-tab:first-child {
    padding-left: 0;
}

.mvs-dashboard-tab.active {
    /* Label is text -> AA-safe variant. The underline is not text, so it keeps
       the unmodified brand colour and stays visually true to the theme. */
    color: var(--mvs-primary-text, var(--mvs-primary));
    border-bottom-color: var(--mvs-primary);
}

/* Theme defense. Some themes (e.g. BuddyX) style bare <button> with a filled
   accent background + full border + radius, which turns these flat tabs into
   chunky colored buttons. Re-assert the intended look so the dashboard reads
   the same on any theme. !important is the standard pattern for a frontend
   plugin defending its own controls inside arbitrary themes. Bug #10019404777.

   IT WAS ALSO DEFENDING A DESIGN THAT NO LONGER EXISTS. Written for the
   horizontal strip, it pinned `border-bottom: 2px solid transparent`,
   `border-radius: 0` and `background: none` with !important — so when the
   sections became a vertical rail, every rule the rail wrote for its own
   appearance lost to armour the plugin had aimed at itself. That is why rail
   items rendered as red underlined links with no active fill and no
   inline-start marker: not the theme winning, the plugin's own past winning.
   Diagnosed by computed style, after `display: flex` from a rule applied while
   `border: 0` from the SAME rule did not.

   What is invariant across both designs stays here. What is per-design moves
   into the breakpoint that owns that design — because below 861px the strip IS
   horizontal, and there an underline is the right marker. */
.mvs-dashboard-tabs .mvs-dashboard-tab {
    background: none !important;
    border: 0 !important;
    box-shadow: none !important;
    /* The theme also forces button text white; without re-asserting the text
       color the label turns white-on-light = invisible once the fill is gone. */
    color: var(--mvs-text) !important;
}

/* Horizontal strip: the active marker is an underline. */
@media (max-width: 860px) {

    .mvs-dashboard-tabs .mvs-dashboard-tab {
        border-bottom: 2px solid transparent !important;
        border-radius: 0 !important;
    }

    .mvs-dashboard-tabs .mvs-dashboard-tab:hover,
    .mvs-dashboard-tabs .mvs-dashboard-tab:focus,
    .mvs-dashboard-tabs .mvs-dashboard-tab.active {
        border-bottom-color: var(--mvs-primary) !important;
        /* Text -> AA-safe variant. These theme-defense rules carry !important,
           so they are where the tab label's colour is actually decided; setting
           the token on the non-important rules alone left it at 4.36:1. */
        color: var(--mvs-primary-text, var(--mvs-primary)) !important;
    }
}

/* Rail: the active marker is the inline-start edge plus a surface fill. An
   underline down a rail reads as a rule under one row, which is what it looked
   like. Declared at the same weight as the armour above, or the armour wins
   again — the whole point of this block. */
/* ── Dashboard tab strip: the scrolling form ───────────────────────────────
 *
 * `max-width: 860px`, NOT 768. These rules lived inside the mobile block and
 * the vertical rail that replaces them starts at `min-width: 861px`, so 769px
 * to 860px was covered by NEITHER: the strip fell back to the base
 * `display: flex` with no wrap and no overflow, and its content — 912px wide
 * with the full tab set — simply spilled out of the container with no way to
 * scroll to it. Three tabs were unreachable across a 78px band of desktop
 * widths, and the page itself did not scroll, so nothing looked wrong.
 *
 * Found by walking every distinct breakpoint at desktop rather than by reading
 * the CSS, which is the only way this shows up: each rule is correct on its own
 * and the bug lives in the gap between them (Basecamp 10252222205).
 *
 * The two boundaries now meet exactly at 860/861. If either moves, move both.
 */
@media (max-width: 860px) {
    .mvs-dashboard-tabs {
        position: sticky;
        top: 0;
        z-index: var(--mvs-z-sticky);
        background: var(--mvs-bg);
        padding: 8px 0;
        border-bottom: 1px solid var(--mvs-border-light);
        /* Inline axis ONLY. `margin: 0 -20px` bled the sticky bar edge-to-edge
           and, being a shorthand, silently reset the base rule's
           `margin-bottom: 24px` — so the toolbar underneath sat flush against
           the tab bar and its focus ring overlapped the border (Basecamp
           10248974871). margin-inline also survives RTL, which the shorthand
           did not. */
        margin-inline: -20px;
        padding-left: 20px;
        padding-right: 20px;
        overflow-x: auto;
        white-space: nowrap;
        -webkit-overflow-scrolling: touch;
        scroll-snap-type: x proximity;
        scrollbar-width: none;
        -ms-overflow-style: none;
        /* Edge-fade so users see there are more tabs to swipe to. */
        -webkit-mask-image: linear-gradient(to right,
            black 0,
            black calc(100% - 24px),
            transparent 100%);
        mask-image: linear-gradient(to right,
            black 0,
            black calc(100% - 24px),
            transparent 100%);
    }

    .mvs-dashboard-tabs::-webkit-scrollbar { display: none; }

    .mvs-dashboard-tabs > .mvs-dashboard-tab {
        scroll-snap-align: start;
    }
}

@media (min-width: 861px) {

    .mvs-dashboard-tabs .mvs-dashboard-tab {
        border-inline-start: 2px solid transparent !important;
        border-radius: var(--mvs-radius-sm) !important;
        /* Leading corners are square so the 2px marker reads as the row's
           EDGE. With the radius all round, the marker becomes a rounded stick
           with a gap above and below and detaches from its own row. */
        border-start-start-radius: 0 !important;
        border-end-start-radius: 0 !important;
        color: var(--mvs-text-secondary) !important;
    }

    /* Hover and active are washes derived from the FOREGROUND, not from
       --mvs-surface. That token resolved to rgb(35,38,47) on Reign dark - the
       exact colour of the rail behind it - so the fill these states are built
       on was not being painted at all. On the active row that left a lone
       floating marker with nothing to anchor it. currentColor is always
       visible on whatever surface a host supplies, and on the active row it
       is the accent, so the wash agrees with the marker.

       These carry !important because the non-important copies of the same
       rules further down are out-specified here; setting the fill there had
       no effect at all until it was set where the cascade ends. */
    .mvs-dashboard-tabs .mvs-dashboard-tab:hover,
    .mvs-dashboard-tabs .mvs-dashboard-tab:focus {
        background: rgba(128, 128, 128, 0.1) !important;
        background: color-mix(in srgb, currentColor 8%, transparent) !important;
        color: var(--mvs-text) !important;
    }

    .mvs-dashboard-tabs .mvs-dashboard-tab.active {
        border-inline-start-color: var(--mvs-primary) !important;
        background: rgba(128, 128, 128, 0.14) !important;
        background: color-mix(in srgb, currentColor 12%, transparent) !important;
        color: var(--mvs-primary-text, var(--mvs-primary)) !important;
    }

    /* A group with no heading still shows its break. */
    .mvs-dashboard-tabs .mvs-dashboard-tab--group-start {
        border-block-start: 1px solid var(--mvs-border-light) !important;
    }
}

/*
 * Every tab's content sits in the SAME wrapper.
 *
 * The panel used to be a bare `min-height` — transparent, no padding, no edge —
 * so each section's content sat directly on the page background and looked
 * unframed next to the rail and streak banner beside it, which are both carded.
 * Whether a screen looked finished depended on whether its content happened to
 * bring its own card: Media did (the upload zone), Albums, Collections,
 * Favorites, Documents and Edit profile did not.
 *
 * Matches the rail's treatment deliberately — same border, radius and surface
 * token — so the two columns read as one interface rather than two.
 *
 * Geometry was already uniform across desktop / iPad / mobile (identical x,
 * width and padding at 1440 / 820 / 390); what was missing was the frame.
 */
/* The panel is a LAYOUT REGION, not a card — it holds cards that draw their own
   border, and an outline here made every tab read as a box inside a box inside a
   box. Depth comes from the tinted ground + padding instead (border contract
   rule 1: a bordered box must not contain another bordered box). */
.mvs-dashboard-panel {
    min-height: 200px;
    padding: var(--mvs-space-4);
    border-radius: var(--mvs-radius-md);
    background: var(--mvs-bg-subtle);
}


@media (max-width: 640px) {
    /* Tighter on a phone: a full-width card with desktop padding wastes the
       narrowest axis, which is the one that matters there. */
    .mvs-dashboard-panel {
        padding: var(--mvs-space-3);
    }
}

/* Interactivity API uses [hidden] attribute for show/hide */
.mvs-dashboard-panel[hidden] {
    display: none !important;
}

.mvs-dashboard-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
}

/* .mvs-btn canonical definition lives in the "Shared button styles" block
 * further down (around line 4727). The legacy block that was here — with
 * different padding (10px 20px vs 0.625rem 1.25rem) — has been removed so
 * button dimensions are deterministic and don't drift based on stylesheet
 * load order. Danger + small modifiers remain here as they're only defined
 * in this place. */

.mvs-btn--danger {
    background: var(--mvs-danger);
    color: var(--mvs-on-danger, #ffffff);
    border: none;
}

.mvs-btn--danger:hover,
.mvs-btn--danger:focus,
.mvs-btn--danger:visited {
    background: var(--mvs-danger);
    color: var(--mvs-on-danger, #ffffff);
    filter: brightness(0.92);
    border: none;
}

.mvs-btn--small {
    padding: 6px 12px;
    font-size: 0.8em;
}

/* Dashboard Grid — default responsive auto-fill when no column-count class
   is applied; when the template emits `.mvs-cols-N` alongside it (as driven
   by the mvs_grid_columns setting), the compound-selector overrides below
   win by source order and honour the chosen column count. */
.mvs-dashboard-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
}

.mvs-dashboard-grid.mvs-cols-2 { grid-template-columns: repeat(2, 1fr); }
.mvs-dashboard-grid.mvs-cols-3 { grid-template-columns: repeat(3, 1fr); }
.mvs-dashboard-grid.mvs-cols-4 { grid-template-columns: repeat(4, 1fr); }
.mvs-dashboard-grid.mvs-cols-5 { grid-template-columns: repeat(5, 1fr); }

/* Narrow-viewport collapse mirrors the .mvs-media-grid behaviour so the
   user never ends up with a 5-col grid squished on mobile. */
@media (max-width: 1024px) {
    .mvs-dashboard-grid.mvs-cols-5 { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    .mvs-dashboard-grid.mvs-cols-3,
    .mvs-dashboard-grid.mvs-cols-4,
    .mvs-dashboard-grid.mvs-cols-5 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
    .mvs-dashboard-grid.mvs-cols-2,
    .mvs-dashboard-grid.mvs-cols-3,
    .mvs-dashboard-grid.mvs-cols-4,
    .mvs-dashboard-grid.mvs-cols-5 { grid-template-columns: 1fr; }
}

.mvs-dashboard-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--mvs-bg);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    transition: box-shadow 0.2s;
}

.mvs-dashboard-card:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* Bulk-select overlay checkbox on each card. Hidden until hover/selection so
   it stays out of the way, but always keyboard-reachable. */
.mvs-bulk-check {
    position: absolute;
    top: var(--mvs-space-2);
    inset-inline-start: var(--mvs-space-2);
    z-index: 3;
    /* 40px minimum tap target (Wbcom responsive contract). The check glyph
       stays 18px; the box carries the hit area. */
    width: 40px;
    height: 40px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 2px solid var(--mvs-on-primary);
    border-radius: var(--mvs-radius-sm);
    background: rgba(0, 0, 0, 0.45);
    color: transparent;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s, background 0.15s;
}

.mvs-dashboard-card:hover .mvs-bulk-check,
.mvs-bulk-check:focus-visible,
.mvs-bulk-check--on {
    opacity: 1;
}

.mvs-bulk-check--on {
    background: var(--mvs-primary);
    border-color: var(--mvs-primary);
    color: var(--mvs-on-primary);
}

.mvs-bulk-check svg {
    width: 18px;
    height: 18px;
}

.mvs-dashboard-card--selected {
    outline: 2px solid var(--mvs-primary);
    outline-offset: -2px;
}

/* Sticky action bar shown while ≥1 card is selected. */
.mvs-bulk-bar {
    position: sticky;
    top: var(--mvs-space-2);
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--mvs-space-2);
    margin-block-end: var(--mvs-space-3);
    padding: var(--mvs-space-2) var(--mvs-space-3);
    background: var(--mvs-bg);
    border: 1px solid var(--mvs-border-light);
    border-radius: var(--mvs-radius-md);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}

.mvs-bulk-count {
    font-weight: 600;
    color: var(--mvs-text);
    margin-inline-end: auto;
}

.mvs-bulk-privacy {
    max-width: 160px;
}

@media (max-width: 480px) {
    .mvs-bulk-bar {
        flex-direction: column;
        align-items: stretch;
    }

    .mvs-bulk-count {
        margin-inline-end: 0;
    }

    .mvs-bulk-privacy {
        max-width: none;
    }
}

.mvs-dashboard-card-thumb {
    position: relative;
    display: block;
    aspect-ratio: 1;
    background: var(--mvs-surface-2);
    overflow: hidden;
    text-decoration: none;
    color: inherit;
}

.mvs-dashboard-card-thumb img:not([hidden]),
.mvs-dashboard-card-thumb video:not([hidden]) {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.mvs-dashboard-card-thumb img[hidden],
.mvs-dashboard-card-thumb video[hidden] {
    display: none !important;
}

.mvs-dashboard-card-body {
    padding: 12px;
    flex: 1;
    display: flex;
    flex-direction: column;
}

.mvs-dashboard-card-title {
    display: block;
    font-weight: 600;
    font-size: 0.9em;
    margin: 0 0 6px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--mvs-text);
    text-decoration: none;
}

a.mvs-dashboard-card-title:hover {
    color: var(--mvs-primary);
}

.mvs-dashboard-card-meta {
    font-size: 0.8em;
    color: var(--mvs-text-muted);
    margin-bottom: 8px;
}

.mvs-dashboard-card-actions {
    display: flex;
    gap: 6px;
    margin-top: auto;
    padding-top: 8px;
}

.mvs-privacy-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 0.7em;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.mvs-privacy-badge--public {
    background: var(--mvs-success-bg);
    color: var(--mvs-success);
}

.mvs-privacy-badge--members {
    background: var(--mvs-warning-bg);
    color: var(--mvs-warning);
}

.mvs-privacy-badge--private {
    background: var(--mvs-danger-bg);
    color: var(--mvs-danger);
}

/* Load More */
.mvs-load-more-wrap {
    text-align: center;
    margin-top: 24px;
}

/* ==========================================================================
   Panel toolbar — the one shape above every list or grid.
   Emitted by TemplateHelpers::render_panel_toolbar(). Used by the four
   dashboard panels (client-driven) and the document drive (GET form), so the
   two rendering engines look like one product.
   ========================================================================== */

.mvs-panel-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--mvs-space-2);
    margin-bottom: var(--mvs-space-4);
}

.mvs-panel-toolbar__search {
    /* Takes the slack so the selects keep their natural width; the floor stops
       it collapsing to nothing once several filters are present. */
    flex: 1 1 12rem;
    min-width: 0;
    min-height: var(--mvs-touch-min);
    padding: 0 var(--mvs-space-3);
    border: 1px solid var(--mvs-border);
    border-radius: var(--mvs-radius-md);
    /* !important fights BuddyX's bx-tokens-applied.css, whose form rule is
       `input:not([type="submit"]):not([type="button"]):not([type="checkbox"])
       :not([type="radio"]):not([type="range"])` — specificity (0,5,1), which no
       sane class selector beats. Without this the search box stayed white on a
       dark dashboard while the select beside it went dark, because the theme's
       grouped selector only carries that weight on its `input` branch.
       Colour only: layout above is left to lose normally. */
    background: var(--mvs-surface) !important;
    color: var(--mvs-text) !important;
    font-size: var(--mvs-font-md);
}

.mvs-panel-toolbar__select {
    min-height: var(--mvs-touch-min);
    /* Width is ours to decide, not the theme's. Left unconstrained, a theme
       that styles `select { width: 100% }` — Astra does — stretched these to
       the full content column (1160px each) and stacked them, while on Reign
       they sat at ~110px. Anything we leave to the theme is only correct by
       luck on the themes we happen to test. */
    width: auto;
    min-width: 9rem;
    max-width: 100%;
    padding: 0 var(--mvs-space-3);
    border: 1px solid var(--mvs-border);
    border-radius: var(--mvs-radius-md);
    background: var(--mvs-surface);
    color: var(--mvs-text);
    font-size: var(--mvs-font-sm);
}

.mvs-panel-toolbar__count {
    color: var(--mvs-text-muted);
    font-size: var(--mvs-font-sm);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.mvs-panel-toolbar__search:focus-visible,
.mvs-panel-toolbar__select:focus-visible {
    outline: 2px solid var(--mvs-primary);
    outline-offset: 2px;
}

@media (max-width: 640px) {

    .mvs-panel-toolbar__search {
        /* The search owns the first row on a phone; the selects wrap beneath it
           rather than each being squeezed to a few characters wide. */
        flex-basis: 100%;
    }

    .mvs-panel-toolbar__select {
        flex: 1 1 auto;
    }

}

/* Dashboard panel loading state.
   Every panel's `loading` flag was already being SET by the store and read by
   nothing, so a slow first request rendered an entirely blank panel: the grid
   was empty and the empty state was deliberately suppressed. This is the other
   half of that pair, and it is the same markup in all four panels so they say
   "loading" the same way. */
.mvs-dashboard-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--mvs-space-2);
    padding: var(--mvs-space-5) var(--mvs-space-3);
    color: var(--mvs-text-muted);
    font-size: var(--mvs-font-sm);
}

.mvs-dashboard-loading__spinner {
    width: 18px;
    height: 18px;
    border: 2px solid var(--mvs-border);
    /* One side inherits the accent so the ring reads as spinning. */
    border-top-color: var(--mvs-primary);
    border-radius: 50%;
    animation: mvs-dashboard-spin 0.7s linear infinite;
}

@keyframes mvs-dashboard-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {

    .mvs-dashboard-loading__spinner {
        /* The label still says "Loading…", so removing the spin costs nothing. */
        animation: none;
    }

}

/* ==========================================================================
   15. Modal
   ========================================================================== */

.mvs-modal-overlay {
    position: fixed;
    inset: 0;
    background: var(--mvs-overlay);
    z-index: var(--mvs-z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    /* Allow taps outside the panel to dismiss without scrolling the body. */
    overscroll-behavior: contain;
}

.mvs-modal {
    background: var(--mvs-bg);
    border-radius: var(--mvs-radius-lg);
    width: 100%;
    max-width: 560px;
    max-height: 85vh;
    overflow-y: auto;
    box-shadow: var(--mvs-shadow-lg);
    position: relative;
}

/* §11 — Bottom-sheet on mobile.
   Modal slides up from the bottom edge with a drag handle, max 90vh,
   safe-area-inset honored for iOS home indicator. Animation respects
   prefers-reduced-motion. */
@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    .mvs-modal-overlay {
        align-items: flex-end;
        padding: 0;
    }

    .mvs-modal {
        max-width: 100%;
        max-height: 90vh;
        border-radius: var(--mvs-radius-lg) var(--mvs-radius-lg) 0 0;
        padding-bottom: env(safe-area-inset-bottom, 0);
        animation: mvs-modal-slide-up var(--mvs-duration-base) var(--mvs-easing) both;
    }

    /* Drag handle pill — pure CSS, no JS dismiss yet but gives the affordance. */
    .mvs-modal::before {
        content: "";
        display: block;
        width: 36px;
        height: 4px;
        background: var(--mvs-text-muted);
        opacity: 0.5;
        border-radius: var(--mvs-radius-pill);
        margin: 8px auto 0;
        flex-shrink: 0;
    }
}

@keyframes mvs-modal-slide-up {
    from {
        transform: translateY(100%);
    }
    to {
        transform: translateY(0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .mvs-modal {
        animation: none !important;
    }
}

.mvs-modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 24px;
    border-bottom: 1px solid var(--mvs-border-light);
}

.mvs-modal-header h2 {
    margin: 0;
    font-size: 1.2em;
}

.mvs-modal-close {
    background: none;
    border: none;
    font-size: 1.5em;
    cursor: pointer;
    color: var(--mvs-text-muted);
    padding: 0;
    line-height: 1;
}

.mvs-modal-close:hover {
    background: none;
    border: none;
    color: var(--mvs-text);
}

.mvs-modal-body {
    padding: 24px;
}

.mvs-modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 16px 24px;
    border-top: 1px solid var(--mvs-border-light);
}

/* Form Fields */
.mvs-field {
    margin-bottom: 16px;
}

.mvs-field label {
    display: block;
    margin-bottom: 4px;
    font-weight: 600;
    font-size: 0.9em;
    color: var(--mvs-text);
}

.mvs-field input[type="text"],
.mvs-field textarea,
.mvs-field select {
    width: 100%;
    padding: 8px 12px;
    background-color: var(--mvs-bg);
    border: 1px solid var(--mvs-border);
    border-radius: 6px;
    font-size: 0.9em;
    font-family: inherit;
    box-sizing: border-box;
}

.mvs-field textarea {
    resize: vertical;
    min-height: 80px;
}

.mvs-field select {
    max-width: 200px;
}

/* ==========================================================================
   16. Tag Input
   ========================================================================== */

.mvs-tag-input-wrap {
    position: relative;
}

.mvs-tag-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 6px;
    border: 1px solid var(--mvs-border-light);
    border-radius: 6px;
    min-height: 38px;
    align-items: center;
    cursor: text;
}

.mvs-tag-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    background: var(--mvs-primary);
    color: var(--mvs-bg);
    border-radius: 12px;
    font-size: 0.8em;
}

.mvs-tag-pill-remove {
    background: none;
    border: none;
    color: var(--mvs-bg);
    cursor: pointer;
    font-size: 1em;
    padding: 0;
    line-height: 1;
    opacity: 0.8;
}

.mvs-tag-pill-remove:hover {
    opacity: 1;
}

.mvs-tag-text-input {
    border: none !important;
    outline: none;
    padding: 2px 4px !important;
    font-size: 0.9em;
    min-width: 80px;
    flex: 1;
}

.mvs-tag-text-input:focus-visible {
    outline: 2px solid var(--mvs-primary, #0073aa);
    outline-offset: -2px;
}

.mvs-tag-text-input:focus-visible {
    outline: 2px solid var(--mvs-primary);
    outline-offset: -2px;
    border-radius: 2px;
}

.mvs-tag-autocomplete {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--mvs-bg);
    border: 1px solid var(--mvs-border-light);
    border-top: none;
    border-radius: 0 0 6px 6px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    z-index: 10;
    max-height: 160px;
    overflow-y: auto;
}

.mvs-tag-autocomplete-item {
    padding: 8px 12px;
    cursor: pointer;
    font-size: 0.9em;
}

.mvs-tag-autocomplete-item:hover {
    background: var(--mvs-info-bg);
    color: var(--mvs-primary);
}

/* ==========================================================================
   17. Confirm Dialog
   ========================================================================== */

.mvs-confirm-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 100001;
    display: flex;
    align-items: center;
    justify-content: center;
}

.mvs-confirm {
    background: var(--mvs-bg);
    border-radius: 12px;
    padding: 28px;
    max-width: 380px;
    width: 90%;
    text-align: center;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}

.mvs-confirm p {
    margin: 0 0 20px;
    font-size: 1em;
    color: var(--mvs-text);
}

.mvs-confirm-actions {
    display: flex;
    justify-content: center;
    gap: 8px;
}

.mvs-report-reason-select {
    display: block;
    width: 100%;
    padding: 8px 12px;
    box-sizing: border-box;
    margin: 12px 0;
    font-size: 14px;
    border: 1px solid var(--mvs-border);
    border-radius: 6px;
    background-color: var(--mvs-surface-2);
    color: var(--mvs-text);
}

/* ==========================================================================
   18. Toast Notification
   ========================================================================== */

.mvs-toast {
    position: fixed;
    bottom: calc(24px + env(safe-area-inset-bottom, 0px));
    right: 24px;
    padding: 12px 24px;
    background: var(--mvs-text);
    color: var(--mvs-bg);
    border-radius: var(--mvs-radius-md);
    font-size: 0.9em;
    z-index: var(--mvs-z-toast);
    box-shadow: var(--mvs-shadow-md);
    animation: mvsToastIn var(--mvs-duration-base) var(--mvs-easing);
    /* Honor the FAB safe area so toasts never collide with the upload button. */
}

@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    .mvs-toast {
        /* On mobile, sit above both the FAB safe area and the home indicator. */
        bottom: calc(var(--mvs-fab-safe) + 8px + env(safe-area-inset-bottom, 0px));
        right: 16px;
        left: 16px;
        max-width: none;
        text-align: center;
    }
}

.mvs-toast--success {
    background: var(--mvs-success);
}

.mvs-toast--error {
    background: var(--mvs-danger);
}

@keyframes mvsToastIn {
    from {
        opacity: 0;
        transform: translateY(20px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ==========================================================================
   Interactivity API [hidden] overrides
   Elements with display:flex/fixed need explicit [hidden] rules because
   data-wp-bind--hidden toggles the HTML hidden attribute.
   ========================================================================== */

/* Any MediaVerse element the Interactivity API hides with the HTML `hidden`
   attribute stays hidden, whatever display value its own rule sets.

   This replaces a hand-maintained allowlist of ten selectors. The allowlist
   missed `.mvs-bulk-bar` and `.mvs-dashboard-loading`, so on any theme that
   does not ship a `[hidden]` reset — Astra, for one — members saw a bulk
   Delete bar reading "0 selected" and a permanent "Loading…" under a finished
   grid. It looked correct on Reign only by luck: Reign ships
   `[hidden]{display:none}` and enqueues after us, and at equal specificity
   source order decided it. An enumerated list guarantees the next control
   added is missed the same way.

   Matched on our own namespace, so no theme element is touched. */
[class*="mvs-"][hidden],
[class*="mvs-"] [hidden] {
    display: none !important;
}

/* ==========================================================================
   19. Media Picker (for album creation)
   ========================================================================== */

.mvs-media-picker {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 8px;
    max-height: 300px;
    overflow-y: auto;
    padding: 8px;
    border: 1px solid var(--mvs-border-light);
    border-radius: 6px;
    margin-top: 8px;
}

.mvs-media-picker-item {
    position: relative;
    aspect-ratio: 1;
    border-radius: 4px;
    overflow: hidden;
    cursor: pointer;
    border: 2px solid transparent;
    transition: border-color 0.2s;
}

.mvs-media-picker-item.selected {
    border-color: var(--mvs-primary);
}

/* The picker reuses the grid's MARKUP classes, but the grid's sizing is scoped
   to `.mvs-grid-item`, so inside a picker cell a video/audio tile matched no
   sizing rule at all and rendered ragged next to the images. Sized here for
   every child the picker can hold, not just `img` (Basecamp 10190678139). */
.mvs-media-picker-item img,
.mvs-media-picker-item video,
.mvs-media-picker-item .mvs-grid-video-preview,
.mvs-media-picker-item .mvs-grid-item-placeholder {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* `.mvs-grid-item-placeholder--video` pins `aspect-ratio: 16 / 9`, which cannot
   fill a square cell that clips its overflow. Inside the picker the cell decides
   the shape. */
.mvs-media-picker-item .mvs-grid-item-placeholder--video,
.mvs-media-picker-item .mvs-grid-item-placeholder--audio {
    aspect-ratio: auto;
}

.mvs-media-picker-check {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 22px;
    height: 22px;
    background: var(--mvs-primary);
    color: var(--mvs-bg);
    border-radius: 50%;
    display: none;
    align-items: center;
    justify-content: center;
    font-size: 0.75em;
    font-weight: 700;
}

.mvs-media-picker-item.selected .mvs-media-picker-check {
    display: flex;
}

/* ==========================================================================
   20. Owner Actions (on single pages)
   ========================================================================== */

.mvs-owner-actions {
    display: inline-flex;
    gap: 6px;
}

.mvs-collection-card-info .mvs-owner-actions {
    display: flex;
    margin-left: 0;
    padding: 12px 0 0;
}

/* Inline edit form on single pages */
.mvs-inline-edit {
    padding: 16px 24px;
    border-top: 1px solid var(--mvs-border-light);
    background: var(--mvs-surface-2);
}

.mvs-inline-edit .mvs-field {
    margin-bottom: 12px;
}

/* Two-column row inside the inline-edit form — used for paired controls
   (Privacy + slug-regenerate checkbox) so they share one row instead
   of stacking. Collapses to single column at ≤640px. */
.mvs-inline-edit .mvs-field-row {
    display: flex;
    gap: 12px;
    margin-bottom: 12px;
    align-items: flex-end;
}

.mvs-inline-edit .mvs-field-row .mvs-field {
    margin-bottom: 0;
}

.mvs-inline-edit .mvs-field--inline {
    flex: 1 1 0;
    min-width: 0;
}

.mvs-inline-edit .mvs-field-row .mvs-field--checkbox {
    flex: 0 0 auto;
    padding-bottom: 10px;
}

.mvs-inline-edit .mvs-field--checkbox label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: 500;
    cursor: pointer;
    font-size: 13px;
    margin: 0;
    white-space: nowrap;
}

.mvs-inline-edit .mvs-field--checkbox input[type="checkbox"] {
    width: auto;
    margin: 0;
    flex-shrink: 0;
}

@media (max-width: 640px) {
    .mvs-inline-edit .mvs-field-row {
        flex-direction: column;
        align-items: stretch;
    }

    .mvs-inline-edit .mvs-field-row .mvs-field--checkbox {
        padding-bottom: 0;
    }
}

.mvs-inline-edit-actions {
    display: flex;
    gap: 8px;
    margin-top: 12px;
}

/* ==========================================================================
   21. Explore Search + Tag Cloud
   ========================================================================== */

.mvs-explore-search {
    margin-bottom: 24px;
}

.mvs-tag-cloud {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 24px;
}

/* On a phone the cloud wraps to as many rows as there are tags — a site with
   22 tags pushed the first media 680px down the page, so Explore opened on a
   screenful of filters with no media in sight. One horizontally scrollable row
   instead: 312px of chips becomes ~56px and the grid is visible immediately.
   Same pattern Pro already uses for .mvs-competition-tabs. */
@media (max-width: 640px) {
    .mvs-tag-cloud {
        flex-wrap: nowrap;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        padding-bottom: 4px;
    }

    .mvs-tag-cloud::-webkit-scrollbar {
        display: none;
    }

    /* Chips must not shrink to fit, or the labels compress instead of scrolling. */
    .mvs-tag-cloud-item {
        flex: 0 0 auto;
    }
}

.mvs-tag-cloud-item {
    display: inline-block;
    padding: 4px 14px;
    background: var(--mvs-surface-2);
    border-radius: 16px;
    font-size: 0.85em;
    color: var(--mvs-text);
    text-decoration: none;
    transition: background 0.2s, color 0.2s;
}

/*
 * Hover/focus on a filter chip. Background and colour ONLY — never a property
 * that changes the box.
 *
 * This selector previously had no declaration block of its own: an edit removed
 * the body and left `.mvs-tag-cloud-item:hover,` dangling above a comment, so
 * the parser joined it to the `.mvs-sr-only` rule below and hovering any chip
 * applied `position:absolute; width:1px; height:1px; clip:rect(0,0,0,0)`. The
 * chip visually vanished, the row reflowed, the pointer left the chip, it came
 * back — the "tabs overlap and the tags jitter" report (Basecamp 10259608476).
 * Keep a real block here; a bodiless selector silently adopts the next rule.
 */
.mvs-tag-cloud-item:hover,
.mvs-tag-cloud-item:focus-visible {
    background: rgba(var(--mvs-primary-rgb, 0, 115, 170), 0.08);
    color: var(--mvs-primary, #0073aa);
}

/*
 * Visually hidden, still announced. Ours on purpose: `.screen-reader-text` is
 * WordPress core's class and its presence depends on the THEME defining it. A
 * label that becomes visible on a theme that does not would be worse than the
 * placeholder-only field it replaced — and "the theme will have it" is the
 * assumption this audit kept finding to be wrong.
 */
.mvs-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;
}

/*
 * The foreground must move WITH the background. This set `--mvs-text` — the
 * body text colour — on top of the primary fill, so the selected chip scored
 * 2.05 in light mode and 1.13 in dark, i.e. the one filter a member has chosen
 * was the least readable thing on the page (Basecamp 10252222011).
 *
 * `--mvs-on-primary` is the token that exists for exactly this: whatever reads
 * correctly on the accent, resolved from the theme. Same pairing the attention
 * badge uses. Renders on Explore, /media-tag/ archives and member profiles, so
 * this one rule covers three surfaces.
 */
.mvs-tag-cloud-item.active {
    background: var(--mvs-primary);
    color: var(--mvs-on-primary, #fff);
}

/* ==========================================================================
   22. Upload Form Enhancement
   ========================================================================== */

.mvs-upload-fields {
    margin-top: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.mvs-upload-fields input[type="text"],
.mvs-upload-fields textarea {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid var(--mvs-border);
    border-radius: 6px;
    font-size: 0.9em;
    font-family: inherit;
    box-sizing: border-box;
}

.mvs-upload-fields textarea {
    resize: vertical;
    min-height: 60px;
}

/* ==========================================================================
   23. Dashboard Inline Upload
   ========================================================================== */

.mvs-dashboard-upload {
    margin-bottom: 24px;
    background: var(--mvs-bg, #fff);
    border: 1px solid var(--mvs-border-light, #dcdcde);
    border-radius: 12px;
    padding: 20px;
}

.mvs-dashboard-dropzone {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    margin: 0 0 1rem;
    padding: 28px 20px;
    border: 2px dashed var(--mvs-border);
    border-radius: 8px;
    cursor: pointer;
    transition: border-color 0.2s, background 0.2s;
    background: var(--mvs-surface-2);
}

.mvs-dashboard-dropzone:hover {
    border-color: var(--mvs-primary);
    background: var(--mvs-info-bg);
}

.mvs-dashboard-dropzone.mvs-drag-active {
    border-color: var(--mvs-primary);
    background: var(--mvs-info-bg);
    border-style: solid;
}

.mvs-dashboard-dropzone-icon {
    font-size: 1.5em;
    line-height: 1;
}

.mvs-dashboard-dropzone-label {
    font-size: 0.95em;
    color: var(--mvs-text-secondary);
}

.mvs-dashboard-upload-fields {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 12px;
    padding: 12px;
    background: var(--mvs-surface-2);
    border-radius: 6px;
}

.mvs-dashboard-upload-fields input[type="text"],
.mvs-dashboard-upload-fields textarea,
.mvs-dashboard-upload-fields select {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid var(--mvs-border);
    border-radius: 6px;
    font-size: 0.9em;
    font-family: inherit;
    box-sizing: border-box;
}

.mvs-dashboard-upload-fields textarea {
    resize: vertical;
    min-height: 60px;
}

.mvs-dashboard-upload-row {
    display: flex;
    gap: 10px;
}

.mvs-dashboard-upload-row input {
    flex: 1;
}

.mvs-dashboard-upload-row select {
    max-width: 160px;
}

.mvs-dashboard-upload-status {
    margin-top: 12px;
    font-size: 0.9em;
    color: var(--mvs-primary);
    font-weight: 600;
}

.mvs-dashboard-upload-status:empty {
    display: none;
}

/* Staged-upload review step: selecting files reveals this instead of
   uploading immediately, so metadata can be filled first. */
.mvs-dashboard-upload-review {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 12px;
    padding: 10px 12px;
    /* Sits INSIDE the bordered upload card — the tinted ground already separates
       it, so an outline here would be the third edge in a row (border contract
       rule 1). */
    background: var(--mvs-bg-subtle, #f6f7f8);
    border-radius: 6px;
}

/* Respect the HTML [hidden] attribute set by data-wp-bind--hidden. The
   display:flex above is a class rule, so it outranks the UA stylesheet's
   [hidden]{display:none} and the review bar stayed on screen with zero files
   staged — the "Upload 0 file(s)" button was visible AND clickable. Same
   guard as .mvs-dashboard-panel[hidden] and the messaging panels. */
.mvs-dashboard-upload-review[hidden] {
    display: none;
}

.mvs-dashboard-upload-review-label {
    font-size: 0.9em;
    color: var(--mvs-text-muted, #555);
}

.mvs-dashboard-upload-review-actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
}

@media (max-width: 640px) {
    .mvs-dashboard-upload-review {
        flex-direction: column;
        align-items: stretch;
    }

    .mvs-dashboard-upload-review-actions {
        justify-content: flex-end;
    }
}

@media (max-width: 600px) {
    .mvs-dashboard-dropzone {
        flex-direction: column;
        padding: 20px;
    }

    .mvs-dashboard-upload-row {
        flex-direction: column;
    }

    .mvs-dashboard-upload-row select {
        max-width: 100%;
    }
}

/* ==========================================================================
   15. Empty State (shared)
   ========================================================================== */

.mvs-empty-state {
    text-align: center;
    padding: 48px 24px;
    color: var(--mvs-text-secondary);
}

.mvs-empty-state .dashicons {
    font-size: 48px;
    width: 48px;
    height: 48px;
    color: var(--mvs-border-light);
    display: block;
    margin: 0 auto 12px;
}

.mvs-empty-state p {
    font-size: 1em;
    margin: 8px 0 0;
    color: var(--mvs-text-muted);
}

/* Section 16 "BuddyPress Profile Media Tab" — removed. Rules were
 * unscoped duplicates of scoped versions already in bp-integration.css:
 *   .mvs-bp-profile-actions     → bp-integration.css section 'Action /
 *                                  toolbar row'
 *   .mvs-bp-dropzone +           → bp-integration.css section 'Upload
 *     .mvs-bp-dropzone-text        surface'
 *   .mvs-bp-upload-status +      → bp-integration.css section 'Upload
 *     --success                    surface'
 *   .mvs-bp-upload-wrap          → see dedicated rules below (this file
 *                                  keeps its copy because
 *                                  templates/album.php reuses the class
 *                                  in a non-BP context).
 */

/* Section 24 "BuddyPress Activity Media" — all rules moved to
 * bp-integration.css (see "Activity stream — media images" and
 * "Activity stream — multi-image grid" sections). The broad
 * `img:not(.avatar):not(.emoji)` catch-all was also narrowed to match
 * only /media/ permalinks during the move, so theme-injected classless
 * images (Reign's .bp-group-preview-cover, etc.) no longer get
 * constrained by our rules. Section header retained as placeholder for
 * the pending sequential renumbering in a later commit. */

/* Activity form controls (attach-media button, preview grid, privacy
 * select, upload state) moved to bp-integration.css. See that file's
 * "Activity form — attach-media button, preview grid, privacy select"
 * section. Only the shared @keyframes stays here since it's referenced
 * by multiple non-BP spinners too. */
@keyframes mvs-spin {
    to {
        transform: rotate(360deg);
    }
}

/* ── 25. Activity Media Lightbox ── (moved to frontend.css Lightbox
 *         social sidebar section; see bottom of file).
 *
 * Sections 26–28 (Inline Album Creation / Inline Upload / Single Album
 * View) moved to bp-integration.css for the BP-only rules
 * (.mvs-bp-album-form, .mvs-bp-album-msg, .mvs-bp-back-link,
 * .mvs-btn-primary, .mvs-btn-secondary).
 *
 * The .mvs-bp-upload-* classes remain here, unscoped, because
 * templates/album.php (non-BP single-album view) reuses the same
 * markup for its "Add Media" inline upload panel. Renaming to
 * .mvs-inline-upload-* would be cleaner but is deferred to avoid
 * touching PHP/JS in this CSS-only migration.
 */

.mvs-bp-upload-wrap {
    margin: 16px 0;
    padding: 20px;
    background: var(--mvs-bg);
    border: 1px solid var(--mvs-border-light);
    border-radius: 8px;
}

/* The dropzone itself. Every sibling above was already unscoped here for
 * templates/album.php; the dropzone was the one that was left behind and
 * only existed under `#buddypress .mvs-bp-screen` in bp-integration.css.
 * On the single-album page that meant no border, no padding, no
 * min-height -- a bare line of text instead of a box, so users aimed at
 * a target that wasn't drawn and dropped on the page background (which
 * navigates the tab to the file). Basecamp #10134243697, cause B. */
.mvs-bp-dropzone {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding: 20px 24px;
    border: 2px dashed var(--mvs-border);
    border-radius: 8px;
    background: var(--mvs-surface-2);
    text-align: center;
    cursor: pointer;
    transition: border-color 0.2s, background 0.2s;
}

.mvs-bp-dropzone.is-dragover {
    border-color: var(--mvs-primary);
    background: color-mix(in srgb, var(--mvs-primary) 5%, var(--mvs-surface-2));
}

.mvs-bp-dropzone-text {
    margin: 0;
    color: var(--mvs-text-secondary);
    font-size: 14px;
}

/* Cloud-upload icon anchors the dropzone, so scale it up from the
 * 16px body-button default. */
.mvs-bp-dropzone svg,
.mvs-bp-dropzone [data-lucide] {
    width: 24px;
    height: 24px;
    stroke-width: 1.75;
    color: var(--mvs-text-secondary);
    flex-shrink: 0;
}

/* Mirrors the BP-scoped mobile override in bp-integration.css so the
 * non-BP single-album dropzone gets the same treatment. Mobile is part of
 * the definition of done, not a follow-up. */
@media (max-width: 640px) {
    .mvs-bp-dropzone {
        padding: 16px;
    }
}

.mvs-bp-upload-preview {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 12px;
}

.mvs-bp-upload-thumb {
    width: 80px;
    text-align: center;
}

.mvs-bp-upload-thumb img {
    width: 80px;
    height: 80px;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid var(--mvs-border-light);
}

.mvs-bp-upload-thumb-name {
    display: block;
    margin-top: 4px;
    font-size: 11px;
    color: var(--mvs-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mvs-bp-upload-form-actions {
    margin-top: 12px;
}

.mvs-bp-upload-status {
    margin-top: 10px;
    padding: 8px 12px;
    border-radius: 6px;
    font-size: 13px;
    background: var(--mvs-info-bg);
    color: var(--mvs-primary);
}

.mvs-bp-upload-status--success {
    background: var(--mvs-success-bg);
    color: var(--mvs-success);
}

/* Used only by templates/album.php (single-album view, Add Media panel). */
.mvs-album-upload-section {
    margin: 16px 0;
}

/* Section 26 "Group Media Sub-Tabs" — removed. Unscoped duplicates of
 * scoped versions already in bp-integration.css section 'Sub-tab row
 * (Media / Albums)':
 *   .mvs-bp-sub-tabs, .mvs-bp-sub-tab, .mvs-bp-sub-tab:hover,
 *   .mvs-bp-sub-tab.active
 *
 * Section 27 "Activity Media Thumbnails (Video/Audio)" — moved to
 * bp-integration.css section 'Activity stream — video thumbnails,
 * placeholders, audio cards', scoped under #buddypress. All
 * .mvs-activity-media--video / --audio / --placeholder rules plus the
 * play icon, vid-link, audio icon/info/title/meta supporting classes.
 */

/* ==========================================================================
   28. Enhanced Single Media Player
   ========================================================================== */

/* `.mvs-media-video video` rule lives in section "Media Content" — single
 * source of truth so width/height/object-fit can't drift. Border-radius
 * applies to the wrapper so it clips the letterbox bars too. */
.mvs-media-video {
    border-radius: 4px;
    overflow: hidden;
}

.mvs-audio-info {
    padding: 12px 24px 0;
    display: flex;
    gap: 8px;
    align-items: center;
    font-size: 0.9em;
    color: var(--mvs-text-secondary);
}

.mvs-audio-artist {
    font-weight: 600;
    color: var(--mvs-text);
}

.mvs-audio-album::before {
    content: "\00b7";
    margin: 0 4px;
}

.mvs-media-audio audio {
    display: block;
    width: 100%;
    margin-top: 8px;
}

/* ==========================================================================
   29. Album Grid Items (Video/Audio)
   ========================================================================== */

.mvs-grid-item {
    position: relative;
}

.mvs-grid-item a {
    display: block;
    position: relative;
    text-decoration: none;
    color: inherit;
}

.mvs-grid-play-icon {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 48px;
    height: 48px;
    background: rgba(0, 0, 0, 0.55);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    z-index: 2;
    pointer-events: none;
    transition: background 0.2s, transform 0.2s;
}

.mvs-grid-play-icon svg {
    width: 22px;
    height: 22px;
    /* Optical centering — the play triangle isn't centered in its bounding box. */
    margin-left: 2px;
}

.mvs-grid-item a:hover .mvs-grid-play-icon {
    background: rgba(0, 0, 0, 0.8);
    transform: translate(-50%, -50%) scale(1.05);
}

/* Unified placeholder frame — audio + video + generic all share aspect ratio
   and centering so grids never collapse based on media type. Specific
   gradients layered via the --video / --audio / --generic modifiers. */
.mvs-grid-item-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    aspect-ratio: 1;
    position: relative;
    color: rgba(255, 255, 255, 0.9);
    background: linear-gradient(135deg, #1a2332, #0a0a0a);
}

.mvs-grid-item-placeholder svg {
    width: 34%;
    height: 34%;
    max-width: 72px;
    max-height: 72px;
    opacity: 0.9;
}

.mvs-grid-item-placeholder--video {
    background: linear-gradient(135deg, #0d2137, #0a0a0a);
    aspect-ratio: 16 / 9;
}

.mvs-grid-item-placeholder--audio {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: #fff;
}

/* A favorited DOCUMENT has no thumbnail and is not an image, so every other
   branch on the card thumb was false and the tile rendered as a blank
   rectangle (Basecamp 10268223103). Audio already had this; documents did not.

   No background fill. A tint was tried and measured on the rendered pixel:
   both a --mvs-surface-* token and a currentColor mix resolved to the card's
   own colour (rgb(250,251,253) on both), so it painted nothing while looking
   like it did something. The glyph alone is the affordance, and it reads
   cleanly on the card. The glyph is sized up from its 20px default because
   here it IS the tile, not an icon sitting beside a filename. */
.mvs-grid-item-placeholder--document {
    color: var(--mvs-text-muted);
}

.mvs-grid-item-placeholder--document .mvs-doc-glyph {
    --mvs-doc-glyph-size: 48px;
}

/* Audio fallback card — used when an audio upload has no embedded album
   art. Lays out a centered icon, title, and meta line so the cell carries
   information at-a-glance instead of just a music glyph. */
.mvs-audio-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 16px 18px;
    text-align: center;
    color: #fff;
}

.mvs-audio-card__waveform {
    display: block;
    width: 100%;
    max-width: 240px;
    height: 64px;
    color: rgba(255, 255, 255, 0.85);
    flex-shrink: 0;
}

.mvs-audio-card__waveform svg {
    width: 100%;
    height: 100%;
    display: block;
    max-width: none;
    max-height: none;
    opacity: 1;
}

.mvs-audio-card__title {
    font-weight: 600;
    line-height: 1.3;
    max-width: 100%;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-word;
}

.mvs-audio-card__meta {
    font-size: 0.85em;
    opacity: 0.85;
    line-height: 1.3;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (max-width: 640px) {
    .mvs-audio-card {
        padding: 12px 14px;
        gap: 6px;
    }
    .mvs-audio-card__waveform {
        height: 44px;
        max-width: 200px;
    }
}

.mvs-grid-item-placeholder--generic {
    background: linear-gradient(135deg, #2b3544, #1a1f2b);
}

/* Document tile — the cell a document takes in a collection grid.
 *
 * A LIGHT, bordered card rather than the dark gradient the generic
 * placeholder uses. The dark tile is right for media that has a picture we
 * could not produce (a video still, a missing thumbnail): it reads as a
 * photograph slot. A document has no picture and never will, so a photo slot
 * is the wrong metaphor — the reported bug was literally "a black tile with a
 * tiny generic icon". This says "file" instead of "image we lost".
 *
 * Token-driven, so it inverts in dark mode; the gradient above is a literal
 * and cannot. */
.mvs-grid-item-placeholder--document {
    background: var(--mvs-surface-2, #f5f6f8);
    border: 1px solid var(--mvs-border-light, #e2e5ea);
    color: var(--mvs-text-secondary, #5c6472);
}

.mvs-doc-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 16px 18px;
    text-align: center;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
}

.mvs-doc-card__glyph {
    --mvs-doc-glyph-size: 44px;
    color: var(--mvs-text-secondary, #5c6472);
    opacity: 0.9;
}

.mvs-doc-card__title {
    font-weight: 600;
    line-height: 1.3;
    max-width: 100%;
    color: var(--mvs-text, #1a1f2b);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-word;
}

.mvs-doc-card__meta {
    font-size: 0.82em;
    color: var(--mvs-text-muted, #7a8494);
}

/* A document opened in the media lightbox (Basecamp 10248528902). A document has
   no displayable image, so instead of a broken <img> it renders as a centered
   card; the lightbox chrome around it (Open / Download) still reaches the file. */
.mvs-lightbox-document.mvs-doc-card {
    width: min(340px, 80vw);
    height: auto;
    gap: 14px;
    padding: 40px 32px;
    background: var(--mvs-bg, #ffffff);
    border-radius: 12px;
}

.mvs-lightbox-document .mvs-doc-card__glyph {
    --mvs-doc-glyph-size: 72px;
}

.mvs-lightbox-document .mvs-doc-card__title {
    font-size: 1.05em;
}

@media (max-width: 480px) {
    .mvs-doc-card {
        gap: 6px;
        padding: 10px 12px;
    }

    .mvs-doc-card__glyph {
        --mvs-doc-glyph-size: 32px;
    }
}

.mvs-grid-audio-icon,
.mvs-grid-generic-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: inherit;
    line-height: 0;
}

.mvs-grid-audio-icon svg,
.mvs-grid-generic-icon svg {
    width: 34%;
    height: 34%;
    max-width: 72px;
    max-height: 72px;
}

/* ==========================================================================
   30. Playlist UI
   ========================================================================== */

.mvs-playlist {
    max-width: 700px;
    margin: 20px auto;
    background: var(--mvs-surface-2);
    border: 1px solid var(--mvs-border-light);
    border-radius: 8px;
    overflow: hidden;
}

.mvs-playlist-player {
    padding: 16px 20px;
    background: var(--mvs-text);
}

.mvs-playlist-player audio {
    width: 100%;
}

.mvs-playlist-now-playing {
    padding: 10px 20px;
    background: var(--mvs-text);
    color: var(--mvs-bg);
    font-size: 0.95em;
    font-weight: 600;
    min-height: 20px;
}

.mvs-playlist-tracks {
    list-style: none;
    margin: 0;
    padding: 0;
}

.mvs-playlist-track {
    display: flex;
    align-items: center;
    border-bottom: 1px solid var(--mvs-border-light);
    transition: background 0.15s;
}

.mvs-playlist-track:last-child {
    border-bottom: none;
}

.mvs-playlist-track:hover {
    background: var(--mvs-surface-2);
}

.mvs-playlist-track--active {
    background: var(--mvs-info-bg);
}

.mvs-playlist-track--active:hover {
    background: var(--mvs-info-bg-light);
}

.mvs-playlist-track-btn {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    font: inherit;
    color: inherit;
    min-width: 0;
}

.mvs-playlist-track-num {
    width: 28px;
    text-align: center;
    color: var(--mvs-text-muted);
    font-size: 0.85em;
    flex-shrink: 0;
}

.mvs-playlist-track--active .mvs-playlist-track-num {
    color: var(--mvs-primary);
    font-weight: 700;
}

.mvs-playlist-track-info {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.mvs-playlist-track-title {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mvs-playlist-track-artist {
    font-size: 0.82em;
    color: var(--mvs-text-muted);
}

.mvs-playlist-track-duration {
    color: var(--mvs-text-muted);
    font-size: 0.85em;
    flex-shrink: 0;
}

.mvs-playlist-track-link {
    padding: 12px 14px;
    color: var(--mvs-text-muted);
    text-decoration: none;
    font-size: 1.1em;
}

.mvs-playlist-track-link:hover {
    color: var(--mvs-primary);
}

/* ==========================================================================
   27. Collections & Rule Builder
   ========================================================================== */

.mvs-collection-card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 14px;
    background: var(--mvs-surface-2);
    border-bottom: 1px solid var(--mvs-border-light);
}

/* --- Collection Single Page --- */

.mvs-single-collection {
    max-width: 1200px;
    margin: 0 auto;
    padding: 24px 16px 0;
}

/* Info Card */
.mvs-collection-card-info {
    background: var(--mvs-bg);
    border: 1px solid var(--mvs-border-light);
    border-radius: 12px;
    padding: 24px 28px;
    margin-bottom: 24px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}

.mvs-collection-card-title {
    margin: 0 0 12px;
    font-size: 1.6em;
    font-weight: 700;
    line-height: 1.3;
    color: var(--mvs-text);
}

.mvs-collection-card-meta {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    font-size: 0.9em;
    color: var(--mvs-text-secondary);
}

.mvs-collection-meta-author {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.mvs-collection-meta-author a {
    color: var(--mvs-danger);
    text-decoration: none;
    font-weight: 500;
}

.mvs-collection-meta-author a:hover {
    text-decoration: underline;
}

.mvs-collection-avatar {
    border-radius: 50%;
    width: 24px;
    height: 24px;
}

.mvs-collection-meta-text {
    color: var(--mvs-text-secondary);
}

.mvs-collection-card-desc {
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--mvs-surface-2);
    color: var(--mvs-text-secondary);
    font-size: 0.95em;
    line-height: 1.6;
}

.mvs-collection-card-desc p {
    margin: 0;
}

/* Search bar — reuses .mvs-explore-search layout */
.mvs-collection-search {
    margin-bottom: 24px;
}

.mvs-collection-search form {
    display: flex;
    gap: 0;
}

.mvs-collection-search-input {
    flex: 1;
    padding: 10px 16px;
    border: 1px solid var(--mvs-border);
    border-radius: 8px 0 0 8px;
    font-size: 0.95em;
    background: var(--mvs-bg);
    transition: border-color 0.2s;
}

.mvs-collection-search-input:focus {
    outline: none;
    border-color: var(--mvs-primary);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.mvs-collection-search-btn {
    padding: 10px 20px;
    border: 1px solid var(--mvs-primary);
    border-left: 0;
    border-radius: 0 8px 8px 0;
    background: var(--mvs-primary);
    color: var(--mvs-bg);
    font-size: 0.95em;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.2s;
}

.mvs-collection-search-btn:hover {
    background: var(--mvs-primary-hover);
}

/* Rules display (inline in meta) */
.mvs-collection-card-meta .mvs-rule-pill {
    padding: 3px 10px;
    border-radius: 16px;
    font-size: 0.85em;
    background: var(--mvs-surface-2);
    color: var(--mvs-text-secondary);
    font-weight: 500;
}

.mvs-collection-type-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 0.75em;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    background: var(--mvs-info-bg);
    color: var(--mvs-primary);
}

.mvs-collection-count {
    font-size: 0.85em;
    color: var(--mvs-text-secondary);
}

.mvs-collection-rules-preview {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 6px;
}

.mvs-rule-pill {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 0.75em;
    background: rgba(var(--mvs-primary-rgb), 0.15);
    color: var(--mvs-primary);
    border: 1px solid rgba(var(--mvs-primary-rgb), 0.30);
}

/* Type toggle */
.mvs-collection-type-toggle {
    display: flex;
    gap: 0;
    border: 1px solid var(--mvs-border);
    border-radius: 6px;
    overflow: hidden;
    width: fit-content;
}

.mvs-toggle-btn {
    padding: 8px 20px;
    border: none;
    background: var(--mvs-bg);
    cursor: pointer;
    font-size: 0.9em;
    color: var(--mvs-text-secondary);
    transition: background 0.15s, color 0.15s;
}

.mvs-toggle-btn:not(:last-child) {
    border-right: 1px solid var(--mvs-border);
}

.mvs-toggle-btn.active {
    background: var(--mvs-primary);
    color: var(--mvs-bg);
}

.mvs-field-hint {
    margin-top: 6px;
    font-size: 0.85em;
    color: var(--mvs-text-muted);
    font-style: italic;
}

/* Rule builder */
.mvs-rules-builder {
    margin-top: 16px;
}

.mvs-rules-builder>label {
    display: block;
    font-weight: 600;
    margin-bottom: 10px;
}

.mvs-rules-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 12px;
}

.mvs-rule-row {
    display: flex;
    gap: 8px;
    align-items: center;
}

.mvs-rule-row select,
.mvs-rule-row input {
    padding: 8px 10px;
    border: 1px solid var(--mvs-border);
    border-radius: 4px;
    font-size: 0.9em;
}

.mvs-rule-key {
    min-width: 140px;
    flex: 0 0 auto;
}

.mvs-rule-value,
.mvs-rule-value-text {
    flex: 1;
    min-width: 120px;
}

.mvs-rule-remove {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border: 1px solid var(--mvs-border);
    border-radius: 4px;
    background: var(--mvs-bg);
    color: var(--mvs-danger);
    font-size: 1.2em;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s;
}

.mvs-rule-remove:hover,
.mvs-rule-remove:focus {
    background: var(--mvs-danger-bg);
    color: var(--mvs-danger);
    border-color: var(--mvs-danger);
}

.mvs-rules-preview {
    margin-top: 12px;
    padding: 10px 14px;
    background: var(--mvs-success-bg);
    border-radius: 6px;
    font-size: 0.9em;
    color: var(--mvs-success);
    font-weight: 500;
}

/* Wide modal variant for collection rule builder */
.mvs-modal--wide {
    max-width: 680px;
}

@media (max-width: 600px) {
    .mvs-rule-row {
        flex-wrap: wrap;
    }

    .mvs-rule-key {
        min-width: 100%;
    }
}

/* Section 27 "Theme Compatibility — Reign / BuddyBoss / Generic"
 * removed. Audit summary:
 *
 *  - .theme-flavor scoped rules — dead: no PHP/JS/template in the plugin
 *    emits .theme-flavor, and no theme adds it to <body>. Every rule was
 *    a no-op.
 *  - .mvs-bp-upload-btn scoped rules — dead: the upload button is
 *    emitted with id="mvs-bp-upload-btn" and class="mvs-btn", never with
 *    class="mvs-bp-upload-btn". Rules were also no-ops.
 *  - .buddyboss-theme .mvs-upload-dropzone / .mvs-upload-block —
 *    .mvs-upload-* only appears inside the media-upload Gutenberg block,
 *    which doesn't render inside BP surfaces; the rule never matched.
 *    Additionally, a dangling `.theme-flavor` selector with no rule body
 *    on the line above was silently hijacking this selector list, so the
 *    rules were doubly broken.
 *  - .buddyboss-theme #mvs-activity-media-btn — redundant with the
 *    consolidated #mvs-activity-media-btn rule now in bp-integration.css
 *    (same display/align/gap values).
 *  - .wb-reign-theme #whats-new-form #mvs-activity-media-btn margin-left
 *    — the only live theme-compat rule. Moved to bp-integration.css
 *    (Theme compatibility section) with the selector updated to scope
 *    on .mvs-activity-media-btn-wrap since that's now the flex row
 *    anchor (button no longer lays out on its own).
 *  - .mvs-bp-upload-btn, #mvs-activity-media-btn { max-width: 100% } —
 *    the button now lives inside .mvs-activity-media-btn-wrap which
 *    already has width: 100%; box-sizing: border-box on all button
 *    children via box-sizing: border-box inheritance from the baseline
 *    reset in bp-integration.css. No longer needed.
 */

/* ==========================================================================
   26. Album Placeholder
   ========================================================================== */

.mvs-grid-item-placeholder--album {
    background: linear-gradient(135deg, #6c757d, #495057);
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.mvs-grid-album-icon {
    font-size: 36px;
    opacity: 0.8;
}

.mvs-grid-item-placeholder--collection {
    background: linear-gradient(135deg, #4a90d9, #357abd);
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.mvs-grid-collection-icon {
    font-size: 36px;
    opacity: 0.8;
}

.mvs-collection-card .mvs-collection-type-badge {
    position: absolute;
    top: 8px;
    right: 8px;
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.mvs-collection-card .mvs-dashboard-card-thumb {
    position: relative;
}

.mvs-collection-card .mvs-dashboard-card-meta {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mvs-collection-card .mvs-collection-rules-preview {
    max-height: 52px;
    overflow: hidden;
}

/* ==========================================================================
   27. Notification Bell
   ========================================================================== */

.mvs-dashboard-header {
    display: flex;
    align-items: center;
    gap: 16px;
}

.mvs-notification-bell {
    position: relative;
    cursor: pointer;
    margin-left: auto;
    user-select: none;
}

.mvs-notification-bell-icon {
    font-size: 1.4em;
}

.mvs-notification-badge {
    position: absolute;
    top: -6px;
    right: -8px;
    background: var(--mvs-danger);
    color: var(--mvs-bg);
    font-size: 11px;
    font-weight: 700;
    min-width: 18px;
    height: 18px;
    line-height: 18px;
    text-align: center;
    border-radius: 9px;
    padding: 0 4px;
}

.mvs-notification-dropdown {
    position: absolute;
    top: 100%;
    right: 0;
    width: 340px;
    max-height: 400px;
    overflow-y: auto;
    background: var(--mvs-bg, #fff);
    border: 1px solid var(--mvs-border-light, #ddd);
    border-radius: 8px;
    box-shadow: var(--mvs-shadow-lg, 0 8px 32px rgba(0, 0, 0, 0.18));
    z-index: 998;
    margin-top: 8px;
}

.mvs-notification-dropdown-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 12px 16px;
    border-bottom: 1px solid var(--mvs-border-light);
    /* Pin the header while the notification list scrolls (the parent
       .mvs-notification-dropdown is the overflow-y:auto scroll container). */
    position: sticky;
    top: 0;
    background: var(--mvs-bg, #fff);
    z-index: 1;
}

.mvs-notification-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.mvs-notification-item {
    padding: 10px 16px;
    border-bottom: 1px solid var(--mvs-surface-2);
    font-size: 0.9em;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.mvs-notification-item:last-child {
    border-bottom: none;
}

.mvs-notification-unread {
    background: var(--mvs-info-bg);
}

.mvs-notification-time {
    color: var(--mvs-text-muted);
    font-size: 0.8em;
}

.mvs-notification-empty {
    padding: 20px 16px;
    text-align: center;
    color: var(--mvs-text-muted);
    margin: 0;
}

/* ==========================================================================
   28. Comment Edit/Delete Actions
   (Base layout rules for .mvs-comment-header/.mvs-comment-avatar/
   .mvs-comment-actions live in section 13. Only edit-form-specific
   styles belong here.)
   ========================================================================== */

.mvs-comment-edit-form {
    margin-top: 6px;
}

.mvs-comment-edit-textarea {
    width: 100%;
    padding: 8px;
    border: 1px solid var(--mvs-border);
    border-radius: 4px;
    font-size: 0.9em;
    resize: vertical;
    box-sizing: border-box;
}

.mvs-comment-edit-actions {
    display: flex;
    gap: 6px;
    margin-top: 6px;
}

/* ==========================================================================
   29. Follow Button
   ========================================================================== */

.mvs-follow-btn-wrap {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
}

.mvs-follow-btn {
    border-radius: 8px;
    padding: 6px 16px;
    font-size: 0.8125rem;
    font-weight: 600;
    transition: all 0.2s;
    background: var(--mvs-primary);
    /* Always use --mvs-on-primary (#fff) here; never --mvs-bg which flips
       to dark in dark-mode themes and becomes unreadable on the red bg. */
    color: var(--mvs-on-primary, #ffffff);
    border: 1px solid var(--mvs-primary);
}

.mvs-follow-btn:hover {
    background: var(--mvs-primary-hover);
    border-color: var(--mvs-primary-hover);
    color: var(--mvs-on-primary, #ffffff);
}

.mvs-follow-btn.active {
    background: var(--mvs-bg);
    color: var(--mvs-text);
    border-color: var(--mvs-border);
}

.mvs-follow-btn.active:hover {
    background: var(--mvs-surface-2);
    border-color: var(--mvs-border);
}

/* ==========================================================================
   30. Album Cover Picker
   ========================================================================== */

/* `.mvs-media-picker-item` is declared in §19 with its full box, including
   `position: relative`. The duplicate rule that used to sit here restated only
   that one property — Coding Rule 12 forbids duplicate class rules. */

.mvs-media-picker-cover-btn {
    position: absolute;
    bottom: 4px;
    left: 4px;
    background: rgba(0, 0, 0, 0.7);
    color: var(--mvs-bg);
    border: none;
    border-radius: 4px;
    padding: 2px 8px;
    font-size: 11px;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.2s;
    z-index: 3;
}

.mvs-media-picker-item:hover .mvs-media-picker-cover-btn,
.mvs-media-picker-cover-btn:focus-visible {
    opacity: 1;
}

/* A hover-only reveal is unreachable on touch: there is no hover on a phone or
   tablet, so "Set Cover" was invisible and effectively untappable — while the
   hint directly above the picker instructs the member to click it. On a coarse
   pointer the button is simply always visible (Basecamp 10190678139). */
@media (hover: none), (pointer: coarse) {
    .mvs-media-picker-cover-btn {
        opacity: 1;
        /* Revealing it is only half the fix — at 28px tall it was still under
           the 40px touch floor, so it would have been visible and awkward to
           hit. Padding rather than height so the label stays centred. */
        min-height: 40px;
        padding-block: 8px;
    }
}

.mvs-media-picker-cover-badge {
    position: absolute;
    top: 4px;
    left: 4px;
    background: var(--mvs-primary);
    color: var(--mvs-bg);
    border-radius: 4px;
    padding: 2px 8px;
    font-size: 11px;
    font-weight: 700;
    z-index: 3;
}

.mvs-media-picker-cover {
    outline: 3px solid var(--mvs-primary);
    outline-offset: -3px;
}

/* ==========================================================================
   27. Grid Pagination
   ========================================================================== */

.mvs-grid-pagination {
    margin-top: 24px;
    text-align: center;
}

.mvs-grid-pagination-info {
    display: block;
    margin-bottom: 12px;
    font-size: 13px;
    color: var(--mvs-text-secondary);
}

.mvs-grid-pagination-links ul {
    display: flex;
    justify-content: center;
    gap: 4px;
    list-style: none;
    margin: 0;
    padding: 0;
    flex-wrap: wrap;
}

.mvs-grid-pagination-links li {
    margin: 0;
}

.mvs-grid-pagination-links a,
.mvs-grid-pagination-links span {
    display: inline-block;
    padding: 8px 14px;
    border: 1px solid var(--mvs-border-light);
    border-radius: 4px;
    text-decoration: none;
    font-size: 13px;
    color: var(--mvs-text);
    background: var(--mvs-bg);
    transition: all 0.15s;
}

.mvs-grid-pagination-links a:hover {
    background: var(--mvs-surface-2);
    border-color: var(--mvs-border-light);
}

.mvs-grid-pagination-links .current {
    background: var(--mvs-primary);
    color: var(--mvs-bg);
    border-color: var(--mvs-primary);
    font-weight: 600;
}

.mvs-grid-pagination-links .dots {
    border-color: transparent;
    background: transparent;
}

/* ==========================================================================
   28. Upload Form Fixes
   ========================================================================== */

.mvs-upload-block {
    max-width: 680px;
    margin: 0 auto;
}

.mvs-upload-dropzone {
    border: 2px dashed var(--mvs-border);
    border-radius: 8px;
    padding: 40px 20px;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s;
    background: var(--mvs-surface-2);
}

.mvs-upload-dropzone:hover,
.mvs-upload-dropzone.mvs-dragover {
    border-color: var(--mvs-primary);
    background: var(--mvs-info-bg);
}

.mvs-upload-icon {
    color: var(--mvs-text-muted);
    margin-bottom: 12px;
}

.mvs-upload-text {
    margin: 0 0 16px;
    font-size: 14px;
    color: var(--mvs-text-secondary);
}

.mvs-upload-input {
    display: none;
}

.mvs-upload-privacy {
    margin-top: 12px;
    padding: 6px 12px;
    border: 1px solid var(--mvs-border);
    border-radius: 4px;
    font-size: 13px;
}

.mvs-upload-fields {
    margin-top: 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.mvs-upload-fields input,
.mvs-upload-fields textarea {
    width: 100%;
    padding: 8px 12px;
    border: 1px solid var(--mvs-border);
    border-radius: 4px;
    font-size: 13px;
    box-sizing: border-box;
}

/* Section 29 "BP Activity Media Button Alignment" — all rules moved to
 * bp-integration.css. Section header retained as a placeholder so
 * renumbering in a later commit stays mechanical. */

/* ==========================================================================
   30. Single Media Page Fixes
   ========================================================================== */

.mvs-media-content img {
    max-width: 100%;
    height: auto;
    border-radius: 4px;
}

/* ONE declaration. There were two, at identical specificity with no media
   query between them — `12px 24px` earlier in the file and this one later —
   so the first was dead code and its `justify-content: space-between` never
   applied to anything. The horizontal value is 24px so the bar's contents line
   up with the title and body of the card it closes; at 16px it sat 8px to
   their left on every media page. */
.mvs-social-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 10px var(--mvs-space-4);
    border-top: 1px solid var(--mvs-border-light);
}

.mvs-reactions {
    display: flex;
    gap: 4px;
}

.mvs-reaction-btn {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 5px 10px;
    border: 1px solid var(--mvs-border);
    border-radius: 20px;
    background: var(--mvs-bg);
    cursor: pointer;
    font-size: 14px;
    transition: all 0.15s;
}

.mvs-reaction-btn:hover,
.mvs-reaction-btn.active {
    background: var(--mvs-info-bg);
    border-color: var(--mvs-primary);
}

.mvs-reaction-btn .mvs-count {
    font-size: 12px;
    color: var(--mvs-text-secondary);
}

.mvs-social-bar button.active .mvs-count {
    color: var(--mvs-bg);
}

/* Read-only reactions for logged-out users */
.mvs-reactions--readonly .mvs-reaction-btn {
    cursor: default;
    opacity: 0.7;
    pointer-events: none;
}

.mvs-favorite-btn {
    padding: 5px 12px;
    border: 1px solid var(--mvs-border);
    border-radius: 20px;
    background: var(--mvs-bg);
    cursor: pointer;
    font-size: 13px;
    transition: all 0.15s;
}

.mvs-favorite-btn:hover,
.mvs-favorite-btn.active {
    background: var(--mvs-danger-bg);
    border-color: var(--mvs-danger);
    color: var(--mvs-danger);
}

.mvs-share-btn {
    padding: 5px 12px;
    border: 1px solid var(--mvs-border);
    border-radius: 20px;
    background: var(--mvs-bg);
    cursor: pointer;
    font-size: 13px;
    transition: all 0.15s;
}

.mvs-share-btn:hover {
    border-color: var(--mvs-primary);
    color: var(--mvs-primary);
}

.mvs-view-count {
    margin-left: auto;
    font-size: 0.8125rem;
    color: var(--mvs-text-muted);
}

/* Single Media Responsive */
@media (max-width: 680px) {
    .mvs-single-media {
        padding: 0;
    }

    .mvs-media-article {
        border-radius: 0;
        border-left: none;
        border-right: none;
    }
}

@media (max-width: 480px) {
    .mvs-media-header {
        padding: 12px var(--mvs-space-3);
    }

    .mvs-media-title {
        font-size: 1.25em;
    }

    .mvs-social-bar {
        padding: 10px 12px;
        gap: 6px;
    }

    .mvs-reaction-btn {
        padding: 4px 8px;
        font-size: 13px;
    }

    .mvs-comments-section {
        padding: 10px var(--mvs-space-3) var(--mvs-space-3);
    }

    .mvs-media-description {
        padding: 10px var(--mvs-space-3) 0;
    }

    .mvs-media-footer {
        padding: 6px var(--mvs-space-3) var(--mvs-space-3);
    }
}

/* ==========================================================================
   31. Explore Page Grid Fixes
   ========================================================================== */

/* Section 31 held two EMPTY media blocks (767 and 480). They shipped nothing
   and inflated the breakpoint count that made this file look like it had more
   responsive behaviour than it does. Removed rather than kept as placeholders. */

/* ==========================================================================
   32. Profile Header (Dashboard) & Profile Edit
   ========================================================================== */

/* The profile CARD that lived above the rail is gone — its avatar, name, bio
   and buttons became the rail head plus the `profile` section, so its rules go
   with it rather than lingering as selectors nothing emits (Coding Rule 12).
   The edit FORM's own styling continues below; only the card is removed. */

.mvs-dashboard-profile-edit-btn {
    flex-shrink: 0;
}

/* Inline Edit Form */
/* `padding-top: 0.5rem` used to live here. It predated the panel wrapper and,
   being a later rule of equal specificity, out-specified it — Edit profile was
   the one tab whose content started higher than every other. Removed rather
   than zeroed, so the panel's own padding applies on all four sides like every
   other tab. */

.mvs-profile-form-inline {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.mvs-profile-field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}

.mvs-profile-form-actions {
    display: flex;
    gap: 0.5rem;
}

/* Standalone Profile Edit Page */
.mvs-profile-edit {
    max-width: 600px;
    margin: 2rem auto;
    padding: 0 1rem;
}

.mvs-profile-edit h2 {
    font-size: 1.5rem;
    margin-bottom: 1.5rem;
}

/* Messages */
.mvs-profile-message {
    padding: 0.75rem 1rem;
    border-radius: 6px;
    margin-bottom: 1rem;
    font-size: 0.875rem;
}

.mvs-profile-message--success {
    background: var(--mvs-success-bg);
    color: var(--mvs-success);
    border: 1px solid #c3e6cb;
}

.mvs-profile-message--error {
    background: var(--mvs-danger-bg);
    color: var(--mvs-danger);
    border: 1px solid #f5c6cb;
}

/* Avatar Section */
.mvs-profile-avatar-section {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    margin-bottom: 2rem;
    padding: 1.5rem;
    background: var(--mvs-surface-2);
    border-radius: 12px;
}

.mvs-profile-avatar-preview {
    flex-shrink: 0;
}

.mvs-profile-avatar-img {
    width: 96px;
    height: 96px;
    border-radius: 50%;
    object-fit: cover;
    border: 3px solid var(--mvs-surface);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.mvs-profile-avatar-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.mvs-profile-avatar-upload-label {
    cursor: pointer;
    display: inline-block;
}

.mvs-profile-avatar-input {
    display: none;
}

.mvs-profile-avatar-remove {
    font-size: 0.8125rem;
    color: var(--mvs-danger);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    text-align: left;
}

.mvs-profile-avatar-remove:hover {
    text-decoration: underline;
}

.mvs-profile-avatar-hint {
    font-size: 0.75rem;
    color: var(--mvs-text-muted);
    margin: 0;
}

/* Form Fields */
.mvs-profile-form {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}

.mvs-profile-field {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}

.mvs-profile-field label {
    font-weight: 600;
    font-size: 0.875rem;
    color: var(--mvs-text);
}

.mvs-profile-field input[type="text"],
.mvs-profile-field textarea {
    padding: 0.625rem 0.75rem;
    border: 1px solid var(--mvs-border);
    border-radius: 6px;
    font-size: 0.9375rem;
    font-family: inherit;
    transition: border-color 0.2s;
    width: 100%;
    box-sizing: border-box;
}

.mvs-profile-field input[type="text"]:focus,
.mvs-profile-field textarea:focus {
    border-color: var(--mvs-primary);
    outline: none;
    box-shadow: 0 0 0 2px rgba(0, 115, 170, 0.15);
}

.mvs-profile-field textarea {
    resize: vertical;
    min-height: 80px;
}

/* Actions */
.mvs-profile-actions {
    margin-top: 0.5rem;
}

/*
 * Colour floor for the button base.
 *
 * `.mvs-btn` carries layout only; every colour lives on a modifier
 * (--primary / --secondary / --danger). That is fine until a call site prints
 * the base class alone — "Add Media" on an album and "Save" on the collection
 * form both do — and then the button falls through to the browser's UA styling
 * (`buttonface` / `ButtonText`). UA button colours answer to `color-scheme`,
 * not to our tokens, so on a dark theme the pill stays light while the theme
 * repaints the label: white on white (Basecamp 10259504401).
 *
 * `:where()` keeps this at ZERO specificity, so every modifier still wins no
 * matter which stylesheet it lives in or what order the sheets load — this is
 * a floor, not a default that has to be fought. Pro's own base rules
 * (gamification.css, dashboard-connectors.css) set no colours either and are
 * covered by the same floor.
 */
:where(.mvs-btn) {
    background: var(--mvs-surface-2);
    color: var(--mvs-text);
}

/* Shared button styles (used across profile and dashboard) */
.mvs-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    padding: 0.625rem 1.25rem;
    border-radius: 6px;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    border: 1px solid transparent;
    transition: background 0.2s, border-color 0.2s;
    text-decoration: none;
    line-height: 1.4;
}

/* Icon sizing inside any .mvs-btn. Lucide's default <svg> is 24x24; at
 * that size it towers over 14px label text. Cap to 16px and inherit
 * the button color so the icon pairs properly with the label. The
 * `[data-lucide]` selector covers the brief moment before
 * lucide.createIcons() swaps the <i> placeholder for <svg>. */
.mvs-btn svg,
.mvs-btn [data-lucide] {
    width: 16px;
    height: 16px;
    stroke-width: 2;
    flex-shrink: 0;
    color: inherit;
}

/* Small-variant buttons use smaller labels, so scale the icon down too. */
.mvs-btn.mvs-btn--small svg,
.mvs-btn.mvs-btn--sm svg,
.mvs-btn.mvs-btn--small [data-lucide],
.mvs-btn.mvs-btn--sm [data-lucide] {
    width: 14px;
    height: 14px;
}

.mvs-btn--primary {
    /* --mvs-btn-primary-* is defined once in the token block above and is what
       the other three copies of this button reference. Never put a theme
       surface/bg token in `color` here; those flip dark in dark-mode themes. */
    background: var(--mvs-btn-primary-bg);
    color: var(--mvs-btn-primary-fg);
    border-color: var(--mvs-btn-primary-bg);
}

.mvs-btn--primary:hover,
.mvs-btn--primary:focus,
.mvs-btn--primary:visited {
    background: var(--mvs-btn-primary-bg-hover);
    color: var(--mvs-btn-primary-fg-hover);
}

.mvs-btn--primary:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* Secondary buttons are a NEUTRAL surface, deliberately distinct from the red
   primary. Do NOT borrow the theme's --button-* tokens here: BuddyX/Reign set
   --button-background-color to their PRIMARY button color (often red), which
   made the secondary button render identical to primary with mismatched text.
   The plugin's own --mvs-* surface/text tokens already flip for dark mode. */
.mvs-btn.mvs-btn--secondary {
    background: var(--mvs-surface-2);
    color: var(--mvs-text);
    border: 1px solid var(--mvs-border);
}

.mvs-btn.mvs-btn--secondary:hover,
.mvs-btn.mvs-btn--secondary:focus {
    background: var(--mvs-border-light);
    color: var(--mvs-text);
    border-color: var(--mvs-border);
}

/* Secondary button icons keep a neutral, theme-agnostic color — some themes
   expose --button-text-color in red/accent, which leaked into lucide SVG fill
   (e.g. Report flag rendered red). Matches the lightbox action convention. */
.mvs-btn.mvs-btn--secondary svg,
.mvs-btn.mvs-btn--secondary [data-lucide] {
    color: var(--mvs-text-secondary);
}

.mvs-btn.mvs-btn--secondary:hover svg,
.mvs-btn.mvs-btn--secondary:focus svg,
.mvs-btn.mvs-btn--secondary:hover [data-lucide],
.mvs-btn.mvs-btn--secondary:focus [data-lucide] {
    color: var(--mvs-text);
}

.mvs-btn--text {
    background: none;
    border: none;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    color: var(--mvs-text-secondary);
    transition: background 0.15s, color 0.15s;
}

.mvs-btn--text:hover {
    background: rgba(214, 54, 56, 0.08);
    color: var(--mvs-danger);
    border: none;
}

/* Block/unblock toggle: de-emphasized next to Follow/Message; danger tint when
   active or hovered so the destructive action reads clearly. */
.mvs-block-toggle {
    color: var(--mvs-text-secondary);
}

.mvs-block-toggle:hover,
.mvs-block-toggle:focus,
.mvs-block-toggle--blocked {
    color: var(--mvs-danger);
}

/* Mobile */
@media (max-width: 480px) {
    .mvs-profile-avatar-section {
        flex-direction: column;
        text-align: center;
    }

    .mvs-profile-avatar-actions {
        align-items: center;
    }

    .mvs-profile-avatar-remove {
        text-align: center;
    }

    .mvs-profile-field-row {
        grid-template-columns: 1fr;
    }
}

/* ==========================================================================
   33a. Search Tabs & User Search
   ========================================================================== */

.mvs-search-bar {
    display: flex;
    align-items: center;
    background: var(--mvs-bg);
    /* Pair the foreground with the background this component sets; without it
       the text inherited the ambient theme colour and went unreadable in dark
       mode (measured 1.07-1.38:1). */
    color: var(--mvs-text);
    border: 1.5px solid var(--mvs-border);
    border-radius: 100px;
    padding: 5px 5px 5px 6px;
    gap: 0;
    transition: border-color 0.2s, box-shadow 0.2s;
    max-width: 600px;
}

.mvs-search-bar:focus-within {
    border-color: var(--mvs-primary);
    box-shadow: 0 0 0 3px rgba(0, 115, 170, 0.12);
}

.mvs-search-mode {
    display: flex;
    background: var(--mvs-surface-2);
    /* Pair the foreground with the background this component sets; without it
       the text inherited the ambient theme colour and went unreadable in dark
       mode (measured 1.07-1.38:1). */
    color: var(--mvs-text);
    border-radius: 100px;
    padding: 2px;
    flex-shrink: 0;
    gap: 2px;
}

.mvs-search-mode-btn {
    padding: 5px 14px;
    border: none;
    background: transparent;
    border-radius: 100px;
    cursor: pointer;
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--mvs-text-secondary);
    transition: background 0.15s, color 0.15s;
    white-space: nowrap;
}

.mvs-search-mode-btn.active {
    background: var(--mvs-bg);
    color: var(--mvs-primary);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}

.mvs-search-field {
    display: flex;
    align-items: center;
    flex: 1;
    padding: 0 12px;
    gap: 8px;
    min-width: 0;
}

.mvs-search-icon {
    width: 16px;
    height: 16px;
    color: var(--mvs-text-muted);
    flex-shrink: 0;
}

.mvs-search-field input {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    font-size: 0.9375rem;
    color: var(--mvs-text);
    padding: 6px 0;
    min-width: 0;
}

.mvs-search-field input#mvs-search-input:focus {
    outline: none;
}

.mvs-search-field input:focus-visible {
    outline: 2px solid var(--mvs-primary, #0073aa);
    outline-offset: -2px;
}

.mvs-search-field input:focus-visible {
    outline: 2px solid var(--mvs-primary);
    outline-offset: -2px;
    border-radius: 2px;
}

.mvs-search-field input::placeholder {
    color: var(--mvs-text-muted);
}

.mvs-user-search-results {
    margin-top: 12px;
}

.mvs-user-search-empty {
    text-align: center;
    color: var(--mvs-text-secondary);
    padding: 16px;
}

.mvs-user-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    border: 1px solid var(--mvs-border-light);
    border-radius: 8px;
    margin-bottom: 8px;
    text-decoration: none;
    transition: border-color 0.2s, box-shadow 0.2s;
}

.mvs-user-card:hover {
    border-color: var(--mvs-primary);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.mvs-user-card-avatar {
    border-radius: 50%;
    object-fit: cover;
}

.mvs-user-card-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.mvs-user-card-info strong {
    font-size: 0.9375rem;
    color: var(--mvs-text);
}

.mvs-user-card-info span {
    font-size: 0.8125rem;
    color: var(--mvs-text-muted);
}

/* Login prompt links styled like buttons */
.mvs-login-prompt {
    opacity: 0.7;
    cursor: pointer;
    text-decoration: none;
}

.mvs-login-to-comment {
    text-align: center;
    padding: 12px;
    color: var(--mvs-text-secondary);
}

.mvs-login-to-comment a {
    color: var(--mvs-primary);
}

/* ==========================================================================
   33. Logged-Out CTA Banner
   ========================================================================== */

.mvs-logged-out-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    /* Fixed purple gradient in both modes — was var(--mvs-bg), which flips to
       near-black in dark mode (dark text on purple). Keep it white. */
    color: #fff;
    padding: 14px 24px;
    border-radius: 8px;
    margin: 0 auto 20px;
    max-width: 1200px;
    position: relative;
}

.wb-grid>.mvs-logged-out-banner {
    flex: 0 0 100%;
}

.mvs-logged-out-banner__content {
    font-size: 0.9375rem;
}

.mvs-logged-out-banner__content strong {
    font-weight: 700;
}

.mvs-logged-out-banner__actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
    margin-right: 15px;
}

.mvs-logged-out-banner__actions .mvs-btn--primary {
    /* White button on the purple banner in both modes — was var(--mvs-bg),
       which flips dark in dark mode and breaks the intended white pill. */
    background: #fff;
    /* #4c51bf, not #667eea. The banner's own indigo scored 3.66 on the white
       pill — a fail, on the single control that decides whether a visitor
       joins at all (Basecamp 10252222150). Same hue family, just dark enough:
       6.49. Not `--mvs-primary`, because this pill sits on a fixed purple
       gradient that does not follow the theme, so its text must not either. */
    color: #4c51bf;
    border: none;
}

.mvs-logged-out-banner__actions .mvs-btn--secondary {
    background: transparent;
    /* Fixed white, for the reason the comment above already gives: the banner
       is a fixed purple gradient in BOTH modes, and `var(--mvs-bg)` flips to
       near-black in dark — dark text on purple. The primary button was fixed
       for exactly this and the secondary was left behind. */
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.6);
}

.mvs-logged-out-banner__close {
    background: none;
    border: none;
    /* Full white, not 70% — at 0.7 on the gradient this was the faintest
       control on a banner shown to every visitor. */
    color: #fff;
    font-size: 20px;
    cursor: pointer;
    /* Was 20x28. A dismiss control needs to be hittable, and this one sits in
       a corner where a thumb is least accurate. Centring the glyph in a
       touch-sized box costs no layout, since the button is absolutely
       positioned. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--mvs-touch-min, 44px);
    min-height: var(--mvs-touch-min, 44px);
    padding: 0;
    line-height: 1;
    position: absolute;
    top: 8px;
    right: 4px;
}

.mvs-logged-out-banner__close:hover {
    /* Not `var(--mvs-bg)`: same dark-mode flip as the buttons above. */
    color: #fff;
    background: none;
    border: none;
}

@media (max-width: 600px) {
    .mvs-logged-out-banner {
        flex-direction: column;
        text-align: center;
        padding: 16px 20px 20px;
    }
}

/* ==========================================================================
   34. Frontend Empty States
   ========================================================================== */

.mvs-empty-state-frontend {
    text-align: center;
    padding: 48px 24px;
    color: var(--mvs-text-secondary);
}

.mvs-empty-state-icon {
    display: block;
    font-size: 48px;
    margin-bottom: 12px;
    line-height: 1;
    opacity: 0.7;
}

/* These were emoji sized by `font-size`. A Lucide <svg> ignores that and ships
   at 24x24, so it needs explicit dimensions to keep the 48px presence the empty
   state was designed around. `currentColor` is the point of the swap: the glyph
   now takes the text colour and follows dark mode, which an emoji never could
   (Basecamp 10253006229). */
.mvs-empty-state-icon svg,
.mvs-empty-state-icon [data-lucide] {
    width: 48px;
    height: 48px;
    stroke-width: 1.5;
    color: inherit;
    margin-inline: auto;
}

.mvs-empty-state-frontend h3 {
    margin: 0 0 8px;
    font-size: 1.125rem;
    color: var(--mvs-text);
}

.mvs-empty-state-frontend p {
    margin: 0 0 16px;
    font-size: 0.9375rem;
    color: var(--mvs-text-secondary);
}

/* -----------------------------------------------------------------------
   Plugin-branded 404 (mvs-empty-state-frontend--404)
   Uses the same design language as the auth gate — centered card with a
   gradient accent, circular icon halo, and popular-tag row below the
   primary CTA.
   ----------------------------------------------------------------------- */
.mvs-archive--404 {
    display: flex;
    justify-content: center;
    padding: clamp(32px, 8vw, 96px) 16px;
}

.mvs-empty-state-frontend--404 {
    position: relative;
    width: 100%;
    max-width: 560px;
    margin: 0;
    padding: clamp(32px, 5vw, 56px) clamp(24px, 4vw, 48px);
    background: var(--mvs-surface, #fff);
    border-radius: var(--mvs-radius-lg, 16px);
    box-shadow:
        0 1px 2px rgba(15, 23, 42, 0.04),
        0 18px 48px -12px rgba(15, 23, 42, 0.18);
    overflow: hidden;
    box-sizing: border-box;
}

.mvs-empty-state-frontend--404::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4px;
    background: linear-gradient(90deg,
        var(--mvs-primary, #0073aa) 0%,
        #6a5acd 50%,
        #ff6aa6 100%);
}

.mvs-empty-state-frontend__glyph {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 88px;
    height: 88px;
    margin: 0 auto 20px;
    border-radius: 50%;
    background: rgba(var(--mvs-primary-rgb, 0, 115, 170), 0.08);
    color: var(--mvs-primary, #0073aa);
    z-index: 1;
}

.mvs-empty-state-frontend__glyph svg {
    width: 44px;
    height: 44px;
    stroke-width: 1.75;
}

.mvs-empty-state-frontend__title {
    margin: 0 0 12px;
    font-family: var(--mvs-font-heading, inherit);
    font-size: clamp(22px, 2.6vw, 28px);
    font-weight: 700;
    line-height: 1.25;
    color: var(--mvs-text, #1e1e1e);
}

.mvs-empty-state-frontend__lede {
    margin: 0 auto 28px;
    max-width: 420px;
    font-size: 15px;
    line-height: 1.55;
    color: var(--mvs-text-secondary, #646970);
}

.mvs-empty-state-frontend--404 .mvs-empty-state-actions {
    display: flex;
    justify-content: center;
    margin-bottom: 24px;
}

.mvs-empty-state-frontend--404 .mvs-btn--primary {
    min-width: 200px;
    padding: 12px 28px;
    font-size: 15px;
    font-weight: 600;
}

.mvs-empty-state-frontend__tags-label {
    margin: 12px 0 10px;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--mvs-text-muted, #a7aaad);
}

.mvs-empty-state-frontend--404 .mvs-empty-state-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: center;
}

.mvs-empty-state-frontend--404 .mvs-tag-cloud-item {
    padding: 6px 14px;
    border-radius: var(--mvs-radius-pill, 100px);
    background: var(--mvs-surface-2, #f6f7f7);
    color: var(--mvs-text, #1e1e1e);
    font-size: 13px;
    text-decoration: none;
    transition: background 150ms ease, color 150ms ease;
}

.mvs-empty-state-frontend--404 .mvs-tag-cloud-item:hover,
.mvs-empty-state-frontend--404 .mvs-tag-cloud-item:focus-visible {
    background: rgba(var(--mvs-primary-rgb, 0, 115, 170), 0.08);
    color: var(--mvs-primary, #0073aa);
}

@media (max-width: 640px) {
    .mvs-archive--404 {
        padding: 24px 12px 48px;
    }
    .mvs-empty-state-frontend--404 {
        padding: 28px 20px 32px;
        box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px -8px rgba(15, 23, 42, 0.15);
    }
    .mvs-empty-state-frontend__glyph {
        width: 72px;
        height: 72px;
        margin-bottom: 16px;
    }
    .mvs-empty-state-frontend__glyph svg {
        width: 40px;
        height: 40px;
    }
    .mvs-empty-state-frontend--404 .mvs-btn--primary {
        width: 100%;
        min-width: 0;
    }
}

/* ==========================================================================
   34b. Profile Completion Prompt
   ========================================================================== */

.mvs-profile-prompt {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--mvs-warning-bg);
    border: 1px solid var(--mvs-warning);
    border-radius: 6px;
    padding: 10px 16px;
    margin-bottom: 16px;
    font-size: 0.875rem;
    position: relative;
}

.mvs-profile-prompt-icon {
    font-size: 20px;
    flex-shrink: 0;
}

.mvs-profile-prompt-text {
    flex: 1;
    color: var(--mvs-warning);
}

.mvs-profile-prompt-text .mvs-dashboard-profile-edit-btn {
    background: transparent;
    color: var(--mvs-warning);
    font-weight: 700;
    text-decoration: underline;
    border: 0;
    padding: 0;
}

.mvs-profile-prompt-text .mvs-dashboard-profile-edit-btn:hover,
.mvs-profile-prompt-text .mvs-dashboard-profile-edit-btn:focus {
    background: transparent;
    color: var(--mvs-primary);
    border: 0;
    padding: 0;
    box-shadow: none;
}

.mvs-profile-prompt-close {
    background: none;
    border: none;
    font-size: 18px;
    cursor: pointer;
    color: var(--mvs-warning);
    padding: 4px;
    line-height: 1;
}

.mvs-profile-prompt-close:hover,
.mvs-profile-prompt-close:focus {
    color: var(--mvs-primary);
    background: none;
    border: none;
}

/* ==========================================================================
   35. Accessibility — Focus & Reduced Motion
   ========================================================================== */

/* Visible focus ring on all interactive elements */
.mvs-btn:focus-visible,
.mvs-reaction-btn:focus-visible,
.mvs-favorite-btn:focus-visible,
.mvs-share-btn:focus-visible,
.mvs-follow-btn:focus-visible,
.mvs-dashboard-tab:focus-visible,
.mvs-comment-form button:focus-visible,
.mvs-comment-form textarea:focus-visible,
.mvs-tag-cloud-item:focus-visible,
.mvs-notification-bell:focus-visible,
.mvs-modal-close:focus-visible,
.mvs-dashboard-dropzone:focus-visible {
    outline: 2px solid var(--mvs-primary);
    outline-offset: 2px;
}

/* Focus trap visual — modals */
.mvs-modal:focus-within {
    box-shadow: 0 0 0 4px rgba(0, 115, 170, 0.15);
}

/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}


/* ==========================================================================
   36. Mobile Touch Targets & Responsiveness
   ========================================================================== */

@media (max-width: 767px) /* below --mvs-bp-tablet */ {

    /* Ensure minimum 44px touch targets */
    .mvs-reaction-btn {
        min-width: 44px;
        min-height: 44px;
        padding: 8px 10px;
    }

    .mvs-favorite-btn,
    .mvs-share-btn {
        min-height: 44px;
        padding: 10px 14px;
    }

    .mvs-follow-btn {
        min-height: 44px;
        padding: 10px 16px;
    }

    /* Increase spacing between action buttons */
    .mvs-social-actions {
        gap: 8px;
    }

    .mvs-social-actions-left {
        gap: 8px;
    }

    /* Sticky dashboard tabs — mobile guideline §5.2 */

    .mvs-dashboard-tab {
        min-height: 44px;
    }

    /* Prevent horizontal overflow on long text */
    .mvs-media-title {
        word-break: break-word;
        overflow-wrap: break-word;
    }

    .mvs-media-author-name {
        max-width: 150px;
    }

    .mvs-grid-item-info {
        max-width: 100%;
        overflow: hidden;
    }

    .mvs-grid-item-author {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: 120px;
    }

    /* Single media — sticky bottom action bar */
    .mvs-social-bar {
        position: sticky;
        bottom: 0;
        background: var(--mvs-bg);
        border-top: 1px solid var(--mvs-border-light);
        padding: 8px 16px;
        margin: 0 -16px;
        z-index: 5;
    }

    .mvs-social-actions {
        flex-wrap: wrap;
    }

    /* Notification bell touch target */
    .mvs-notification-bell {
        min-width: 44px;
        min-height: 44px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    /* Upload dropzone larger on mobile */
    .mvs-dashboard-dropzone {
        min-height: 120px;
    }

    /* Tag cloud horizontal scroll */
    .mvs-tag-cloud {
        overflow-x: auto;
        white-space: nowrap;
        -webkit-overflow-scrolling: touch;
        padding-bottom: 8px;
    }

    .mvs-tag-cloud-item {
        min-height: 36px;
        padding: 6px 12px;
    }

    /* Modal full-screen on mobile */
    .mvs-modal {
        width: 100%;
        max-width: 100%;
        height: 100%;
        max-height: 100%;
        border-radius: 0;
        margin: 0;
    }

    .mvs-modal-body {
        max-height: calc(100vh - 120px);
        overflow-y: auto;
    }
}

/* Bottom navigation bar for mobile logged-in users */
@media (max-width: 600px) {
    .mvs-explore-header h1 {
        font-size: 1.25rem;
    }

    .mvs-explore-search input {
        font-size: 16px;
        /* Prevent zoom on iOS */
    }

    .mvs-media-grid.mvs-cols-3,
    .mvs-media-grid.mvs-cols-4,
    .mvs-media-grid.mvs-cols-5 {
        grid-template-columns: repeat(2, 1fr);
    }

    .mvs-dashboard-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    .mvs-dashboard-card-actions {
        flex-wrap: wrap;
    }

    /* Comment form stacked on mobile */
    .mvs-comment-form {
        flex-direction: column;
    }

    .mvs-comment-form button {
        align-self: flex-end;
    }
}

/* ==========================================================================
   RTL Support
   ========================================================================== */

[dir="rtl"] [dir="rtl"] [dir="rtl"] [dir="rtl"] [dir="rtl"] .mvs-grid-item-info {
    flex-direction: row-reverse;
}

[dir="rtl"] .mvs-grid-item-overlay {
    text-align: right;
}

[dir="rtl"] .mvs-search-bar {
    flex-direction: row-reverse;
}

[dir="rtl"] .mvs-search-field {
    flex-direction: row-reverse;
}

[dir="rtl"] .mvs-activity-media-remove {
    right: auto;
    left: 4px;
}

[dir="rtl"] .mvs-explore-header {
    text-align: right;
}

[dir="rtl"] .mvs-tag-cloud {
    direction: rtl;
}

[dir="rtl"] [dir="rtl"] [dir="rtl"] .mvs-logged-out-banner__content {
    text-align: right;
}

[dir="rtl"] .mvs-logged-out-banner__close {
    right: auto;
    left: 16px;
}

[dir="rtl"] .mvs-chat-trigger {
    right: auto;
    left: 20px;
}

[dir="rtl"] .mvs-bp-album-form,
[dir="rtl"] .mvs-bp-album-msg {
    text-align: right;
}

/* ==========================================================================
   Mobile — Search Bar Responsive Fixes
   ========================================================================== */

@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    .mvs-search-bar {
        max-width: 100%;
    }

    .mvs-search-mode-btn {
        padding: 5px 10px;
        font-size: 0.75rem;
    }
}

@media (max-width: 480px) {
    .mvs-explore-header h1 {
        font-size: 1.25rem;
    }

    .mvs-search-bar {
        border-radius: var(--mvs-radius-lg);
        padding: 4px;
    }

    .mvs-search-mode {
        display: none;
        /* Collapsed on very small screens — defaults to media mode */
    }
}


/* ── Lightbox social sidebar ───────────────────────────────────────── */
.mvs-lightbox-overlay {
    position: fixed;
    inset: 0;
    z-index: 100000;
    background: rgba(0, 0, 0, 0.92);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2.5vh 2.5vw;          /* ~5% breathing room on every edge so the */
    box-sizing: border-box;        /* lightbox doesn't bleed into theme chrome */
    animation: mvs-fade-in 0.2s ease;
}

.mvs-lightbox {
    position: relative;
    display: flex;
    width: 100%;
    height: 100%;
    max-width: 95vw;               /* explicit caps reinforce the inset padding */
    max-height: 95vh;
    /* Container background matches the inner surfaces so the rounded corners
       never expose a lighter sliver when --mvs-surface resolves to white in
       a theme that doesn't define a dark variant. The outer overlay scrim
       handles the page-backdrop dimming. */
    background: transparent;
    border-radius: 12px;
    overflow: hidden;
    animation: mvs-slide-up 0.3s ease;
}

.mvs-lightbox-close {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 10;
    background: rgba(0, 0, 0, .5);
    border: none;
    border-radius: 50%;
    width: 44px;
    height: 44px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    /* Always-light icon — the backdrop is rgba(0,0,0,.5) regardless of
       theme, so referencing --mvs-surface (which flips dark in dark mode)
       turned the close icon invisible (dark on dark). The lightbox itself
       overlays the page with a dark veil, so #fff is the right fixed
       choice here. */
    color: #fff;
}

.mvs-lightbox-close:hover {
    background: rgba(0, 0, 0, .7);
}

/* Fullscreen toggle — sits just left of the close button, same pill treatment. */
.mvs-lightbox-fullscreen {
    position: absolute;
    top: 12px;
    right: 64px;
    z-index: 10;
    background: rgba(0, 0, 0, .5);
    border: none;
    border-radius: 50%;
    width: 44px;
    height: 44px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: #fff;
}

.mvs-lightbox-fullscreen:hover,
.mvs-lightbox-fullscreen:focus-visible {
    background: rgba(0, 0, 0, .7);
}

/* Swap expand/compress icon by state (the fullscreen class rides on .mvs-lightbox). */
.mvs-lightbox-fs-compress {
    display: none;
}

.mvs-lightbox--fullscreen .mvs-lightbox-fs-expand {
    display: none;
}

.mvs-lightbox--fullscreen .mvs-lightbox-fs-compress {
    display: block;
}

/* Fullscreen: fill the viewport and hide the comments sidebar so the media
   gets the whole space (customer request, Basecamp 9822552686). The media
   pane is flex:1, so it expands on its own once the sidebar is gone. */
.mvs-lightbox--fullscreen {
    max-width: 100vw;
    max-height: 100vh;
    border-radius: 0;
}

.mvs-lightbox--fullscreen .mvs-lightbox-sidebar {
    display: none;
}

.mvs-lightbox-close svg {
    width: 20px;
    height: 20px;
}

.mvs-lightbox-loading {
    flex: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.mvs-lightbox-media {
    flex: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #000;
    position: relative;
}

/* The <picture> wrapper is the actual flex item — the <img> is its child, not
 * a child of .mvs-lightbox-media. Nothing styled `picture` at all, so it had no
 * definite height: `height: 100%` on the img resolved against an indefinite
 * containing block, fell back to `auto`, the img rendered at its intrinsic
 * height, and `.mvs-lightbox { overflow: hidden }` silently cut the overflow.
 * With align-items:center the cut splits top and bottom, which is why tall
 * portraits lost their heads and feet (Basecamp #10153546608). Measured before
 * this rule: a 900x1600 upload rendered a 1351px-tall <picture> inside a 739px
 * media box — 612px clipped.
 *
 * Constraining `picture` gives the img a definite box to contain into. Video
 * and audio are direct children of .mvs-lightbox-media and were never affected,
 * which is the asymmetry that confirmed the diagnosis. */
.mvs-lightbox-media picture {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Definite, not just capped. max-height alone leaves the height
     * indeterminate, so the img's `height: 100%` still resolved to auto and it
     * kept rendering at intrinsic size inside a correctly-sized picture. */
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
}

.mvs-lightbox-media img {
    /* Sized by the max-* pair, not width/height 100%, so object-fit has a real
     * box to contain into and the image scales DOWN to fit rather than
     * overflowing its parent. */
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    display: block;
}

/* `display: flex` above beats the HTML `hidden` attribute — the same trap the
 * img/video/audio rule below already documents. shared-ui-frame.php binds
 * data-wp-bind--hidden on the <picture> to swap between image and video items,
 * so without this a hidden <picture> would keep rendering over the video. */
.mvs-lightbox-media picture[hidden] {
    display: none !important;
}

.mvs-lightbox-media img[hidden],
.mvs-lightbox-media video[hidden],
.mvs-lightbox-media audio[hidden] {
    display: none !important;
}

.mvs-lightbox-video {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    border-radius: 0;
    background: #000;
}

.mvs-lightbox-audio {
    width: min(400px, 80%);
    display: block;
    border-radius: 12px;
    padding: 8px;
    filter: invert(1);
}

.mvs-lightbox-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(255, 255, 255, .85);
    border: none;
    border-radius: 50%;
    width: 44px;
    height: 44px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .2);
}

.mvs-lightbox-nav:hover {
    background: var(--mvs-surface);
}

.mvs-lightbox-nav--prev {
    left: 12px;
}

.mvs-lightbox-nav--next {
    right: 12px;
}

.mvs-lightbox-position {
    position: absolute;
    bottom: 12px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, .6);
    color: var(--mvs-surface);
    padding: 4px 12px;
    border-radius: 12px;
    font-size: 13px;
}

/* Sidebar */
.mvs-lightbox-sidebar {
    width: 380px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    border-left: 1px solid var(--mvs-border-light);
    overflow-y: auto;
    background: var(--mvs-surface);
}

.mvs-lightbox-author {
    padding: 14px 16px;
    border-bottom: 1px solid var(--mvs-border-light);
}

.mvs-lightbox-author-link {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    color: inherit;
}

.mvs-lightbox-author-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
}

.mvs-lightbox-stats {
    padding: 6px 16px;
    font-size: 13px;
    color: var(--mvs-text-secondary);
}

.mvs-lightbox-reactions {
    display: flex;
    gap: 2px;
    padding: 8px 16px;
    border-bottom: 1px solid var(--mvs-border-light);
}

.mvs-lightbox-reaction {
    background: transparent;
    border: 1px solid transparent;     /* No "color patches" at rest — borders */
    border-radius: 20px;               /* only appear on hover/active state to */
    padding: 4px 10px;                 /* keep the strip clean against the */
    cursor: pointer;                   /* light sidebar background */
    min-block-size: var(--mvs-touch-min, 44px);
    justify-content: center;
    font-size: 14px;
    display: flex;
    align-items: center;
    gap: 4px;
    transition: background .15s, border-color .15s;
}

.mvs-lightbox-reaction>span:empty {
    display: none;
}

.mvs-lightbox-reaction:hover {
    background: var(--mvs-surface-2);
    border-color: var(--mvs-border-light);
}

.mvs-lightbox-reaction.active {
    background: var(--mvs-info-bg);
    border-color: var(--mvs-primary);
    color: var(--mvs-primary);
}

/* Action toolbar — 5 buttons (Favorite / Share / Download / Open / Report)
   share the row evenly. flex:1 + space-between makes them auto-fit the
   sidebar at any width without horizontal scroll. The previous design
   used inline-flex + margin-left:auto on Report which overflowed the
   380 px sidebar (5 × ~70 px + gaps + padding ≈ 414 px). Customer
   reported a horizontal scrollbar on both desktop and mobile. */
.mvs-lightbox-actions {
    display: flex;
    gap: 4px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--mvs-border-light);
    justify-content: space-between;
    align-items: stretch;
    flex-wrap: nowrap;
}

.mvs-lightbox-action {
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 5px;
    background: none;
    border: none;
    cursor: pointer;
    font-size: 13px;
    padding: 6px 4px;
    text-align: center;
    border-radius: 6px;
    text-decoration: none;
    color: var(--mvs-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mvs-lightbox-action:hover,
.mvs-lightbox-action:focus {
    background: var(--mvs-surface-2);
    color: var(--mvs-text);
    border: none;
}

.mvs-lightbox-action.active {
    color: var(--mvs-danger);
}

.mvs-lightbox-action [data-lucide],
.mvs-lightbox-action svg {
    width: 18px;
    height: 18px;
    stroke-width: 2;
}

/* Theme defense for the lightbox controls — keep the reaction pills and action
   buttons clean (no theme-injected fill/border/radius) so the lightbox reads as
   a premium, uncluttered surface on any theme. !important guards against themes
   that style bare <button>. Bug #10019404777. */
.mvs-lightbox-reaction {
    background: transparent !important;
    border: 1px solid transparent !important;
    border-radius: 20px !important;
    box-shadow: none !important;
}

.mvs-lightbox-reaction:hover {
    background: var(--mvs-surface-2) !important;
    border-color: var(--mvs-border-light) !important;
}

.mvs-lightbox-reaction.active {
    background: var(--mvs-info-bg) !important;
    border-color: var(--mvs-primary) !important;
}

.mvs-lightbox-action {
    background: none !important;
    border: 0 !important;
    border-radius: 6px !important;
    box-shadow: none !important;
    color: var(--mvs-text) !important;
}

.mvs-lightbox-action:hover,
.mvs-lightbox-action:focus {
    background: var(--mvs-surface-2) !important;
    color: var(--mvs-text) !important;
}

/* The active (favorited) state must survive the theme-defense guard above —
   without the !important here the guard's color pin wins and the heart never
   changes color even though the state toggles. */
.mvs-lightbox-action.active,
.mvs-lightbox-action.active:hover,
.mvs-lightbox-action.active:focus {
    color: var(--mvs-danger) !important;
}

.mvs-lb-fav.active svg {
    fill: currentColor;
}

/* Fullscreen — when the lightbox enters browser fullscreen via the
   Fullscreen API, override the dialog max sizes so the media fills the
   actual viewport. ESC and the F shortcut both exit. */
.mvs-lightbox:fullscreen {
    width: 100vw;
    height: 100vh;
    max-width: none;
    max-height: none;
    border-radius: 0;
}

.mvs-lightbox:fullscreen .mvs-lightbox-media {
    max-height: calc(100vh - 64px);
}

.mvs-lightbox:fullscreen .mvs-lightbox-media picture,
.mvs-lightbox:fullscreen .mvs-lightbox-media img,
.mvs-lightbox:fullscreen .mvs-lightbox-video {
    max-height: calc(100vh - 64px);
    object-fit: contain;
}

/* High-contrast focus ring on every lightbox action — keyboard a11y for
   the new Download / Fullscreen buttons + the existing Share / Open. */
.mvs-lightbox-action:focus-visible,
.mvs-lightbox-close:focus-visible,
.mvs-lightbox-nav:focus-visible {
    outline: 2px solid var(--mvs-primary, #2271b1);
    outline-offset: 2px;
}

.mvs-lightbox-action--report {
    /* No margin-left:auto — flex:1 + space-between on the parent already
       distributes the 5 buttons evenly. Forcing Report to the right used
       to overflow the sidebar's 380 px width. */
    color: var(--mvs-text-secondary);
    font-size: 13px;
}

.mvs-lightbox-action--report:hover {
    color: var(--mvs-danger);
    background: var(--mvs-danger-bg);
}

/* ─── Lightbox — mobile responsive layout ──────────────────────────────────
   Stack vertically below tablet so the 380 px sidebar doesn't push the
   media off-screen and force a horizontal scrollbar on narrow viewports.
   Action buttons distribute evenly (flex:1) so 5 fit on one row at any
   width without scrolling. */
@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    .mvs-lightbox {
        flex-direction: column;
        max-width: 100vw;
        max-height: 100vh;
        height: 100vh;
        width: 100vw;
        border-radius: 0;
    }

    .mvs-lightbox-media {
        flex: 0 0 auto;
        max-height: 50vh;
        min-height: 240px;
    }

    .mvs-lightbox-sidebar {
        width: 100%;
        flex: 1 1 auto;
        border-left: none;
        border-top: 1px solid var(--mvs-border-light);
    }

    /* 5-button row distributes evenly; no scroll, no margin-left:auto pushing
       Report off-row. Padding tightens so labels fit. */
    .mvs-lightbox-actions {
        gap: 0;
        padding: 8px 4px;
        flex-wrap: nowrap;
        overflow-x: visible;
        justify-content: space-around;
    }

    .mvs-lightbox-action {
        flex: 1 1 0;
        min-width: 0;
        padding: 6px 4px;
        font-size: 12px;
    }

    /* Override --report's margin-left:auto so the 5 buttons distribute
       evenly via space-around above. */
    .mvs-lightbox-action--report {
        margin-left: 0;
        font-size: 12px;
    }

    .mvs-lightbox-action [data-lucide],
    .mvs-lightbox-action svg {
        width: 20px;
        height: 20px;
    }
}

/* Very narrow phones — drop labels to icons-only so 5 still fit. */
@media (max-width: 380px) {
    .mvs-lightbox-action {
        font-size: 0; /* hide the text node */
        padding: 8px 4px;
    }

    .mvs-lightbox-action [data-lucide],
    .mvs-lightbox-action svg {
        width: 22px;
        height: 22px;
    }
}

.mvs-lightbox-desc {
    padding: 10px 16px;
    font-size: 14px;
    line-height: 1.5;
    border-bottom: 1px solid var(--mvs-border-light);
}

.mvs-lightbox-desc strong {
    margin-right: 6px;
}

.mvs-lightbox-comments {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Lightbox comment input — bigger AND uses the sidebar's own color tokens
   so it stays readable in any theme that flips dark mode independently of
   our `--mvs-bg` / `--mvs-text` variables. The default rule one block above
   uses `--mvs-bg` for the background which can resolve to white when the
   active theme (e.g. BuddyX Pro dark) doesn't override that var, leaving
   light theme-text rendered against a white box — visually unreadable. */
.mvs-lightbox-sidebar .mvs-comment-form textarea,
.mvs-lightbox-sidebar textarea[placeholder*="comment" i] {
    min-height: 72px;
    line-height: 1.4;
    background-color: var(--mvs-surface);
    color: var(--mvs-text, inherit);
    border-color: var(--mvs-border-light, var(--mvs-border));
}

.mvs-lightbox-sidebar .mvs-comment-form textarea::placeholder,
.mvs-lightbox-sidebar textarea[placeholder*="comment" i]::placeholder {
    color: var(--mvs-text-secondary);
    opacity: 0.85;
}

.mvs-lightbox-comments-heading {
    margin: 0;
    padding: 14px 16px 8px;
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--mvs-text-muted, #6b7280);
    border-bottom: 1px solid var(--mvs-border-light, #e5e7eb);
}

.mvs-lightbox-comment-list {
    flex: 1;
    overflow-y: auto;
    padding: 8px 0;
    margin: 0;
    list-style: none;
}

.mvs-lightbox-comment {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--mvs-border-light, #e5e7eb);
}

.mvs-lightbox-comment:last-child {
    border-bottom: none;
}

.mvs-lightbox-comment-avatar-link {
    flex-shrink: 0;
    display: block;
    text-decoration: none;
    line-height: 0;
}

.mvs-lightbox-comment-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

.mvs-lightbox-comment-body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.mvs-lightbox-comment-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    flex-wrap: wrap;
}

.mvs-lightbox-comment-author-link {
    text-decoration: none;
    color: inherit;
}

.mvs-lightbox-comment-author-link:hover .mvs-lightbox-comment-author {
    text-decoration: underline;
}

.mvs-lightbox-comment-author {
    font-weight: 600;
    font-size: 14px;
    color: var(--mvs-text, #111827);
}

.mvs-lightbox-comment-time {
    font-size: 12px;
    color: var(--mvs-text-muted, #6b7280);
    white-space: nowrap;
    flex-shrink: 0;
}

.mvs-lightbox-comment-content {
    margin: 0;
    font-size: 14px;
    line-height: 1.5;
    color: var(--mvs-text, #111827);
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

/* Edit/delete controls under a lightbox comment. Edit-mode Save/Cancel reuse
   .mvs-btn; the own-comment Edit/Delete are micro icon buttons (below). */
.mvs-lightbox-comment-actions,
.mvs-lightbox-comment-edit-actions {
    display: flex;
    gap: var(--mvs-space-2, 0.5rem);
    margin-block-start: var(--mvs-space-2, 0.5rem);
}

/* Micro icon buttons: a small pencil/trash on a member's own comment, not the
   large filled .mvs-btn. Transparent until hover so they read as icons, with a
   comfortable 28px hit area around the 14px glyph. */
.mvs-lightbox-comment-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 24px;
    block-size: 24px;
    /* The 24px box is deliberate and stays. The TARGET grows instead, via the
       ::after below — see the note there. */
    position: relative;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--mvs-text-muted, #64748b);
    border-radius: var(--mvs-radius-sm, 6px);
    cursor: pointer;
    transition: background-color 0.15s ease, color 0.15s ease;
}

/*
 * Hit area, not icon size.
 *
 * These measured 24x24 against a 40px floor, but the 24px glyph is a
 * deliberate design choice for this row and enlarging it would rebuild the
 * comment meta line. So the target grows and the button does not.
 *
 * 40x44, not 44x44: the siblings sit 8.5px apart, so a 44px-wide area would
 * overlap its neighbour by ~11px and the later button in the DOM would win
 * every click in the overlap — silently turning "edit" into "delete" near the
 * boundary. 40 wide leaves 0.5px between areas and still clears the floor.
 * Basecamp 10252222115.
 */
.mvs-lightbox-comment-action::after {
    content: '';
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    translate: -50% -50%;
    inline-size: 40px;
    block-size: var(--mvs-touch-min, 44px);
}

.mvs-lightbox-comment-action:hover,
.mvs-lightbox-comment-action:focus-visible {
    background: var(--mvs-surface-2, rgba(100, 116, 139, 0.12));
    color: var(--mvs-text, #0f172a);
}

.mvs-lightbox-comment-action--danger:hover,
.mvs-lightbox-comment-action--danger:focus-visible {
    color: var(--mvs-danger, #dc2626);
}

.mvs-lightbox-comment-action svg {
    inline-size: 14px;
    block-size: 14px;
}

/* The edit/delete icons line up on the right, under the timestamp, rather
   than floating on the left under the comment text. */
.mvs-lightbox-comment-actions {
    justify-content: flex-end;
    margin-block-start: 0;
}

.mvs-lightbox-comment-edit {
    margin-block-start: var(--mvs-space-2, 0.5rem);
}

.mvs-lightbox-comment-edit-input {
    width: 100%;
    font: inherit;
    padding: var(--mvs-space-2, 0.5rem);
    border: 1px solid var(--mvs-border);
    border-radius: var(--mvs-radius-md, 8px);
    background: var(--mvs-bg);
    color: var(--mvs-text);
    resize: vertical;
}

.mvs-lightbox-no-comments {
    color: var(--mvs-text-muted, #6b7280);
    font-size: 14px;
    text-align: center;
    padding: 32px 16px;
    margin: 0;
}

.mvs-lightbox-view-all-comments {
    display: block;
    text-align: center;
    padding: 12px 16px;
    font-size: 13px;
    font-weight: 600;
    color: var(--mvs-primary, #E35656);
    text-decoration: none;
    border-top: 1px solid var(--mvs-border-light, #e5e7eb);
}

.mvs-lightbox-view-all-comments:hover {
    background: var(--mvs-bg-muted, #f9fafb);
}

.mvs-lightbox-comment-form {
    display: flex;
    gap: 8px;
    align-items: center;
    /* Composer sits at the top of the thread (Twitter/LinkedIn), so the
       divider goes below it, above the comment list. */
    border-block-end: 1px solid var(--mvs-border-light, #e5e7eb);
    padding: 12px 16px;
    /* Was #fff — the lightbox sidebar is var(--mvs-surface) (dark in dark mode)
       and the input text is var(--mvs-text) (light), so a white bar made the
       typed text invisible. Match the sidebar surface. */
    background: var(--mvs-surface);
}

/*
 * The logged-OUT half of the composer slot.
 *
 * `.mvs-lightbox-comment-form` above and this prompt are the two branches of
 * the same row — one renders for a member, one for a visitor — but only the
 * member branch had a rule. The visitor got a bare `<p>`: no padding, so the
 * text sat flush against the sidebar edge while every other section was inset
 * 16px, and the theme's paragraph font-size, so it rendered larger than the
 * heading it sat under.
 *
 * Same box as the composer, deliberately: padding, divider and surface are
 * copied so the slot occupies identical space whichever branch renders and the
 * sidebar does not reflow between logged-in and logged-out.
 */
.mvs-lightbox-login-prompt {
    margin: 0;
    padding: 12px 16px;
    border-block-end: 1px solid var(--mvs-border-light, #e5e7eb);
    background: var(--mvs-surface);
    font-size: 14px;
    line-height: 1.4;
}

.mvs-lightbox-login-prompt a {
    color: var(--mvs-primary, #0073aa);
    text-decoration: none;
}

.mvs-lightbox-login-prompt a:hover,
.mvs-lightbox-login-prompt a:focus-visible {
    text-decoration: underline;
}

.mvs-lightbox-comment-input {
    flex: 1;
    /* Explicit surface + text tokens (not the theme's input styling) so the
       field is a defined rounded control that stays readable in dark mode. */
    border: 1px solid var(--mvs-border, #d1d5db);
    border-radius: 999px;
    padding: 9px 14px;
    font-size: 14px;
    outline: none;
    background: var(--mvs-surface);
    color: var(--mvs-text, inherit);
    min-width: 0;
}

.mvs-lightbox-comment-input:focus {
    border-color: var(--mvs-primary, #E35656);
}

.mvs-lightbox-comment-input:focus-visible {
    outline: 2px solid var(--mvs-primary, #E35656);
    outline-offset: -2px;
}

.mvs-lightbox-comment-post {
    flex-shrink: 0;
    min-block-size: var(--mvs-touch-min, 44px);
    background: var(--mvs-primary, #E35656);
    border: none;
    color: #ffffff;
    font-weight: 600;
    padding: 9px 18px;
    border-radius: 999px;
    cursor: pointer;
    font-size: 14px;
    transition: filter 0.15s ease, opacity 0.15s ease;
}

.mvs-lightbox-comment-post:hover:not(:disabled) {
    filter: brightness(0.93);
}

/* Faded primary while empty, not the theme's grey disabled bg. */
.mvs-lightbox-comment-post:disabled {
    opacity: 0.45;
    cursor: default;
}

.mvs-lightbox-comment-post:disabled {
    background: var(--mvs-border-light, #e5e7eb);
    color: var(--mvs-text-muted, #6b7280);
    cursor: default;
}

.mvs-lightbox-comment-post:not(:disabled):hover {
    filter: brightness(0.92);
    color: #ffffff;
}

@media (max-width: 767px) /* below --mvs-bp-tablet */ {
    .mvs-lightbox {
        flex-direction: column;
        width: 100vw;
        height: 100vh;
        height: 100dvh;
        border-radius: 0;
    }

    .mvs-lightbox-media {
        flex: 1;
        min-height: 0;
    }

    .mvs-lightbox-sidebar {
        width: 100%;
        flex: 0 0 45%;
        border-left: none;
        border-top: 1px solid var(--mvs-border-light);
    }
}
/* Replace-file row inside edit modal */
.mvs-replace-file-row {
    padding: 0 24px 12px;
    border-top: 1px solid var(--mvs-border-light);
    margin-top: 12px;
    padding-top: 12px;
}

.mvs-replace-file-label {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.mvs-replace-file-hint {
    color: var(--mvs-text-secondary);
    font-size: 12px;
    margin-left: 8px;
}

/* -----------------------------------------------------------------------
   Auth gate — premium empty-state shown on plugin pages that require login
   (e.g. /my-media/ dashboard shortcode while logged out).
   ----------------------------------------------------------------------- */
.mvs-auth-gate {
    display: flex;
    justify-content: center;
    padding: clamp(32px, 8vw, 96px) 16px;
}

.mvs-auth-gate__card {
    position: relative;
    width: 100%;
    max-width: 560px;
    padding: clamp(32px, 5vw, 56px) clamp(24px, 4vw, 48px);
    background: var(--mvs-surface, #fff);
    border-radius: var(--mvs-radius-lg, 16px);
    box-shadow:
        0 1px 2px rgba(15, 23, 42, 0.04),
        0 18px 48px -12px rgba(15, 23, 42, 0.18);
    text-align: center;
    overflow: hidden;
    color: var(--mvs-primary);
    box-sizing: border-box;
}

.mvs-auth-gate__card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4px;
    background: linear-gradient(90deg,
        var(--mvs-primary, #0073aa) 0%,
        #6a5acd 50%,
        #ff6aa6 100%);
}

.mvs-auth-gate__card::after {
    content: "";
    position: absolute;
    width: 280px;
    height: 280px;
    right: -120px;
    top: -140px;
    background: radial-gradient(circle,
        rgba(var(--mvs-primary-rgb, 0, 115, 170), 0.08) 0%,
        transparent 70%);
    pointer-events: none;
    z-index: 0;
}

.mvs-auth-gate__glyph {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 88px;
    height: 88px;
    margin: 0 auto 20px;
    border-radius: 50%;
    background: rgba(var(--mvs-primary-rgb, 0, 115, 170), 0.08);
    color: var(--mvs-primary, #0073aa);
    z-index: 1;
}

.mvs-auth-gate__glyph svg {
    width: 44px;
    height: 44px;
    stroke-width: 1.75;
}

.mvs-auth-gate__title {
    position: relative;
    margin: 0 0 12px;
    font-family: var(--mvs-font-heading, inherit);
    font-size: clamp(22px, 2.6vw, 28px);
    font-weight: 700;
    line-height: 1.25;
    color: var(--mvs-text, #1e1e1e);
    z-index: 1;
}

.mvs-auth-gate__lede {
    position: relative;
    margin: 0 auto 28px;
    max-width: 420px;
    font-size: 15px;
    line-height: 1.55;
    color: var(--mvs-text-secondary, #646970);
    z-index: 1;
}

.mvs-auth-gate__benefits {
    position: relative;
    list-style: none;
    padding: 0;
    margin: 0 0 32px;
    text-align: left;
    display: grid;
    gap: 12px;
    z-index: 1;
}

.mvs-auth-gate__benefits li {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    background: var(--mvs-surface-2, #f6f7f7);
    border-radius: var(--mvs-radius-md, 10px);
    font-size: 14px;
    color: var(--mvs-text, #1e1e1e);
    line-height: 1.4;
}

.mvs-auth-gate__benefit-icon {
    flex: 0 0 36px;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--mvs-surface, #fff);
    border-radius: 50%;
    color: var(--mvs-primary, #0073aa);
    line-height: 1;
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
}

.mvs-auth-gate__benefit-icon svg {
    width: 18px;
    height: 18px;
    stroke-width: 2;
}

.mvs-auth-gate__actions {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    z-index: 1;
}

.mvs-auth-gate__primary {
    min-width: 240px;
    padding: 12px 28px;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.mvs-auth-gate__secondary {
    font-size: 14px;
    /* --mvs-text-secondary measured 3.52:1 on the gate's own background, under
       the 4.5 floor for text, and the link was 18.2px tall against a 40px
       contract - the earlier pass fixed the PRIMARY call to action on this gate
       and stopped there (QA re-verify, Basecamp 10252222150).
       Body text colour carries the contrast; inline-flex + min-height give the
       control a real hit area without turning it into a button. */
    color: var(--mvs-text, #1e1e1e);
    display: inline-flex;
    align-items: center;
    min-height: var(--mvs-touch-min, 40px);
    text-decoration: none;
    transition: color 150ms ease;
}

.mvs-auth-gate__secondary:hover,
.mvs-auth-gate__secondary:focus-visible {
    color: var(--mvs-primary, #0073aa);
}

.mvs-auth-gate__secondary strong {
    color: var(--mvs-primary, #0073aa);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 2px;
}

@media (max-width: 640px) {
    .mvs-auth-gate {
        padding: 24px 12px 48px;
    }
    .mvs-auth-gate__card {
        padding: 28px 20px 32px;
        box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px -8px rgba(15, 23, 42, 0.15);
    }
    .mvs-auth-gate__glyph {
        width: 72px;
        height: 72px;
        margin-bottom: 16px;
    }
    .mvs-auth-gate__glyph svg {
        width: 44px;
        height: 44px;
    }
    .mvs-auth-gate__primary {
        width: 100%;
        min-width: 0;
    }
}

/* Dark-mode auth-gate card. The earlier `prefers-color-scheme` media query
   here was removed (per Jetonomy 2026-04-29 lesson — theme-class is the
   single source of truth for dark mode; OS-level pref races with the theme
   toggle and produces inconsistent state when a user is on a light theme
   with a dark OS or vice versa). Hooks the same dark-mode selector group
   defined at the top of the file so any theme that flips dark mode via
   class also flips this card. */
[data-theme="dark"] .mvs-auth-gate__card,
[data-bx-mode="dark"] .mvs-auth-gate__card,
html.dark-mode .mvs-auth-gate__card,
body.dark-mode .mvs-auth-gate__card {
    background: var(--mvs-surface, #1e1e1e);
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.2),
        0 18px 48px -12px rgba(0, 0, 0, 0.55);
}

/* Album cover hint inside FAB upload modal (album mode, files selected). */
.mvs-modal-album-cover-hint {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    margin-top: 8px;
    background: var(--mvs-surface-2, #f5f5f5);
    border-left: 3px solid var(--mvs-primary, #2271b1);
    border-radius: 4px;
    font-size: 13px;
    color: var(--mvs-text-secondary, #555);
}
.mvs-modal-album-cover-hint[hidden] { display: none; }
.mvs-modal-album-cover-hint svg {
    flex-shrink: 0;
    color: var(--mvs-primary, #2271b1);
}
@media (max-width: 640px) {
    .mvs-modal-album-cover-hint { font-size: 12px; padding: 6px 10px; }
}

/* ============================================================================
   Single album page — cover selection overlay (owner only).
   Renders a "Set as cover" button on each image tile and a "Cover" badge on
   the current cover. Pairs with templates/album.php inline JS that PUTs to
   /albums/{id}/cover. set_cover() is atomic (adds the media to the album in
   the same request if not already there), so no client-side ordering needed.
   ============================================================================ */
.mvs-album-item-wrap {
    position: relative;
    display: block;
}

.mvs-album-cover-badge {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 2;
    padding: 4px 10px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--mvs-surface, #fff);
    background: var(--mvs-primary, #2271b1);
    border-radius: 999px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
    pointer-events: none;
}

.mvs-album-set-cover {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 6px 10px;
    font-size: 12px;
    font-weight: 500;
    color: var(--mvs-surface, #fff);
    background: rgba(17, 17, 17, 0.72);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    cursor: pointer;
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.15s ease, transform 0.15s ease, background 0.15s ease;
}

.mvs-album-item-wrap:hover .mvs-album-set-cover,
.mvs-album-set-cover:focus-visible {
    opacity: 1;
    transform: translateY(0);
}

.mvs-album-set-cover:hover,
.mvs-album-set-cover:focus-visible {
    background: var(--mvs-primary, #2271b1);
    border-color: var(--mvs-primary, #2271b1);
    outline: none;
}

.mvs-album-set-cover[disabled] {
    opacity: 0.7;
    cursor: progress;
}

.mvs-album-set-cover .dashicons {
    width: 14px;
    height: 14px;
    font-size: 14px;
    line-height: 1;
}

@media (max-width: 640px) {
    .mvs-album-set-cover {
        /* Mobile: always visible (no hover). */
        opacity: 1;
        transform: none;
        padding: 4px 8px;
        font-size: 11px;
    }
    .mvs-album-cover-badge {
        padding: 3px 8px;
        font-size: 10px;
    }
}

/* ==========================================================================
   Usage history (mvs_transactions ledger — [mvs_usage_history] / dashboard)
   ========================================================================== */

.mvs-usage-history {
    background: var(--mvs-surface);
    border: 1px solid var(--mvs-border-light);
    border-radius: var(--mvs-radius-lg);
    padding: var(--mvs-space-4);
    color: var(--mvs-text);
}

.mvs-usage-history__title {
    margin: 0 0 var(--mvs-space-3);
    font-size: 1.125rem;
    color: var(--mvs-text);
}

.mvs-usage-history__empty {
    text-align: center;
    padding: var(--mvs-space-5) var(--mvs-space-3);
    color: var(--mvs-text-muted);
}

.mvs-usage-history__empty p {
    margin: 0 0 var(--mvs-space-1);
}

.mvs-usage-history__table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9375rem;
}

.mvs-usage-history__table th,
.mvs-usage-history__table td {
    text-align: start;
    padding: var(--mvs-space-2) var(--mvs-space-3);
    border-bottom: 1px solid var(--mvs-border-light);
}

.mvs-usage-history__table th {
    color: var(--mvs-text-secondary);
    font-weight: 600;
}

.mvs-usage-history__type {
    display: inline-block;
    padding: 2px 10px;
    border-radius: var(--mvs-radius-pill);
    font-size: 0.8125rem;
    background: var(--mvs-info-bg);
    color: var(--mvs-text);
}

.mvs-usage-history__delta {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.mvs-usage-history__delta--up {
    color: var(--mvs-success);
}

.mvs-usage-history__delta--down {
    color: var(--mvs-danger);
}

.mvs-usage-history__more {
    margin: var(--mvs-space-3) 0 0;
    color: var(--mvs-text-muted);
    font-size: 0.8125rem;
}


/* Blocked members (Edit Profile) — manage/undo blocks. */
.mvs-blocked-members { margin-top: var(--mvs-space-xl, 24px); }
.mvs-blocked-members__title { font-size: var(--mvs-font-lg, 18px); font-weight: 700; margin: 0 0 var(--mvs-space-md, 12px); }
.mvs-blocked-members__empty { color: var(--mvs-text-secondary, #6b7280); font-size: var(--mvs-font-sm, 14px); }
.mvs-blocked-members__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--mvs-space-sm, 8px); }
.mvs-blocked-members__row { display: flex; align-items: center; gap: var(--mvs-space-md, 12px); padding: var(--mvs-space-sm, 8px) var(--mvs-space-md, 12px); border: 1px solid var(--mvs-border, #e5e7eb); border-radius: var(--mvs-radius-md, 8px); }
.mvs-blocked-members__avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.mvs-blocked-members__name { font-weight: 600; }
.mvs-blocked-members__unblock { margin-inline-start: auto; }

/* Profile actions — overflow (⋯) menu + report dialog. */
.mvs-actions-overflow { position: relative; display: inline-block; }
.mvs-actions-menu { position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + 4px); min-inline-size: 160px; background: var(--mvs-bg, #fff); border: 1px solid var(--mvs-border, #e5e7eb); border-radius: var(--mvs-radius-md, 8px); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12); padding: 4px; z-index: 100; display: flex; flex-direction: column; }
.mvs-actions-menu[hidden] { display: none; }
.mvs-actions-menu__item { display: block; width: 100%; text-align: start; padding: 8px 12px;
    box-sizing: border-box; background: none; border: 0; border-radius: var(--mvs-radius-sm, 4px); cursor: pointer; font-size: var(--mvs-font-sm, 14px); color: var(--mvs-text, #111827); }
.mvs-actions-menu__item:hover, .mvs-actions-menu__item:focus-visible { background: var(--mvs-bg-muted, #f3f4f6); }
.mvs-actions-menu .mvs-block-toggle--blocked { color: var(--mvs-danger, #dc2626); }
/* Report dialog reuses the .mvs-modal component; ensure controls fill the field. */
.mvs-report-reason, .mvs-report-details { width: 100%; }

/* Followers / Following modal (reuses .mvs-modal) + clickable member stats. */
.mvs-member-photos-card__stat--clickable { cursor: pointer; border-radius: var(--mvs-radius-sm, 4px); }
.mvs-member-photos-card__stat--clickable:hover, .mvs-member-photos-card__stat--clickable:focus-visible { background: var(--mvs-bg-muted, #f3f4f6); }
.mvs-follows-tabs { display: flex; gap: var(--mvs-space-md, 12px); }
.mvs-follows-tab { background: none; border: 0; padding: 4px 2px; font-size: var(--mvs-font-md, 16px); font-weight: 600; color: var(--mvs-text-secondary, #6b7280); cursor: pointer; border-bottom: 2px solid transparent; }
.mvs-follows-tab.is-active { color: var(--mvs-text, #111827); border-bottom-color: var(--mvs-accent, #7c3aed); }
.mvs-follows-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--mvs-space-sm, 8px); }
.mvs-follows-row { display: flex; align-items: center; gap: var(--mvs-space-md, 12px); }
.mvs-follows-row__avatar { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.mvs-follows-row__name { font-weight: 600; }
.mvs-follows-row .mvs-follows-follow { margin-inline-start: auto; }
.mvs-follows-state { text-align: center; color: var(--mvs-text-secondary, #6b7280); padding: var(--mvs-space-xl, 24px) 0; }

/* Clickable follower/following counts in the profile header. */
.mvs-profile-header-stats__link { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; }
.mvs-profile-header-stats__link:hover, .mvs-profile-header-stats__link:focus-visible { text-decoration: underline; }

/* Stat buttons (clickable follower/following counts) reset to look like text. */
button.mvs-follows-open { background: none; border: 0; font: inherit; color: inherit; cursor: pointer; padding: 0; }

/* Tag-cloud dynamic items wrapper — display:contents keeps the flat flex row while
   making the data-wp-each <template> the sole child of its parent (avoids an
   Interactivity API hydration mismatch from a static sibling). */
.mvs-tag-cloud-items { display: contents; }

/* ---------------------------------------------------------------------------
   Access gate — shown inside .mvs-media-content on the single media page when
   the viewer is not permitted to see protected media (privacy-denied). Keeps
   the same page container; only the media slot is replaced. Token-driven so it
   adapts to dark mode automatically.
   --------------------------------------------------------------------------- */
.mvs-media-gate {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	text-align: center;
	gap: 0.75rem;
	min-height: 320px;
	padding: 3rem 1.5rem;
	background: var(--mvs-surface-2);
	border: 1px solid var(--mvs-surface-2);
	border-radius: var(--mvs-radius-lg);
}
.mvs-media-gate__glyph {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 64px;
	height: 64px;
	border-radius: 50%;
	background: var(--mvs-surface);
	color: var(--mvs-text-secondary);
}
.mvs-media-gate__glyph svg {
	width: 28px;
	height: 28px;
}
.mvs-media-gate__title {
	margin: 0;
	font-family: var(--mvs-font-heading);
	font-size: 1.25rem;
	font-weight: 600;
	color: var(--mvs-text);
}
.mvs-media-gate__lede {
	margin: 0;
	max-width: 42ch;
	color: var(--mvs-text-secondary);
}
.mvs-media-gate__cta {
	margin-top: 0.5rem;
}

/* ---------------------------------------------------------------------------
 * Document listing — /explore-document
 *
 * ROWS, not tiles. A grid of identical PDF icons carries no information, and a
 * media grid asked to draw a PDF produces a broken tile. A row shows what
 * actually distinguishes one document from another.
 * ------------------------------------------------------------------------- */
.mvs-documents__header {
	display: flex;
	align-items: baseline;
	gap: var(--mvs-space-sm, 8px);
	margin-block-end: var(--mvs-space-lg, 24px);
}

.mvs-documents__count {
	color: var(--mvs-text-muted, #646970);
	margin: 0;
}

.mvs-documents__list {
	list-style: none;
	margin: 0;
	padding: 0;
	border: 1px solid var(--mvs-border-light, #dcdcde);
	border-radius: var(--mvs-radius-md, 8px);
	overflow: hidden;
}

.mvs-documents__row {
	display: grid;
	/* The four metadata tracks are FIXED, not auto.
	 *
	 * With `auto auto auto auto` each track sized to its own row's content, so
	 * every row laid out independently and the columns never lined up - the
	 * date column started at 1147 / 1165 / 1171 / 1171 across four rows, 24px
	 * of drift, and worse on a real site where usernames and file sizes vary
	 * more than a demo's (Basecamp 10253006024). A column has to be the same
	 * width in every row or it is not a column. */
	grid-template-columns: auto minmax(0, 1fr) 4rem 6rem 4.5rem 7rem;
	align-items: center;
	gap: var(--mvs-space-md, 16px);
	padding: var(--mvs-space-md, 16px);
	border-block-end: 1px solid var(--mvs-border-light, #dcdcde);
	background: var(--mvs-surface, #fff);
}

.mvs-documents__row:last-child {
	border-block-end: 0;
}

.mvs-documents__icon {
	display: inline-flex;
	color: var(--mvs-text-muted, #646970);
}

.mvs-documents__main {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.mvs-documents__name {
	font-weight: 600;
	text-decoration: none;
	color: var(--mvs-text, #1d2327);
	/* 40px tap target on touch without stretching the row on desktop. */
	padding-block: 4px;
}

.mvs-documents__name:hover,
.mvs-documents__name:focus-visible {
	text-decoration: underline;
}

.mvs-documents__excerpt,
.mvs-documents__owner,
.mvs-documents__size,
.mvs-documents__date {
	color: var(--mvs-text-muted, #646970);
	font-size: var(--mvs-font-sm, 0.875rem);
}

/* Reserve a slot per metadata field so the columns line up down the list.
 *
 * The row laid its metadata out as a bare flex run pushed right, so every row
 * sized itself independently and each field started wherever the previous
 * one happened to end. Measured across four rows at 1440px: the date column
 * started at 1147 / 1165 / 1171 / 1171 - 24px of drift on a four-row list, and
 * a row with no type chip shifted owner, size and date left into the wrong
 * slots entirely (Basecamp 10253006024).
 *
 * Four rows is the BEST case: the drift is driven by the rendered width of the
 * owner name and the size string, so a real site with mixed usernames and file
 * sizes gets visibly worse. Fixed widths make a row's layout independent of its
 * own content, which is the property a column needs.
 *
 * text-align differs per field on purpose: a size reads right-aligned because
 * the eye compares magnitudes, a name reads left-aligned because it is a word. */
/* Each cell is pinned to ITS OWN track.
 *
 * Without this, a row whose document has no type chip renders five children
 * instead of six, and auto-placement slides owner, size and date one track to
 * the left - so that row's metadata sat under the wrong headings entirely.
 * Explicit placement leaves the chip track empty instead of collapsing it,
 * which is the only way to keep a column stable when a cell is optional.
 *
 * (An earlier attempt used `flex: 0 0 <width>` here. Flex properties are
 * ignored on grid items, so it changed nothing - the row has been display:grid
 * all along.) */
.mvs-documents__icon  { grid-column: 1; }
.mvs-documents__main  { grid-column: 2; }
.mvs-documents__chip  { grid-column: 3; }

.mvs-documents__owner {
	grid-column: 4;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.mvs-documents__size {
	grid-column: 5;
	text-align: end;
	/* Lining figures so sizes compare down the column. */
	font-variant-numeric: tabular-nums;
}

.mvs-documents__date {
	grid-column: 6;
	text-align: end;
	white-space: nowrap;
}

.mvs-documents__excerpt {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.mvs-documents__chip {
	display: inline-flex;
	align-items: center;
	padding: 2px var(--mvs-space-sm, 8px);
	border-radius: var(--mvs-radius-pill, 999px);
	background: var(--mvs-surface-2, #f0f0f1);
	color: var(--mvs-text-muted, #646970);
	font-size: var(--mvs-font-xs, 0.75rem);
	font-weight: 600;
	letter-spacing: 0.02em;
}

/*
 * Document type colours, owned by Free.
 *
 * They lived only in Pro's document-drive.css, so My Drive showed a red PDF and
 * a blue folder while Explore Documents — a FREE template — rendered every type
 * in the same neutral. Free cannot read a Pro stylesheet, so the palette moves
 * down here beside `DocumentTypes`, which is Free's vocabulary too, and Pro's
 * drive chips consume the same tokens (Basecamp 10263186054).
 *
 * Text variants, not the base semantic colours: these are labels on a tint, and
 * `--mvs-danger` / `--mvs-success` are tuned for use as a fill (10253005724).
 */
.mvs-documents__chip--folder,
.mvs-documents__chip--word,
.mvs-documents__chip--odf_text {
	background: var(--mvs-info-bg);
	color: var(--mvs-primary-text, var(--mvs-primary));
}

.mvs-documents__chip--pdf {
	background: var(--mvs-danger-bg);
	color: var(--mvs-danger-text, var(--mvs-danger));
}

.mvs-documents__chip--excel,
.mvs-documents__chip--csv,
.mvs-documents__chip--odf_sheet {
	background: var(--mvs-success-bg);
	color: var(--mvs-success-text, var(--mvs-success));
}

.mvs-documents__chip--powerpoint,
.mvs-documents__chip--odf_presentation {
	background: var(--mvs-warning-bg);
	color: var(--mvs-warning);
}

.mvs-documents__pagination {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--mvs-space-md, 16px);
	margin-block-start: var(--mvs-space-lg, 24px);
}

/* Under 640px the metadata columns stop earning their width, so the row stops
 * being a grid entirely.
 *
 * It cannot stay a grid with the meta cells sharing one area: four siblings
 * assigned to the same grid-area STACK on top of each other, which is exactly
 * what the 390px screenshot showed — "PDF", the owner, the size and the date
 * all overprinting in one box. Flex-wrap flows them instead. */
@media (max-width: 640px) {
	.mvs-documents__row {
		display: flex;
		flex-wrap: wrap;
		align-items: flex-start;
		column-gap: var(--mvs-space-sm, 8px);
		row-gap: 4px;
	}

	.mvs-documents__main {
		/* Take the rest of the first line, pushing the meta to the next. */
		flex: 1 1 60%;
	}

	.mvs-documents__excerpt {
		white-space: normal;
	}

	/* The row is the whole visual hit area on a phone, but the LINK is what a
	 * finger aims at, and at desktop line-height it lands at 30px. Raise it past
	 * the 40px floor rather than relying on the row's padding, which is not
	 * clickable. */
	.mvs-documents__name {
		min-height: 40px;
		display: flex;
		align-items: center;
	}

	.mvs-documents__chip,
	.mvs-documents__owner,
	.mvs-documents__size,
	.mvs-documents__date {
		flex: 0 0 auto;
	}

	/* The meta line starts under the icon, not beside it. */
	.mvs-documents__chip {
		margin-inline-start: calc(20px + var(--mvs-space-md, 16px));
	}
}

/* ============================================================================
 * Document card + download button.
 *
 * Free renders this card on the single-document page when Pro is absent, and
 * Pro's viewer reuses the same class names for its tier-3/4 card — one card,
 * one stylesheet, so the two cannot drift apart.
 *
 * The button is styled here rather than reusing `.mvs-btn-primary` because that
 * class is only ever defined inside a `#buddypress` scope: outside BuddyPress it
 * matches nothing and the download rendered as bare text. All four link states
 * are set explicitly — a theme's `a:visited` will happily repaint an anchor
 * button that only declared its resting colour.
 *
 * @since 2.4.0
 * ========================================================================== */

.mvs-media-document {
    width: 100%;
}

.mvs-doc-card {
    display: flex;
    align-items: center;
    gap: var(--mvs-space-3);
    padding: var(--mvs-space-4);
    border: 1px solid var(--mvs-border-light);
    border-radius: var(--mvs-radius-md);
    background: var(--mvs-surface);
}

.mvs-doc-card__icon {
    flex: 0 0 auto;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--mvs-radius-sm);
    background: var(--mvs-bg-muted);
}

.mvs-doc-card__meta {
    display: flex;
    flex-direction: column;
    gap: var(--mvs-space-1);
    min-width: 0;
}

.mvs-doc-card__name {
    overflow-wrap: anywhere;
    color: var(--mvs-text);
    font-weight: 600;
}

.mvs-doc-card__detail {
    color: var(--mvs-text-muted);
    font-size: var(--mvs-font-sm);
}

.mvs-doc-download,
.mvs-doc-download:link,
.mvs-doc-download:visited {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: flex-start;
    gap: var(--mvs-space-2);
    /* A download is the primary action on this page and has to clear the
       minimum tap target on a phone. */
    min-height: var(--mvs-touch-min);
    padding: var(--mvs-space-2) var(--mvs-space-4);
    border-radius: var(--mvs-radius-md);
    background: var(--mvs-primary);
    color: var(--mvs-on-primary);
    font-size: var(--mvs-font-md);
    font-weight: 600;
    text-decoration: none;
    transition: background var(--mvs-duration-fast) var(--mvs-easing);
}

.mvs-doc-download:hover,
.mvs-doc-download:focus-visible {
    background: var(--mvs-primary-hover);
    color: var(--mvs-on-primary);
    text-decoration: none;
}

.mvs-doc-download:focus-visible {
    outline: 2px solid var(--mvs-primary);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {

    .mvs-doc-download {
        transition: none;
    }
}

@media (max-width: 640px) {

    .mvs-doc-card {
        /* Icon and text stay side by side; the name wraps to its own line only
           when it genuinely cannot fit. */
        flex-wrap: wrap;
    }

    .mvs-doc-download {
        align-self: stretch;
    }
}

/* A registered dashboard tab is an anchor, not a button — its content lives on
   another page. It has to look and size identically to the button tabs beside
   it, or the row reads as two different kinds of control. */
.mvs-dashboard-tab--link {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

.mvs-dashboard-tab--link:hover,
.mvs-dashboard-tab--link:focus-visible {
    text-decoration: none;
}


/* ============================================================================
 * Document page tabs — Public / My Drive / Shared with me / Recent.
 *
 * Free renders this nav, so Free styles it. It previously lived in Pro's drive
 * stylesheet, which is only enqueued when the DRIVE renders — so on the Public
 * tab, and for every logged-out visitor, the nav degraded to a bullet list.
 * Markup and its CSS belong to the same plugin (Coding Rule 12).
 *
 * @since 2.4.0
 * ========================================================================== */


.mvs-documents__tabs ul {
	display: flex;
	flex-wrap: wrap;
	gap: var(--mvs-space-1);
	margin: 0 0 var(--mvs-space-4);
	padding: 0;
	list-style: none;
	border-bottom: 1px solid var(--mvs-border-light);
}

.mvs-documents__tab {
	display: inline-flex;
	align-items: center;
	min-height: var(--mvs-touch-min);
	padding: 0 var(--mvs-space-4);
	border-bottom: 2px solid transparent;
	color: var(--mvs-text-muted);
	font-weight: 500;
	text-decoration: none;
}

.mvs-documents__tab:hover,
.mvs-documents__tab:focus-visible {
	color: var(--mvs-text);
}

.mvs-documents__tab.is-active {
	border-bottom-color: var(--mvs-primary);
	color: var(--mvs-primary);
}



/* ============================================================================
 * My Media — vertical rail.
 *
 * The dashboard began with four tabs and carries eight. A horizontal row stops
 * working somewhere around six: it wraps to two lines, the wrap point moves
 * with the theme, and nothing groups. A rail with headings makes eight items
 * readable and leaves an obvious place for the ninth.
 *
 * @since 2.4.0
 * ========================================================================== */

/* The grid goes on `.mvs-dashboard__body`, which holds ONLY the rail and the
   panels. Putting it on `.mvs-dashboard` swept the streak bar, the profile
   header and the completion notice into the rail column too — every child of a
   grid is a grid item, including the ones that were never meant to be. */
@media (min-width: 861px) {

    .mvs-dashboard__body {
        display: grid;
        grid-template-columns: 13rem minmax(0, 1fr);
        /* Two rows, and the SECOND one absorbs the slack. Without this both
           rows shared the panel's height, so a tall panel pushed the sections
           box a long way down the page and left the rail looking broken in
           two. The head is auto — as tall as a name and a link — and
           everything left over goes to the sections column. */
        grid-template-rows: auto 1fr;
        gap: var(--mvs-space-5);
        align-items: start;
    }

    /* The identity sits above the sections, in the rail column. */
    .mvs-dashboard-rail-head {
        grid-column: 1;
        grid-row: 1;
    }

    .mvs-dashboard-tabs {
        position: sticky;
        top: var(--mvs-space-4);
        grid-column: 1;
        grid-row: 2;
        display: flex;
        flex-direction: column;
        gap: 2px;
        padding: var(--mvs-space-2);
        border: 1px solid var(--mvs-border-light);
        border-radius: var(--mvs-radius-md);
        background: var(--mvs-bg-subtle);
    }

    /* Every panel stacks into the same cell; only the active one is visible,
       so the column is as tall as whichever is showing. Spanning both rail rows
       so the panel starts level with the member's name rather than level with
       the first section below it. */
    .mvs-dashboard-panel {
        grid-column: 2;
        grid-row: 1 / span 2;
        min-width: 0;
    }

    /* `a.` bumps specificity past the base tab rule, which sets a bottom
       border every rail item inherited — a red rule under each row. */
    .mvs-dashboard-tabs a.mvs-dashboard-tab,
    .mvs-dashboard-tabs button.mvs-dashboard-tab {
        justify-content: flex-start;
        gap: var(--mvs-space-2);
        /* The horizontal tab styling puts a border on the bottom of every item,
           which down a rail reads as a rule under each row. All four sides are
           reset here rather than only the active one — that was the red line
           under Favorites and Collections. */
        border: 0;
        border-inline-start: 2px solid transparent;
        border-radius: var(--mvs-radius-sm);
        padding: var(--mvs-space-2) var(--mvs-space-3);
        /* No min-height reset here. The rail's padding alone lands a row at
           ~37px, and this selector outranks the base tab rule - which is why
           the 40px floor added there was inert on desktop (QA re-verify,
           Basecamp 10252222115). The base rule's --mvs-touch-min now applies. */
        color: var(--mvs-text-secondary);
        font-size: var(--mvs-font-sm);
        font-weight: 500;
        line-height: 1.4;
        text-align: start;
    }

    .mvs-dashboard-tabs .mvs-dashboard-tab:hover {
        background: var(--mvs-surface);
        color: var(--mvs-text);
    }

    .mvs-dashboard-tabs .mvs-dashboard-tab:focus-visible {
        outline: 2px solid var(--mvs-primary);
        outline-offset: -2px;
    }

    /* The horizontal tab styling underlines the active item, which on a rail
       reads as a stray rule under one row. The selector carries the rail so it
       outranks the base tab rule. */
    .mvs-dashboard-tabs a.mvs-dashboard-tab.active,
    .mvs-dashboard-tabs button.mvs-dashboard-tab.active {
        border-inline-start-color: var(--mvs-primary);

        /* Fill + squared leading corners live in the !important block above,
           which out-specifies this rule. */
        color: var(--mvs-primary-text, var(--mvs-primary));
        font-weight: 600;
    }
}


.mvs-dashboard-tabs__group {
    padding: var(--mvs-space-3) var(--mvs-space-3) var(--mvs-space-1);
    color: var(--mvs-text-muted);
    font-size: var(--mvs-font-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* A rail item is an anchor now, so it has to be told not to look like body
   copy — themes style `a` inside content aggressively. */
a.mvs-dashboard-tab,
a.mvs-dashboard-tab:hover,
a.mvs-dashboard-tab:focus-visible {
    text-decoration: none;
}

@media (max-width: 860px) {

    /* Below the rail's width the group headings earn nothing — they cost a
       whole row in a horizontal scroller. */
    .mvs-dashboard-tabs__group {
        display: none;
    }
}


/* The count sits at the far end of a rail item, quiet until you look for it. */
.mvs-dashboard-tab__label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mvs-dashboard-tab__count {
    flex: 0 0 auto;
    color: var(--mvs-text-muted);
    font-size: var(--mvs-font-xs);
    font-variant-numeric: tabular-nums;
}

.mvs-dashboard-tab.active .mvs-dashboard-tab__count {
    color: var(--mvs-primary);
}

.mvs-dashboard-tabs__group:first-child {
    padding-top: var(--mvs-space-1);
}

/* ---------------------------------------------------------------------------
   The rail states its own appearance.

   Everything above sets rail styling inside a min-width media query, so below
   that breakpoint — and anywhere the theme's `a` rules are more specific — the
   items fell back to being links: red, underlined, with the horizontal tab's
   bottom border still under each row. Three items showed it and three did not,
   which is the signature of inherited styling rather than declared styling.

   These rules are unconditional and name every property a theme is likely to
   set on an anchor, so the rail looks the same on any theme. This is the
   "plugin owns its classes" rule (Varun, 2026-08-09): a surface must not
   depend on a property it never declares.
   --------------------------------------------------------------------------- */
.mvs-dashboard-tabs a.mvs-dashboard-tab,
.mvs-dashboard-tabs button.mvs-dashboard-tab {
    display: flex;
    align-items: center;
    gap: var(--mvs-space-2);
    box-sizing: border-box;
    width: 100%;
    /* Declared, not inherited: the theme's link colour and the horizontal
       tab's bottom border are both overwritten here rather than fought
       selector by selector when a screenshot catches them. */
    border: 0;
    border-inline-start: 2px solid transparent;
    border-radius: var(--mvs-radius-sm);
    background: transparent;
    box-shadow: none;
    color: var(--mvs-text-secondary);
    text-decoration: none;
    font-weight: 500;
}

.mvs-dashboard-tabs a.mvs-dashboard-tab:hover,
.mvs-dashboard-tabs a.mvs-dashboard-tab:focus,
.mvs-dashboard-tabs button.mvs-dashboard-tab:hover,
.mvs-dashboard-tabs button.mvs-dashboard-tab:focus {
    border-block-end: 0;
    background: var(--mvs-surface);
    color: var(--mvs-text);
    text-decoration: none;
}

.mvs-dashboard-tabs a.mvs-dashboard-tab.active,
.mvs-dashboard-tabs button.mvs-dashboard-tab.active {
    border-block-end: 0;
    border-inline-start-color: var(--mvs-primary);
    background: var(--mvs-surface);
    /* Text -> AA-safe variant. These theme-defense rules out-specify the base
       .mvs-dashboard-tab.active rule, so setting the token there alone left the
       label at 4.36:1 - the fix has to land where the cascade actually ends. */
    color: var(--mvs-primary-text, var(--mvs-primary));
    font-weight: 600;
}

/* The count is a quantity, not part of the name — a pill at the far end reads
   as one and stops "Documents 6" scanning as a title. */
.mvs-dashboard-tabs .mvs-dashboard-tab__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5rem;
    padding: 0.0625rem var(--mvs-space-1);
    border-radius: 999px;
    background: var(--mvs-border-light, var(--mvs-surface));
    color: var(--mvs-text-muted);
    font-size: var(--mvs-font-xs);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.5;
}

.mvs-dashboard-tabs .mvs-dashboard-tab.active .mvs-dashboard-tab__count {
    background: var(--mvs-primary);
    color: var(--mvs-on-primary);
}

/* ---------------------------------------------------------------------------
   The rail head: who you are, and the way to your public profile.

   Unconditional, for the same reason the rail items are: a theme that paints
   anchors will paint these, and declaring appearance only inside a min-width
   query is what left half the rail looking like links. Every property a theme
   is likely to set on an anchor is named here.
   --------------------------------------------------------------------------- */
.mvs-dashboard-rail-head {
    display: flex;
    align-items: center;
    gap: var(--mvs-space-3);
    margin-bottom: var(--mvs-space-3);
    padding: var(--mvs-space-3);
    border: 1px solid var(--mvs-border-light);
    border-radius: var(--mvs-radius-md);
    background: var(--mvs-bg-subtle);
}

.mvs-dashboard-rail-head__avatar {
    display: block;
    width: 40px;
    height: 40px;
    border-radius: 999px;
    object-fit: cover;
}

.mvs-dashboard-rail-head__avatar-link {
    display: block;
    flex: 0 0 auto;
    border: 0;
    background: none;
    box-shadow: none;
    line-height: 0;
    text-decoration: none;
}

.mvs-dashboard-rail-head__id {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

/* WRAPS rather than truncating. A 13rem rail minus a 40px avatar cut
   "journey-member" to "journey-mem…" — and a name is the one string on this
   page where the end matters, because that is where people put the digits that
   tell two accounts apart. Two lines are cheaper than the wrong name. */
.mvs-dashboard-rail-head__name {
    color: var(--mvs-text);
    font-size: var(--mvs-font-sm);
    font-weight: 600;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

.mvs-dashboard-rail-head__link {
    border: 0;
    background: none;
    box-shadow: none;
    /* Text, so it takes the AA-safe variant - the raw brand measured 4.36:1. */
    color: var(--mvs-primary-text, var(--mvs-primary));
    font-size: var(--mvs-font-xs);
    text-decoration: none;
}

.mvs-dashboard-rail-head__link:hover,
.mvs-dashboard-rail-head__link:focus-visible {
    color: var(--mvs-primary);
    text-decoration: underline;
}

/* Below the rail breakpoint the rail is a horizontal scroller, so the head
   stops being a column header and becomes a line above it. */
@media (max-width: 860px) {

    .mvs-dashboard-rail-head {
        margin-bottom: var(--mvs-space-2);
    }
}

/* A group that earned no heading still gets its break. Drawn on the item that
   starts it, because the rail is a flat list of items — there is no element
   standing for "the group" to hang a border on.

   The border itself lives with the theme-defense block, which is the only
   place that can outrank its own `border: 0 !important`; spacing is free to
   live here. One border, one implementation. */
.mvs-dashboard-tabs .mvs-dashboard-tab--group-start {
    margin-block-start: var(--mvs-space-2);
    padding-block-start: var(--mvs-space-2);
}


/* =============================================================================
 *  Document type glyphs — ONE mechanism for every document surface.
 *
 *  Used by the BP activity card, the profile Documents row, and the grid tile
 *  a document takes in a collection. They previously disagreed: one drew a
 *  Lucide <i>, one hardcoded an empty box, and the grid drew a PHOTO icon on a
 *  spreadsheet.
 *
 *  A CSS MASK rather than an <svg> or a Lucide element, because the surfaces
 *  do not share a JS environment. Activity content is baked into
 *  bp_activity.content and re-rendered by whatever theme or feed it lands in,
 *  where Lucide is not enqueued (measured: `window.lucide === undefined` on the
 *  member activity stream) and where kses strips `data-lucide` on save anyway.
 *  A mask needs nothing but this stylesheet.
 *
 *  Painted with `background-color: currentColor`, so the glyph takes the
 *  colour of whatever it sits in and inverts with dark mode for free — a
 *  background-image with a baked fill would stay one colour in both themes.
 *
 *  Artwork is the Lucide set the rest of the product draws (v0.460.0), read
 *  back from the live hydrator so the shapes match exactly.
 *  Size with --mvs-doc-glyph-size at the call site.
 * ========================================================================== */
.mvs-doc-glyph {
    display: block;
    flex: 0 0 auto;
    width: var(--mvs-doc-glyph-size, 20px);
    height: var(--mvs-doc-glyph-size, 20px);
    background-color: currentColor;
    -webkit-mask-image: var(--mvs-doc-glyph);
    mask-image: var(--mvs-doc-glyph);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    -webkit-mask-size: contain;
    mask-size: contain;
}

.mvs-doc-glyph--file-text {
    --mvs-doc-glyph: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M15%202H6a2%202%200%200%200-2%202v16a2%202%200%200%200%202%202h12a2%202%200%200%200%202-2V7Z%22%2F%3E%3Cpath%20d%3D%22M14%202v4a2%202%200%200%200%202%202h4%22%2F%3E%3Cpath%20d%3D%22M10%209H8%22%2F%3E%3Cpath%20d%3D%22M16%2013H8%22%2F%3E%3Cpath%20d%3D%22M16%2017H8%22%2F%3E%3C%2Fsvg%3E");
}

.mvs-doc-glyph--file-type {
    --mvs-doc-glyph: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M15%202H6a2%202%200%200%200-2%202v16a2%202%200%200%200%202%202h12a2%202%200%200%200%202-2V7Z%22%2F%3E%3Cpath%20d%3D%22M14%202v4a2%202%200%200%200%202%202h4%22%2F%3E%3Cpath%20d%3D%22M9%2013v-1h6v1%22%2F%3E%3Cpath%20d%3D%22M12%2012v6%22%2F%3E%3Cpath%20d%3D%22M11%2018h2%22%2F%3E%3C%2Fsvg%3E");
}

.mvs-doc-glyph--file-spreadsheet {
    --mvs-doc-glyph: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M15%202H6a2%202%200%200%200-2%202v16a2%202%200%200%200%202%202h12a2%202%200%200%200%202-2V7Z%22%2F%3E%3Cpath%20d%3D%22M14%202v4a2%202%200%200%200%202%202h4%22%2F%3E%3Cpath%20d%3D%22M8%2013h2%22%2F%3E%3Cpath%20d%3D%22M14%2013h2%22%2F%3E%3Cpath%20d%3D%22M8%2017h2%22%2F%3E%3Cpath%20d%3D%22M14%2017h2%22%2F%3E%3C%2Fsvg%3E");
}

.mvs-doc-glyph--presentation {
    --mvs-doc-glyph: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M2%203h20%22%2F%3E%3Cpath%20d%3D%22M21%203v11a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2V3%22%2F%3E%3Cpath%20d%3D%22m7%2021%205-5%205%205%22%2F%3E%3C%2Fsvg%3E");
}

.mvs-doc-glyph--file {
    --mvs-doc-glyph: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M15%202H6a2%202%200%200%200-2%202v16a2%202%200%200%200%202%202h12a2%202%200%200%200%202-2V7Z%22%2F%3E%3Cpath%20d%3D%22M14%202v4a2%202%200%200%200%202%202h4%22%2F%3E%3C%2Fsvg%3E");
}

.mvs-doc-glyph--file-code {
    --mvs-doc-glyph: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M10%2012.5%208%2015l2%202.5%22%2F%3E%3Cpath%20d%3D%22m14%2012.5%202%202.5-2%202.5%22%2F%3E%3Cpath%20d%3D%22M14%202v4a2%202%200%200%200%202%202h4%22%2F%3E%3Cpath%20d%3D%22M15%202H6a2%202%200%200%200-2%202v16a2%202%200%200%200%202%202h12a2%202%200%200%200%202-2V7z%22%2F%3E%3C%2Fsvg%3E");
}



/* ── Tap targets ───────────────────────────────────────────────────────────
 *
 * The member-side controls the audit measured under the 40px contract:
 * mvs-search-mode-btn 66x26, mvs-tag-cloud-item 44x31, mvs-dashboard-tab
 * 202x37, mvs-dashboard-rail-head__link 81x19 (Basecamp 10252222115).
 *
 * One rule on the component bases rather than per instance. `min-height` only
 * raises, and `align-items: center` keeps the label optically centred instead
 * of sitting at the top of a taller box.
 */
.mvs-search-mode-btn,
.mvs-tag-cloud-item,
.mvs-dashboard-tab,
.mvs-dashboard-rail-head__link {
    display: inline-flex;
    align-items: center;
    min-height: var(--mvs-touch-min);
}

/*
 * Upload form fields.
 *
 * These four carried NO rule of their own — every pixel of their size came
 * from whatever the active theme does to a bare input. On the QA baseline that
 * produced 42 / 60 / 36 / 42 for four fields in one form: the tags input under
 * the 40px floor, and visibly shorter than the title input beside it. On
 * another theme the numbers are different and so is which one is wrong.
 *
 * The floor is ours; the flavour stays the theme's — this sets a minimum and
 * nothing else, so a theme's padding, border and font still come through.
 * Basecamp 10252222115.
 */
.mvs-upload-meta-title,
.mvs-upload-meta-tags,
.mvs-upload-meta-privacy {
    min-height: var(--mvs-touch-min);
}
