@charset "UTF-8";
/**
 * Mini Games — shared chrome.
 *
 * Design rules (learned from v0.1.0 user feedback):
 *
 * 1. Accent color = INTERACTIVE STATE only.
 *    - Title text uses --mg-ink (always readable regardless of accent).
 *    - Buttons are tinted with accent only via background fill or icon color,
 *      NEVER by changing text color to match border (the v0.1 bug where
 *      hover made text and border the same red, making the button invisible).
 * 2. Buttons are FLAT FILL by default — solid background, white text,
 *    radius 8px, brightness shift on hover. No outline-style buttons.
 * 3. No backdrop-filter on overlays — it blurs the game state behind it,
 *    which is bad for Minesweeper game-over (user wants to see what they hit).
 *    Use a tinted scrim instead.
 * 4. Stat label colors stay subtle so they don't compete with values.
 *
 * Two themes via wrapper class:
 *   .mg-theme-clean   → modern flat (default)
 *   .mg-theme-arcade  → system monospace stack + CRT scanlines (opt-in)
 *
 * Light-mode token overrides come from MG_Shortcode::build_custom_css()
 * via wp_add_inline_style.
 */

/* =========================================================================
 * BASE TOKENS — both themes
 * ========================================================================= */

.mg-frame, .mg-scoreboard, .mg-error, .mg-themed {
    --mg-radius:       10px;
    --mg-radius-sm:     8px;
    --mg-radius-xs:     6px;
    --mg-pad:          18px;
    --mg-pad-sm:       12px;

    /* Sane fallbacks if inline-style override doesn't load */
    --mg-accent:       #0a84ff;
    --mg-accent-soft:  rgba(10, 132, 255, 0.10);
    --mg-accent-glow:  rgba(10, 132, 255, 0.25);
    --mg-accent-ink:   #ffffff;   /* text color on solid accent fill */
}

.mg-frame *, .mg-scoreboard *, .mg-themed * { box-sizing: border-box; }

/* Mobile tap-highlight reset.
 *
 * Mobile Chrome / Safari paint a translucent blue-ish overlay over any
 * tappable element (canvas, button, link, [data-action]) when the user
 * touches it. Looks like an <a> visited-link halo and clashes with our
 * own canvas-rendered selection rings + button hover states.
 *
 * The property only takes visual effect on elements the browser
 * considers tappable, so applying it universally inside our frames is
 * safe — non-interactive elements ignore it. Saves us from having to
 * enumerate every interactive selector in every game's stylesheet.
 *
 * Buttons keep their own :active feedback (scale + glow); canvases own
 * their own selection rendering. The browser overlay just gets in the way.
 */
.mg-frame, .mg-frame *,
.mg-scoreboard, .mg-scoreboard *,
.mg-themed, .mg-themed *,
.game-2048-container, .game-2048-container * {
    -webkit-tap-highlight-color: transparent;
}

/* Canvases are interactive game surfaces — never let the browser draw
 * its own focus ring on them. We render selection / focus state via
 * the canvas itself (gold rings, green dots, etc.). */
.mg-frame canvas:focus,
.mg-frame canvas:focus-visible,
.mg-themed canvas:focus,
.mg-themed canvas:focus-visible {
    outline: none;
}

/* =========================================================================
 * THEME: CLEAN (default)
 * ========================================================================= */

.mg-theme-clean {
    --mg-bg:           #1c1c1e;
    --mg-bg-2:         #2c2c2e;
    --mg-bg-3:         #3a3a3c;
    --mg-line:         #38383a;
    --mg-line-strong:  #545458;
    --mg-ink:          #ffffff;
    --mg-ink-muted:    rgba(235, 235, 245, 0.65);
    --mg-ink-subtle:   rgba(235, 235, 245, 0.35);

    /* Game-internal palette — fixed across user theme settings */
    --mg-cyan:    #64d2ff;
    --mg-yellow:  #ffd60a;
    --mg-pink:    #ff375f;
    --mg-green:   #30d158;
    --mg-red:     #ff453a;
    --mg-purple:  #bf5af2;
    --mg-orange:  #ff9f0a;
    --mg-blue:    #0a84ff;

    /* Type — system stack */
    --mg-display: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
    --mg-body:    -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
    --mg-mono:    ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
}

/* =========================================================================
 * THEME: ARCADE (opt-in)
 * Arcade theme uses a system monospace stack — no web fonts loaded.
 * ========================================================================= */

/*
 * Arcade theme web font — Cubic 11 (俐方體11號)
 *
 * Bundled directly inside the plugin at assets/fonts/Cubic_11.woff2
 * (~400KB) — no CDN dependency, served from the user's own server.
 * Font URL is rewritten by PHP to the actual plugin URL via
 * wp_add_inline_style on the mg-fonts-arcade handle (so we don't
 * have to hardcode a server-relative path that breaks under different
 * WP install layouts).
 *
 * Pixel-art font with full Traditional Chinese coverage (4800+ Big5
 * characters), so 排行榜/掃雷/俄羅斯方塊 all render uniformly with
 * Latin numbers in 11×11 dot-matrix style.
 *
 * License: SIL OFL 1.1 — see assets/fonts/OFL.txt.
 * Source: https://github.com/ACh-K/Cubic-11
 *
 * @font-face is declared via PHP inline style attached to the
 * mg-fonts-arcade handle. The arcade theme's --mg-display var
 * references 'Cubic 11' so it picks up automatically.
 */

.mg-theme-arcade {
    --mg-bg:           #0a0e14;
    --mg-bg-2:         #14181f;
    --mg-bg-3:         #1d2128;
    --mg-line:         #2a3038;
    --mg-line-strong:  #3a414b;
    --mg-ink:          #f5f5f0;
    --mg-ink-muted:    #a8aeb5;
    --mg-ink-subtle:   #6b7280;

    --mg-cyan:    #6fdcdf;
    --mg-yellow:  #e8d97a;
    --mg-pink:    #e878b3;
    --mg-green:   #6fdf94;
    --mg-red:     #e57373;
    --mg-purple:  #b79ad9;
    --mg-orange:  #e89e6f;
    --mg-blue:    #7ab3e5;

    /*
     * Cubic 11 first (covers Latin + Traditional Chinese in 11×11 pixel
     * art), then system monospace fallback. The CJK fallbacks
     * (PingFang TC etc.) catch any rare glyphs Cubic 11 might be missing.
     */
    --mg-display: 'Cubic 11', ui-monospace, 'SF Mono', Menlo, Consolas,
                  'Liberation Mono', 'PingFang TC', 'Microsoft JhengHei',
                  'Microsoft YaHei', 'Noto Sans CJK TC', monospace;
    --mg-body:    'Cubic 11', ui-monospace, 'SF Mono', Menlo, Consolas,
                  'Liberation Mono', 'PingFang TC', 'Microsoft JhengHei',
                  'Microsoft YaHei', 'Noto Sans CJK TC', monospace;
    --mg-mono:    'Cubic 11', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

    --mg-radius:    4px;
    --mg-radius-sm: 3px;
    --mg-radius-xs: 2px;
}

/* =========================================================================
 * FRAME — wrapper around any game embed
 * ========================================================================= */

.mg-frame {
    background: var(--mg-bg);
    color: var(--mg-ink);
    font-family: var(--mg-body);
    border: 1px solid var(--mg-line);
    border-radius: var(--mg-radius);
    padding: 28px 24px;   /* taller than wide — gives breathing room above/below */
    margin: 16px 0;
    position: relative;
    /* Defensive reset — some site layouts wrap shortcodes in <li> or
       similar list elements, which then inherit list-item padding from
       the theme. Setting list-style:none + ::marker margin nukes those
       leaks. Box-sizing ensures padding stays inside our declared width. */
    list-style: none;
    box-sizing: border-box;
}

/* ---- Sound toggle button (top-right corner of every game frame) ----
 *
 * This button lives inside the user's site theme, which often slaps
 * its own :hover styles onto every <button> (heavy box-shadows, accent
 * backgrounds, scale transforms, custom borders, etc.). All of those
 * leak in and make the toggle look like a giant action CTA.
 *
 * To prevent that we (a) raise specificity with .mg-frame, (b) hard-
 * reset the obvious offenders with !important, and (c) keep the visual
 * deliberately small + grey so a theme override would have to be very
 * targeted to ruin it. The mute state is communicated by the SVG icon
 * change alone (no red border).
 */
.mg-frame .mg-sound-toggle {
    position: absolute;
    top: 10px;
    right: 10px;
    z-index: 4;
    width: 28px;
    height: 28px;
    padding: 0 !important;
    margin: 0 !important;
    border: 1px solid var(--mg-line) !important;
    border-radius: 999px !important;
    background: var(--mg-bg-2) !important;
    background-image: none !important;
    color: var(--mg-ink-muted) !important;
    cursor: pointer;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    opacity: 0.45;
    box-shadow: none !important;
    text-shadow: none !important;
    text-transform: none !important;
    letter-spacing: normal !important;
    line-height: 1 !important;
    font-size: 0 !important;     /* defeats theme button text-sizing */
    /* Limit transitions to JUST the things we care about — overrides
       any blanket `transition: all` from the theme that would animate
       theme-injected effects (translateY, filter, etc.) */
    transition: opacity 0.15s ease, color 0.15s ease !important;
    transform: none !important;
    filter: none !important;
}
.mg-frame .mg-sound-toggle:hover,
.mg-frame .mg-sound-toggle:focus,
.mg-frame .mg-sound-toggle:active {
    opacity: 0.95;
    color: var(--mg-ink) !important;
    background: var(--mg-bg-2) !important;
    background-image: none !important;
    border-color: var(--mg-line) !important;
    box-shadow: none !important;
    text-shadow: none !important;
    transform: none !important;
    filter: none !important;
    outline: none;
}
.mg-frame .mg-sound-toggle__icon {
    width: 14px;
    height: 14px;
    display: block;
}
/* Muted state: just dim further + show X — no aggressive red border. */
.mg-frame .mg-sound-toggle.is-muted {
    opacity: 0.6;
    color: var(--mg-ink-muted) !important;
}
.mg-frame .mg-sound-toggle.is-muted:hover {
    opacity: 0.95;
    color: var(--mg-ink) !important;
}
/* Icon swap: ON = wave, OFF = X. JS toggles the `is-muted` class. */
.mg-sound-toggle__off  { display: none; }
.mg-sound-toggle.is-muted .mg-sound-toggle__on  { display: none; }
.mg-sound-toggle.is-muted .mg-sound-toggle__off { display: inline; }

/* For ported games (2048) the inner card already provides chrome —
   pin the toggle to the outer frame edge regardless. */
.mg-frame--ported .mg-sound-toggle {
    top: 6px;
    right: 6px;
}
.mg-frame::marker { content: ""; }

/* Ported games (e.g. 2048) ship their own self-contained card styling.
   The .mg-frame--ported variant strips visible chrome from the INNER
   container so we don't get a card-in-a-card. The outer .mg-frame
   provides the unified bg + border + glow.
   
   .game-2048-container is the only ported inner today, but the rule
   uses generic positioning so any future ported game can opt in by
   registering with no_frame=true. */
.mg-frame--ported > .game-2048-container {
    background: transparent !important;
    border-radius: 0 !important;
    /* Original 2048 has padding: 60px 6% !important — we need our own
       !important to override. Tighten so the 2048 game sits just inside
       the outer .mg-frame's 28px/24px padding. */
    padding: 0 !important;
    margin: 0 !important;
}

.mg-theme-clean.mg-frame {
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.06),
        0 8px 24px rgba(0, 0, 0, 0.10);
}

.mg-theme-arcade.mg-frame {
    box-shadow:
        inset 0 0 0 1px rgba(255,255,255,0.02),
        0 0 0 1px rgba(0,0,0,0.4);
    background-image: linear-gradient(
        to bottom,
        transparent 0px, transparent 1px,
        rgba(255,255,255,0.012) 1px, rgba(255,255,255,0.012) 2px
    );
    background-size: 100% 3px;
}
.mg-theme-arcade.mg-frame::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    box-shadow: inset 0 0 24px var(--mg-accent-glow);
}

/* =========================================================================
 * GAME HEADER
 * ========================================================================= */

.mg-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    margin: 0 0 18px;
    flex-wrap: wrap;
}

/* Title — uses ink color in clean theme. Accent only used as colored
 * underline in arcade mode (where the CRT vibe needs it). This was the
 * v0.1 bug: title used accent, so red accent → red title → unreadable. */
.mg-title {
    margin: 0;
    line-height: 1;
    color: var(--mg-ink);
    font-family: var(--mg-display);
    /* Long Chinese titles like 俄羅斯方塊 (Tetris) and English ones
       like "Minesweeper" can squeeze the header on narrow viewports.
       Allow wrapping rather than overflowing the frame. */
    white-space: normal;
    overflow-wrap: anywhere;
}
.mg-theme-clean .mg-title {
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -0.01em;
}
.mg-theme-arcade .mg-title {
    font-size: 32px;
    font-weight: 700;
    color: var(--mg-accent);
    letter-spacing: 0.04em;
    text-shadow: 0 0 8px var(--mg-accent-glow), 0 0 1px var(--mg-accent);
    /* Uppercase for the retro arcade look. CJK text is unaffected
       (Chinese has no uppercase form), so this only kicks in on
       Latin scripts — keeping the visual identity consistent
       regardless of locale. */
    text-transform: uppercase;
}

/* Stat pills */
.mg-stat-row {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}
.mg-stat {
    background: var(--mg-bg-2);
    border-radius: var(--mg-radius-sm);
    padding: 10px 16px 8px;
    min-width: 90px;
    text-align: center;
}
.mg-theme-arcade .mg-stat {
    border: 1px solid var(--mg-line);
}
.mg-stat__label {
    display: block;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.10em;
    color: var(--mg-ink-subtle);
    margin-bottom: 4px;
    font-weight: 500;
}
.mg-theme-arcade .mg-stat__label { letter-spacing: 0.18em; font-size: 12px; }

.mg-stat__value {
    font-family: var(--mg-mono);
    font-size: 26px;
    font-weight: 700;
    line-height: 1.1;
    color: var(--mg-ink);
    font-variant-numeric: tabular-nums;
}
.mg-theme-arcade .mg-stat__value {
    font-family: var(--mg-display);
    font-size: 24px;
    font-weight: 700;
}

/* Tinted variants — these use FIXED game-palette colors, not accent.
 * That way changing the user's accent color doesn't make every stat pill
 * the same color. */
.mg-stat--accent .mg-stat__value { color: var(--mg-accent); }
.mg-stat--cyan   .mg-stat__value { color: var(--mg-cyan); }
.mg-stat--yellow .mg-stat__value { color: var(--mg-yellow); }
.mg-stat--green  .mg-stat__value { color: var(--mg-green); }
.mg-stat--pink   .mg-stat__value { color: var(--mg-pink); }
.mg-stat--purple .mg-stat__value { color: var(--mg-purple); }
.mg-stat--orange .mg-stat__value { color: var(--mg-orange); }

/* =========================================================================
 * BUTTONS — modern flat fill, NEVER outline-only.
 *
 * Cyberpunk neon — buttons feel like glowing arcade signs, not Bootstrap.
 * Three styles:
 *   .mg-btn              — secondary: outlined ink color, soft glow
 *   .mg-btn--primary     — outlined accent, full neon glow, slight pulse
 *   .mg-btn--ghost       — same as primary but quieter
 *
 * Tactile feedback on click: the glow snaps brighter and the button
 * presses down 1px (like a physical arcade button). On hover, a slow
 * "breathing" glow pulse pulls the eye toward interactive elements.
 *
 * Border thickness deliberately 1.5px — anything thicker reads as
 * cartoonish, thinner doesn't carry the glow well.
 * ========================================================================= */

.mg-frame button.mg-btn,
.mg-scoreboard button.mg-btn,

.mg-themed button.mg-btn,
.mg-btn {
    /* Compound selector with `button.mg-btn` to outweigh site themes
       that set `button { color: #333 }` or similar global rules. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    background: transparent;
    /* 1.5px border — solid + filter glow combination = neon tube look */
    border: 1.5px solid var(--mg-line-strong);
    color: var(--mg-ink);
    font-family: var(--mg-body);
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    padding: 10px 18px;
    border-radius: var(--mg-radius-sm);
    cursor: pointer;
    text-decoration: none;
    line-height: 1.2;
    position: relative;
    transition:
        border-color .18s ease,
        box-shadow   .18s ease,
        color        .18s ease,
        background   .18s ease,
        transform    .04s ease;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    /* Subtle baseline glow so even idle buttons read as "interactive". */
    box-shadow:
        0 0 0 0 rgba(255, 255, 255, 0),
        inset 0 0 0 0 rgba(255, 255, 255, 0);
}

.mg-btn:hover, .mg-btn:focus-visible {
    border-color: var(--mg-ink);
    color: var(--mg-ink);
    background: rgba(255, 255, 255, 0.04);
    outline: none;
    /* Breathing pulse — keyframe defined further down */
    animation: mg-btn-pulse 1.6s ease-in-out infinite;
}

.mg-btn:active {
    transform: translateY(1px);
    /* Sharp flash on click — kills the pulse animation for an instant
       so the button "snaps" instead of fading. */
    animation: none;
    box-shadow:
        0 0 12px 2px rgba(255, 255, 255, 0.20),
        inset 0 0 8px rgba(255, 255, 255, 0.14);
}

/* The HTML `hidden` attribute must defeat display: inline-flex above.
   Without this, calling el.hidden = true from JS (e.g. to hide the
   resign button for spectators, or hide the share-link button for
   public matches) is silently ignored — `display: inline-flex` wins
   over the user-agent default `[hidden] { display: none }` rule. */
.mg-btn[hidden] {
    display: none !important;
}

.mg-btn:disabled, .mg-btn[disabled] {
    opacity: 0.4;
    cursor: not-allowed;
    transform: none;
    animation: none;
    box-shadow: none;
}

/* Compact button modifier (v0.23.15: promoted from bigtwo.css — used in
   bigtwo + doudizhu pvp-lobby seat slots; should be a shared utility so
   any game enqueuing only core.css gets consistent button sizing).
   `!important` overrides the base .mg-btn padding/font-size declared
   above (same specificity, but spec order in some shared CSS pipelines
   isn't deterministic — `!important` makes the override robust). */
.mg-btn--small {
    font-size: 12px !important;
    padding: 4px 10px !important;
}

/* Primary — the accent-color star of the show. Outlined, not filled.
   The glow uses --mg-accent so it shifts color when the user picks a
   different accent in settings. */
.mg-frame button.mg-btn--primary,
.mg-scoreboard button.mg-btn--primary,

.mg-themed button.mg-btn--primary,
.mg-btn--primary {
    background: transparent;
    color: var(--mg-accent);
    border-color: var(--mg-accent);
    /* Idle glow — subtle but always-on so primary buttons stand out */
    box-shadow:
        0 0 8px var(--mg-accent-glow),
        inset 0 0 6px var(--mg-accent-soft);
    text-shadow: 0 0 4px var(--mg-accent-glow);
}

.mg-btn--primary:hover, .mg-btn--primary:focus-visible {
    background: var(--mg-accent-soft);
    color: var(--mg-accent);
    border-color: var(--mg-accent);
    /* Stronger glow + breathing pulse */
    animation: mg-btn-pulse-accent 1.4s ease-in-out infinite;
}

.mg-btn--primary:active {
    /* Snap flash — full intensity for one frame */
    animation: none;
    transform: translateY(1px);
    background: var(--mg-accent);
    color: var(--mg-accent-ink);
    box-shadow:
        0 0 24px 4px var(--mg-accent),
        inset 0 0 12px rgba(255, 255, 255, 0.30);
    text-shadow: none;
}

.mg-btn--ghost {
    background: transparent;
    color: var(--mg-accent);
    border-color: var(--mg-accent);
    /* Quieter than primary — no idle glow, no text-shadow */
}
.mg-btn--ghost:hover, .mg-btn--ghost:focus-visible {
    background: var(--mg-accent-soft);
    color: var(--mg-accent);
    border-color: var(--mg-accent);
    box-shadow: 0 0 8px var(--mg-accent-glow);
}

/* -----------------------------------------------------------------
 * Pulsing glow keyframes.
 *
 * `mg-btn-pulse` for secondary buttons (white/ink glow, subtle).
 * `mg-btn-pulse-accent` for primary buttons (accent-color, intense).
 *
 * Each cycle is one breath in (peak glow) and one breath out (rest).
 * Box-shadow is animated rather than opacity because it lets the
 * surrounding area light up rather than just the border.
 * ----------------------------------------------------------------- */
@keyframes mg-btn-pulse {
    0%, 100% {
        box-shadow:
            0 0 0 0 rgba(255, 255, 255, 0.10),
            inset 0 0 0 0 rgba(255, 255, 255, 0);
    }
    50% {
        box-shadow:
            0 0 14px 2px rgba(255, 255, 255, 0.18),
            inset 0 0 8px rgba(255, 255, 255, 0.06);
    }
}

@keyframes mg-btn-pulse-accent {
    0%, 100% {
        box-shadow:
            0 0 8px var(--mg-accent-glow),
            inset 0 0 6px var(--mg-accent-soft);
    }
    50% {
        box-shadow:
            0 0 22px 3px var(--mg-accent),
            inset 0 0 12px var(--mg-accent-soft);
    }
}

/* Respect reduced-motion preference — kill the breathing animation
   for users who've requested less motion (vestibular sensitivity etc.) */
@media (prefers-reduced-motion: reduce) {
    .mg-btn,
    .mg-btn--primary,
    .mg-btn--ghost {
        animation: none !important;
    }
}

/* Arcade theme — buttons already inherit neon from base. We just sharpen
   corners (radius from var) and use monospace font for the cabinet feel. */
.mg-theme-arcade .mg-btn {
    font-family: var(--mg-body);
    /* radius already comes from --mg-radius-sm which arcade sets to 3px */
}

/* =========================================================================
 * MODAL OVERLAY (game-over / pause)
 *
 * NO backdrop-filter on the scrim — for Minesweeper loss state, the user
 * needs to see exactly which cells were mines. We use a soft tinted
 * gradient that darkens edges but keeps the center readable, and the
 * dialog box itself sits above the scrim with its own bg.
 * ========================================================================= */

.mg-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Radial gradient — darker at edges, transparent at center.
       Lets the player see the board state behind the dialog. */
    background: radial-gradient(
        ellipse at center,
        rgba(0,0,0,0.10) 0%,
        rgba(0,0,0,0.55) 80%
    );
    z-index: 5;
    border-radius: inherit;
    text-align: center;
    padding: 20px;
}
.mg-overlay[hidden] { display: none; }

/* Dialog box inside the overlay — opaque, so text is always crisp */
.mg-overlay__dialog {
    background: var(--mg-bg-2);
    border: 1px solid var(--mg-line);
    border-radius: var(--mg-radius);
    padding: 24px 28px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
    max-width: 360px;
    width: min(360px, 90%);
}
.mg-theme-arcade .mg-overlay__dialog {
    background: var(--mg-bg);
}

.mg-overlay__title {
    margin: 0 0 6px;
    line-height: 1.1;
    font-family: var(--mg-display);
    /* Wrap long titles like "Minesweeper" rather than overflowing
       the dialog box. Important on the 42px arcade-theme size. */
    white-space: normal;
    overflow-wrap: anywhere;
    text-align: center;
}
.mg-theme-clean  .mg-overlay__title { font-size: 24px; font-weight: 700; letter-spacing: -0.02em; }
.mg-theme-arcade .mg-overlay__title { font-size: 36px; }

/* Color is set by modifier class — defaults are pink for loss, green for win */
.mg-overlay__title             { color: var(--mg-pink); }
.mg-overlay--win .mg-overlay__title { color: var(--mg-green); }
.mg-overlay--info .mg-overlay__title { color: var(--mg-ink); }

.mg-theme-arcade .mg-overlay__title             { text-shadow: 0 0 12px rgba(255, 55, 95, 0.5); }
.mg-theme-arcade .mg-overlay--win .mg-overlay__title { text-shadow: 0 0 12px rgba(48, 209, 88, 0.5); }

.mg-overlay__msg {
    font-size: 14px;
    color: var(--mg-ink-muted);
    margin: 0 0 18px;
    line-height: 1.4;
}
.mg-overlay__actions {
    display: flex;
    gap: 10px;
    justify-content: center;
    flex-wrap: wrap;
}

/* =========================================================================
 * SAVE-STATUS FLASH
 * ========================================================================= */

.mg-flash {
    margin-top: 12px;
    padding: 10px 14px;
    border-radius: var(--mg-radius-sm);
    font-size: 13px;
    font-weight: 500;
    background: var(--mg-bg-2);
    color: var(--mg-ink-muted);
}
.mg-flash[hidden] { display: none; }
.mg-flash--success { color: var(--mg-green);  background: rgba(48,209,88,0.10); }
.mg-flash--error   { color: var(--mg-red);    background: rgba(255,69,58,0.10); }
.mg-flash--info    { color: var(--mg-accent); background: var(--mg-accent-soft); }

/* =========================================================================
 * SCOREBOARD WIDGET
 * ========================================================================= */

.mg-scoreboard {
    background: var(--mg-bg);
    color: var(--mg-ink);
    font-family: var(--mg-body);
    border: 1px solid var(--mg-line);
    border-radius: var(--mg-radius);
    padding: 18px 20px;
    margin: 16px 0;
}
.mg-theme-clean .mg-scoreboard { box-shadow: 0 1px 2px rgba(0,0,0,0.08); }

.mg-scoreboard__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--mg-line);
    padding-bottom: 12px;
}
.mg-scoreboard__title {
    margin: 0;
    line-height: 1;
    color: var(--mg-ink);              /* not accent — keeps it readable */
    font-family: var(--mg-display);
}
.mg-theme-clean  .mg-scoreboard__title { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; }
.mg-theme-arcade .mg-scoreboard__title { font-size: 22px; font-weight: 700; color: var(--mg-accent); letter-spacing: 0.04em; }

.mg-scoreboard__meta {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.10em;
    color: var(--mg-ink-subtle);
    font-weight: 500;
}
.mg-scoreboard__empty {
    color: var(--mg-ink-muted);
    font-size: 14px;
    text-align: center;
    padding: 28px 0;
    margin: 0;
}
.mg-scoreboard__list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.mg-scoreboard__row {
    display: grid;
    grid-template-columns: 32px 44px 1fr auto;
    align-items: center;
    gap: 12px;
    padding: 10px 4px;
    border-bottom: 1px solid var(--mg-line);
}
.mg-scoreboard__row:last-child { border-bottom: 0; }

/* Top-3 ranks get colored numbers — but NEVER the user's accent (since
 * leaderboard text shouldn't shift when user changes accent) */
.mg-scoreboard__row[data-rank="1"] .mg-scoreboard__rank { color: var(--mg-yellow); }
.mg-scoreboard__row[data-rank="2"] .mg-scoreboard__rank { color: var(--mg-ink); }
.mg-scoreboard__row[data-rank="3"] .mg-scoreboard__rank { color: var(--mg-orange); }

.mg-scoreboard__rank {
    font-family: var(--mg-display);
    line-height: 1;
    text-align: center;
    color: var(--mg-ink-subtle);
}
.mg-theme-clean  .mg-scoreboard__rank { font-size: 17px; font-weight: 700; }
.mg-theme-arcade .mg-scoreboard__rank { font-size: 24px; }

.mg-scoreboard__avatar img {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    display: block;
    object-fit: cover;
    background: var(--mg-bg-3);
}
.mg-theme-arcade .mg-scoreboard__avatar img {
    border-radius: var(--mg-radius-sm);
    border: 1px solid var(--mg-line);
}
.mg-scoreboard__name {
    font-size: 14px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
/* W/L/D record under display name on multiplayer ELO leaderboards.
   Smaller, muted, monospace so digits align cleanly. */
.mg-scoreboard__sub {
    font-size: 11px;
    font-weight: 400;
    color: var(--mg-ink-muted);
    font-family: var(--mg-mono);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mg-scoreboard__score {
    font-family: var(--mg-mono);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    text-align: right;
    color: var(--mg-ink);
}
.mg-theme-clean  .mg-scoreboard__score { font-size: 16px; font-weight: 700; }
.mg-theme-arcade .mg-scoreboard__score { font-family: var(--mg-display); font-size: 18px; font-weight: 700; color: var(--mg-yellow); }


/* =========================================================================
 * ERROR FALLBACK
 * ========================================================================= */

.mg-error {
    background: rgba(255, 69, 58, 0.08);
    color: var(--mg-pink);
    border: 1px solid rgba(255, 69, 58, 0.30);
    border-radius: var(--mg-radius);
    padding: 14px 18px;
    font-family: var(--mg-body);
    font-size: 14px;
    margin: 16px 0;
}

/* =========================================================================
 * TOASTS
 *
 * Floating notifications used by window.MG.toast(). Fixed at top-center
 * of viewport, stack vertically, auto-dismiss after 4 seconds. The
 * container is created lazily by core.js when the first toast fires.
 * Painted in the framework's accent palette so it visually belongs
 * with the rest of the games even when shown over arbitrary site
 * content (e.g. error from a REST call during multiplayer game).
 * ========================================================================= */
.mg-toast-container {
    position: fixed;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 99999;
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
    max-width: calc(100vw - 32px);
    width: max-content;
}
.mg-toast {
    pointer-events: auto;
    background: #1c1c1e;
    color: #f5f5f0;
    border: 1px solid rgba(255,255,255,0.12);
    border-radius: 6px;
    padding: 10px 16px;
    font-size: 14px;
    font-family: inherit;
    /* v0.23.96: text-align:center so trailing spaces / stack-widening
       doesn't make short messages look left-anchored. Toast container
       uses width:max-content; when a long toast in the stack widens
       the container, shorter siblings get stretched and their text
       (default left-aligned) hung to the left. Centring keeps every
       message visually anchored to its toast box. User: "toast 有些
       不置中了 還是因為後面有些空格". */
    text-align: center;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);
    opacity: 0;
    transform: translateY(-8px);
    transition: opacity .2s ease, transform .2s ease;
    max-width: 100%;
    word-break: break-word;
}
.mg-toast.is-visible {
    opacity: 1;
    transform: translateY(0);
}
.mg-toast--error {
    border-color: rgba(255, 82, 82, 0.5);
    background: #2a1414;
    color: #ff8a8a;
}
.mg-toast--success {
    border-color: rgba(111, 223, 148, 0.5);
    background: #14241a;
    color: #6fdf94;
}
.mg-toast--info {
    /* default colors — same as base .mg-toast */
}

/* =========================================================================
 * COMBINED SCOREBOARD (e.g. Minesweeper easy/medium/hard tabbed)
 *
 * Used by [minigame_scoreboard slug="minesweeper"]. Tabs along the top,
 * three panes underneath with one visible at a time. Inherits all the
 * standard leaderboard styling for the inner .mg-scoreboard element.
 * ========================================================================= */

.mgs-combined {
    background: var(--mg-bg);
    color: var(--mg-ink);
    font-family: var(--mg-body);
    border: 1px solid var(--mg-line);
    border-radius: var(--mg-radius);
    padding: 20px;
    margin: 16px 0;
    position: relative;
}
.mg-theme-arcade.mgs-combined::after {
    /* Same accent inner-glow as .mg-frame for visual consistency */
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    box-shadow: inset 0 0 24px var(--mg-accent-glow);
}

.mgs-combined__tabs {
    display: inline-flex;
    gap: 4px;
    margin-bottom: 16px;
    padding: 4px;
    background: var(--mg-bg-2);
    border-radius: var(--mg-radius-sm);
    position: relative;
    z-index: 2;
}

button.mgs-combined__tab,
.mgs-combined__tab {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--mg-ink-muted);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    padding: 7px 14px;
    border-radius: var(--mg-radius-xs);
    cursor: pointer;
    transition: color .12s, background .12s;
}
button.mgs-combined__tab:hover,
.mgs-combined__tab:hover {
    color: var(--mg-ink);
}
button.mgs-combined__tab.is-active,
.mgs-combined__tab.is-active {
    background: var(--mg-bg-3);
    color: var(--mg-ink);
    box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}

.mgs-combined__pane {
    position: relative;
    z-index: 2;
}

/* When a leaderboard is rendered inside the combined wrapper, drop its
   own outer chrome — the wrapper provides it. */
.mgs-combined .mg-scoreboard {
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
}


/* =========================================================================
 * 2048 THEME INTEGRATION
 *
 * The ported 2048 plugin ships its own self-contained styling
 * (.game-2048-container with its own #232323 bg, padding, border radius).
 * To avoid a double-frame, we DO NOT override its visual chrome.
 *
 * Instead, the outer .mg-themed wrapper (added by sc_game when
 * no_frame=true) carries the framework chrome: dark bg, border, accent
 * glow. The 2048 container sits inside that wrapper as its own card.
 *
 * The only override here is font-family in arcade theme (so Cubic 11
 * applies to all of 2048's text), since 2048's CSS doesn't declare a
 * font and elements were inheriting from the page <body>.
 * ========================================================================= */

/*
 * Arcade theme: apply the pixel font (Cubic 11) to the entire 2048
 * widget. The original 2048 plugin doesn't declare font-family
 * anywhere — all elements inherit from <body>, which means in arcade
 * theme they fall back to whatever the site default is, NOT our pixel
 * font. Setting it on the container cascades to tiles, score chips,
 * intro paragraph, buttons, and overlay messages.
 */
.mg-theme-arcade .game-2048-container,
.mg-theme-arcade .game-2048-container .tile,
.mg-theme-arcade .game-2048-container .score-container,
.mg-theme-arcade .game-2048-container .best-container,
.mg-theme-arcade .game-2048-container .restart-button,
.mg-theme-arcade .game-2048-container .undo-button,
.mg-theme-arcade .game-2048-container .keep-playing-button,
.mg-theme-arcade .game-2048-container .retry-button,
.mg-theme-arcade .game-2048-container .game-message,
.mg-theme-arcade .game-2048-container .game-intro,
.mg-theme-arcade .game-2048-container .undo-count {
    font-family: var(--mg-display);
}

/* Title — let it pick up the accent color + neon glow in arcade theme */
.mg-theme-arcade .game-2048-container h1.title {
    color: var(--mg-accent);
    text-shadow: 0 0 8px var(--mg-accent-glow);
    font-family: var(--mg-display);
}
@media (max-width: 540px) {
    .mg-frame {
        /* Horizontal margin keeps the frame off the screen edges so the
           red inner-glow border has room to breathe. Vertical padding
           bumped slightly so titles and stats aren't pressed against
           the border. */
        padding: 22px 16px;
        margin: 16px 12px;
    }
    .mgs-combined {
        padding: 16px 14px;
        margin: 16px 12px;
    }
    .mg-theme-clean  .mg-title { font-size: 22px; }
    .mg-theme-arcade .mg-title { font-size: 26px; }
    .mg-stat { min-width: 72px; padding: 8px 10px 6px; }
    .mg-stat__label { font-size: 10px; }
    .mg-stat__value { font-size: 22px; }
    .mg-theme-arcade .mg-stat__value { font-size: 22px; }
    .mg-scoreboard__row {
        grid-template-columns: 28px 36px 1fr auto;
        gap: 10px;
    }
    .mg-scoreboard__avatar img { width: 36px; height: 36px; }
}

/* ---- Theme picker — [minigame_theme_picker] shortcode ---- */
.mg-theme-picker {
    /* Inherits .mg-frame chrome (background, border, padding, radius). */
}
.mg-theme-picker__title {
    font-family: var(--mg-display);
    font-size: 16px;
    font-weight: 600;
    margin: 0 0 6px;
    color: var(--mg-ink);
}
.mg-theme-picker__hint {
    font-size: 12px;
    color: var(--mg-ink-muted);
    margin: 0 0 14px;
    line-height: 1.5;
}
.mg-theme-picker__row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
/* Native <input type="color"> looks dreadful out of the box across
   browsers. We wrap it in a label, hide the input, and show our own
   round swatch that mirrors the input's value via CSS. */
.mg-theme-picker__swatch {
    position: relative;
    display: inline-block;
    width: 36px;
    height: 36px;
    border-radius: 999px;
    border: 2px solid var(--mg-line-strong);
    cursor: pointer;
    overflow: hidden;
    background: var(--mg-accent);
    flex: 0 0 auto;
    transition: border-color 0.15s, transform 0.15s;
}
.mg-theme-picker__swatch:hover {
    border-color: var(--mg-ink);
    transform: scale(1.08);
}
.mg-theme-picker__input {
    /* Stretched to cover the swatch so click → color picker.
       Opacity 0 keeps native chrome invisible; we still want pointer
       events so the click reaches the input element. */
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
    opacity: 0;
    background: transparent;
}
.mg-theme-picker__hex {
    font-family: var(--mg-mono, monospace);
    font-size: 13px;
    color: var(--mg-ink);
    padding: 4px 8px;
    border-radius: 4px;
    background: var(--mg-bg-2);
    border: 1px solid var(--mg-line);
    user-select: all;
}

/* ============================================================
   Card-back picker (v0.22.71)
   ============================================================
   Sits below the accent row inside the same .mg-theme-picker
   frame. Six selectable swatches in a responsive grid. Each
   swatch shows a miniature card preview using the same data
   attribute that controls the live cards, scoped via
   .mg-theme-picker__cardback-preview[data-mg-cardback-preview="..."].
   ============================================================ */
.mg-theme-picker__section {
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--mg-line);
}
.mg-theme-picker__subtitle {
    font-size: 14px;
    font-weight: 600;
    margin: 0 0 4px;
    color: var(--mg-ink);
    letter-spacing: 0.01em;
}
.mg-theme-picker__cardbacks {
    display: grid;
    grid-template-columns: repeat(6, minmax(58px, 1fr));
    gap: 10px;
    margin-top: 10px;
}
@media (max-width: 640px) {
    .mg-theme-picker__cardbacks {
        grid-template-columns: repeat(3, 1fr);
    }
}

.mg-theme-picker__cardback {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 8px 6px;
    background: var(--mg-bg-2);
    border: 1px solid var(--mg-line);
    border-radius: var(--mg-radius-sm);
    cursor: pointer;
    transition: border-color 140ms ease, background 140ms ease,
                transform 80ms ease;
    font: inherit;
    color: inherit;
}
.mg-theme-picker__cardback:hover {
    border-color: var(--mg-line-strong);
    background: color-mix(in srgb, var(--mg-bg-2) 92%, var(--mg-accent));
}
.mg-theme-picker__cardback:active {
    transform: scale(0.96);
}
.mg-theme-picker__cardback:focus-visible {
    outline: 2px solid var(--mg-accent);
    outline-offset: 2px;
}
.mg-theme-picker__cardback[aria-checked="true"] {
    border-color: var(--mg-accent);
    background: color-mix(in srgb, var(--mg-bg-2) 84%, var(--mg-accent));
    box-shadow: 0 0 0 1px var(--mg-accent),
                0 2px 8px color-mix(in srgb, var(--mg-accent) 24%, transparent);
}

/* Mini card preview. Width/height tuned for grid slot — six fit
   comfortably across at 320px+ frame widths. Same proportions as
   real cards (~0.7 ratio). */
.mg-theme-picker__cardback-preview {
    display: block;
    width: 44px;
    height: 62px;
    background-color: var(--mg-accent);
    border: 1px solid var(--mg-line-strong);
    border-radius: 5px;
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.22),
        inset 0 1px 0 rgba(255, 255, 255, 0.12);
    position: relative;
    flex-shrink: 0;
}
/* v0.22.74: hardcode label colors with !important specificity-beat
   armor. Site themes (Elementor / parent themes) commonly ship a
   `button:hover { color: ... }` rule that bleeds into our label.
   The label is inside a <button>, so its `color: var(...)` rule loses
   to a site `button:hover { color: black !important }` declaration.
   Same pattern as the chat close-button armor.
   - Default state:  --mg-ink-muted (defined on .mg-theme-clean via PHP)
   - Hover state:    --mg-ink (slightly brighter for affordance)
   - Selected state: white (works on the dark accent-tinted bg). */
.mg-theme-picker__cardback-label {
    font-size: 11px;
    line-height: 1.2;
    color: var(--mg-ink-muted, rgba(235, 235, 245, 0.65)) !important;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.mg-theme-picker__cardback:hover .mg-theme-picker__cardback-label {
    color: var(--mg-ink, #ffffff) !important;
}
.mg-theme-picker__cardback[aria-checked="true"] .mg-theme-picker__cardback-label {
    color: #ffffff !important;
    font-weight: 600;
}

/* Per-variant preview rendering — mirrors the six rules in
   bigtwo.css but scoped to the preview swatch. */
.mg-theme-picker__cardback-preview[data-mg-cardback-preview="hash-spade"] {
    background-image: repeating-linear-gradient(
        45deg,
        transparent 0,
        transparent 6px,
        color-mix(in srgb, var(--mg-accent-ink) 16%, transparent) 6px,
        color-mix(in srgb, var(--mg-accent-ink) 16%, transparent) 7px);
}
.mg-theme-picker__cardback-preview[data-mg-cardback-preview="hash-spade"]::before {
    content: "♠";
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--mg-accent-ink);
    font-size: 18px;
    font-weight: 900;
    line-height: 1;
    opacity: 0.26;
}

.mg-theme-picker__cardback-preview[data-mg-cardback-preview="dot"] {
    background-image: radial-gradient(
        circle,
        color-mix(in srgb, var(--mg-accent-ink) 60%, transparent) 0.6px,
        transparent 1.3px);
    background-size: 5px 5px;
}

.mg-theme-picker__cardback-preview[data-mg-cardback-preview="band"] {
    background-image: linear-gradient(
        90deg,
        transparent 0%,
        transparent 35%,
        color-mix(in srgb, var(--mg-accent-ink) 18%, transparent) 35%,
        color-mix(in srgb, var(--mg-accent-ink) 18%, transparent) 65%,
        transparent 65%,
        transparent 100%);
}

.mg-theme-picker__cardback-preview[data-mg-cardback-preview="split"] {
    background-image: linear-gradient(
        135deg,
        transparent 0%,
        transparent 50%,
        color-mix(in srgb, var(--mg-accent-ink) 28%, transparent) 50%,
        color-mix(in srgb, var(--mg-accent-ink) 28%, transparent) 100%);
}

.mg-theme-picker__cardback-preview[data-mg-cardback-preview="checker"] {
    background-image:
        linear-gradient(45deg,
            color-mix(in srgb, var(--mg-accent-ink) 12%, transparent) 25%,
            transparent 25%,
            transparent 75%,
            color-mix(in srgb, var(--mg-accent-ink) 12%, transparent) 75%),
        linear-gradient(45deg,
            color-mix(in srgb, var(--mg-accent-ink) 12%, transparent) 25%,
            transparent 25%,
            transparent 75%,
            color-mix(in srgb, var(--mg-accent-ink) 12%, transparent) 75%);
    background-size: 5px 5px;
    background-position: 0 0, 2.5px 2.5px;
}

.mg-theme-picker__cardback-preview[data-mg-cardback-preview="solid"]::before {
    content: "♠";
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--mg-accent-ink);
    font-size: 22px;
    font-weight: 900;
    line-height: 1;
    opacity: 0.28;
}

/* Anchor badge: the picker frame must be positioned. */
.mg-theme-picker { position: relative; }

/* Floating pill badge — top-right corner of the picker frame.
   Auto-fades after 1.8s via inline `hidden` toggle in JS. */
.mg-theme-picker__badge {
    position: absolute;
    top: 14px;
    right: 14px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.02em;
    transition: opacity 200ms ease;
    pointer-events: none;
}
.mg-theme-picker__badge[hidden] { opacity: 0; }
.mg-theme-picker__badge:not([hidden]) { opacity: 1; }

.mg-theme-picker__badge-icon {
    display: inline-block;
    line-height: 1;
}

/* State colours — uses plugin color tokens (matches mg-flash convention)
   so the badge auto-adapts to clean vs arcade theme:
   - loading/saving: muted ink (neutral, no semantic weight)
   - saved:          green (success)
   - reset:          muted ink (neutral, distinct from saved success state)
   - error:          red (failure)
   Background alphas use the same hex as the matching --mg-X token's
   default-theme value, since rgba() can't read CSS vars directly. The
   tints are subtle enough that the small token shift between clean
   (#30d158) and arcade (#6fdf94) themes is imperceptible at 0.12 alpha. */
.mg-theme-picker__badge[data-kind="loading"],
.mg-theme-picker__badge[data-kind="saving"] {
    background: rgba(235, 235, 245, 0.10);
    color: var(--mg-ink-muted);
}
.mg-theme-picker__badge[data-kind="loading"] .mg-theme-picker__badge-icon,
.mg-theme-picker__badge[data-kind="saving"]  .mg-theme-picker__badge-icon {
    animation: mg-theme-spin 1s linear infinite;
}
.mg-theme-picker__badge[data-kind="saved"] {
    /* v0.22.71: green lifted from --mg-green (#30d158, too dark on a
       0.12-alpha tinted pill) to a brighter, more legible value. The
       token --mg-green is left untouched because win overlays and
       stats use it on solid surfaces where the deeper saturation
       reads correctly. */
    background: rgba(110, 231, 162, 0.16);
    color: #5eed8a;
}
.mg-theme-picker__badge[data-kind="reset"] {
    background: rgba(235, 235, 245, 0.10);
    color: var(--mg-ink-muted);
}
.mg-theme-picker__badge[data-kind="error"] {
    background: rgba(255, 69, 58, 0.12);
    color: var(--mg-red);
}

/* Honour user's reduced-motion preference: skip the spin and the
   transition, just toggle visibility. */
@media (prefers-reduced-motion: reduce) {
    .mg-theme-picker__badge { transition: none; }
    .mg-theme-picker__badge[data-kind="loading"] .mg-theme-picker__badge-icon,
    .mg-theme-picker__badge[data-kind="saving"]  .mg-theme-picker__badge-icon {
        animation: none;
    }
}

@keyframes mg-theme-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* WP-standard hidden-but-readable text. Most themes ship this, but
   we redeclare here so the picker is accessible even when bundled
   with minimal themes. */
.mg-theme-picker .screen-reader-text {
    border: 0; clip: rect(1px, 1px, 1px, 1px); -webkit-clip-path: inset(50%); clip-path: inset(50%);
    height: 1px; width: 1px; overflow: hidden; padding: 0; position: absolute !important; word-wrap: normal !important;
}

/* =========================================================================
 * CHAT PANEL (v0.22.58 — theme-token rebuild on top of v0.22.57)
 *
 * v0.22.57 was a working layout (size, positioning, mobile bottom-sheet,
 * desktop sidebar) but every color/radius/font was hardcoded — so the
 * panel ignored the user's accent setting and looked the same red even
 * when the rest of the site was running arcade theme. The "好醜 / doesn't
 * match clean & arcade themes" feedback in v0.22.57 is fixed here by
 * routing every visual decision through --mg-* design tokens that the
 * theme classes (.mg-theme-clean / .mg-theme-arcade) and the admin
 * accent injection (build_custom_css) already populate.
 *
 * What stayed the same from v0.22.57:
 *   - 360×560 desktop / 70vh mobile (size confirmed OK)
 *   - LEFT-DOCKED panel + bottom-left launcher
 *   - Auto-open on attach
 *   - HSL-hash avatars for other users, self-bubbles right-aligned w/o avatar
 *   - Spectator (data-readonly=1) hides the input row
 *   - !important armor on launcher position/visibility (theme overrides)
 *
 * What changed:
 *   - Every color → var(--mg-…) token. No more #e63946 / #1c1c1e.
 *   - Radii via var(--mg-radius*) → automatically sharper in arcade.
 *   - Fonts via var(--mg-body) → automatically Cubic 11 monospace in
 *     arcade theme, system sans in clean theme.
 *   - Header switched from red gradient bar to a flat accent stripe with
 *     a thin under-line — quieter, matches the rest of the system.
 *   - Free-toggle button no longer a dashed-border outlier; styled like
 *     the secondary buttons elsewhere in the UI (outlined + accent-on-hover).
 *   - Free-send is a flat accent fill (matches .mg-btn--primary:active).
 *   - Other-user bubbles use --mg-bg-2 + --mg-line, like cards in the
 *     scoreboard/dialog UI elsewhere.
 *   - Self bubbles use the accent fill with --mg-accent-ink for text,
 *     so they read correctly regardless of which accent the user picked
 *     (the v0.1 contrast bug fixed in the .mg-btn redesign).
 *   - Arcade theme: subtle scanline overlay echoing .mg-theme-arcade.mg-frame,
 *     plus an accent-glow text-shadow on the title.
 *   - No backdrop-filter on the mobile scrim (design rule #3 — would
 *     blur the game state the user might want to glance at).
 * ========================================================================= */

.mg-chat {
    /* Width is a local token so the desktop side-dock and the
       mobile bottom-sheet can override independently. */
    --mg-chat-w: 360px;
    --mg-chat-max-h: 560px;

    display: flex;
    flex-direction: column;
    width: var(--mg-chat-w);
    max-height: var(--mg-chat-max-h);

    background: var(--mg-bg);
    border: 1px solid var(--mg-line);
    border-radius: var(--mg-radius);
    color: var(--mg-ink);
    font-family: var(--mg-body);
    font-size: 14.5px;
    line-height: 1.45;
    overflow: hidden;

    /* Drop shadow only — matches .mg-theme-clean.mg-frame baseline.
       Arcade theme adds a faint accent halo via a separate rule below
       (where the matching .mg-frame treatment also lives). */
    box-shadow:
        0 1px 2px rgba(0, 0, 0, 0.20),
        0 12px 36px rgba(0, 0, 0, 0.40);
}

/* v0.22.58 [hidden] armor.
 *
 * The chat free-text row toggles between two states via the HTML
 * `hidden` attribute on the toggle / input / send elements. The UA
 * default `[hidden] { display: none }` should be enough, but on the
 * production deployment we saw all three elements visible at once,
 * squashing the input to ~35% of the row.
 *
 * The cause: the surrounding WP site CSS sets `button { display:
 * inline-block }` (Elementor + most themes do this without realising
 * it overrides the UA rule). Once any rule with the same specificity
 * as `[hidden]` sets `display`, the UA fallback loses.
 *
 * Fix: scope-armored !important inside the chat panel only. Outside
 * the panel this rule does nothing — site themes that use `[hidden]`
 * for their own widgets are unaffected. */
.mg-chat [hidden],
.mg-chat-launcher[hidden] {
    display: none !important;
}

/* Arcade overlay: faint horizontal scanlines + inner accent glow,
   identical pattern to .mg-theme-arcade.mg-frame so the chat reads
   as part of the same family of surfaces. */
.mg-chat.mg-theme-arcade {
    background-image: linear-gradient(
        to bottom,
        transparent 0px, transparent 1px,
        rgba(255,255,255,0.018) 1px, rgba(255,255,255,0.018) 2px
    );
    background-size: 100% 3px;
    background-color: var(--mg-bg);
    box-shadow:
        0 0 0 1px rgba(0, 0, 0, 0.4),
        0 12px 36px rgba(0, 0, 0, 0.50),
        inset 0 0 24px var(--mg-accent-glow);
}

/* ---- Header: flat accent stripe ---- */

.mg-chat__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    background: var(--mg-accent);
    color: var(--mg-accent-ink);
    border-bottom: 1px solid rgba(0, 0, 0, 0.25);
    user-select: none;
    -webkit-user-select: none;
}

.mg-chat__title {
    font-weight: 700;
    font-size: 15px;
    letter-spacing: 0.02em;
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: var(--mg-display);
    /* v0.22.67: use var(--mg-ink) instead of hardcoded #ffffff.
     * Same visual result in the standard dark themes (ink resolves to
     * a white-ish value there), but stays coherent with the rest of
     * the design-token system — if a future theme variant shifts
     * --mg-ink (say to off-white #f0f0f5), the chat title comes
     * along automatically rather than being a standalone #ffffff
     * island. The text-shadow below still protects legibility if a
     * user picks a very light accent for the header stripe. */
    color: var(--mg-ink);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.mg-chat__title::before {
    content: '';
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 999px;
    /* Dot follows the same ink token so they read as one unit. */
    background: var(--mg-ink);
    box-shadow:
        0 0 6px rgba(255, 255, 255, 0.5),
        0 1px 2px rgba(0, 0, 0, 0.35);
    flex: 0 0 auto;
}
/* Arcade title style — uppercase letter-spacing borrowed from
 * .mg-title arcade.
 *
 * v0.22.68: removed the neon white-glow text-shadow override that
 * was making the white-on-dark title look out of focus. v0.22.69
 * cleanup: the explicit `text-shadow: none` reset has also been
 * removed from source — the cascade handles it cleanly. The base
 * .mg-chat__title rule's subtle dark drop shadow still applies in
 * arcade theme, which is invisible against the dark accent stripe
 * and helps legibility on the rare light-accent setup. */
.mg-chat.mg-theme-arcade .mg-chat__title {
    letter-spacing: 0.06em;
}

/* Close button.
   v0.22.59 redesign — the old version used rgba(0,0,0,0.18) bg with
   a darker-black hover. On top of the accent stripe that read as a
   muddy hole that got worse on hover (especially with bright accents).
   New design:
   - transparent background; just the SVG glyph in --mg-accent-ink
   - 1px translucent border using currentColor for a quiet frame
   - hover wash uses a ::before overlay filled with currentColor at
     low alpha — so the tint automatically matches the ink (light ink
     → white wash; dark ink → dark wash). No hardcoded black, no
     dependency on relative-color syntax.
   - subtle rotate + scale on the X glyph for tactile feedback
   - SVG glyph (not the '×' Unicode character) so it renders
     identically across systems and inherits --mg-accent-ink.

   v0.22.61 — IMPORTANT element change. Previous versions used
   <button>, but on sites where the theme has a high-priority rule
   like `button:hover { background-color: <brand-red>; }`, no amount
   of CSS specificity armor reliably wins after a page-cache /
   CDN-served stale CSS state. Switched to <div role="button"
   tabindex="0"> in core.js — themes targeting `button` selector
   simply do not match. Accessibility preserved via role + tabindex
   + keydown handler for Enter/Space (see buildPanel() in core.js).

   The CSS still scopes via .mg-chat .mg-chat__header for specificity
   so any future styling stays predictable. Inline-style armor on
   background remains as defense against the rare theme that targets
   div[role="button"] or similar broad selectors. */
.mg-chat .mg-chat__header .mg-chat__close {
    position: relative;
    isolation: isolate;
    /* Longhands win over `background: ...` shorthand from themes. */
    background-color: transparent !important;
    background-image: none !important;
    /* v0.22.70: border 1px → 3px to match the bumped-up X stroke
     * (v0.22.69 → v0.22.70). A 1px hairline ring around a 4-unit-stroke
     * X read mismatched — the ring should carry similar weight to the
     * glyph inside it.
     * v0.23.23: border 3px → 2px, paired with the X stroke dropping
     * 4 → 2.5 (see core.js). The heavier ring read as boxy / harsh;
     * lighter ring + lighter X balances against the rounded shape and
     * the rest of the header glyphs. */
    border: 2px solid currentColor;
    /* v0.22.67: align with title — use --mg-ink (white in dark themes)
     * instead of --mg-accent-ink (auto-computed black/white per accent).
     * The close X is part of the header's "identity strip" along with
     * the title and dot, so they all share the same ink token. The
     * hover wash below still tints with currentColor → light wash
     * over light ink, dark wash if a future theme ever flips ink dark. */
    color: var(--mg-ink);
    cursor: pointer;
    width: 26px;
    height: 26px;
    /* v0.23.23: rounded-square (--mg-radius-xs ≈ 4px) → circle. The
     * squared close button read as a leftover from a different design
     * language — the rest of the chat UI is built on soft / pill
     * shapes (rounded bubbles, dot indicator, pill-shaped chips). A
     * round close pairs cleanly with the header's status dot and the
     * chat bubbles' corner radius. Paired with the lighter 2px border
     * and 2.5-unit X stroke (see comments above + core.js) for a chip
     * feel rather than a stop-sign feel. */
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    font-family: inherit;
    opacity: 0.72;
    transition:
        opacity 140ms ease,
        transform 160ms ease,
        border-color 140ms ease;
    flex: 0 0 auto;
    /* Div is not a button — re-establish button-y behavior */
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    /* Suppress any theme box-shadow on buttons — would compete with
       our ::before wash visually. focus-visible adds its own outline
       below, scoped to that pseudo-class only. */
    box-shadow: none !important;
}
.mg-chat .mg-chat__header .mg-chat__close:hover,
.mg-chat .mg-chat__header .mg-chat__close:focus,
.mg-chat .mg-chat__header .mg-chat__close:focus-visible,
.mg-chat .mg-chat__header .mg-chat__close:active {
    background-color: transparent !important;
    background-image: none !important;
    box-shadow: none !important;
}
.mg-chat .mg-chat__header .mg-chat__close::before {
    /* Hover/active wash. currentColor at low alpha means the wash
       follows --mg-accent-ink: light ink gets a white-ish tint,
       dark ink (pale accents picked by user) gets a dark tint.
       Either way the result reads as "highlight" not "hole". */
    content: '';
    position: absolute;
    inset: 0;
    background-color: currentColor;
    opacity: 0;
    border-radius: inherit;
    transition: opacity 140ms ease;
    z-index: -1;
    pointer-events: none;
}
.mg-chat .mg-chat__header .mg-chat__close > svg {
    display: block;
    transition: transform 200ms ease;
    /* Keep the X above the hover wash. */
    position: relative;
    z-index: 1;
    pointer-events: none;   /* clicks bubble to parent div */
}
.mg-chat .mg-chat__header .mg-chat__close:hover {
    opacity: 1;
    border-color: currentColor;
}
.mg-chat .mg-chat__header .mg-chat__close:hover::before  { opacity: 0.16; }
.mg-chat .mg-chat__header .mg-chat__close:hover > svg    { transform: rotate(90deg) scale(1.08); }
.mg-chat .mg-chat__header .mg-chat__close:active         { transform: scale(0.94); }
.mg-chat .mg-chat__header .mg-chat__close:active::before { opacity: 0.28; }
.mg-chat .mg-chat__header .mg-chat__close:focus-visible {
    outline: 2px solid var(--mg-ink) !important;
    outline-offset: 2px !important;
}

/* ---- Messages area ---- */

.mg-chat__messages {
    flex: 1 1 auto;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 14px 14px 6px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-height: 180px;
    scrollbar-width: thin;
    scrollbar-color: var(--mg-line-strong) transparent;
}
.mg-chat__messages::-webkit-scrollbar { width: 8px; }
.mg-chat__messages::-webkit-scrollbar-track { background: transparent; }
.mg-chat__messages::-webkit-scrollbar-thumb {
    background: var(--mg-line-strong);
    border-radius: 4px;
}
.mg-chat__messages::-webkit-scrollbar-thumb:hover {
    background: var(--mg-ink-subtle);
}

/* Each message: avatar (left) + bubble. Self messages flip with .mg-chat__msg[data-self="1"]. */
.mg-chat__msg {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    max-width: 100%;
    animation: mg-chat-msg-in 220ms ease both;
}
@keyframes mg-chat-msg-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.mg-chat__msg-avatar {
    width: 30px;
    height: 30px;
    flex: 0 0 30px;
    border-radius: 999px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-weight: 700;
    font-size: 13px;
    font-family: var(--mg-display);
    /* Background is set inline by JS via avatarColor() (HSL hash of user_id). */
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.10);
    user-select: none;
    -webkit-user-select: none;
}
/* Arcade theme: square avatars to match the pixel/blocky look. */
.mg-chat.mg-theme-arcade .mg-chat__msg-avatar {
    border-radius: var(--mg-radius-xs);
}

.mg-chat__msg-bubble {
    background: var(--mg-bg-2);
    border: 1px solid var(--mg-line);
    border-radius: var(--mg-radius-sm);
    padding: 7px 11px 8px;
    max-width: calc(100% - 38px);
    min-width: 0;
    word-wrap: break-word;
    overflow-wrap: anywhere;
}

.mg-chat__msg-meta {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 11px;
    color: var(--mg-ink-subtle);
    margin-bottom: 2px;
}
.mg-chat__msg-name {
    font-weight: 600;
    font-size: 12px;
    color: var(--mg-ink-muted);
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mg-chat__msg-time {
    font-variant-numeric: tabular-nums;
    font-size: 10.5px;
}
.mg-chat__msg-body {
    color: var(--mg-ink);
    font-size: 14px;
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ---- Self messages: right-aligned, accent fill, no avatar ---- */

.mg-chat__msg[data-self="1"] {
    flex-direction: row-reverse;
}
.mg-chat__msg[data-self="1"] .mg-chat__msg-avatar { display: none; }
.mg-chat__msg[data-self="1"] .mg-chat__msg-bubble {
    background: var(--mg-accent);
    border-color: var(--mg-accent);
    color: var(--mg-accent-ink);
    max-width: 85%;
}
.mg-chat__msg[data-self="1"] .mg-chat__msg-meta {
    justify-content: flex-end;
}
.mg-chat__msg[data-self="1"] .mg-chat__msg-name {
    color: var(--mg-accent-ink);
    opacity: 0.82;
}
.mg-chat__msg[data-self="1"] .mg-chat__msg-time {
    color: var(--mg-accent-ink);
    opacity: 0.7;
}
.mg-chat__msg[data-self="1"] .mg-chat__msg-body {
    color: var(--mg-accent-ink);
}

/* Quick-chip messages (server-validated phrase keys) get a soft accent
   left border so they're visually distinguishable from free-text. The
   self-bubble override above wins for the sender's own chips. */
.mg-chat__msg[data-kind="quick"]:not([data-self="1"]) .mg-chat__msg-bubble {
    border-left: 3px solid var(--mg-accent);
    padding-left: 9px;
}

/* ---- Empty state ---- */

.mg-chat__empty {
    margin: auto;
    text-align: center;
    color: var(--mg-ink-subtle);
    padding: 32px 16px;
    font-size: 13px;
    line-height: 1.5;
    user-select: none;
    -webkit-user-select: none;
}
.mg-chat__empty::before {
    /* v0.22.58: was content:'💬' which rendered as the system emoji
       font (different look on Windows / macOS / Linux, always heavy and
       colorful against a quiet empty state). Now an inline SVG chat-
       bubble mask, colored by --mg-ink-subtle so it visually recedes. */
    content: '';
    display: block;
    width: 32px;
    height: 32px;
    margin: 0 auto 10px;
    background: var(--mg-ink-subtle);
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z'/></svg>") center/contain no-repeat;
            mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z'/></svg>") center/contain no-repeat;
}

/* ---- Input area: chips row + free-text row ---- */

.mg-chat__input {
    border-top: 1px solid var(--mg-line);
    padding: 12px 14px 14px;
    background: var(--mg-bg);
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.mg-chat__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* Chips: pill version of .mg-btn — outlined, accent on hover/focus.
   Smaller padding + pill radius. Inherits the same family of motion. */
.mg-chat__chip {
    background: transparent;
    color: var(--mg-ink);
    border: 1px solid var(--mg-line-strong);
    border-radius: 999px;
    padding: 6px 12px;
    font-size: 13px;
    font-weight: 500;
    font-family: var(--mg-body);
    cursor: pointer;
    line-height: 1.25;
    transition: border-color 140ms ease, color 140ms ease,
                background 140ms ease, box-shadow 140ms ease;
}
.mg-chat__chip:hover,
.mg-chat__chip:focus-visible {
    border-color: var(--mg-accent);
    color: var(--mg-accent);
    background: var(--mg-accent-soft);
    outline: none;
    box-shadow: 0 0 0 1px var(--mg-accent-soft),
                0 0 8px var(--mg-accent-glow);
}
.mg-chat__chip:active {
    background: var(--mg-accent);
    color: var(--mg-accent-ink);
    border-color: var(--mg-accent);
    transform: translateY(1px);
    box-shadow: none;
}
.mg-chat__chip:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}
/* Arcade chips: sharp radius matching the rest of arcade UI.
   The pill radius doesn't fit the dot-matrix font/look. */
.mg-chat.mg-theme-arcade .mg-chat__chip {
    border-radius: var(--mg-radius-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-size: 12px;
}

/* ---- Free-text row: collapsed toggle → expanded input + send ---- */

.mg-chat__free {
    display: flex;
    gap: 8px;
    align-items: center;
    min-width: 0;
}

.mg-chat__free-toggle {
    background: var(--mg-bg-2);
    border: 1px solid var(--mg-line);
    color: var(--mg-ink-muted);
    border-radius: var(--mg-radius-sm);
    padding: 8px 12px;
    font-size: 12.5px;
    cursor: pointer;
    width: 100%;
    text-align: center;
    font-family: var(--mg-body);
    transition: border-color 140ms ease, color 140ms ease,
                background 140ms ease;
    line-height: 1.3;
}
.mg-chat__free-toggle:hover,
.mg-chat__free-toggle:focus-visible {
    color: var(--mg-ink);
    border-color: var(--mg-accent);
    background: var(--mg-bg-2);
    outline: none;
}

/*
 * Free-text input — armored against WP theme overrides.
 *
 * v0.22.58 user report: in a site running an Elementor-based theme,
 * the input's focus border showed up red even when the user's accent
 * was cyan (the send button right next to it WAS cyan). Cause: the
 * theme set `input:focus { border-color: ...; box-shadow: ... }` on
 * a generic `input:focus` selector which beats a plain class
 * selector. Send button uses solid `background` which themes leave
 * alone more often than border/shadow.
 *
 * Fix: compound selector (`input.mg-chat__free-input`) for higher
 * specificity in the steady state, plus !important on the focus
 * state's border-color and box-shadow — the two properties WP
 * themes most commonly override on focus. Armor is scoped tightly
 * to this one class so nothing else is affected.
 */
.mg-chat__free-input,
input.mg-chat__free-input,
input[type="text"].mg-chat__free-input {
    flex: 1 1 auto;
    min-width: 0;
    background: var(--mg-bg-2);
    color: var(--mg-ink);
    border: 1px solid var(--mg-line-strong);
    border-radius: var(--mg-radius-sm);
    padding: 8px 12px;
    font-size: 14px;
    font-family: var(--mg-body);
    line-height: 1.3;
    transition: border-color 140ms ease, box-shadow 140ms ease;
    /* outline reset belongs in the steady state too — some themes
       set a default outline on text inputs that we don't want. */
    outline: none;
}
.mg-chat__free-input::placeholder,
input.mg-chat__free-input::placeholder {
    color: var(--mg-ink-subtle);
}
.mg-chat__free-input:focus,
.mg-chat__free-input:focus-visible,
input.mg-chat__free-input:focus,
input.mg-chat__free-input:focus-visible,
input[type="text"].mg-chat__free-input:focus,
input[type="text"].mg-chat__free-input:focus-visible {
    outline: none !important;
    border-color: var(--mg-accent) !important;
    box-shadow: 0 0 0 2px var(--mg-accent-soft) !important;
}

/* Send button: flat accent fill — primary action style. Matches the
   on-press state of .mg-btn--primary (the unambiguous "this is the
   action" look), used here in the steady state because the input row
   is itself the gate (toggle to reveal). Compound selector + !important
   on background/border for the same site-theme-bleed reason as the
   input above (same patch, v0.22.58). */
.mg-chat__free-send,
button.mg-chat__free-send {
    background: var(--mg-accent) !important;
    color: var(--mg-accent-ink) !important;
    border: 1px solid var(--mg-accent) !important;
    border-radius: var(--mg-radius-sm);
    padding: 8px 16px;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    font-family: var(--mg-body);
    line-height: 1.3;
    flex: 0 0 auto;
    transition: filter 140ms ease, box-shadow 140ms ease, transform 80ms ease;
    box-shadow: 0 0 0 1px var(--mg-accent-soft);
}
.mg-chat__free-send:hover,
.mg-chat__free-send:focus-visible,
button.mg-chat__free-send:hover,
button.mg-chat__free-send:focus-visible {
    outline: none;
    filter: brightness(1.08);
    box-shadow: 0 0 0 2px var(--mg-accent-soft),
                0 0 12px var(--mg-accent-glow);
}
.mg-chat__free-send:active,
button.mg-chat__free-send:active { transform: translateY(1px); }
.mg-chat__free-send:disabled,
button.mg-chat__free-send:disabled {
    opacity: 0.45;
    cursor: not-allowed;
    box-shadow: none;
    filter: none;
}

/* Spectator / closed-match mode: hide input affordances entirely.
   Server enforces the same; this just prevents the UI from teasing. */
.mg-chat[data-readonly="1"] .mg-chat__input { display: none; }

/* Tiny status hint for rate-limit / banned / closed cases. */
.mg-chat__hint {
    font-size: 11.5px;
    color: var(--mg-ink-muted);
    text-align: center;
    padding: 2px 0;
    min-height: 14px;
    line-height: 1.3;
}

/* ---- Mobile bottom-sheet variant (≤720px) ---- */

@media (max-width: 720px) {
    .mg-chat {
        width: 100%;
        max-height: none;
        border-radius: var(--mg-radius) var(--mg-radius) 0 0;
        font-size: 14.5px;
    }
    .mg-chat.mg-theme-arcade {
        border-radius: var(--mg-radius) var(--mg-radius) 0 0;
    }
    .mg-chat__chip {
        font-size: 14px;
        padding: 8px 14px;
    }
}

/* ---- Launcher (FAB, bottom-left) ---- */
/*
 * Smaller and quieter than v0.22.57's 56px red circle. Accent-colored
 * so it follows the user's theme, and the !important armor stays in
 * place because the v0.22.56 deployment investigation showed site
 * themes occasionally override fixed buttons with `position: static`
 * or zero-out z-index. We don't pulse it anymore now that the panel
 * auto-opens — the launcher is a re-open affordance, not an attention-
 * grabber.
 */
.mg-chat-launcher {
    position: fixed !important;
    left: 16px !important;
    right: auto !important;
    bottom: 16px !important;
    z-index: 999999 !important;
    background: var(--mg-accent) !important;
    color: var(--mg-accent-ink) !important;
    border: 1px solid var(--mg-accent) !important;
    border-radius: 999px !important;
    width: 48px !important;
    height: 48px !important;
    font-size: 22px !important;
    cursor: pointer !important;
    box-shadow:
        0 4px 14px rgba(0, 0, 0, 0.35),
        0 0 0 1px var(--mg-accent-soft),
        0 0 12px var(--mg-accent-glow) !important;
    display: none;
    align-items: center !important;
    justify-content: center !important;
    font-family: var(--mg-body, inherit);
    visibility: visible !important;
    opacity: 1 !important;
    transition: transform 160ms ease, box-shadow 160ms ease, filter 160ms ease !important;
    line-height: 1 !important;
    padding: 0 !important;
}
.mg-chat-launcher:hover {
    transform: scale(1.06) !important;
    filter: brightness(1.06);
    box-shadow:
        0 6px 18px rgba(0, 0, 0, 0.40),
        0 0 0 2px var(--mg-accent-soft),
        0 0 18px var(--mg-accent-glow) !important;
}
.mg-chat-launcher:active { transform: scale(0.96) !important; }
.mg-chat-launcher[data-visible="1"] { display: flex !important; }

/* Inline-SVG icon inside the launcher. v0.22.57 used a 💬 emoji glyph;
   v0.22.58 swaps it for SVG that inherits currentColor (= --mg-accent-ink),
   so the icon stroke contrasts with the accent fill on any theme color. */
.mg-chat-launcher__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    color: inherit;
    line-height: 0;
}
.mg-chat-launcher__icon svg {
    display: block;
    width: 22px;
    height: 22px;
    /* Slight optical lift — the bubble tail anchors visually low otherwise */
    transform: translateY(-1px);
}

/* Arcade launcher: square instead of circle, glowing accent border. */
.mg-theme-arcade .mg-chat-launcher,
.mg-chat-launcher.mg-theme-arcade {
    border-radius: var(--mg-radius) !important;
}

.mg-chat-launcher__badge {
    position: absolute;
    top: -2px;
    right: -2px;
    background: var(--mg-bg);
    color: var(--mg-accent);
    font-size: 10px;
    font-weight: 800;
    border-radius: 999px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    display: none;
    align-items: center;
    justify-content: center;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
    border: 1.5px solid var(--mg-accent);
    font-family: var(--mg-body);
    line-height: 1;
}
.mg-chat-launcher__badge[data-count]:not([data-count="0"]) {
    display: flex;
}

/* ---- Sheet positioning ---- */
/*
 * .mg-chat--sheet is the panel root class. Two modes:
 *   - Mobile (≤720px): bottom-sheet, slides up from below
 *   - Desktop (≥721px): left-docked sidebar above the launcher
 *
 * Left-docked (not right-docked) is deliberate — v0.22.57 user request:
 * right side of the site has other widgets.
 */
.mg-chat--sheet {
    position: fixed !important;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 999998 !important;
    max-height: 70vh;
    transform: translateY(100%);
    transition: transform 240ms cubic-bezier(0.22, 1, 0.36, 1);
    visibility: visible !important;
    opacity: 1 !important;
}
.mg-chat--sheet[data-open="1"] { transform: translateY(0); }

@media (min-width: 721px) {
    .mg-chat--sheet {
        left: 16px !important;
        right: auto !important;
        bottom: 78px !important;   /* room for the 48px launcher beneath */
        width: 360px !important;
        max-height: 560px !important;
        border-radius: var(--mg-radius) !important;
        transform: translateY(8px) !important;
        opacity: 0 !important;
        pointer-events: none !important;
        transition:
            opacity 180ms ease,
            transform 240ms cubic-bezier(0.22, 1, 0.36, 1) !important;
    }
    .mg-chat--sheet[data-open="1"] {
        transform: translateY(0) !important;
        opacity: 1 !important;
        pointer-events: auto !important;
    }
    /* Backdrop is a mobile convention; on desktop the side-dock doesn't
       dim the game behind it (the player wants to keep watching the board). */
    .mg-chat-backdrop { display: none !important; }
    /* Close button is mobile-primary; on desktop keep it available but lower-visibility.
       Selectors chained to match the specificity of the main armor at the
       top of the file — otherwise these short selectors would lose the
       cascade tie against the higher-specificity hover rules above. */
    .mg-chat .mg-chat__header .mg-chat__close { opacity: 0.85; }
    .mg-chat .mg-chat__header .mg-chat__close:hover { opacity: 1; }
}

.mg-chat-backdrop {
    position: fixed;
    inset: 0;
    /* NO backdrop-filter blur — see design rule #3 at the top of the file:
       blurring the game state behind the chat is bad UX, especially on
       move-and-respond timers (Big Two, Xiangqi). Simple scrim only. */
    background: rgba(0, 0, 0, 0.45);
    z-index: 999997 !important;
    display: none;
}
.mg-chat-backdrop[data-open="1"] { display: block; }

@media (prefers-reduced-motion: reduce) {
    .mg-chat--sheet { transition: none !important; }
    .mg-chat__chip { transition: none; }
    .mg-chat__free-toggle,
    .mg-chat__free-input,
    .mg-chat__free-send { transition: none; }
    .mg-chat-launcher { transition: none !important; }
    .mg-chat__msg { animation: none; }
}
