:root {
    --bg: #0a0a0a;
    --surface: #121212;
    --border: #1f1f1f;
    --text: #e7e7e7;
    --text-muted: #9a9a9a;
    --brand: #7c3aed;
    --brand-hover: #8b5cf6;
    --danger: #f43f5e;
    color-scheme: dark;
}

* {
    box-sizing: border-box;
}

/* Respect the HTML `hidden` attribute even on elements whose class sets display.
   (e.g. `.btn` uses inline-block which would otherwise win.) */
[hidden] {
    display: none !important;
}

/* Lucide sprite baseline. Every <svg class="lucide"> referencing a symbol in
   /assets/icons/lucide.svg picks up these defaults, so icons inherit text
   colour, render with the canonical 2px stroke, and stay flex-stable inside
   row layouts. Per-call width/height attrs override sizing. */
.lucide {
    display: inline-block;
    vertical-align: -0.15em;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex-shrink: 0;
}

/* Payment-method brand icon. Sister to .lucide but filled (most brand
   marks are solid silhouettes, not strokes). Sprite at
   /assets/icons/payment-icons.svg, helper `View::paymentIcon($code)`
   in PHP and `window.paymentIcon(code)` in JS. Inherits text colour
   via fill: currentColor; add .is-color and set --brand-color on the
   element to render the brand hue. */
.payment-icon {
    display: inline-block;
    vertical-align: -0.15em;
    fill: currentColor;
    flex-shrink: 0;
}

.payment-icon.is-color {
    fill: var(--brand-color, currentColor);
}

html,
body {
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--text);
    font: 16px/1.5 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
    min-height: 100vh;
    /* Kill any horizontal overflow on the page level. Absolute-
       positioned tooltip chips near a right-edge anchor (e.g. the
       DM sidebar rows near the viewport edge) would otherwise stick
       out past the viewport and add a horizontal scrollbar. `clip`
       prevents both scroll AND the "dragging right reveals empty
       space" behaviour without breaking sticky positioning. */
    overflow-x: clip;
}

body {
    display: flex;
    flex-direction: column;
}

a {
    color: inherit;
}

.container {
    flex: 1 0 auto;
    max-width: 64rem;
    margin: 0 auto;
    padding: 3rem 1.25rem;
    width: 100%;
}

/* Chat page: escape the container constraints and fill the viewport.
   The page never scrolls - only the .chat-messages list does. The site
   footer is hidden so vertical space goes entirely to the channel view. */
.container:has(> .chat) {
    max-width: none;
    padding: 0;
    display: flex;
    min-height: 0;
    flex: 1 1 0;
}
.container:has(> .chat) > .chat { flex: 1 1 auto; min-height: 0; }

body:has(.chat) {
    /* 100dvh follows the mobile URL-bar show/hide. 100vh bakes in
       the taller (URL-bar-hidden) measurement, which on mobile
       pushes the composer beneath the visible area and allows a
       phantom page scroll. Fallback to 100vh for old browsers. */
    height: 100vh;
    height: 100dvh;
    max-height: 100dvh;
    overflow: hidden; /* kill any residual page-level scroll - only .chat-messages should scroll */
    min-height: 0;
    overflow: hidden;
}

/* Also lock the <html> element on chat / robot-pang pages. Without
   this, the global `html { min-height: 100vh }` lets html grow to
   the URL-bar-hidden viewport height (taller than 100dvh while the
   bar is visible). Body is overflow:hidden but html is the actual
   scroll root on mobile - so the user could scroll html up/down and
   reveal an empty strip below the body. Pinning html to 100dvh +
   overflow:hidden eliminates that phantom scroll. The :has() selector
   is supported on every browser version that supports 100dvh, so the
   fallback chain is consistent. */
html:has(.chat),
html:has(.rb) {
    height: 100dvh;
    max-height: 100dvh;
    min-height: 0;
    overflow: hidden;
}

body:has(.chat) .site-footer {
    display: none;
}

/* Robot Pang page: same treatment as the chat page - the page itself
   is locked to viewport height and never scrolls; only the conversation
   thread inside .rb-stage scrolls. The site footer hides so vertical
   space goes entirely to the chat. Applies to both the public
   /robot-pang and the staff /robot-pang since both wrap their content
   in a top-level .rb element. */
body:has(.rb) {
    height: 100vh;
    height: 100dvh;
    max-height: 100dvh;
    overflow: hidden;
    min-height: 0;
}
body:has(.rb) .site-footer { display: none; }
/* Bottom-left FAB stays visible on Robot Pang. The page uses dvh height
   and a sticky composer; the FAB sits in the same fixed slot it occupies
   on every other page so users have one consistent place to find Help /
   Announcements / Settings / Sign out. */

/* Old rule targeted `.container > .rb` directly. With the new
   `.rb-shell` wrapper the chain became broken - .rb-shell didn't get
   `flex: 1 1 auto`, so .rb-stage couldn't compute a definite height
   for `min-height: 0; overflow-y: auto;` to actually scroll. Add a
   parallel rule for the wrapper. */
.container:has(> .rb-shell) {
    max-width: none;
    padding: 0;
    display: flex;
    min-height: 0;
    flex: 1 1 0;
}
.container:has(> .rb-shell) > .rb-shell { flex: 1 1 auto; min-height: 0; }
.container:has(> .rb) {
    max-width: none;
    padding: 0 1rem;
    display: flex;
    min-height: 0;
    flex: 1 1 0;
}
.container:has(> .rb) > .rb { flex: 1 1 auto; min-height: 0; }

/* Marketplace + guides pages: break out of the 64rem container cap
   so the content fills the viewport width. Each shell manages its own
   max-width and padding. */
.container:has(> .mkt-shell) {
    max-width: none;
    padding: 0;
}
.container:has(> .gd-shell) {
    max-width: none;
    padding: 0;
}

.site-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.5rem;
    border-bottom: 1px solid var(--border);
    background: var(--surface);
}

.site-header .brand {
    font-weight: 600;
    letter-spacing: 0.02em;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    line-height: 0;
}
.site-header .brand .brand-logo {
    /* LogoOnlyText.png is 767x277 (~2.77:1). Height drives the
       rendered size; width auto-scales. High-quality interpolation
       for both Chrome and Firefox keeps the small copy crisp. */
    height: 1.6rem;
    width: auto;
    display: block;
    image-rendering: auto;
    image-rendering: -webkit-optimize-contrast;
    -webkit-user-drag: none;
    user-drag: none;
    user-select: none;
    pointer-events: none;
}

.site-header nav {
    display: flex;
    gap: 1rem;
    align-items: center;
}

.nav-link,
.linklike {
    background: none;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    font: inherit;
    padding: 0;
    text-decoration: none;
}

.nav-link:hover,
.linklike:hover {
    color: var(--text);
}

.nav-user {
    color: var(--text-muted);
    font-size: 0.9rem;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.6rem;
    border-radius: 999px;
    transition: background 0.15s, color 0.15s;
}
.nav-user:hover,
.nav-user:focus-visible {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    outline: none;
}
.nav-user-avatar {
    width: 1.85rem;
    height: 1.85rem;
    border-radius: 50%;
    object-fit: cover;
    display: inline-block;
    background: #1f1f1f;
    border: 1px solid rgba(255, 255, 255, 0.08);
    flex-shrink: 0;
    -webkit-user-drag: none;
    user-drag: none;
    user-select: none;
    pointer-events: none;
}
.nav-user-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text);
}
.nav-user-name {
    font-weight: 500;
    letter-spacing: 0.01em;
}

/* Primary top-nav (Chat | Marketplace). Lives inside .site-header
   between the brand logo and the right-side user controls. Two pill
   tabs that highlight the active page via .is-active. Mobile shrinks
   them to icon-led pills so the header stays one row at 360 px. */
.site-header-left {
    display: flex;
    align-items: center;
    gap: 1.25rem;
    min-width: 0; /* let the nav clip rather than push the right side off-screen */
}

/* "Beta" badge sitting next to the logo. Plain inline-block button (no
   native chrome), explicit padding, animated background-position shine. */
.brand-beta {
    display: inline-block;
    flex-shrink: 0;
    margin-left: -0.5rem;
    padding: 6px 14px;
    border: 0;
    border-radius: 999px;
    color: #ffffff;
    font-family: inherit;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    background-color: #8b5cf6;
    background-image: linear-gradient(
        110deg,
        #6366f1 0%,
        #8b5cf6 25%,
        #ffffff66 50%,
        #ec4899 75%,
        #8b5cf6 100%
    );
    background-size: 250% 100%;
    background-position: 100% 0;
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.18) inset,
        0 4px 12px -3px rgba(139, 92, 246, 0.55);
    animation: brand-beta-shine 4s ease-in-out infinite;
    transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.brand-beta-text { color: inherit; }

.brand-beta:hover {
    transform: translateY(-1px) scale(1.05);
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.28) inset,
        0 8px 20px -4px rgba(236, 72, 153, 0.65);
}
@keyframes brand-beta-shine {
    0%   { background-position: 100% 0; }
    50%  { background-position: 0%   0; }
    100% { background-position: 100% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .brand-beta { animation: none; }
}
@media (max-width: 560px) {
    .brand-beta {
        font-size: 10px;
        padding: 5px 12px;
    }
}

/* ----- Beta info modal ----- */
.brand-beta-card {
    position: relative;
    text-align: center;
    /* Top padding pushes the icon below the gradient flair so the
       title + body sit cleanly on the solid surface. */
    padding: 2.75rem 1.75rem 1.75rem;
    overflow: hidden;
    max-width: 460px;
}
.brand-beta-modal-flair {
    position: absolute;
    inset: 0 0 auto 0;
    height: 90px;
    background:
        radial-gradient(circle at 30% 0%, rgba(99, 102, 241, 0.45), transparent 60%),
        radial-gradient(circle at 80% 0%, rgba(236, 72, 153, 0.4), transparent 65%),
        linear-gradient(180deg, rgba(139, 92, 246, 0.18), transparent);
    pointer-events: none;
}
.brand-beta-modal-icon {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 72px;
    height: 72px;
    margin: 0.5rem auto 1rem;
    border-radius: 999px;
    background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
    color: #ffffff;
    box-shadow:
        0 0 0 4px rgba(139, 92, 246, 0.18),
        0 8px 24px -6px rgba(139, 92, 246, 0.55);
    animation: brand-beta-icon-bob 3s ease-in-out infinite;
}
.brand-beta-modal-icon img {
    width: 44px;
    height: 44px;
    object-fit: contain;
    -webkit-user-drag: none;
}
@keyframes brand-beta-icon-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-3px); }
}
.brand-beta-modal-title {
    position: relative;
    z-index: 1;
    margin: 0 0 0.6rem;
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--text);
}
.brand-beta-modal-msg {
    position: relative;
    z-index: 1;
    margin: 0 0 1.4rem;
    color: var(--text-muted);
    line-height: 1.55;
    font-size: 0.95rem;
}
.brand-beta-modal-actions {
    position: relative;
    z-index: 1;
    display: flex;
    justify-content: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.brand-beta-close-x {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
    z-index: 2;
    transition: background 0.15s, color 0.15s;
}
.brand-beta-close-x:hover {
    background: rgba(255, 255, 255, 0.08);
    color: var(--text);
}
@media (max-width: 480px) {
    .brand-beta-card { padding: 1.6rem 1.25rem 1.25rem; }
    .brand-beta-modal-actions { flex-direction: column; }
    .brand-beta-modal-actions .btn { width: 100%; }
}

.primary-nav {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    flex-wrap: nowrap;
    min-width: 0;
}

.primary-nav-item {
    position: relative; /* anchor for .primary-nav-badge */
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.45rem 0.85rem;
    border-radius: 999px;
    border: 1px solid transparent;
    color: var(--text-muted);
    font-size: 0.9rem;
    font-weight: 500;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    min-height: 2.25rem;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}

/* Red dot badge on the Marketplace pill when the viewer has at least
   one order needing action (status=accepted or completed-not-reviewed
   from the /api/marketplace/my-orders summary.action_needed_count).
   site.js owns the fetch + paint; the badge is appended to the pill. */
.primary-nav-item .primary-nav-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ef4444;
    border: 2px solid var(--bg);
    pointer-events: none;
}

.primary-nav-item:hover,
.primary-nav-item:focus-visible {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    outline: none;
}

.primary-nav-item.is-active {
    background: rgba(124, 58, 237, 0.15);
    border-color: rgba(124, 58, 237, 0.45);
    color: var(--text);
}
.primary-nav-item.is-active:hover {
    background: rgba(124, 58, 237, 0.22);
    border-color: var(--brand);
}

/* Overflow trigger - shown when one or more primary-nav items don't
   fit the available header width. site.js measures + moves items
   into the dropdown below. */
.primary-nav-overflow-btn {
    appearance: none;
    border: 1px solid transparent;
    background: transparent;
    color: var(--text-muted);
    width: 2.4rem;
    height: 2.4rem;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.15s, color 0.15s;
}
.primary-nav-overflow-btn:hover,
.primary-nav-overflow-btn:focus-visible,
.primary-nav-overflow-btn[aria-expanded="true"] {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    outline: none;
}
.primary-nav-overflow-btn[hidden] { display: none; }

/* Floating dropdown of overflowed items. Pure CSS visual; site.js
   positions it on open via fixed coords. */
.primary-nav-overflow-menu {
    position: fixed;
    z-index: 70;
    min-width: 12rem;
    /* Clamp to the visible viewport so a near-edge trigger can't push
       the menu off-screen on a 360 px device. */
    max-width: min(18rem, calc(100vw - 1rem));
    padding: 0.35rem;
    border-radius: 0.6rem;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 10px 30px -8px rgba(0, 0, 0, 0.45),
                0 0 0 1px rgba(255, 255, 255, 0.02) inset;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    animation: rb-meta-popin 0.12s ease-out;
}
.primary-nav-overflow-menu[hidden] { display: none; }
.primary-nav-overflow-menu .primary-nav-item {
    /* Items inside the dropdown render full-width like a standard list */
    width: 100%;
    box-sizing: border-box;
    justify-content: flex-start;
    border-radius: 0.4rem;
    padding: 0.55rem 0.7rem;
}
@keyframes rb-meta-popin {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0);    }
}

/* Marketplace pill swaps the purple active accent for gold so the two
   sections are visually distinct in the top nav. Inactive state keeps
   the neutral muted colour shared with the Chat pill - the gold only
   kicks in once the pill carries `.is-active`. Matches the gold used
   for marketplace stars / seller-rating chips elsewhere (#fbbf24). */
.primary-nav-item[data-mkt-pill].is-active {
    background: rgba(251, 191, 36, 0.18);
    border-color: rgba(251, 191, 36, 0.5);
    color: #fbbf24;
}
.primary-nav-item[data-mkt-pill].is-active:hover {
    background: rgba(251, 191, 36, 0.26);
    border-color: #fbbf24;
}

/* Tools pill is a <button>, not an <a>. Strip default button styling
   so it visually matches the other primary-nav-item anchors, then add
   the small caret affordance. The dropdown menu (#primary-nav-tools-
   menu) is a sibling positioned by site.js. */
.primary-nav-item-button {
    appearance: none;
    background: transparent;
    cursor: pointer;
    font: inherit;
    border-style: solid;
    color: var(--text-muted);
}
.primary-nav-item-button[aria-expanded="true"] {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
}
.primary-nav-item .primary-nav-caret {
    flex-shrink: 0;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    opacity: 0.7;
    transition: transform 0.15s;
}
.primary-nav-item-button[aria-expanded="true"] .primary-nav-caret {
    transform: rotate(180deg);
}
@media (max-width: 560px) {
    .primary-nav-item .primary-nav-caret { display: none; }
}

/* Dropdown menu for the Tools pill. Mirrors the overflow-menu look so
   both header dropdowns feel like one component. site.js positions
   this with fixed coords; on mobile it pins to the screen edges. */
.primary-nav-tools-menu {
    position: fixed;
    z-index: 70;
    min-width: 12rem;
    max-width: min(18rem, calc(100vw - 1rem));
    padding: 0.35rem;
    border-radius: 0.6rem;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 10px 30px -8px rgba(0, 0, 0, 0.45),
                0 0 0 1px rgba(255, 255, 255, 0.02) inset;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    animation: rb-meta-popin 0.12s ease-out;
}
.primary-nav-tools-menu[hidden] { display: none; }

/* Section header inside the "More" dropdown (Tools / Community). */
.primary-nav-tools-section {
    padding: 0.5rem 0.85rem 0.25rem;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted, #8b8b94);
}
.primary-nav-tools-section:not(:first-child) {
    margin-top: 0.2rem;
    border-top: 1px solid var(--border, #27272a);
    padding-top: 0.55rem;
}

.primary-nav-tools-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.6rem 0.7rem;
    min-height: 2.5rem;
    color: var(--text-muted);
    text-decoration: none;
    border-radius: 0.4rem;
    font-size: 0.9rem;
    line-height: 1;
    transition: background 0.12s, color 0.12s;
}
.primary-nav-tools-item:hover,
.primary-nav-tools-item:focus-visible {
    background: rgba(124, 58, 237, 0.16);
    color: var(--text);
    outline: none;
}
.primary-nav-tools-item-icon {
    flex-shrink: 0;
    color: inherit;
}

@media (max-width: 720px) {
    /* On mobile we span the full screen width (with safe insets) so the
       dropdown is easy to thumb. site.js sets left/right; max-width
       lifts the desktop cap. */
    .primary-nav-tools-menu {
        max-width: none;
        min-width: 0;
    }
    .primary-nav-tools-item {
        padding: 0.85rem 0.85rem;
        font-size: 0.95rem;
        min-height: 2.75rem;
    }
}

.primary-nav-icon {
    flex-shrink: 0;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* Image-based variant (e.g. the Robot Pang icon) - drops the SVG-only
   stroke rules and just renders the image at the icon slot's size. */
.primary-nav-icon-img {
    stroke: none;
    fill: initial;
    object-fit: contain;
    display: block;
    -webkit-user-drag: none;
}

.primary-nav-label {
    overflow: hidden;
    text-overflow: ellipsis;
}

@media (max-width: 768px) {
    .site-header {
        padding: 0.65rem 0.85rem;
        gap: 0.5rem;
    }
    .site-header-left {
        gap: 0.6rem;
        flex: 1 1 auto;
    }
    .site-header .brand .brand-logo {
        height: 1.35rem;
    }
    /* On mobile the desktop primary-nav (Chat / Marketplace / etc.)
       and the Beta pill collide with the brand logo + user chip on
       320-414 px viewports. Collapse them all into a hamburger drawer
       (#mobile-nav-drawer) so the header is just [☰] [Logo] [...] [Avatar]. */
    .primary-nav,
    .brand-beta,
    .nav-premium-cta {
        display: none !important;
    }
    .nav-user-name {
        /* Avatar alone is plenty on small screens; the name pushes the
           right side past the viewport on long display names. */
        display: none;
    }
}

@media (max-width: 420px) {
    .site-header { padding: 0.55rem 0.6rem; }
    .site-header-left { gap: 0.4rem; }
}

/* Hamburger trigger. Hidden on desktop; shown only at &lt;= 768 px. */
.mobile-nav-btn {
    display: none;
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text);
    cursor: pointer;
    width: 40px;
    height: 40px;
    border-radius: 0.4rem;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.mobile-nav-btn:hover,
.mobile-nav-btn:focus-visible,
.mobile-nav-btn:active {
    background: rgba(255, 255, 255, 0.06);
    outline: none;
}
@media (max-width: 768px) {
    .mobile-nav-btn { display: inline-flex; }
}

/* Fullscreen-ish drawer that holds the cloned nav items on mobile.
   Slides in from the left; backdrop dims the page behind it. Body
   gets `modal-open` while open so the page underneath doesn't
   scroll. */
.mobile-nav-drawer {
    position: fixed;
    inset: 0;
    z-index: 250;
    display: flex;
}
.mobile-nav-drawer[hidden] { display: none; }
.mobile-nav-drawer-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    animation: modal-fade 0.12s ease-out;
}
.mobile-nav-drawer-card {
    position: relative;
    z-index: 1;
    width: min(20rem, 88vw);
    max-width: 100%;
    height: 100%;
    background: var(--surface);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.5);
    animation: mobile-nav-slide 0.15s ease-out;
    /* Honour the iOS notch / home-bar safe area so nothing hides
       behind the system chrome. */
    padding-top: env(safe-area-inset-top, 0);
    padding-bottom: env(safe-area-inset-bottom, 0);
}
@keyframes mobile-nav-slide {
    from { transform: translateX(-100%); }
    to   { transform: translateX(0);     }
}
.mobile-nav-drawer-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid var(--border);
}
.mobile-nav-drawer-title {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.mobile-nav-drawer-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    width: 40px;
    height: 40px;
    border-radius: 0.4rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.mobile-nav-drawer-close:hover,
.mobile-nav-drawer-close:focus-visible,
.mobile-nav-drawer-close:active {
    color: var(--text);
    background: rgba(255, 255, 255, 0.06);
    outline: none;
}
.mobile-nav-drawer-list {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.65rem;
    overflow-y: auto;
    flex: 1 1 auto;
}
/* Each item in the drawer is a row that mirrors the desktop pill:
   icon + label, full width, 44 px tall touch target. The cloned
   items get .mobile-nav-drawer-item added by site.js so their
   appearance differs from the original pills. */
.mobile-nav-drawer-item {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.65rem 0.8rem;
    min-height: 44px;
    border-radius: 0.5rem;
    color: var(--text);
    text-decoration: none;
    font-size: 0.95rem;
    background: transparent;
    border: 1px solid transparent;
    cursor: pointer;
    appearance: none;
    text-align: left;
    width: 100%;
    box-sizing: border-box;
}
.mobile-nav-drawer-item:hover,
.mobile-nav-drawer-item:focus-visible,
.mobile-nav-drawer-item:active {
    background: rgba(124, 58, 237, 0.10);
    border-color: rgba(124, 58, 237, 0.25);
    color: var(--text);
    outline: none;
}
.mobile-nav-drawer-item.is-active {
    background: rgba(124, 58, 237, 0.18);
    border-color: rgba(124, 58, 237, 0.45);
    color: #ddd6fe;
}
.mobile-nav-drawer-item.is-mkt.is-active {
    background: rgba(251, 191, 36, 0.14);
    border-color: rgba(251, 191, 36, 0.45);
    color: #fbbf24;
}
.mobile-nav-drawer-item.is-premium {
    margin-top: 0.45rem;
    border-top: 1px solid var(--border);
    padding-top: 0.85rem;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
}
.mobile-nav-drawer-item-icon {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    /* Lucide icons stroke with currentColor; default <svg> would
       render black-fill / black-stroke without these. The Robot
       Pang `<img>` icon overrides this via the second selector
       below. */
    stroke: currentColor;
    fill: none;
    color: inherit;
}
.mobile-nav-drawer-item-icon.primary-nav-icon-img,
img.mobile-nav-drawer-item-icon {
    /* Bitmap icons (Robot Pang) don't use stroke/fill - reset so the
       transparent-fill rule above doesn't blank them out. */
    stroke: none;
    fill: initial;
    object-fit: contain;
}
.mobile-nav-drawer-item-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

form.inline {
    display: inline;
    margin: 0;
    padding: 0;
}

.site-footer {
    padding: 1.5rem;
    text-align: center;
    color: var(--text-muted);
    border-top: 1px solid var(--border);
}
.site-footer-link {
    color: var(--text-muted);
    text-decoration: none;
    border-bottom: 1px dotted color-mix(in srgb, var(--text-muted) 60%, transparent);
}
.site-footer-link:hover {
    color: var(--text);
    border-bottom-color: var(--text);
}

/* Legal pages (/terms, /privacy). Long-form prose, comfortable
   reading width, dark-theme friendly. */
.legal-page {
    max-width: 760px;
    margin: 2rem auto 4rem;
    padding: 0 1rem;
    color: var(--text);
    line-height: 1.65;
}
.legal-head {
    margin-bottom: 1.75rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--border);
}
.legal-head h1 {
    margin: 0 0 0.35rem;
    font-size: 1.85rem;
    color: #fff;
    letter-spacing: 0.01em;
}
.legal-updated {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.legal-section {
    margin: 1.6rem 0;
}
.legal-section h2 {
    font-size: 1.1rem;
    color: #fff;
    margin: 0 0 0.55rem;
    letter-spacing: 0.01em;
}
.legal-section p {
    margin: 0 0 0.75rem;
    color: #d4d4d8;
}
.legal-section ul {
    margin: 0.4rem 0 1rem 1.25rem;
    padding: 0;
    color: #d4d4d8;
}
.legal-section ul li { margin-bottom: 0.4rem; }
.legal-section strong { color: #fff; }
.legal-section code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85em;
    background: rgba(255,255,255,0.06);
    border: 1px solid var(--border);
    padding: 0.1rem 0.35rem;
    border-radius: 0.3rem;
}
.legal-foot {
    margin-top: 2rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
    color: var(--text-muted);
    font-size: 0.9rem;
}
.legal-link {
    color: var(--brand, #a78bfa);
    text-decoration: none;
}
.legal-link:hover { text-decoration: underline; }
@media (max-width: 560px) {
    .legal-page { margin: 1rem auto 3rem; }
    .legal-head h1 { font-size: 1.5rem; }
}

.hero {
    text-align: center;
    padding: 4rem 0;
}

.hero .eyebrow {
    text-transform: uppercase;
    letter-spacing: 0.25em;
    font-size: 0.75rem;
    color: var(--brand);
    margin: 0;
}

.hero h1 {
    font-size: clamp(2rem, 4vw, 3.25rem);
    margin: 0.5rem 0 1rem;
    letter-spacing: -0.02em;
}

.hero .lead {
    color: var(--text-muted);
    font-size: 1.1rem;
    max-width: 40rem;
    margin: 0 auto 2rem;
}

.actions {
    display: flex;
    gap: 0.75rem;
    justify-content: center;
    flex-wrap: wrap;
}

.btn {
    display: inline-block;
    padding: 0.65rem 1.25rem;
    border-radius: 0.375rem;
    text-decoration: none;
    font-weight: 500;
    font-size: 0.95rem;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
    cursor: pointer;
    border: 1px solid transparent;
}

.btn-primary {
    background: var(--brand);
    color: #fff;
}

.btn-primary:hover {
    background: var(--brand-hover);
}

.btn-ghost {
    background: var(--surface);
    border-color: var(--border);
    color: var(--text);
}

.btn-ghost:hover {
    border-color: #3a3a3a;
}

.btn-danger {
    background: var(--danger);
    color: #fff;
}

.btn-danger:hover {
    background: #e11d48;
}

.note {
    color: var(--text-muted);
    font-size: 0.8rem;
    margin-top: 2rem;
}

.signin {
    max-width: 28rem;
    margin: 4rem auto;
    text-align: center;
}

.signin h1 {
    font-size: 1.75rem;
    letter-spacing: -0.01em;
    margin: 0 0 0.5rem;
}

.signin .lead {
    color: var(--text-muted);
    margin: 0 0 2rem;
}

.signin-providers {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-bottom: 2rem;
}

.btn-discord {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.625rem;
    padding: 0.75rem 1.25rem;
    border-radius: 0.375rem;
    background: #5865f2;
    color: #fff;
    text-decoration: none;
    font-weight: 500;
    transition: background 0.15s;
}

.btn-discord:hover {
    background: #4752c4;
}

.btn-discord svg {
    flex-shrink: 0;
}

.signin-note {
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* "or with email" separator between the Discord button and the form. */
.signin-divider {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 1rem 0 0.75rem;
    color: var(--text-muted);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.signin-divider::before,
.signin-divider::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: var(--border);
}

.signin-error {
    margin: 0 0 0.75rem;
    padding: 0.55rem 0.75rem;
    background: rgba(239, 68, 68, 0.08);
    border: 1px solid rgba(239, 68, 68, 0.35);
    border-radius: 0.4rem;
    color: #fca5a5;
    font-size: 0.85rem;
}

.signin-form {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-bottom: 1rem;
}
.signin-field {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.signin-label {
    font-size: 0.8rem;
    color: var(--text-muted);
    letter-spacing: 0.02em;
}
.signin-input {
    width: 100%;
    padding: 0.55rem 0.7rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    font-size: 0.95rem;
}
.signin-input:focus {
    outline: none;
    border-color: var(--brand);
}
.signin-input.is-readonly {
    color: var(--text-muted);
    background: #0a0a0a;
}

/* Password input with the show/hide eye toggle pinned to its right
   edge. The wrapper is the positioning context; the input gets extra
   right padding so the caret / text don't bleed under the button. */
.signin-password-wrap {
    position: relative;
    display: block;
}
.signin-input-password {
    padding-right: 2.6rem;
}
.signin-password-toggle {
    position: absolute;
    right: 0.35rem;
    top: 50%;
    transform: translateY(-50%);
    appearance: none;
    background: transparent;
    border: 0;
    width: 2rem;
    height: 2rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    cursor: pointer;
    border-radius: 0.3rem;
    transition: color 0.12s, background 0.12s;
}
.signin-password-toggle:hover,
.signin-password-toggle:focus-visible {
    color: var(--text);
    background: rgba(255, 255, 255, 0.06);
    outline: none;
}
.signin-password-toggle svg[hidden] { display: none; }
.signin-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.8rem;
}
.signin-submit {
    margin-top: 0.25rem;
    justify-content: center;
}
.signin-consent {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    margin: 0.25rem 0 0.5rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.4;
    cursor: pointer;
}
.signin-consent input[type="checkbox"] {
    margin-top: 0.2rem;
    accent-color: var(--brand, #a78bfa);
    flex-shrink: 0;
}
.signin-consent-text { color: #d4d4d8; }
.signin-consent a {
    color: var(--brand, #a78bfa);
    text-decoration: underline;
    text-underline-offset: 0.15em;
}
.signin-consent a:hover { color: #fff; }

/* --------------------------- Chat --------------------------- */
body.has-chat,
body.has-chat .container {
    /* Let the chat take the full viewport under the header. */
    padding: 0;
    max-width: none;
}

.chat {
    display: grid;
    grid-template-columns: 4.5rem 16rem 1fr;
    height: 100%;
    min-height: 0;
    border-top: 1px solid var(--border);
}
/* When a server-channel right-side member panel is active, switch to
   a 4-column layout. The panel itself sits on the rightmost column. */
.chat:has(.chat-members-panel) {
    grid-template-columns: 4.5rem 16rem 1fr 16rem;
}

.chat-sidebar {
    border-right: 1px solid var(--border);
    background: var(--surface);
    padding: 1rem 0.5rem;
    overflow-y: auto;
    min-height: 0;
}

.chat-sidebar-title {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.2em;
    color: var(--text-muted);
    margin: 0.25rem 0.75rem 0.75rem;
}

/* ---- Channel + category sidebar (Discord-style tree) ---- */
.chat-sidebar-title-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
}
.chat-channel-add {
    background: transparent;
    border: 0;
    color: #71717a;
    cursor: pointer;
    padding: 0.25rem;
    border-radius: 0.3rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.chat-channel-add:hover { color: #c4b5fd; background: rgba(124, 58, 237, 0.08); }
.chat-channel-tree {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.chat-channel-cat { display: flex; flex-direction: column; }
.chat-channel-cat-head {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.55rem 0.4rem 0.25rem;
    color: #a1a1aa;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 700;
    cursor: default;
    user-select: none;
}
.chat-channel-cat-head:hover { color: #d4d4d8; }
.chat-channel-cat-head:hover .chat-channel-cat-add { opacity: 1; }
.chat-channel-cat-caret {
    flex-shrink: 0;
    transition: transform 0.12s ease;
    color: #71717a;
}
.chat-channel-cat.is-collapsed .chat-channel-cat-caret { transform: rotate(-90deg); }
.chat-channel-cat-name { flex: 1 1 auto; }
.chat-channel-cat-add {
    background: transparent;
    border: 0;
    color: #71717a;
    cursor: pointer;
    padding: 0;
    width: 1rem;
    height: 1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.25rem;
    opacity: 0;
    transition: opacity 0.12s ease;
}
.chat-channel-cat-add:hover { color: #c4b5fd; background: rgba(124, 58, 237, 0.08); opacity: 1; }
.chat-channel-cat.is-collapsed .chat-channel-list { display: none; }

/* Drag-and-drop visual cues for channel + category reordering. */
.chat-channel-tree [draggable="true"] { cursor: grab; }
/* Disable text selection on draggable rows + headers. Without this
   a slow mousedown starts a selection instead of a drag, so the
   first attempt at drag-and-drop silently fails. The category
   header is also draggable so it gets the same treatment.
   Marked !important to beat any later page-level user-select rules
   (and the browser default) - selection inside the sidebar would
   actively break drag-init in every browser. */
.chat-channel-tree[data-can-manage="1"] .chat-channel-list > li,
.chat-channel-tree[data-can-manage="1"] .chat-channel-list > li *,
.chat-channel-tree[data-can-manage="1"] .chat-channel-cat-head,
.chat-channel-tree[data-can-manage="1"] .chat-channel-cat-head * {
    -webkit-user-select: none !important;
    -moz-user-select:    none !important;
    -ms-user-select:     none !important;
    user-select:         none !important;
    -webkit-touch-callout: none !important;
}
/* Channel link inside a draggable <li> still defaults to cursor:
   pointer because of the <a>. Override so the drag affordance is
   obvious for managers. Read-only members keep the pointer. */
.chat-channel-tree[data-can-manage="1"] .chat-channel-list > li .chat-channel-link {
    cursor: grab;
}
.chat-channel-tree[data-can-manage="1"] .chat-channel-list > li.is-dragging .chat-channel-link,
.chat-channel-tree[data-can-manage="1"] .chat-channel-list > li .chat-channel-link:active {
    cursor: grabbing;
}
.chat-channel-tree .is-dragging { opacity: 0.45; }
.chat-channel-tree .chat-channel-list > li.is-drop-above::before,
.chat-channel-tree .chat-channel-list > li.is-drop-below::after,
.chat-channel-tree .chat-channel-cat.is-drop-above::before,
.chat-channel-tree .chat-channel-cat.is-drop-below::after {
    content: '';
    display: block;
    height: 2px;
    margin: 0 0.4rem;
    background: linear-gradient(90deg, transparent 0%, #a78bfa 25%, #7c3aed 50%, #a78bfa 75%, transparent 100%);
    border-radius: 999px;
    box-shadow: 0 0 6px rgba(167, 139, 250, 0.6);
    pointer-events: none;
}
.chat-channel-tree .chat-channel-list.is-drop-into,
.chat-channel-tree .chat-channel-cat-head.is-drop-into {
    background: rgba(124, 58, 237, 0.12);
    border-radius: 0.35rem;
}

.chat-channel-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.chat-channel-link {
    display: block;
    padding: 0.35rem 0.75rem;
    color: var(--text-muted);
    text-decoration: none;
    border-radius: 0.25rem;
    font-size: 0.95rem;
}

.chat-channel-link:hover {
    background: #1a1a1a;
    color: var(--text);
}

.chat-channel-link.is-active {
    background: var(--brand);
    color: #fff;
}

.chat-channel-hash {
    color: var(--text-muted);
    margin-right: 0.25rem;
}

.chat-channel-link.is-active .chat-channel-hash {
    color: rgba(255,255,255,0.7);
}

/* Per-channel mute indicator. The hash is replaced with a small
   speaker-with-slash glyph in red-ish when the user has muted that
   channel; the channel name text itself dims for additional cue.
   The mute icon is hidden by default and revealed only when the
   parent .chat-channel-link carries .is-muted (toggled live by
   refreshChannelMuteIndicators in chat-client.js). */
.chat-channel-mute-icon {
    align-items: center;
    margin-right: 0.25rem;
    color: #fca5a5;
    flex-shrink: 0;
    display: none !important;
}
.chat-channel-link.is-muted .chat-channel-hash { display: none !important; }
.chat-channel-link.is-muted .chat-channel-mute-icon { display: inline-flex !important; }
.chat-channel-link.is-muted {
    color: rgba(228, 228, 231, 0.55);
}
.chat-channel-link.is-muted.is-active {
    color: rgba(255, 255, 255, 0.85);
}
.chat-channel-link.is-muted.is-active .chat-channel-mute-icon {
    color: #fecaca;
}

.chat-main {
    display: flex;
    flex-direction: column;
    min-height: 0;
    /* Default grid-item min-width is `auto`, which lets a wide
       intrinsic child (long URL, unbreakable inline-flex, oversized
       attachment thumb, etc.) push the 1fr column past 100% of the
       viewport on mobile. The visible symptom: day dividers and
       system pills appear centered inside a column that extends past
       the right edge, so labels + pills look "cut" - the whole row
       is just wider than the screen. Pinning min-width: 0 forces the
       column to honour its 1fr width regardless of children, and
       overflow-x: clip below contains anything that still tries to
       overflow inside the column. */
    min-width: 0;
    /* Contain tooltip chips (absolutely positioned ::after from
       data-tooltip) so they clip at the chat-main edge instead of
       spilling into a horizontal scroll on the DM column. `clip`
       over `hidden` preserves sticky positioning. */
    overflow-x: clip;
}

.chat-header {
    padding: 1rem 1.5rem;
    border-bottom: 1px solid var(--border);
}

.chat-header h1 {
    margin: 0;
    font-size: 1.1rem;
}

.chat-topic {
    margin: 0.25rem 0 0;
    color: var(--text-muted);
    font-size: 0.85rem;
}

.chat-status {
    padding: 0.35rem 1.5rem;
    font-size: 0.8rem;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border);
    background: #0d0d0d;
}

.chat-status[data-state="open"]       { color: #34d399; }
.chat-status[data-state="connecting"] { color: #fbbf24; }
.chat-status[data-state="closed"]     { color: var(--danger); }

.chat-messages {
    list-style: none;
    padding: 1rem 1.5rem;
    margin: 0;
    flex: 1 1 auto;
    overflow-y: auto;
    overflow-x: clip;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    /* Same min-width:0 fix as on the parent .chat-main. Without it
       the flex item's default min-width:auto lets children with
       wide intrinsic widths (long URLs, atomic inline-flex blocks,
       wide attachment previews) force the list past its assigned
       width on mobile, which makes day dividers + system pills
       render centered inside a row that extends past the viewport. */
    min-width: 0;
}

/* Subtle gradient tints for informal chats. DMs get an indigo /
   pink wash; groups get a teal / violet wash. Either variant is
   barely there - just enough to differentiate a personal chat from
   a server channel. The tint covers the header + status strip +
   message list consistently via data-channel-kind on .chat-main. */
.chat-main[data-channel-kind="dm"] {
    background:
        radial-gradient(120% 80% at 0% 0%,   rgba(99, 102, 241, 0.10), transparent 55%),
        radial-gradient(100% 70% at 100% 100%, rgba(236, 72, 153, 0.07), transparent 60%),
        var(--bg);
}
.chat-main[data-channel-kind="group"] {
    background:
        radial-gradient(120% 80% at 0% 0%,   rgba(45, 212, 191, 0.09), transparent 55%),
        radial-gradient(100% 70% at 100% 100%, rgba(139, 92, 246, 0.08), transparent 60%),
        var(--bg);
}
/* Madrigal Labs community server (public channels) - a warm amber ->
   crimson wash to feel distinct from the personal DM / group tints. */
.chat-main[data-channel-kind="public"] {
    background:
        radial-gradient(120% 80% at 0% 0%,   rgba(251, 191, 36, 0.08), transparent 55%),
        radial-gradient(100% 70% at 100% 100%, rgba(244, 63, 94, 0.07), transparent 60%),
        var(--bg);
}
.chat-main[data-channel-kind="dm"] .chat-messages,
.chat-main[data-channel-kind="group"] .chat-messages,
.chat-main[data-channel-kind="public"] .chat-messages {
    background: transparent;
}

/* Day divider - a thin horizontal rule with a centred pill labelling the
   local date. Helps disambiguate "23:47" messages that actually belong to
   the previous day in the viewer's timezone. */
.chat-day-divider {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    color: var(--text-muted);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    margin: 0.25rem 0 -0.25rem;
    user-select: none;
}

.chat-day-divider::before,
.chat-day-divider::after {
    content: '';
    flex: 1 1 auto;
    height: 1px;
    background: var(--border);
}

.chat-day-divider-label {
    padding: 0.2rem 0.6rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    white-space: nowrap;
}

/* Top-of-chat "loading older messages" indicator - shown only while a
   lazy-load fetch is in flight. Animations run when `is-loading` is present;
   toggling the class on each show guarantees they replay every time. */
.chat-load-spinner {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    padding: 0.7rem 0.75rem;
    margin: 0 -1.5rem 0.5rem;     /* extend to full width beyond chat-messages padding */
    font-size: 0.85rem;
    font-weight: 500;
    color: #c4b5fd;
    background: rgba(124, 58, 237, 0.10);
    border-top:    1px solid rgba(124, 58, 237, 0.22);
    border-bottom: 1px solid rgba(124, 58, 237, 0.22);
    overflow: hidden;
}

.chat-load-spinner[hidden] { display: none; }

.chat-load-spinner.is-loading {
    animation: chat-loader-fade 0.18s ease-out;
}

.chat-load-spinner-dot {
    width: 0.95rem;
    height: 0.95rem;
    border: 2px solid rgba(196, 181, 253, 0.22);
    border-top-color: var(--brand);
    border-radius: 50%;
    flex-shrink: 0;
}

.chat-load-spinner.is-loading .chat-load-spinner-dot {
    animation: settings-spin 0.8s linear infinite;
}

/* Sliding indeterminate bar along the bottom of the panel. */
.chat-load-spinner::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 2px;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(124, 58, 237, 0.0) 20%,
        var(--brand) 50%,
        rgba(124, 58, 237, 0.0) 80%,
        transparent 100%);
    background-size: 50% 100%;
    background-repeat: no-repeat;
}

.chat-load-spinner.is-loading::after {
    animation: chat-loader-sweep 1.2s linear infinite;
}

@keyframes chat-loader-sweep {
    0%   { background-position: -50% 0; }
    100% { background-position: 150% 0; }
}

@keyframes chat-loader-fade {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

/* Appears only after DOM-eviction: tells the user they're looking at a
   trimmed window and offers a one-click return to the live view. */
.chat-jump-latest {
    position: absolute;
    bottom: 4.5rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 5;
    padding: 0.5rem 1rem;
    background: var(--brand);
    color: #fff;
    border: 0;
    border-radius: 999px;
    font: inherit;
    font-size: 0.82rem;
    cursor: pointer;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
    transition: background 0.15s, opacity 0.15s;
    animation: chat-jump-in 0.18s ease-out;
}

@keyframes chat-jump-in {
    from { opacity: 0; transform: translateX(-50%) translateY(6px); }
    to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.chat-jump-latest[hidden] { display: none; }

.chat-jump-latest:hover {
    background: var(--brand-hover);
}
/* Livelier styling when the button is surfaced because new messages
   arrived while the user was scrolled up (vs. the default "catch up
   to tail" case). Subtle pulse so a glance catches it. */
.chat-jump-latest.has-unread {
    background: #0ea5e9;
    padding-right: 1.15rem;
    box-shadow: 0 8px 24px rgba(14, 165, 233, 0.45),
                0 0 0 4px rgba(14, 165, 233, 0.18);
    animation: chat-jump-in 0.18s ease-out, chat-jump-pulse 1.6s ease-in-out infinite;
}
.chat-jump-latest.has-unread:hover {
    background: #0284c7;
}
@keyframes chat-jump-pulse {
    0%, 100% { box-shadow: 0 8px 24px rgba(14, 165, 233, 0.40), 0 0 0 4px rgba(14, 165, 233, 0.14); }
    50%      { box-shadow: 0 10px 30px rgba(14, 165, 233, 0.55), 0 0 0 8px rgba(14, 165, 233, 0.08); }
}

/* The button is positioned relative to .chat-main, which is already
   position:relative? Explicitly ensure it becomes an anchor. */
.chat-main { position: relative; }

.chat-message {
    display: grid;
    grid-template-columns: 2.75rem 1fr;
    gap: 0.75rem;
    padding: 0.25rem 0.5rem;
    border-radius: 0.35rem;
    transition: background 0.1s ease;
    position: relative;
    /* Grid columns default to `auto` min-width, which lets children
       (long URLs, wide embeds, giant emoji) push the row past the
       container. `min-width: 0` lets the 1fr column shrink as
       intended; `overflow-wrap: anywhere` + word-break combine to
       break unbreakable strings on narrow viewports. */
    min-width: 0;
    overflow-wrap: anywhere;
}
.chat-message > * {
    min-width: 0;
}
.chat-message-content,
.chat-message-body {
    min-width: 0;
    max-width: 100%;
    overflow-wrap: anywhere;
    word-break: break-word;
}

.chat-message:hover {
    background: rgba(255, 255, 255, 0.03);
}

/* Hover action buttons (edit / delete) live in a group docked on the right. */
.chat-message-actions {
    position: absolute;
    top: 50%;
    right: 0.5rem;
    transform: translateY(-50%);
    display: flex;
    gap: 0.25rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s ease;
}

.chat-message:hover .chat-message-actions {
    opacity: 1;
    pointer-events: auto;
}

.chat-message.is-editing .chat-message-actions {
    display: none;
}

.chat-message-delete,
.chat-message-edit-btn,
.chat-message-reply-btn,
.chat-message-pin-btn,
.chat-message-react-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: 0.3rem;
    cursor: pointer;
    transition: color 0.12s ease, background 0.12s ease;
}

.chat-message-edit-btn:hover,
.chat-message-reply-btn:hover,
.chat-message-pin-btn:hover,
.chat-message-react-btn:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.06);
}
/* Amber tint on the pin button when the message is already pinned, so
   the hover affordance visually matches the meta badge. */
.chat-message.is-pinned .chat-message-pin-btn { color: rgba(250, 204, 21, 0.9); }

.chat-message-delete:hover {
    color: #fff;
    background: var(--danger);
}

/* ---- View-reactions modal ---------------------------------------- */
/* Two-pane layout: emoji tabs on the left (vertical scroll), reactor
   list on the right. Switches by clicking a tab. */
.rxv-grid {
    display: grid;
    grid-template-columns: 7rem 1fr;
    gap: 0.85rem;
    max-height: 60vh;
}
.rxv-emoji-list,
.rxv-user-list {
    list-style: none;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    min-height: 0;
}
.rxv-emoji-list { border-right: 1px solid #1f1f23; padding-right: 0.5rem; }
.rxv-emoji {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    padding: 0.45rem 0.55rem;
    background: transparent;
    border: 0;
    border-radius: 0.4rem;
    color: #d4d4d8;
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
    text-align: left;
}
.rxv-emoji:hover { background: rgba(255, 255, 255, 0.05); }
.rxv-emoji.is-active {
    background: rgba(124, 58, 237, 0.22);
    color: #ede9fe;
}
.rxv-emoji-glyph {
    display: inline-flex;
    align-items: center;
    line-height: 1;
}
.rxv-emoji-glyph img.emoji,
.rxv-emoji-glyph img.chat-emoji-custom {
    height: 1.3rem;
    width: 1.3rem;
    margin: 0;
    vertical-align: middle;
    object-fit: contain;
}
.rxv-emoji-count {
    color: #a1a1aa;
    font-variant-numeric: tabular-nums;
    margin-left: auto;
    font-weight: 600;
}
.rxv-emoji.is-active .rxv-emoji-count { color: #ede9fe; }
.rxv-user-list { padding: 0; }
.rxv-user { padding: 0; }
.rxv-user-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.4rem 0.5rem;
    border-radius: 0.4rem;
    color: #e4e4e7;
    text-decoration: none;
}
.rxv-user-row:hover { background: rgba(255, 255, 255, 0.04); }
.rxv-user-av {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    object-fit: cover;
    background: #1f1f23;
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    flex-shrink: 0;
}
.rxv-user-av-fb {
    background: linear-gradient(135deg, #6366f1, #a855f7);
}
.rxv-user-name { font-weight: 600; }
.rxv-user-handle { color: #71717a; font-size: 0.78rem; }
.rxv-empty {
    color: #71717a;
    font-size: 0.85rem;
    padding: 1rem 0;
    text-align: center;
}
.rxv-loading {
    color: #71717a;
    font-size: 0.82rem;
    padding: 0.5rem;
    list-style: none;
}

/* ---- Reactions row ----------------------------------------------- */
/* Lives directly under the message body, beneath any embeds. Pills
   wrap freely. The "+ add reaction" trigger is invisible by default
   and reveals on row hover - matches Discord's affordance. When the
   row has at least one pill the trigger stays visible so you don't
   have to hover to add another. */
.chat-reactions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-top: 0.25rem;
    align-items: center;
}
.chat-reactions:empty { display: none; }
.chat-reaction-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.1rem 0.45rem;
    background: rgba(124, 58, 237, 0.10);
    border: 1px solid rgba(124, 58, 237, 0.32);
    color: #d4d4d8;
    border-radius: 999px;
    font-size: 0.8rem;
    line-height: 1.4;
    cursor: pointer;
    transition: background 0.1s, border-color 0.1s, transform 0.05s;
    font: inherit;
    font-size: 0.8rem;
    user-select: none;
}
.chat-reaction-pill:hover {
    background: rgba(124, 58, 237, 0.22);
    border-color: rgba(124, 58, 237, 0.55);
}
.chat-reaction-pill:active { transform: scale(0.96); }
.chat-reaction-pill.is-mine {
    background: rgba(124, 58, 237, 0.32);
    border-color: rgba(167, 139, 250, 0.7);
    color: #ede9fe;
}
.chat-reaction-pill.is-mine:hover {
    background: rgba(124, 58, 237, 0.42);
}
.chat-reaction-glyph {
    display: inline-flex;
    align-items: center;
    line-height: 1;
}
.chat-reaction-glyph img.emoji,
.chat-reaction-glyph img.chat-emoji-custom {
    height: 1.1rem;
    width: 1.1rem;
    margin: 0;
    vertical-align: middle;
    object-fit: contain;
}
.chat-reaction-count {
    font-variant-numeric: tabular-nums;
    color: #a1a1aa;
    font-weight: 600;
    font-size: 0.78rem;
}
.chat-reaction-pill.is-mine .chat-reaction-count { color: #ede9fe; }

/* The "Add reaction" trigger now lives in the right-side action
   group (.chat-message-react-btn) - see the shared action-button
   rules above. The legacy `.chat-reactions-add` styles were removed
   when the button moved out of the reactions row. */

/* Wrapper for the (edited) badge — sits after .chat-message-content so it
   is visible on both grouped and non-grouped rows (meta is hidden on grouped). */
.chat-message-edited-wrap:empty { display: none; }
.chat-message-edited-wrap { margin-top: 0.1rem; }

/* "(edited)" tag - subtle so it doesn't dominate the line, but still readable. */
.chat-message-edited {
    font-size: 0.72rem;
    color: var(--text-muted);
    opacity: 0.7;
    font-style: italic;
    margin-left: 0;
}

/* Admin / staff edited someone else's message - same text size but red and
   slightly more prominent so it's unmistakable at a glance. */
.chat-message-edited.is-by-admin {
    color: #f43f5e;
    opacity: 1;
}

.chat-message-edited-by {
    color: #fb7185;
    text-decoration: none;
    font-weight: 500;
}

.chat-message-edited-by:hover {
    text-decoration: underline;
    color: #fda4af;
}

/* ---------- Deleted messages (admins only) ---------- */

/* Staff session-level toggle: when `deleted-hidden` is set on the messages
   container, every .is-deleted row is hidden. The state is intentionally not
   persisted - rows come back on any page reload. */
.chat-messages.deleted-hidden .chat-message.is-deleted {
    display: none;
}

.chat-message.is-deleted {
    background: rgba(244, 63, 94, 0.07);
    border-left: 3px solid rgba(244, 63, 94, 0.45);
    padding-left: calc(0.5rem - 3px);     /* keep total padding aligned to 0.5rem */
}

.chat-message.is-deleted:hover {
    background: rgba(244, 63, 94, 0.10);
}

.chat-message.is-deleted .chat-message-content {
    color: var(--text-muted);
    text-decoration: line-through;
    text-decoration-color: rgba(244, 63, 94, 0.55);
    text-decoration-thickness: 1px;
}

/* Non-staff viewers see an empty content div on deleted rows - the original
   text was stripped server-side / on the live event. Drop a subtle "message
   deleted" placeholder so the row isn't just a silent blank. Staff rows keep
   the original content so this :empty rule doesn't fire for them. */
.chat-message.is-deleted .chat-message-content:empty::before {
    content: 'Message deleted';
    color: var(--text-muted);
    font-style: italic;
    opacity: 0.7;
    text-decoration: none;
}

.chat-message-deleted-by {
    font-size: 0.72rem;
    color: #f43f5e;
    font-style: italic;
    font-weight: 500;
    margin-left: 0.15rem;
}

.chat-message-deleted-by-link {
    color: #fb7185;
    text-decoration: none;
    font-weight: 500;
    font-style: normal;
}

.chat-message-deleted-by-link:hover {
    text-decoration: underline;
    color: #fda4af;
}

/* ---------- Reply reference + composer banner ---------- */

/* Small pill above a replied-to message showing the quoted author + snippet. */
.chat-reply-ref {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin: 0 0 0.2rem;
    padding: 0.1rem 0.5rem 0.1rem 0.35rem;
    font-size: 0.78rem;
    color: var(--text-muted);
    background: rgba(255, 255, 255, 0.03);
    border-left: 2px solid rgba(124, 58, 237, 0.6);
    border-radius: 0 0.25rem 0.25rem 0;
    text-decoration: none;
    max-width: 100%;
    min-width: 0;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.chat-reply-ref:hover {
    background: rgba(124, 58, 237, 0.12);
    color: var(--text);
}
.chat-reply-ref-arrow {
    display: inline-flex;
    align-items: center;
    color: rgba(124, 58, 237, 0.85);
    line-height: 1;
}
.chat-reply-ref-arrow svg {
    display: block;
}
.chat-reply-ref-author {
    color: var(--text);
    font-weight: 500;
    flex-shrink: 0;
    max-width: 10rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-reply-ref-snippet {
    color: var(--text-muted);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-reply-ref.is-deleted .chat-reply-ref-deleted {
    font-style: italic;
    color: var(--text-muted);
}

/* Persistent tint on messages that are replies to something the viewer
   authored. Purely local to each viewer - the server doesn't know or care
   who is looking. A thin purple left-border acts like a mention accent,
   plus a faint background wash so the row reads as "about you" at a glance. */
.chat-message.is-reply-to-me {
    background: rgba(124, 58, 237, 0.08);
    border-left: 3px solid var(--brand);
    padding-left: calc(0.5rem - 3px); /* keep text aligned with unhighlighted rows */
}
.chat-message.is-reply-to-me:hover {
    background: rgba(124, 58, 237, 0.12);
}

/* Subtle row tint for messages that contain a valid @everyone ping.
   Uses an amber accent so it visually reads as "everyone got tagged"
   without competing with the purple reply-to-me highlight. Both can
   stack (rare but possible) - the reply rule wins on the bg, this
   one only paints the left border. */
.chat-message.is-everyone {
    background: rgba(251, 191, 36, 0.08);
    border-left: 3px solid #fbbf24;
    padding-left: calc(0.5rem - 3px);
}
.chat-message.is-everyone:hover {
    background: rgba(251, 191, 36, 0.12);
}
.chat-message.is-everyone.is-reply-to-me {
    /* When both apply, lean reply-to-me's purple wash + amber border. */
    background: rgba(124, 58, 237, 0.10);
    border-left-color: #fbbf24;
}

/* Brief highlight when jumping to a message via a reply-ref click. */
.chat-message.is-jump-flash {
    animation: chat-jump-flash 1.4s ease-out;
}
@keyframes chat-jump-flash {
    0%   { background-color: rgba(124, 58, 237, 0.25); }
    100% { background-color: transparent; }
}

/* Discord-style typing indicator: small strip above the composer
   with three bouncing dots + "<name> is typing" text. Population is
   driven by chat-client.js - the element stays `hidden` until at
   least one person is typing in the active channel. */
.chat-typing {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.25rem;
    padding: 0.15rem 0.4rem;
    font-size: 0.78rem;
    color: #a1a1aa;
    letter-spacing: 0.01em;
    min-height: 1.3rem;
}
.chat-typing-dots {
    display: inline-flex;
    align-items: center;
    gap: 0.18rem;
}
.chat-typing-dots i {
    width: 0.32rem;
    height: 0.32rem;
    border-radius: 50%;
    background: #a1a1aa;
    animation: chat-typing-bounce 1.2s ease-in-out infinite;
}
.chat-typing-dots i:nth-child(2) { animation-delay: 0.15s; }
.chat-typing-dots i:nth-child(3) { animation-delay: 0.3s;  }
@keyframes chat-typing-bounce {
    0%, 60%, 100% { transform: translateY(0);    opacity: 0.4; }
    30%           { transform: translateY(-0.2rem); opacity: 1;   }
}
.chat-typing-text strong { color: #e5e7eb; font-weight: 600; }
/* Clickable name buttons inside the typing indicator. Reset the
   browser default button look + make them inherit the surrounding
   indicator font-size so role-paint's em-based icon sizing matches.
   Hover underlines the text WITHOUT fighting any gradient-clip the
   inner .role-name-text span may have applied. */
/* Explicit reset instead of `all: unset` - the latter was collapsing
   role-paint's inline `style="color: <tint>"` on the child
   .role-icon spans (browser interpretation of all:unset on a <button>
   ancestor made the cascade interact weirdly with icon tints),
   causing icons to render gray. This targeted reset keeps the button
   looking like inline text while leaving icon styling untouched. */
.chat-typing-name {
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    font-weight: 600;
    color: #e5e7eb;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    line-height: inherit;
    vertical-align: baseline;
    text-align: inherit;
}
.chat-typing-name:hover,
.chat-typing-name:focus-visible {
    text-decoration: underline;
    outline: none;
}
/* Typing indicator keeps the name styling (gradient / effects) via
   role-paint, but suppresses the role-icon stripe - it'd crowd the
   tight "X is typing" strip and the name alone carries enough
   identity. */
.chat-typing .role-icons { display: none !important; }

/* Discord-style :shortcode: autocomplete popover. Anchors to the
   bottom of the chat-textbox-stack and opens UPWARD so it doesn't
   fight with the composer row below. A keyboard-navigable list of
   emoji matches - arrow keys cycle, Enter / Tab / click commits. */
.chat-emoji-ac {
    /* Body-anchored popover - position/size are computed at render
       time to match the @-mention popover's behaviour. Escapes any
       ancestor overflow / transform clipping. */
    position: fixed;
    top: 0;
    left: 0;
    min-width: 14rem;
    max-width: min(22rem, calc(100vw - 1rem));
    max-height: min(18rem, 60vh);
    overflow-y: auto;
    background: #0e0e10;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
    /* Matches the mention popover so both use the same layer. */
    z-index: 1200;
}
.chat-emoji-ac[hidden] { display: none; }
.chat-emoji-ac-list {
    list-style: none;
    margin: 0;
    padding: 0.25rem 0;
    max-height: 17rem;
    overflow-y: auto;
}
.chat-emoji-ac-item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.38rem 0.7rem;
    cursor: pointer;
    color: #d4d4d8;
    font-size: 0.9rem;
    line-height: 1.2;
}
.chat-emoji-ac-item.is-active,
.chat-emoji-ac-item:hover {
    background: rgba(124, 58, 237, 0.22);
    color: #fff;
}
.chat-emoji-ac-glyph {
    font-size: 1.25rem;
    line-height: 1;
    width: 1.5rem;
    text-align: center;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
/* Twemoji replaces the raw glyph with an <img class="emoji">; size it
   to match the surrounding text so each row's height stays stable
   regardless of whether we have a native glyph or a Twemoji SVG. */
.chat-emoji-ac-glyph img.emoji {
    width: 1.15rem;
    height: 1.15rem;
    display: block;
}
.chat-emoji-ac-code {
    font-family: ui-monospace, 'Courier New', monospace;
    font-size: 0.82rem;
    color: #a1a1aa;
}
.chat-emoji-ac-item.is-active .chat-emoji-ac-code,
.chat-emoji-ac-item:hover .chat-emoji-ac-code {
    color: #e5e7eb;
}
@media (prefers-reduced-motion: reduce) {
    .chat-typing-dots i { animation: none !important; opacity: 0.7; }
}

/* "Replying to Name - snippet" banner above the composer. */
.chat-reply-banner {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.35rem;
    padding: 0.35rem 0.5rem 0.35rem 0.6rem;
    background: rgba(124, 58, 237, 0.1);
    border-left: 3px solid var(--brand);
    border-radius: 0 0.3rem 0.3rem 0;
    font-size: 0.82rem;
    color: var(--text);
}
.chat-reply-banner[hidden] { display: none; }
.chat-reply-banner-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-muted);
}
.chat-reply-banner-label strong {
    color: var(--text);
    font-weight: 500;
}
.chat-reply-banner-snippet {
    color: var(--text-muted);
    opacity: 0.9;
}
.chat-reply-banner-cancel {
    flex-shrink: 0;
    width: 1.4rem;
    height: 1.4rem;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    color: #fff;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}
.chat-reply-banner-cancel:hover { background: var(--danger); }

/* Inline edit mode replacing the message content. */
.chat-message-edit-form {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-top: 0.15rem;
}

/* Edit-mode stack overrides - tighter padding + font to match the inline
   look of a chat message. The .chat-textbox base already sets most props.
   Cap is generous (~40rem or 65% of viewport, whichever is smaller) so
   typical message edits never need internal scroll; a visible thin
   scrollbar appears for the rare oversized message so the user actually
   sees they can reach the bottom. The composer (.chat-textbox without
   .is-edit) keeps its small 10rem cap + hidden scrollbar - it's a
   compose surface, not a long-text editor. */
.chat-textbox-stack.is-edit {
    min-height: 2rem;
    max-height: min(40rem, 65vh);
    border-radius: 0.35rem;
}
.chat-textbox-stack.is-edit .chat-textbox-mirror,
.chat-textbox-stack.is-edit .chat-message-edit-input {
    padding: 0.5rem 0.65rem;
    line-height: 1.4;
    min-height: 2rem;
    max-height: min(40rem, 65vh);
}
/* Show the textarea's scrollbar in edit mode so the user sees there's
   more content below the visible area (the composer hides this for
   caret-glyph alignment with the mirror; the trade-off here is the
   opposite - we accept ~6 px of mirror/text misalignment when an edit
   actually overflows in exchange for the user being able to see and
   use the scrollbar). */
.chat-textbox-stack.is-edit .chat-message-edit-input {
    scrollbar-width: thin;
    -ms-overflow-style: auto;
}
.chat-textbox-stack.is-edit .chat-message-edit-input::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}
.chat-textbox-stack.is-edit .chat-message-edit-input::-webkit-scrollbar-thumb {
    background: #2a2a2f;
    border-radius: 999px;
}
.chat-textbox-stack.is-edit .chat-message-edit-input::-webkit-scrollbar-thumb:hover {
    background: #3a3a3f;
}

.chat-message-edit-hint {
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    font-size: 0.72rem;
    color: var(--text-muted);
}

.chat-message-edit-actions {
    display: inline-flex;
    gap: 0.75rem;
}

.chat-message-edit-actions .linklike {
    font-size: inherit;
}

.chat-message-edit-save {
    color: var(--brand);
}
.chat-message-edit-save:hover {
    color: var(--brand-hover);
}

/* Cancel in the edit hint is destructive-ish - tint it red-muted so the
   eye groups it with "dismiss changes" instead of "save". */
.chat-message-edit-hint [data-edit-action="cancel"] {
    color: #fb7185;
}
.chat-message-edit-hint [data-edit-action="cancel"]:hover {
    color: #fda4af;
}

/* ---------- Custom context menu ---------- */

.context-menu {
    position: fixed;
    z-index: 200;
    min-width: 11rem;
    /* Clamp on small viewports so the menu never spills off a 360 px
       device. The min-width still shapes the menu on desktop. */
    max-width: calc(100vw - 1rem);
    padding: 0.3rem;
    background: #0a0a0a;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
    font-size: 0.85rem;
    user-select: none;
}

.context-menu[hidden] { display: none; }

.context-menu-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    padding: 0.4rem 0.6rem;
    background: transparent;
    border: 0;
    color: var(--text);
    text-align: left;
    cursor: pointer;
    border-radius: 0.25rem;
    font: inherit;
}

.context-menu-item:hover {
    background: rgba(255, 255, 255, 0.05);
}

.context-menu-item.danger {
    color: #fb7185;
}

.context-menu-item.danger:hover {
    color: #fff;
    background: var(--danger);
}

.context-menu-item.warn {
    color: #fcd34d;
}
.context-menu-item.warn:hover {
    color: #fde68a;
    background: rgba(245, 158, 11, 0.18);
}

/* ---------- Confirm dialog ---------- */

.confirm-dialog {
    padding: 1.75rem 1.75rem 1.5rem;
    text-align: center;
}

.confirm-icon {
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 50%;
    margin: 0 auto 1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--border);
    transition: color 0.2s, background 0.2s, border-color 0.2s, box-shadow 0.2s;
}

.confirm-icon.danger {
    color: #fb7185;
    background: rgba(244, 63, 94, 0.10);
    border-color: rgba(244, 63, 94, 0.4);
    box-shadow:
        0 0 0 6px rgba(244, 63, 94, 0.06),
        0 0 24px rgba(244, 63, 94, 0.18);
}
/* Crown variant for ownership / promotion prompts - gold glow
   matching the in-app crown badges. */
.confirm-icon.crown {
    color: #facc15;
    background: radial-gradient(circle at 50% 30%, rgba(250, 204, 21, 0.22), rgba(250, 204, 21, 0.06) 70%);
    border-color: rgba(250, 204, 21, 0.5);
    box-shadow:
        0 0 0 6px rgba(250, 204, 21, 0.08),
        0 0 24px rgba(250, 204, 21, 0.28),
        inset 0 0 12px rgba(250, 204, 21, 0.15);
    filter: drop-shadow(0 2px 6px rgba(250, 204, 21, 0.4));
    animation: confirm-crown-pulse 2.2s ease-in-out infinite;
}
@keyframes confirm-crown-pulse {
    0%, 100% { box-shadow: 0 0 0 6px rgba(250, 204, 21, 0.08), 0 0 24px rgba(250, 204, 21, 0.28), inset 0 0 12px rgba(250, 204, 21, 0.15); }
    50%      { box-shadow: 0 0 0 10px rgba(250, 204, 21, 0.10), 0 0 32px rgba(250, 204, 21, 0.42), inset 0 0 16px rgba(250, 204, 21, 0.22); }
}
.confirm-icon.info {
    color: #60a5fa;
    background: rgba(59, 130, 246, 0.10);
    border-color: rgba(59, 130, 246, 0.4);
    box-shadow:
        0 0 0 6px rgba(59, 130, 246, 0.06),
        0 0 24px rgba(59, 130, 246, 0.18);
}

.confirm-title {
    margin: 0 0 0.5rem;
    font-size: 1.15rem;
    letter-spacing: -0.01em;
    color: var(--text);
}

.confirm-message {
    margin: 0 0 1.5rem;
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.55;
    max-width: 22rem;
    margin-left: auto;
    margin-right: auto;
    /* Preserve newlines so multi-line warnings (e.g. the external-link
       prompt with the URL on its own line) render as paragraphs. */
    white-space: pre-line;
    /* Long URLs should wrap instead of overflowing the modal card. */
    overflow-wrap: anywhere;
}

.confirm-message[hidden] { display: none; }

.confirm-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.6rem;
    margin-top: 0.25rem;
}

.confirm-actions .btn {
    flex: 1 1 9rem;
    max-width: 13rem;
    padding: 0.55rem 1rem;
    white-space: nowrap;
}

/* Circular avatar with a presence circle sitting behind it, offset downward.
   When online, only the bottom arc of the green circle peeks out beneath the
   avatar. When offline, the background circle is transparent = invisible. */
.chat-message-avatar {
    position: relative;
    width: 2.75rem;
    height: 2.75rem;
    display: block;
    overflow: visible;
    isolation: isolate;
    text-decoration: none;
}

.chat-message-avatar::before {
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0.2rem;                    /* thin sliver peeks out at the bottom */
    border-radius: 50%;
    background: transparent;
    z-index: 0;
    transition: background 0.2s ease, box-shadow 0.2s ease;
    pointer-events: none;
}

.chat-message-avatar img {
    position: relative;
    z-index: 1;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    background: #1a1a1a;
}

.chat-message-avatar.is-online::before {
    background: #22c55e;
    box-shadow: 0 0 4px rgba(34, 197, 94, 0.7);
}

/* ============================================================
   Universal custom tooltip (portal-rendered).
   The actual element is injected by site.js at body level as
   `.app-tooltip` and positioned via `position: fixed`. That means:
   - Never clipped by any parent overflow (sidebar, modal, etc).
   - Always painted on top of the page (z-index above modals).
   - Flipped / clamped in JS to stay inside the viewport.
   Elements opt in with `data-tooltip="text"` and optional
   `data-tooltip-pos="above|below|left|right"`.
   ============================================================ */
.app-tooltip {
    position: fixed;
    top: 0;
    left: 0;
    padding: 0.35rem 0.6rem;
    background: #2a2a30;
    color: #f4f4f5;
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: 0.35rem;
    font-size: 0.78rem;
    font-weight: 500;
    line-height: 1.35;
    /* Wrap long tooltip text to multiple lines instead of forcing it
       to one and overflowing the max-width box. Short tooltips still
       render on a single line because they fit inside the cap.
       overflow-wrap covers unbroken URLs / long words. */
    white-space: normal;
    overflow-wrap: break-word;
    word-break: break-word;
    max-width: min(22rem, calc(100vw - 1rem));
    pointer-events: none;
    z-index: 2147483647; /* above every modal / popover */
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55),
                0 0 0 1px rgba(0, 0, 0, 0.35);
}
.app-tooltip[hidden] {
    display: none !important;
}

.chat-message-body .chat-message-meta {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin-bottom: 0.1rem;
}

.chat-message-author {
    font-weight: 600;
}

.chat-message-time {
    color: var(--text-muted);
    font-size: 0.75rem;
}

.chat-message-content {
    white-space: pre-wrap;
    word-break: break-word;
}

.chat-message.is-pending { opacity: 0.55; }

/* Discord-style consecutive-message grouping. The first message in a
   cluster keeps its avatar + author/time meta; subsequent messages
   from the same author within ~5 minutes hide the avatar (the grid
   column stays reserved so bubbles stay aligned) and collapse the
   meta row, so each follow-up snaps tight under the previous one.
   Reply / forward / system / blocked rows always show full chrome. */
.chat-message.is-grouped {
    padding-top: 0.05rem;
    padding-bottom: 0.05rem;
}
.chat-message.is-grouped > .chat-message-avatar > img,
.chat-message.is-grouped > .chat-message-avatar::before {
    display: none;
}
.chat-message.is-grouped > .chat-message-avatar {
    pointer-events: none;
    cursor: default;
}
.chat-message.is-grouped .chat-message-meta {
    display: none;
}

.chat-input {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.75rem 1.5rem 1.25rem;
    border-top: 1px solid var(--border);
    background: var(--surface);
}

/* While a message is being sent (esp. while attachments are uploading), dim
   the composer so the user has visual feedback that hammering Enter is a
   no-op - the JS side-guard is the authoritative stop, this is just UX. */
.chat-input.is-sending { opacity: 0.75; pointer-events: none; }

/* Transient toast notice near the composer - used for rate-limit hits
   and other short server-side feedback. Auto-dismisses from JS; the
   `is-visible` class toggles opacity + slide-in. Non-interactive. */
.chat-toast {
    position: fixed;
    bottom: 5rem;
    left: 50%;
    transform: translate(-50%, 8px);
    padding: 0.5rem 1rem;
    background: rgba(15, 15, 15, 0.95);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    font-size: 0.85rem;
    max-width: min(28rem, 92vw);
    text-align: center;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.55);
    opacity: 0;
    pointer-events: none;
    user-select: none;
    z-index: 500;
    transition: opacity 0.18s, transform 0.18s;
}
.chat-toast.is-visible {
    opacity: 1;
    transform: translate(-50%, 0);
}
.chat-toast.is-warn {
    border-color: #f59e0b;
    color: #fde68a;
}
.chat-toast.is-error {
    border-color: var(--danger);
    color: #fecaca;
}

/* Site-wide toast stack (window.showSiteToast in site.js). Bottom-right
   stack on every page; sits below the in-app DM notif stack (99999) so
   a site toast can never cover a DM notification. Pointer-events:none
   on the stack so it never steals clicks; each toast re-enables them so
   future "Undo" affordances can be wired without restructuring. */
#site-toast-stack {
    position: fixed;
    bottom: 1rem;
    right: 1rem;
    z-index: 99998;
    display: flex;
    flex-direction: column-reverse;
    gap: 0.5rem;
    pointer-events: none;
    max-width: min(320px, calc(100vw - 2rem));
}
.site-toast {
    pointer-events: auto;
    background: #1f1f23;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.6rem 0.8rem;
    color: var(--text);
    font-size: 0.88rem;
    line-height: 1.35;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
    transform: translateX(120%);
    opacity: 0;
    transition: transform 0.25s ease, opacity 0.25s ease;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.site-toast.is-visible { transform: translateX(0); opacity: 1; }
.site-toast.is-leaving { transform: translateX(120%); opacity: 0; }
.site-toast-ok    { border-left: 3px solid #22c55e; }
.site-toast-warn  { border-left: 3px solid #fbbf24; }
.site-toast-error { border-left: 3px solid #ef4444; }
@media (max-width: 560px) {
    #site-toast-stack {
        bottom: calc(env(safe-area-inset-bottom, 0px) + 0.5rem);
        right: 0.5rem;
        left: 0.5rem;
        max-width: none;
    }
}

/* Anchored toast variant. JS positions left/top in viewport-fixed
   coordinates next to the triggering element (e.g. an Add-to-cart
   button). Visually identical to the stack toast but with a pop-in
   scale animation and a small chevron pointing at the anchor. JS
   adds .placement-above or .placement-below to pick the side. */
.site-toast-anchored {
    position: fixed;
    z-index: 99998;
    pointer-events: auto;
    background: #1f1f23;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.6rem 0.8rem;
    color: var(--text);
    font-size: 0.88rem;
    line-height: 1.35;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
    max-width: min(280px, calc(100vw - 1rem));
    overflow-wrap: anywhere;
    word-break: break-word;
    transform: scale(0.92) translateY(6px);
    opacity: 0;
    transition: transform 0.18s ease, opacity 0.18s ease;
}
.site-toast-anchored.is-visible { transform: scale(1) translateY(0); opacity: 1; }
.site-toast-anchored.is-leaving { transform: scale(0.92) translateY(-6px); opacity: 0; }
.site-toast-anchored.placement-above { transform-origin: bottom center; }
.site-toast-anchored.placement-below { transform-origin: top center; }
.site-toast-anchored.placement-above::after,
.site-toast-anchored.placement-below::after {
    content: '';
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
}
.site-toast-anchored.placement-above::after {
    bottom: -6px;
    border-top: 6px solid var(--border);
}
.site-toast-anchored.placement-below::after {
    top: -6px;
    border-bottom: 6px solid var(--border);
}

/* Inline text-formatting toolbar. Positioned by JS in viewport-fixed
   coordinates: centered above the current selection, flipping below when
   there's no room above. */
.chat-fmt-toolbar {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 400;
    display: inline-flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 0.15rem;
    background: #0a0a0a;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
    width: fit-content;
}
.chat-fmt-toolbar[hidden] { display: none; }
.chat-fmt-row {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

.chat-fmt-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.8rem;
    height: 1.8rem;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    border-radius: 0.25rem;
    cursor: pointer;
    transition: color 0.12s, background 0.12s;
}
.chat-fmt-btn:hover {
    color: #fff;
    background: rgba(124, 58, 237, 0.18);
}
.chat-fmt-btn:active {
    background: rgba(124, 58, 237, 0.3);
}

/* Text-label variant of .chat-fmt-btn (H1 / H2 / H3 / -#). Same chip
   shape, just typographic labels instead of an SVG. */
.chat-fmt-btn-text { font-weight: 700; }
.chat-fmt-text {
    font-size: 0.78rem;
    letter-spacing: 0.02em;
    line-height: 1;
}
.chat-fmt-text-sm { font-size: 0.7rem; }

/* Rendered markdown inside message bodies. Defaults for <strong>, <em>, <s>,
   <u> are fine but we nudge them so they match the chat typography. */
.chat-message-content strong { font-weight: 700; }
.chat-message-content em     { font-style: italic; }
.chat-message-content s      { text-decoration-thickness: 1px; opacity: 0.85; }
.chat-message-content u      { text-decoration-thickness: 1px; text-underline-offset: 2px; }

/* Mirror-only rendering: formatting MUST NOT change character widths or the
   caret in the transparent textarea layer drifts out of alignment.
   - Bold: fake it with text-shadow (doubles the stroke visually without
     touching font metrics) instead of real font-weight:700.
   - Italic: skip - italic variants ship slightly different advance widths
     and would drift after a few characters. Just dim/tint instead.
   - Strikethrough + underline: pure text-decoration, no metric change.   */
.chat-textbox-mirror strong {
    font-weight: inherit;
    text-shadow: 0.55px 0 0 currentColor, -0.55px 0 0 currentColor;
}
.chat-textbox-mirror em {
    font-style: inherit;
    color: #e0d7ff;          /* subtle tint to indicate italic without metric change */
}
.chat-textbox-mirror s { text-decoration: line-through; text-decoration-thickness: 1px; opacity: 0.85; }
.chat-textbox-mirror u { text-decoration: underline;    text-decoration-thickness: 1px; text-underline-offset: 2px; }

/* Mirror-only: dim the literal markdown markers (**, *, __, ~~, ||) so the
   user sees what they typed, but the formatted text between them dominates
   visually. No effect on rendered message rows - those strip markers out. */
.chat-textbox-mirror .md-marker {
    color: var(--text-muted);
    opacity: 0.55;
    font-weight: 400;
    font-style: normal;
    text-decoration: none;
}

/* Spoiler in a rendered message: a tinted block with blurred text so the
   shape of the content is visible (readers know it's a spoiler to click),
   but the actual words are unreadable until revealed.
   - Use `filter: blur()` on an inner wrapper instead of `color: transparent`
     so the text is present in the flow and reads as "masked text".
   - The ::before pseudo-element covers any residual edge artifacts from
     blur bleed with a subtle tinted overlay. */
/* @-mention pill: purple-tinted link that still reads as clickable.
   Applies both inside message bodies and inside the mirror preview
   (same class). */
.md-mention {
    color: #c4b5fd;
    background: rgba(124, 58, 237, 0.22);
    padding: 0 0.3rem;
    border-radius: 0.25rem;
    text-decoration: none;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.md-mention:hover,
.md-mention:focus-visible {
    background: rgba(124, 58, 237, 0.4);
    color: #fff;
    outline: none;
}
.md-mention-everyone {
    color: #fbbf24;
    background: rgba(251, 191, 36, 0.18);
    cursor: default;
}

.chat-message-content .md-spoiler {
    position: relative;
    background: rgba(124, 58, 237, 0.18);
    color: inherit;
    border-radius: 0.25rem;
    padding: 0 0.25rem;
    cursor: pointer;
    user-select: none;
    transition: background 0.15s, filter 0.15s;
    /* The text itself gets blurred. Keep color so the reader sees a hint of
       the length + shape of what's hidden. */
    filter: blur(4px);
    /* Blur bleeds past the box; clip so neighbouring text stays crisp. */
    -webkit-mask-image: linear-gradient(#000, #000);
            mask-image: linear-gradient(#000, #000);
}
.chat-message-content .md-spoiler:hover {
    background: rgba(124, 58, 237, 0.28);
}
.chat-message-content .md-spoiler.is-revealed {
    background: rgba(255, 255, 255, 0.06);
    cursor: auto;
    user-select: text;
    filter: none;
}
.chat-message-content .md-spoiler:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 1px;
}

/* Composer mirror: the user is authoring this text, so show them the
   contents with a faint tinted background instead of the opaque block. */
.chat-textbox-mirror .md-spoiler {
    background: rgba(255, 255, 255, 0.08);
    color: var(--text);
    border-radius: 0.25rem;
    padding: 0 0.1rem;
}

/* Fenced code blocks. Rendered messages get the full styled <pre>;
   the composer mirror gets a lighter monospace pass so the user can
   still see the exact ``` markers they typed (Discord-style).
   highlight.js drops a `.hljs` class on the inner <code> and ships
   its own colour palette via the atom-one-dark stylesheet loaded in
   the layout - we only style the surrounding chrome. */
.chat-message-content pre.md-codeblock {
    margin: 0.25rem 0;
    padding: 0.6rem 0.8rem;
    background: #1f2329;
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 6px;
    overflow-x: auto;
    font-size: 0.85rem;
    line-height: 1.45;
    max-width: 100%;
}
.chat-message-content pre.md-codeblock code.hljs {
    background: transparent;
    padding: 0;
    font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
    color: #e5e7eb;
    white-space: pre;
    display: block;
}
.chat-message-content pre.md-codeblock[data-lang]::before {
    content: attr(data-lang);
    display: block;
    font-size: 0.7rem;
    color: rgba(255, 255, 255, 0.45);
    text-transform: lowercase;
    letter-spacing: 0.04em;
    margin-bottom: 0.35rem;
    font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
}
@media (max-width: 768px) {
    .chat-message-content pre.md-codeblock {
        font-size: 0.8rem;
        padding: 0.5rem 0.6rem;
    }
}

/* Composer mirror: keep the code body monospace + faintly tinted so
   it visually reads as "this is code" while the ``` markers still
   render through .md-marker styling. White-space inheritance from
   .chat-textbox-mirror (pre-wrap) keeps the chars aligned with the
   underlying textarea. */
.chat-textbox-mirror .md-codeblock-preview {
    font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
    background: rgba(255, 255, 255, 0.05);
    border-radius: 3px;
    padding: 0 0.15rem;
}

/* Discord-style headings inside message bodies. Tighter margins than
   standalone <h*> defaults so a heading sandwiched between regular
   chat lines doesn't blow the row apart. */
.chat-message-content .md-heading {
    margin: 0.3rem 0 0.15rem;
    line-height: 1.25;
    font-weight: 700;
    color: var(--text);
}
.chat-message-content .md-h1 { font-size: 1.5rem; }
.chat-message-content .md-h2 { font-size: 1.25rem; }
.chat-message-content .md-h3 { font-size: 1.05rem; }
.chat-message-content .md-heading:first-child { margin-top: 0; }

/* Subtext: small + dim. Pairs with `-#` prefix. */
.chat-message-content .md-subtext {
    display: block;
    font-size: 0.78rem;
    color: var(--text-muted);
    line-height: 1.35;
}

/* Single-line / grouped blockquote. Vertical bar on the left, slight
   padding, no background tint so it stays readable in the bubble. */
.chat-message-content .md-quote {
    border-left: 3px solid rgba(255, 255, 255, 0.25);
    padding: 0.05rem 0 0.05rem 0.55rem;
    margin: 0.15rem 0;
    color: var(--text);
    /* Preserve any embedded newlines from grouped `> a\n> b` runs. */
    white-space: pre-wrap;
}

/* Inline `code` spans inside message bodies. Same monospace family as
   the fenced blocks but smaller padding so they sit naturally inline. */
.chat-message-content .md-inline-code {
    font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 3px;
    padding: 0 0.3rem;
    font-size: 0.88em;
    color: #f3f4f6;
    word-break: break-word;
}

/* Composer mirror equivalents - same family/colour as the rendered
   versions but NO size changes, so the chars line up with the
   textarea behind. The .md-marker class above already dims the
   prefix tokens. */
.chat-textbox-mirror .md-h1-preview,
.chat-textbox-mirror .md-h2-preview,
.chat-textbox-mirror .md-h3-preview {
    font-weight: 700;
    color: var(--text);
}
.chat-textbox-mirror .md-subtext-preview {
    color: var(--text-muted);
}
.chat-textbox-mirror .md-quote-preview {
    color: var(--text);
}
.chat-textbox-mirror .md-inline-code-preview {
    font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
    background: rgba(255, 255, 255, 0.05);
    border-radius: 3px;
    padding: 0 0.15rem;
}

/* Markdown links rendered in a message body. External-link click handler
   intercepts these to show a "leaving Madrigal Labs" warning. */
.chat-message-content a.md-link {
    color: #a78bfa;
    text-decoration: underline;
    text-underline-offset: 2px;
    word-break: break-word;
}
.chat-message-content a.md-link:hover {
    color: #c4b5fd;
}

/* Preview in the composer mirror: link text uses the same brand-purple as
   rendered messages so the user can tell at a glance their `[text](url)`
   is going to be a link. URL half uses .md-marker for a muted look. */
.chat-textbox-mirror .md-link {
    color: #a78bfa;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.chat-textbox-mirror .md-url {
    text-decoration: none;
}

/* Link preview (embed) card rendered under a message body. Discord-style:
   thin left accent, text on the left, thumbnail on the right, stacks on
   narrow viewports. Up to 4 per message. */
.chat-message-embeds {
    margin-top: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    max-width: 34rem;
}

.chat-embed {
    display: flex;
    align-items: stretch;
    gap: 0.85rem;
    padding: 0.65rem 0.85rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-left: 3px solid var(--brand);
    border-radius: 0.4rem;
    overflow: hidden;
}

.chat-embed-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.chat-embed-site {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.72rem;
    color: var(--text-muted);
    letter-spacing: 0.01em;
}
.chat-embed-favicon {
    width: 14px;
    height: 14px;
    border-radius: 3px;
    object-fit: cover;
}

.chat-embed-title {
    font-size: 0.92rem;
    font-weight: 600;
    color: #a78bfa;
    text-decoration: none;
    line-height: 1.3;
    word-break: break-word;
}
.chat-embed-title:hover { text-decoration: underline; color: #c4b5fd; }

.chat-embed-desc {
    font-size: 0.85rem;
    color: var(--text-muted);
    line-height: 1.45;
    /* Clamp to a handful of lines so a huge description can't dominate. */
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}

.chat-embed-thumb {
    flex-shrink: 0;
    display: block;
    line-height: 0;
    border-radius: 0.35rem;
    overflow: hidden;
    max-width: 5rem;
    max-height: 5rem;
}
.chat-embed-thumb img {
    width: 100%;
    height: 100%;
    max-width: 5rem;
    max-height: 5rem;
    object-fit: cover;
    display: block;
}

/* Narrow viewports → stack the thumbnail above the text. */
@media (max-width: 520px) {
    .chat-embed { flex-direction: column; }
    .chat-embed-thumb {
        max-width: 100%;
        max-height: 10rem;
    }
    .chat-embed-thumb img {
        max-width: 100%;
        max-height: 10rem;
    }
}

/* Big media embeds (direct image URLs, og:type=photo, og:video). No card
   chrome - just the media, large. Shares max-size envelope with attachments. */
.chat-embed-media {
    display: block;
    padding: 0;
    background: transparent;
    border: 0;
    overflow: hidden;
}
.chat-embed-image,
.chat-att.chat-embed-image {
    /* Link wrapper: must use the same 30 / 22 rem envelope as the
       inner img. Without the combined-class selector, `.chat-att`
       (defined later in the file with the same specificity) wins
       and pins the link to 22 / 16 rem - then its overflow: hidden
       clips the legitimately-larger image inside. min(...) clamp
       on mobile so a 30rem ceiling never exceeds the column width
       and the image never gets cut by the chat-messages overflow. */
    position: relative;
    display: block;
    max-width: min(30rem, 100%);
    max-height: 22rem;
    line-height: 0;
    border-radius: 0.35rem;
    overflow: hidden;
}
/* Bare media URLs in chat message bodies render INLINE at the URL's
   position (instead of as a separate OG card after the body). The
   parent .chat-embed-image / .chat-embed-video rules above already
   set `display: block` + max sizes, so the image breaks onto its own
   line at the URL's spot. We just nudge a small vertical margin so
   it doesn't sit flush against surrounding text, and add a content
   break so any text that ran on the same source line lands BELOW
   the image instead of awkwardly to its right. */
.chat-inline-media-image,
.chat-inline-media-video {
    margin: 0.35rem 0;
    clear: both;
}
/* Selector must out-specificity `.chat-att img` (which forces opacity:0 and
   relies on a `.chat-att-wrap.is-loaded` sibling class to fade in - absent
   from the embed markup). Combining both classes (.chat-att.chat-embed-image)
   bumps specificity from 0,2,0 to 0,3,0 so the image actually renders. */
.chat-att.chat-embed-image img {
    display: block;
    max-width: min(30rem, 100%);
    max-height: 22rem;
    width: auto;
    height: auto;
    object-fit: contain;
    /* Transparent backdrop so PNG/WebP transparency stays transparent
       and shows the chat background through, instead of the old #0a0a0a
       letterbox that made transparent images look like they had a dark
       sheet pasted behind them. */
    background: transparent;
    opacity: 1;
}
.chat-embed-video {
    display: block;
    max-width: min(30rem, 100%);
    max-height: 22rem;
    /* width:auto (not 100%) so the browser preserves the video's
       native aspect ratio inside the max envelope. width:100% with
       a max-height ceiling forced the box to a non-native aspect
       and the default object-fit:fill stretched / cut the frame. */
    width: auto;
    height: auto;
    object-fit: contain;
    border-radius: 0.35rem;
    /* Transparent so the chat background shows through any letterbox
       gap rather than a hard black bar around vertical / odd-ratio
       embed videos. */
    background: transparent;
}

/* YouTube / Vimeo iframe embed: fixed 16:9 aspect ratio, responsive. */
.chat-embed-iframe {
    position: relative;
    width: 34rem;
    max-width: 100%;
    aspect-ratio: 16 / 9;
    border-radius: 0.35rem;
    overflow: hidden;
    background: #000;
}
.chat-embed-iframe iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}
/* Tenor embed iframe: smaller + closer-to-square because most
   Tenor gifs aren't 16:9 video clips. The iframe self-letterboxes
   any non-square gif inside this frame, so we get a consistent
   tile size without needing the gif's intrinsic dimensions. */
.chat-embed-iframe.chat-embed-tenor-frame {
    width: 22rem;
    aspect-ratio: 1 / 1;
    background: rgba(255, 255, 255, 0.02);
}
.chat-embed-play {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 3rem;
    height: 3rem;
    margin: -1.5rem 0 0 -1.5rem;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

/* URL-prompt modal: reuses .modal / .modal-card-sm infrastructure. */
.chat-link-prompt .chat-link-input {
    width: 100%;
    margin: 0.2rem 0 1rem;
    padding: 0.55rem 0.75rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    color: var(--text);
    font: inherit;
}
.chat-link-prompt .chat-link-input:focus {
    outline: none;
    border-color: var(--brand);
}

.chat-input-row {
    display: flex;
    gap: 0.5rem;
    align-items: flex-end;
}

/* Send button with the live character counter nested inside. Flex-column
   centers the "Send" label vertically when the counter is hidden, and
   stacks label + counter when typing. A fixed min-height keeps the
   button's footprint constant whether or not the counter is visible -
   typing in the textarea doesn't resize the composer row. */
.chat-send-btn {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    flex-shrink: 0;
    min-width: 4.5rem;
    min-height: 2.65rem;
    padding-top: 0.35rem;
    padding-bottom: 0.35rem;
    line-height: 1;
}
.chat-send-label {
    font-size: 0.95rem;
    font-weight: 500;
    line-height: 1.05;
}

/* Character counter rendered inside the Send button. Tiny, white at 80%
   opacity by default; amber within 100 of the cap; red at it. Visible as
   soon as the user types one character. */
.chat-char-count {
    font-size: 0.58rem;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: rgba(255, 255, 255, 0.8);
    user-select: none;
    transition: color 0.15s;
}
.chat-char-count[hidden] { display: none; }
.chat-char-count.is-warn { color: #fde047; }
.chat-char-count.is-over { color: #fecaca; font-weight: 600; }

/* Shared styles for both "formattable" textboxes (composer + inline edit)
   and their mirrors. The textarea is transparent-text; the mirror behind
   it paints the live-rendered markdown. Selection highlight is drawn by
   the textarea on top, so you still see what you're highlighting. */
.chat-textbox-stack {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.375rem;
    transition: border-color 0.12s;
    /* Match the textarea's sizing envelope so the stack grows with the text. */
    min-height: 2.5rem;
    max-height: 10rem;
    overflow: hidden; /* scroll happens inside the textarea */
}
.chat-textbox-stack:focus-within { border-color: var(--brand); }

/* Mirror sits underneath the textarea and paints the rendered markdown. */
.chat-textbox-mirror {
    position: absolute;
    inset: 0;
    padding: 0.6rem 0.85rem;
    font: inherit;
    line-height: 1.45;
    color: var(--text);
    white-space: pre-wrap;
    word-wrap: break-word;
    overflow: hidden;
    pointer-events: none;
    user-select: none;
    box-sizing: border-box;
    /* Feels nicer when the live preview animates weights slightly. */
    -webkit-font-smoothing: antialiased;
}

/* Textarea must have transparent bg + transparent text so the mirror shows
   through, plus a visible caret/selection. Padding + line-height MUST match
   the mirror exactly or the two layers misalign.
   Scrollbar is intentionally hidden: a visible browser scrollbar would
   shrink the textarea's inner content width by ~15px the moment the
   user crosses the max-height threshold, while the mirror (overflow:
   hidden, no scrollbar) keeps its full width. The two layers would then
   wrap text at different columns and the caret drifts to the right of
   the rendered glyphs. Hiding the scrollbar keeps both content widths
   identical at every state. The textarea still scrolls via keyboard /
   wheel / touch so long content remains accessible. */
.chat-textbox {
    position: relative;
    z-index: 1;
    display: block;
    width: 100%;
    padding: 0.6rem 0.85rem;
    background: transparent;
    border: 0;
    color: transparent;
    caret-color: var(--text);
    font: inherit;
    line-height: 1.45;
    resize: none;
    overflow-y: auto;
    min-height: 2.5rem;
    max-height: 10rem;
    box-sizing: border-box;
    scrollbar-width: none;       /* Firefox */
    -ms-overflow-style: none;    /* Edge legacy */
}
.chat-textbox::-webkit-scrollbar { width: 0; height: 0; }
.chat-textbox:focus { outline: none; }
/* Selection still highlights normally despite the transparent text. */
.chat-textbox::selection { color: transparent; background: rgba(124, 58, 237, 0.35); }

/* Attach button on the left of the composer. */
.chat-attach-btn {
    flex-shrink: 0;
    width: 2.5rem;
    height: 2.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.375rem;
    color: var(--text-muted);
    cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.chat-attach-btn:hover {
    color: var(--text);
    border-color: var(--brand);
    background: rgba(124, 58, 237, 0.08);
}

/* "+" popover that opens above the chat-plus-btn. Same dark surface as
   the rest of the chat chrome with a small pop animation. Items are
   touch-friendly (>= 40 px) so mobile taps work. */
.chat-plus-menu {
    position: fixed;
    z-index: 70;
    min-width: 220px;
    background: #181a1f;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 10px 32px rgba(0, 0, 0, 0.45);
    padding: 0.35rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.chat-plus-menu[hidden] { display: none; }
.chat-plus-menu-item {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 0.92rem;
    padding: 0.55rem 0.7rem;
    border-radius: 0.35rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    text-align: left;
    min-height: 40px;
    transition: background 0.12s, color 0.12s;
}
.chat-plus-menu-item:hover,
.chat-plus-menu-item:focus-visible {
    background: rgba(124, 58, 237, 0.18);
    color: #fff;
    outline: none;
}
.chat-plus-menu-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    width: 22px;
    flex-shrink: 0;
}
.chat-plus-menu-item:hover .chat-plus-menu-icon,
.chat-plus-menu-item:focus-visible .chat-plus-menu-icon {
    color: #fff;
}

/* "Choose from your storage" picker. Lazy-mounted modal that lists the
   viewer's previously-uploaded attachments so they can re-attach without
   re-uploading. Mirrors the marketplace storage picker visually but is
   self-contained inside chat-client.js. Mobile: full-screen below 720px. */
.chat-storage-modal {
    position: fixed; inset: 0;
    z-index: 9100;
    display: flex; align-items: center; justify-content: center;
}
.chat-storage-modal[hidden] { display: none; }
.chat-storage-backdrop {
    position: absolute; inset: 0;
    background: rgba(8, 8, 12, 0.72);
    backdrop-filter: blur(2px);
}
.chat-storage-card {
    position: relative;
    width: min(880px, calc(100vw - 2rem));
    max-height: calc(100dvh - 3rem);
    display: flex; flex-direction: column;
    background: #15151a;
    border: 1px solid #27272a;
    border-radius: 0.75rem;
    box-shadow: 0 30px 60px -20px rgba(0,0,0,0.6);
    overflow: hidden;
}
.chat-storage-head {
    display: flex; align-items: center; gap: 0.85rem;
    padding: 0.85rem 1rem;
    border-bottom: 1px solid #27272a;
}
.chat-storage-head h2 {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 700;
    color: #fff;
    flex: 1;
}
.chat-storage-filters {
    display: inline-flex;
    gap: 0.85rem;
    color: var(--text-muted, #a1a1aa);
    font-size: 0.82rem;
}
.chat-storage-filters label {
    display: inline-flex; align-items: center; gap: 0.35rem;
    cursor: pointer;
}
.chat-storage-close {
    width: 2rem; height: 2rem;
    background: transparent;
    border: 0;
    color: var(--text-muted, #a1a1aa);
    font-size: 1.4rem; line-height: 1;
    border-radius: 0.4rem;
    cursor: pointer;
}
.chat-storage-close:hover { color: #fff; background: rgba(255,255,255,0.06); }
.chat-storage-body {
    padding: 0.85rem 1rem;
    overflow-y: auto;
    flex: 1;
    min-height: 12rem;
}
.chat-storage-empty {
    text-align: center;
    color: var(--text-muted, #a1a1aa);
    padding: 3rem 1rem;
    font-size: 0.9rem;
}
.chat-storage-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 0.6rem;
}
.chat-storage-tile {
    position: relative;
    display: flex; flex-direction: column;
    background: #0f0f12;
    border: 1px solid #27272a;
    border-radius: 0.55rem;
    padding: 0;
    cursor: pointer;
    overflow: hidden;
    text-align: left;
    color: inherit;
    transition: border-color 0.12s, transform 0.12s;
}
.chat-storage-tile:hover { border-color: #52525b; }
.chat-storage-tile.is-selected {
    border-color: #8b5cf6;
    box-shadow: 0 0 0 1px #8b5cf6;
}
.chat-storage-media {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1;
    background: #0a0a0d;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
}
.chat-storage-media img,
.chat-storage-media video {
    width: 100%; height: 100%; object-fit: cover; display: block;
}
.chat-storage-media.is-file .chat-attach-fileicon {
    color: var(--text-muted, #a1a1aa);
    display: inline-flex; flex-direction: column; align-items: center; gap: 0.3rem;
    font-size: 0.72rem;
}
.chat-storage-media .chat-attach-play {
    position: absolute; right: 0.4rem; bottom: 0.4rem;
    background: rgba(0,0,0,0.55); color: #fff;
    border-radius: 999px; padding: 0.2rem 0.35rem;
    display: inline-flex; align-items: center; justify-content: center;
}
.chat-storage-meta {
    display: flex; flex-direction: column;
    padding: 0.45rem 0.55rem;
    gap: 0.15rem;
}
.chat-storage-name {
    font-size: 0.78rem;
    color: #e4e4e7;
    overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.chat-storage-dim {
    font-size: 0.7rem;
    color: var(--text-muted, #71717a);
    font-variant-numeric: tabular-nums;
}
.chat-storage-check {
    position: absolute; top: 0.4rem; right: 0.4rem;
    width: 1.4rem; height: 1.4rem;
    border-radius: 999px;
    background: rgba(8,8,12,0.65);
    color: transparent;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid rgba(255,255,255,0.18);
}
.chat-storage-tile.is-selected .chat-storage-check {
    background: #8b5cf6; color: #fff; border-color: #8b5cf6;
}
.chat-storage-foot {
    display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
    padding: 0.7rem 1rem;
    border-top: 1px solid #27272a;
    background: #111114;
}
.chat-storage-summary {
    color: var(--text-muted, #a1a1aa);
    font-size: 0.78rem;
    flex: 1;
    min-width: 8rem;
}
.chat-storage-pager,
.chat-storage-cancel,
.chat-storage-confirm {
    background: #1f1f23;
    color: #e4e4e7;
    border: 1px solid #27272a;
    padding: 0.45rem 0.85rem;
    border-radius: 0.4rem;
    font: inherit;
    cursor: pointer;
}
.chat-storage-pager:hover:not(:disabled),
.chat-storage-cancel:hover { border-color: #52525b; color: #fff; }
.chat-storage-pager:disabled { opacity: 0.4; cursor: not-allowed; }
.chat-storage-confirm {
    background: #6366f1;
    border-color: #6366f1;
    color: #fff;
}
.chat-storage-confirm:disabled { opacity: 0.45; cursor: not-allowed; }
.chat-storage-confirm:hover:not(:disabled) { background: #5a5fe8; }
@media (max-width: 720px) {
    .chat-storage-card {
        width: 100vw; max-width: 100vw;
        max-height: 100dvh;
        border-radius: 0;
    }
    .chat-storage-grid {
        grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    }
}

/* Codeblock modal: themed prompt with a generous textarea + language
   selector. Sized larger than confirmDialog because users will paste
   multi-line code into it. */
.chat-codeblock-modal .modal-card {
    width: min(640px, calc(100vw - 2rem));
}
.chat-codeblock-dialog {
    text-align: left;
}
.chat-codeblock-dialog .confirm-title { text-align: left; }
.chat-codeblock-dialog .confirm-message {
    text-align: left;
    margin-bottom: 0.75rem;
}
.chat-codeblock-lang-label {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 0.85rem;
    color: var(--text-muted);
    margin-bottom: 0.6rem;
}
.chat-codeblock-lang {
    appearance: none;
    -webkit-appearance: none;
    background: #0f0f0f;
    border: 1px solid var(--border);
    color: var(--text);
    font: inherit;
    font-size: 0.9rem;
    padding: 0.4rem 0.6rem;
    border-radius: 0.35rem;
    cursor: pointer;
    flex: 0 0 auto;
}
.chat-codeblock-lang:focus {
    outline: none;
    border-color: var(--brand);
}
.chat-codeblock-input {
    width: 100%;
    min-height: 220px;
    max-height: 50dvh;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    font: 0.88rem/1.5 "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace;
    padding: 0.7rem 0.8rem;
    resize: vertical;
    box-sizing: border-box;
    white-space: pre;
    overflow: auto;
    tab-size: 4;
    -moz-tab-size: 4;
}
.chat-codeblock-input:focus {
    outline: none;
    border-color: var(--brand);
}
@media (max-width: 768px) {
    .chat-codeblock-input { min-height: 160px; font-size: 0.82rem; }
}

/* Emoji button - mirrors .chat-attach-btn styling so the composer row has
   a consistent look across the left-side helpers. */
.chat-emoji-btn {
    flex-shrink: 0;
    width: 2.5rem;
    height: 2.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.375rem;
    color: var(--text-muted);
    cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.chat-emoji-btn:hover {
    color: var(--text);
    border-color: var(--brand);
    background: rgba(124, 58, 237, 0.08);
}

/* -------- Emoji picker popover -------- */
.chat-emoji-popover {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 420;
    width: 22rem;
    max-width: 94vw;
    height: 24rem;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    background: #0a0a0a;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
    overflow: hidden;
}
.chat-emoji-popover[hidden] { display: none; }
/* When hosted inside the unified picker shell, the panel is just a
   flex-fill child; the shell owns the position + outer chrome. */
.chat-emoji-popover.is-in-picker {
    position: static;
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    flex: 1 1 auto;
    min-height: 0;
}

/* ---- Combined emoji + GIF picker shell ---- */
.chat-picker-popover {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 420;
    width: 26rem;
    max-width: 94vw;
    height: 30rem;
    max-height: 75vh;
    display: flex;
    flex-direction: column;
    background: #0a0a0a;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    box-shadow: 0 12px 36px rgba(0, 0, 0, 0.6);
    overflow: hidden;
}
.chat-picker-popover[hidden] { display: none; }
.chat-picker-tabs {
    display: flex;
    gap: 0.25rem;
    padding: 0.4rem 0.4rem 0;
    border-bottom: 1px solid var(--border);
    background: #050505;
    flex-shrink: 0;
    align-items: center;
}
/* Close (X) button on the picker. Right-aligned via auto-margin so
   it sits at the end of the tab row regardless of how many tabs are
   present. 40 px tap target. Always visible (desktop + mobile) - on
   mobile the picker covers most of the viewport and the user
   otherwise had no obvious dismiss affordance. */
.chat-picker-close {
    margin-left: auto;
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    width: 40px;
    height: 40px;
    border-radius: 0.4rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.chat-picker-close:hover,
.chat-picker-close:focus-visible,
.chat-picker-close:active {
    color: var(--text);
    background: rgba(255, 255, 255, 0.06);
    outline: none;
}
.chat-picker-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: transparent;
    border: 0;
    color: #a1a1aa;
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0.55rem 0.85rem;
    border-radius: 0.45rem 0.45rem 0 0;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.chat-picker-tab:hover { color: #e4e4e7; background: rgba(124, 58, 237, 0.08); }
.chat-picker-tab.is-active {
    color: #fff;
    background: #0a0a0a;
    border-bottom-color: #7c3aed;
}
.chat-picker-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    position: relative;
}

.chat-emoji-search-row {
    padding: 0.5rem;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.chat-emoji-search {
    width: 100%;
    padding: 0.4rem 0.55rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.3rem;
    color: var(--text);
    font: inherit;
    font-size: 0.85rem;
}
.chat-emoji-search:focus {
    outline: none;
    border-color: var(--brand);
}

.chat-emoji-tabs {
    display: flex;
    gap: 2px;
    padding: 0.25rem 0.4rem;
    border-bottom: 1px solid var(--border);
    background: #0c0c0c;
    overflow-x: auto;
    scrollbar-width: none;
    flex-shrink: 0;
}
.chat-emoji-tabs::-webkit-scrollbar { display: none; }
.chat-emoji-tab {
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    border: 0;
    background: transparent;
    border-radius: 0.3rem;
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s;
}
.chat-emoji-tab:hover { background: rgba(255, 255, 255, 0.06); }

.chat-emoji-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0.5rem;
}

.chat-emoji-section-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    margin: 0.35rem 0.25rem 0.35rem;
}

.chat-emoji-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(2rem, 1fr));
    gap: 2px;
}

.chat-emoji-cell {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1;
    border: 0;
    background: transparent;
    border-radius: 0.3rem;
    cursor: pointer;
    font-size: 1.3rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: background 0.1s;
}
.chat-emoji-cell:hover { background: rgba(124, 58, 237, 0.15); }
.chat-emoji-char {
    pointer-events: none;
    /* Fallback font stack if Twemoji hasn't loaded. */
    font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif;
}
/* Twemoji swaps the native char for <img class="emoji">. Size matches the
   surrounding text so inline flow stays tidy, slightly lifted to line up
   with the typographic baseline.
   NOTE: do NOT set `font-size: 0` here - em units on height/width would
   collapse the image to 0×0. If the svg 404s the alt text shows at the
   surrounding font size, which is a reasonable fallback. */
img.emoji {
    height: 1.2em;
    width:  1.2em;
    margin: 0 0.05em 0 0.05em;
    vertical-align: -0.25em;
    display: inline-block;
}

/* Jumbomoji: Discord-style large render for messages that are nothing but
   emojis. Opt-in per message via JS (.chat-message-content.is-jumbomoji).
   Extra vertical breathing room so tall glyphs don't crop the row. */
.chat-message-content.is-jumbomoji {
    padding: 0.25rem 0;
    line-height: 1;
}
.chat-message-content.is-jumbomoji img.emoji {
    height: 3rem;
    width:  3rem;
    margin: 0.1rem 0.15rem;
    vertical-align: middle;
}
/* Inside the picker cells, scale up so the images fill the cell properly. */
.chat-emoji-cell img.emoji {
    height: 1.3rem;
    width:  1.3rem;
    margin: 0;
    vertical-align: middle;
}
/* Inside tab buttons, same treatment. */
.chat-emoji-tab img.emoji {
    height: 1.15rem;
    width:  1.15rem;
    margin: 0;
    vertical-align: middle;
}

.chat-emoji-star {
    position: absolute;
    top: 1px;
    right: 2px;
    font-size: 0.6rem;
    color: rgba(255, 255, 255, 0.15);
    line-height: 1;
    pointer-events: auto;
    transition: color 0.12s;
}
.chat-emoji-cell:hover .chat-emoji-star { color: rgba(255, 255, 255, 0.4); }
.chat-emoji-cell.is-fav .chat-emoji-star { color: #fbbf24; }
.chat-emoji-star:hover { color: #fde047; }

.chat-emoji-empty {
    grid-column: 1 / -1;
    padding: 0.5rem;
    font-size: 0.8rem;
    color: var(--text-muted);
    text-align: center;
}

/* Preview chips shown above the composer once files are staged. */
.chat-attach-previews {
    list-style: none;
    margin: 0;
    padding: 0.25rem 0 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}
.chat-attach-previews[hidden] { display: none; }

.chat-attach-chip {
    position: relative;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: 0.35rem;
    overflow: hidden;
    background: #0f0f0f;
    border: 1px solid var(--border);
}

.chat-attach-chip img,
.chat-attach-chip video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Video chip: overlay a play badge on top of the first-frame poster so you
   can tell at a glance it's a video, not an image. */
.chat-attach-chip.is-video .chat-attach-play {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1.6rem;
    height: 1.6rem;
    margin: -0.8rem 0 0 -0.8rem;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.chat-attach-chip.is-uploading img { opacity: 0.65; }

/* Upload progress bar - width is driven by JS on each `progress` event. */
.chat-attach-progress {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 3px;
    width: 0;
    background: var(--brand);
    box-shadow: 0 0 6px rgba(124, 58, 237, 0.7);
    transition: width 0.12s linear;
    pointer-events: none;
}
.chat-attach-chip:not(.is-uploading):not(.is-done):not(.is-processing) .chat-attach-progress { display: none; }

/* Bytes are fully uploaded but the server is still Imagick-processing the
   image. Show an indeterminate sliding bar + a dimmed "Processing…" label
   so the user knows work is still happening (can be several seconds for a
   large animated GIF). */
.chat-attach-chip.is-processing img { opacity: 0.55; }
.chat-attach-chip.is-processing .chat-attach-progress {
    width: 40% !important;
    background: linear-gradient(90deg, transparent, var(--brand), transparent);
    box-shadow: none;
    animation: chat-attach-processing 1.1s linear infinite;
}
.chat-attach-chip.is-processing .chat-attach-err {
    background: rgba(124, 58, 237, 0.85);
    font-weight: 500;
}
.chat-attach-chip.is-processing::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1.25rem;
    height: 1.25rem;
    margin: -0.625rem 0 0 -0.625rem;
    border: 2px solid rgba(255,255,255,0.2);
    border-top-color: #fff;
    border-radius: 50%;
    animation: settings-spin 0.8s linear infinite;
    pointer-events: none;
}

/* × must stay clickable even through the is-uploading overlay + processing
   spinner. Explicit z-index puts it above both pseudo-elements. */
.chat-attach-remove { z-index: 2; }
@keyframes chat-attach-processing {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}
.chat-attach-chip.is-done .chat-attach-progress {
    /* Briefly flash a full green bar to confirm success, then fade. */
    background: #10b981;
    box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
    animation: chat-attach-done 0.6s ease-out forwards;
}
@keyframes chat-attach-done {
    0%   { opacity: 1; }
    60%  { opacity: 1; }
    100% { opacity: 0; }
}

/* Persistent green "ready to send" check, shown once a file's upload finishes.
   Clearer than the brief progress-bar flash, and it tells the user a held send
   (Enter pressed while the file was still uploading) can now go through. Only
   present in the DOM while the chip is .is-done. */
.chat-attach-check {
    position: absolute;
    bottom: 0.2rem;
    right: 0.2rem;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
    background: #10b981;
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.55);
    z-index: 2;
    pointer-events: none;
    animation: chat-attach-check-pop 0.18s ease-out;
}
@keyframes chat-attach-check-pop {
    from { transform: scale(0.4); opacity: 0; }
    to   { transform: scale(1);   opacity: 1; }
}

/* Composer is HOLDING a send until an in-flight upload finishes (the user
   pressed Enter while a file was still uploading). Subtle pulse on the send
   button so it's clear the send is queued, not ignored. The input deliberately
   stays fully usable - unlike .is-sending we do NOT disable pointer events, so
   the user can keep typing; the green chip checks signal when it's ready. */
.chat-input.is-awaiting-upload .chat-send-btn {
    animation: chat-send-waiting 1s ease-in-out infinite;
}
@keyframes chat-send-waiting {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

.chat-attach-chip.is-error {
    border-color: var(--danger);
}
.chat-attach-chip.is-error img { opacity: 0.35; }

.chat-attach-err {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 0.15rem 0.2rem;
    font-size: 0.65rem;
    color: #fff;
    background: rgba(244, 63, 94, 0.9);
    text-align: center;
}

.chat-attach-remove {
    position: absolute;
    top: 0.15rem;
    right: 0.15rem;
    width: 1.2rem;
    height: 1.2rem;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.chat-attach-remove:hover {
    background: var(--danger);
}

/* Rendered attachments inside a chat message. min(Nrem, 100%) caps
   the desktop footprint at the same Nrem value but lets the same
   element shrink to fit the column on mobile where 22rem (~352px) /
   30rem (~480px) / 36rem (~576px) easily exceed a 320-414px viewport
   minus the chat-messages padding. */
.chat-message-attachments {
    margin-top: 0.35rem;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    max-width: min(36rem, 100%);
    min-width: 0;
}

.chat-att {
    display: block;
    border-radius: 0.3rem;
    overflow: hidden;
    line-height: 0;
    max-width: min(22rem, 100%);
    max-height: 16rem;
}

.chat-att-wrap {
    position: relative;
    display: inline-block;
    /* min-width on mobile would push the wrap past the column; clamp
       to whichever is smaller (the design-intent 6rem on desktop, or
       100% on a narrow viewport where 6rem already takes most of the
       row anyway). */
    min-width: min(6rem, 100%);
    min-height: 4rem;
    max-width: 100%;
    line-height: 0;
}

.chat-att img {
    max-width: min(22rem, 100%);
    max-height: 16rem;
    /* contain (not cover) so a tall portrait or wide panoramic
       letterboxes inside the envelope instead of being cropped. The
       max bounds still cap the visible footprint. width/height: auto
       keeps the image at its intrinsic ratio when smaller than caps. */
    width: auto;
    height: auto;
    object-fit: contain;
    display: block;
    opacity: 0;
    transition: opacity 0.18s ease-out;
}
.chat-att-wrap.is-loaded img { opacity: 1; }

/* Placeholder shimmer + spinner shown while the image is still decoding. */
.chat-att-wrap::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
        110deg,
        rgba(255,255,255,0.02) 20%,
        rgba(255,255,255,0.06) 40%,
        rgba(255,255,255,0.02) 60%);
    background-size: 200% 100%;
    animation: chat-att-shimmer 1.2s linear infinite;
    pointer-events: none;
    opacity: 1;
    transition: opacity 0.18s ease-out;
}
.chat-att-wrap.is-loaded::before,
.chat-att-wrap.is-error::before { opacity: 0; }

.chat-att-spinner {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1.5rem;
    height: 1.5rem;
    margin: -0.75rem 0 0 -0.75rem;
    border: 2px solid rgba(255,255,255,0.15);
    border-top-color: var(--brand);
    border-radius: 50%;
    animation: settings-spin 0.8s linear infinite;
    pointer-events: none;
    opacity: 1;
    transition: opacity 0.18s ease-out;
}
.chat-att-wrap.is-loaded .chat-att-spinner,
.chat-att-wrap.is-error   .chat-att-spinner { opacity: 0; }

.chat-att-wrap.is-error::after {
    content: 'failed to load';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.75rem;
    color: var(--danger);
    /* Keep a faint background only for the error state so the message is
       readable on top of whatever was (or wasn't) rendered. */
    background: rgba(26, 10, 10, 0.9);
    line-height: 1.2;
}

@keyframes chat-att-shimmer {
    0%   { background-position:  200% 0; }
    100% { background-position: -200% 0; }
}

/* No hover chrome - images render as-is; clickability is conveyed by cursor. */

/* Inline video attachment: same footprint budget as single images, uses
   native <video controls> UI (play/seek/volume/fullscreen). 30rem
   (~480px) is wider than any phone viewport, so we clamp to 100% on
   mobile while keeping the desktop ceiling. */
.chat-att-video {
    display: block;
    max-width: min(30rem, 100%);
    max-height: 22rem;
    border-radius: 0.3rem;
    overflow: hidden;
    line-height: 0;
}
.chat-att-video-el {
    display: block;
    max-width: min(30rem, 100%);
    max-height: 22rem;
    width: auto;
    height: auto;
    object-fit: contain;
    /* Transparent so vertical / odd-ratio videos letterbox onto the
       chat background instead of a hard black bar. */
    background: transparent;
}

/* Video cell inside a multi-attachment gallery: show the poster frame with
   a centered play badge. Actual playback happens in the lightbox when
   clicked (avoids N videos loading at once). */
.chat-gallery-cell.is-video { position: relative; }
.chat-gallery-cell.is-video video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    opacity: 1;
    pointer-events: none;
    /* Transparent so the gallery cell shows the chat background
       through any seam, matching the image cells (which never had
       a backdrop). */
    background: transparent;
}
.chat-gallery-play {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 2.5rem;
    height: 2.5rem;
    margin: -1.25rem 0 0 -1.25rem;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

/* ------------------------ Multi-image gallery ------------------------ */

/* 2-10 images in a single message render as one clickable grid instead of
   a flex-wrap of separate tiles. Each layout is hand-picked per count for
   a Discord-like feel: single/big for 1, split for 2, 2-up-with-tall-first
   for 3, 2x2 for 4, and a balanced grid for 5-10. */
.chat-gallery {
    display: grid;
    gap: 2px;
    border-radius: 0.4rem;
    overflow: hidden;
    width: fit-content;
    max-width: min(30rem, 100%);
}

.chat-gallery-cell {
    position: relative;
    display: block;
    line-height: 0;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    /* No background - if an image hasn't decoded yet, the gap shows through
       as a thin transparent seam. No border/border-radius on cells so the
       images butt up against each other. */
    aspect-ratio: 1 / 1;
    border: 0;
    border-radius: 0;
    max-width: none;
    max-height: none;
}
.chat-gallery-cell img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.18s ease-out;
    max-width: none;
    max-height: none;
}
.chat-gallery-cell img[data-loading] { opacity: 0; }
.chat-gallery-cell img:not([data-loading]) { opacity: 1; }

/* 2 images: side-by-side, each a landscape rectangle. */
.chat-gallery[data-count="2"] {
    grid-template-columns: 1fr 1fr;
    aspect-ratio: 2 / 1;
}
.chat-gallery[data-count="2"] .chat-gallery-cell { aspect-ratio: auto; }

/* 3 images: tall left image + 2 stacked on the right. */
.chat-gallery[data-count="3"] {
    grid-template-columns: 2fr 1fr;
    grid-template-rows: 1fr 1fr;
    aspect-ratio: 4 / 3;
}
.chat-gallery[data-count="3"] .chat-gallery-cell { aspect-ratio: auto; }
.chat-gallery[data-count="3"] .chat-gallery-cell:nth-child(1) {
    grid-row: 1 / span 2;
}

/* 4 images: clean 2x2. */
.chat-gallery[data-count="4"] {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
    aspect-ratio: 1 / 1;
}
.chat-gallery[data-count="4"] .chat-gallery-cell { aspect-ratio: auto; }

/* 5-6 images: 3 columns. For 5 the first two span the top in larger cells
   via auto placement; we keep the simple 3x2 grid for visual balance. */
.chat-gallery[data-count="5"],
.chat-gallery[data-count="6"] {
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(2, 1fr);
    aspect-ratio: 3 / 2;
}
.chat-gallery[data-count="5"] .chat-gallery-cell,
.chat-gallery[data-count="6"] .chat-gallery-cell { aspect-ratio: auto; }
/* For 5, let the first cell span two columns so we don't get an empty slot. */
.chat-gallery[data-count="5"] .chat-gallery-cell:nth-child(1) {
    grid-column: 1 / span 2;
}

/* 7-8 images: 4 columns. 7 gets a wider first cell so there's no hole. */
.chat-gallery[data-count="7"],
.chat-gallery[data-count="8"] {
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(2, 1fr);
    aspect-ratio: 2 / 1;
}
.chat-gallery[data-count="7"] .chat-gallery-cell,
.chat-gallery[data-count="8"] .chat-gallery-cell { aspect-ratio: auto; }
.chat-gallery[data-count="7"] .chat-gallery-cell:nth-child(1) {
    grid-column: 1 / span 2;
}

/* 9-10 images: 5 columns, 2 rows. For 9, first cell spans two columns. */
.chat-gallery[data-count="9"],
.chat-gallery[data-count="10"] {
    grid-template-columns: repeat(5, 1fr);
    grid-template-rows: repeat(2, 1fr);
    aspect-ratio: 5 / 2;
}
.chat-gallery[data-count="9"] .chat-gallery-cell,
.chat-gallery[data-count="10"] .chat-gallery-cell { aspect-ratio: auto; }
.chat-gallery[data-count="9"] .chat-gallery-cell:nth-child(1) {
    grid-column: 1 / span 2;
}

/* Tiny loading spinner inside a cell (existing pattern from single-image). */
.chat-gallery-cell .chat-att-spinner {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1.5rem;
    height: 1.5rem;
    margin: -0.75rem 0 0 -0.75rem;
    border: 2px solid rgba(255,255,255,0.15);
    border-top-color: var(--brand);
    border-radius: 50%;
    animation: settings-spin 0.8s linear infinite;
    pointer-events: none;
    opacity: 1;
    transition: opacity 0.2s;
}
.chat-gallery-cell img:not([data-loading]) + .chat-att-spinner {
    opacity: 0;
}

.chat-empty {
    padding: 4rem 1.5rem;
    text-align: center;
    color: var(--text-muted);
}

/* Clickable author in chat */
.chat-message-avatar {
    display: block;
    text-decoration: none;
    position: relative;                   /* anchor for the presence pill */
    overflow: visible;
}
.chat-message-avatar:hover img { filter: brightness(1.1); }

.chat-message-author {
    text-decoration: none;
    color: inherit;
}
.chat-message-author:hover { text-decoration: underline; }


/* ------------------------ Profile page ------------------------ */

.profile {
    max-width: 36rem;
    margin: 3rem auto;
    padding: 0 1.25rem;
}

.profile-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.75rem;
    padding: 2rem 2rem 1.5rem;
    text-align: center;
    /* Flex column so every child stacks on its own line, even when
       role-paint forces a child (e.g. <h2 class="profile-name">) to
       inline-block for gradient / effect rendering. Without this, the
       inline-block h2 sits next to the inline-block avatar wrap and
       both drift off-centre together. */
    display: flex;
    flex-direction: column;
    align-items: center;
}
/* Children that should still span the full card width (bio paragraph,
   discord-tag/joined dl). Flex column defaulted them to shrink-wrap
   because align-items is center; force stretch for these. */
.profile-card .profile-facts,
.profile-card .profile-bio,
.profile-card .profile-roles {
    align-self: stretch;
}

.profile-avatar-wrap {
    position: relative;
    display: inline-block;
    margin: 0 auto 1rem;
}

.profile-avatar {
    width: 8.5rem;
    height: 8.5rem;
    border-radius: 50%;
    object-fit: cover;
    background: #1a1a1a;
    border: 3px solid var(--border);
    display: block;
}

/* Presence dot overlay on the big profile avatar */
.profile-avatar-wrap .presence-dot {
    position: absolute;
    right: 0.35rem;
    bottom: 0.35rem;
    width: 1.15rem;
    height: 1.15rem;
    border: 3px solid var(--surface);
}

/* Reusable presence dots */
.presence-dot {
    display: inline-block;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    background: var(--text-muted);
    vertical-align: middle;
}

.presence-online {
    background: #22c55e;
    box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.18);
}

.presence-offline {
    background: #555;
}

.profile-presence {
    /* Sits in the .profile-card flex column directly under the rating
       strip (or under the handle when no rating is shown). Avoid a
       negative top margin here - it used to pull the line up under the
       handle, but with the rating strip slotted between handle + presence
       that pull caused the gold stars to overlap the "Online" / "Last seen"
       text. The handle + rating already supply enough vertical space. */
    margin: 0 0 1.25rem;
    color: var(--text-muted);
    font-size: 0.9rem;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    justify-content: center;
}

/* Friend-action strip under the presence line in the profile popup. */
.profile-actions {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin: 0 0 1.25rem;
    flex-wrap: wrap;
}
.profile-action {
    padding: 0.45rem 1rem;
    font-size: 0.9rem;
}
.profile-action:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.profile-name {
    font-size: 1.75rem;
    margin: 0 0 0.25rem;
    letter-spacing: -0.01em;
}

.profile-handle {
    color: var(--text-muted);
    /* Was 1.5rem; trimmed to 0.4rem because the .profile-presence row
       below already supplies its own bottom margin. The old gap left a
       big void when no .profile-rating row was present, but the column
       layout (rating/presence) handles spacing now. */
    margin: 0 0 0.4rem;
    font-size: 0.95rem;
}

.profile-roles {
    list-style: none;
    padding: 0;
    margin: 0 0 1.5rem;
    display: flex;
    gap: 0.35rem;
    justify-content: center;
    flex-wrap: wrap;
}

.profile-role {
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 0.25rem 0.55rem;
    border-radius: 999px;
    border: 1px solid transparent;
}

.profile-role.tone-danger {
    background: rgba(244, 63, 94, 0.12);
    color: #fb7185;
    border-color: rgba(244, 63, 94, 0.3);
}

.profile-role.tone-warn {
    background: rgba(251, 191, 36, 0.1);
    color: #fbbf24;
    border-color: rgba(251, 191, 36, 0.3);
}

.profile-role.tone-accent {
    background: rgba(124, 58, 237, 0.15);
    color: #c4b5fd;
    border-color: rgba(124, 58, 237, 0.4);
}

.profile-role.tone-muted {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text-muted);
    border-color: var(--border);
}

.profile-roles-empty {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin: 0 0 1.5rem;
}

.profile-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
    gap: 1rem;
    margin: 0 0 1.25rem;
    padding: 1rem;
    background: rgba(255, 255, 255, 0.02);
    border-radius: 0.5rem;
    border: 1px solid var(--border);
}

.profile-facts > div { text-align: center; }

.profile-facts dt {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: var(--text-muted);
    margin-bottom: 0.25rem;
}

/* Icon next to each fact label (Discord logo, calendar, …). Inherits
   currentColor so the tint matches the label's muted grey. */
.profile-fact-icon {
    flex-shrink: 0;
    opacity: 0.85;
}

.profile-facts dd {
    margin: 0;
    font-weight: 500;
}

.profile-bio {
    text-align: left;
    color: var(--text);
    line-height: 1.5;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border);
    margin-top: 0.5rem;
}

.profile-viewer-note {
    font-size: 0.8rem;
    color: var(--text-muted);
    margin-top: 1rem;
}

/* ------------------------- Modal ------------------------- */

.modal[hidden] { display: none; }

.modal {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

/* The confirm dialog is always spawned on top of whatever called it - including
   another modal like Settings. Bump its stacking level so it never hides. */
#confirm-modal { z-index: 300; }

.modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    animation: modal-fade 0.12s ease-out;
}

.modal-card {
    position: relative;
    z-index: 1;
    width: min(28rem, 100%);
    /* 100dvh follows the mobile URL-bar show/hide; 100vh bakes in the
       taller measurement so on iOS the bottom of long modals would
       spill below the visible area when the URL bar reappeared. */
    max-height: calc(100vh - 2rem);
    max-height: calc(100dvh - 2rem);
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.75rem;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
    animation: modal-pop 0.14s ease-out;
}

.modal-close {
    position: absolute;
    top: 0.5rem;
    right: 0.75rem;
    z-index: 2;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
}

.modal-close:hover { color: var(--text); background: rgba(255, 255, 255, 0.05); }

.modal-body { padding: 0; }

.modal-loading,
.modal-error {
    padding: 3rem 1rem;
    text-align: center;
    color: var(--text-muted);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.75rem;
}

.modal-profile {
    margin: 0;
    border: 1px solid var(--border);
}

.modal-profile-link {
    margin: 1rem 0 0;
    font-size: 0.8rem;
}

.modal-profile-link a {
    color: var(--text-muted);
    text-decoration: underline;
}

.modal-profile-link a:hover { color: var(--text); }

/* Profile modal rating link - whole strip is clickable, jumps to
   /u/<id>#reviews on the full profile page. Scoped to .modal-profile so
   the SSR /u/:id header (which renders .profile-rating as a plain <p>)
   keeps its existing styling. */
.modal-profile .profile-rating[href] {
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: filter 0.15s ease;
}
.modal-profile .profile-rating[href]:hover,
.modal-profile .profile-rating[href]:focus-visible {
    filter: brightness(1.15);
    outline: none;
}

body.modal-open { overflow: hidden; }

/* ---------- Settings modal ---------- */

.nav-settings {
    background: none;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    padding: 0.3rem;
    margin: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.25rem;
    transition: color 0.15s, background 0.15s;
}
.nav-settings:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.05);
}

.settings-form {
    padding: 1.5rem;
}

.settings-heading {
    margin: 0 0 1.25rem;
    font-size: 1.25rem;
    letter-spacing: -0.01em;
}

.settings-section {
    margin-bottom: 1.25rem;
}

.settings-label {
    display: block;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    margin-bottom: 0.5rem;
}

.settings-input {
    width: 100%;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    background: #0f0f0f;
    color: var(--text);
    font: inherit;
}
.settings-input:focus {
    outline: none;
    border-color: var(--brand);
}

.settings-avatar-row {
    display: flex;
    gap: 1rem;
    align-items: center;
}

.settings-avatar-preview {
    width: 4.5rem;
    height: 4.5rem;
    border-radius: 50%;
    object-fit: cover;
    background: #1a1a1a;
    border: 2px solid var(--border);
    flex-shrink: 0;
}

.settings-avatar-controls {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.3rem;
    min-width: 0;
}

.settings-file-label {
    cursor: pointer;
    padding: 0.45rem 0.9rem;
    font-size: 0.85rem;
}

.settings-filename {
    font-size: 0.8rem;
    color: var(--text-muted);
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.settings-hint {
    font-size: 0.72rem;
    color: var(--text-muted);
    opacity: 0.7;
}

/* Inline checkbox + label row used for the Discord-Tag-hidden toggle. */
.settings-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.5rem 0 0.3rem;
    font-size: 0.82rem;
    color: var(--text-muted);
    cursor: pointer;
    user-select: none;
}
.settings-toggle input[type="checkbox"] {
    width: 1rem;
    height: 1rem;
    accent-color: var(--brand);
    cursor: pointer;
    margin: 0;
}
.settings-toggle:hover { color: var(--text); }

/* "Hidden" placeholder on profile pages when the owner has chosen to
   hide a fact (like the Discord tag). Muted + italic so the reader sees
   it's not the real value. */
.profile-fact-hidden {
    font-style: italic;
    color: var(--text-muted);
    opacity: 0.75;
}

.settings-actions {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding-top: 1rem;
    margin-top: 0.25rem;
    border-top: 1px solid var(--border);
}

.settings-status {
    font-size: 0.85rem;
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 1rem;
}

.settings-status[data-state="success"] { color: #22c55e; }
.settings-status[data-state="error"]   { color: #f43f5e; }

/* Inline spinner shown only while a request is in flight. */
.settings-status[data-state="working"]::before {
    content: '';
    width: 0.9rem;
    height: 0.9rem;
    border: 2px solid rgba(255, 255, 255, 0.15);
    border-top-color: var(--brand);
    border-radius: 50%;
    animation: settings-spin 0.8s linear infinite;
    flex-shrink: 0;
}

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

/* ---------- Avatar cropper ---------- */

.settings-cropper {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
}

.crop-viewport {
    position: relative;
    width: 240px;
    height: 240px;
    margin: 0 auto;
    background: #000;
    border-radius: 0.5rem;
    overflow: hidden;
    touch-action: none;
    user-select: none;
    cursor: grab;
}
.crop-viewport:active { cursor: grabbing; }

.crop-viewport img {
    position: absolute;
    max-width: none;
    max-height: none;
    pointer-events: none;
    user-select: none;
    -webkit-user-drag: none;
}

/* Circle cutout highlighting the live crop. */
.crop-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* A huge outset shadow inside a 100%-circle mask gives a clean cutout. */
    background: radial-gradient(
        circle at center,
        transparent 0,
        transparent calc(50% - 1px),
        rgba(0, 0, 0, 0.55) 50%
    );
}
.crop-overlay::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.6);
    pointer-events: none;
}

.crop-controls {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.crop-zoom-label {
    font-size: 0.8rem;
    color: var(--text-muted);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

#crop-zoom {
    flex: 1;
    accent-color: var(--brand);
    cursor: pointer;
}

.crop-zoom-val {
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
    min-width: 3.5rem;
    text-align: right;
}

/* Narrower card for small dialogs (crop, confirm, etc.) */
.modal-card-sm,
.modal-card-crop {
    width: min(22rem, 100%);
}

.crop-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.6rem;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}

.crop-hint {
    margin: 0.75rem 0 0;
}

.settings-avatar-revert {
    padding: 0.35rem 0.75rem;
    font-size: 0.8rem;
}

/* ---------- Settings: multi-section shell (Discord-style) ---------- */

/* Wider card so the sidebar + content fit side-by-side. */
.modal-card-settings {
    width: min(56rem, 100%);
    /* 100dvh follows the mobile URL-bar; 100vh fallback for old browsers. */
    max-height: min(42rem, calc(100vh - 2rem));
    max-height: min(42rem, calc(100dvh - 2rem));
    overflow: hidden; /* inner panes scroll */
}

.settings-shell {
    display: flex;
    min-height: 30rem;
    max-height: inherit;
}

.settings-nav {
    flex-shrink: 0;
    width: 13rem;
    padding: 1rem 0.5rem;
    background: #0c0c0c;
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.settings-nav-title {
    padding: 0.25rem 0.75rem 0.5rem;
    font-size: 0.7rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.settings-nav-item {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.75rem;
    background: transparent;
    border: 0;
    border-radius: 0.35rem;
    color: var(--text-muted);
    font-size: 0.92rem;
    text-align: left;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.settings-nav-item:hover {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
}
.settings-nav-item.is-active {
    background: rgba(124, 58, 237, 0.18);
    color: #fff;
}
.settings-nav-item svg {
    flex-shrink: 0;
    color: inherit;
    opacity: 0.85;
}

.settings-content {
    flex: 1 1 auto;
    overflow-y: auto;
    min-width: 0;
}

.settings-pane { display: none; }
.settings-pane.is-active { display: block; }

/* The Appearance pane uses the existing .settings-form styles, no change. */

/* ---------- Uploads pane ---------- */

.settings-uploads {
    padding: 1.5rem;
}

.settings-uploads-hint {
    margin: 0 0 1rem;
}

/* Usage meter at the top of the Uploads pane. */
.settings-usage {
    margin: 0 0 1.25rem;
    padding: 0.85rem 1rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
}
.settings-usage-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: 0.5rem;
    gap: 1rem;
}
.settings-usage-label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}
.settings-usage-text {
    font-variant-numeric: tabular-nums;
    font-size: 0.95rem;
    color: var(--text);
}
.settings-usage-bar {
    position: relative;
    height: 10px;
    background: #1a1a1a;
    border-radius: 999px;
    overflow: visible;
}
/* Fill sits inside a clipped pseudo-wrapper so the bar's rounded
   corners hide fill overflow, while the tick marker above can float
   outside the bar without getting clipped. */
.settings-usage-fill { border-radius: 999px; }
.settings-usage-fill {
    height: 100%;
    width: 0%;
    background: linear-gradient(90deg, var(--brand), #a855f7);
    transition: width 0.3s ease-out, background 0.2s;
}
.settings-usage.is-warn .settings-usage-fill {
    background: linear-gradient(90deg, #f59e0b, #f97316);
}
.settings-usage.is-full .settings-usage-fill {
    background: linear-gradient(90deg, #ef4444, var(--danger));
}

/* Premium fill: rainbow-adjacent gradient for the portion up to the
   free-tier tick, then gold for the premium-only remainder. Purely
   cosmetic so users feel the benefit. */
.settings-usage.is-premium .settings-usage-fill {
    background: linear-gradient(90deg, #a855f7, #ec4899, #fbbf24);
}

/* Tick marker at the free-tier (3 GB) position on premium bars. */
.settings-usage-tick {
    position: absolute;
    top: -4px;
    bottom: -4px;
    /* left set inline by JS */
    display: flex;
    flex-direction: column;
    align-items: center;
    pointer-events: auto;
    z-index: 2;
}
.settings-usage-tick-line {
    width: 2px;
    height: calc(100% + 0px);
    background: #fbbf24;
    box-shadow: 0 0 5px rgba(251, 191, 36, 0.7);
    border-radius: 1px;
    transform: translateX(-1px);
}
.settings-usage-tick-label {
    position: absolute;
    top: calc(100% + 4px);
    transform: translateX(-50%);
    color: #fbbf24;
    font-size: 0.65rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    font-weight: 600;
}

/* PREMIUM pill beside the "Storage used" label. */
.settings-usage-premium {
    display: inline-block;
    margin-left: 0.5rem;
    padding: 0.06rem 0.45rem;
    border-radius: 999px;
    background: linear-gradient(90deg, rgba(168,85,247,0.25), rgba(251,191,36,0.25));
    border: 1px solid rgba(251, 191, 36, 0.5);
    color: #fde68a;
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    font-weight: 700;
    vertical-align: middle;
}
.settings-usage-head { align-items: center; }
/* Reserve vertical space below the bar for the tick label so the
   footnote below doesn't overlap it. */
.settings-usage.is-premium .settings-usage-bar { margin-bottom: 1.25rem; }

/* Non-premium upsell card under the storage bar. */
.settings-usage-upsell {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 100%;
    margin-top: 0.85rem;
    padding: 0.7rem 0.9rem;
    border-radius: 0.55rem;
    border: 1px solid rgba(251, 191, 36, 0.35);
    background: linear-gradient(90deg, rgba(244,114,182,0.08), rgba(168,85,247,0.08), rgba(251,191,36,0.08));
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.settings-usage-upsell:hover {
    transform: translateY(-1px);
    border-color: #fbbf24;
    box-shadow: 0 4px 14px -6px rgba(251, 191, 36, 0.55);
}
.settings-usage-upsell-tube {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: rgba(251, 191, 36, 0.1);
    animation: premium-tube-pulse 3.5s ease-in-out infinite;
}
.settings-usage-upsell-tube svg { width: 18px; height: 18px; }
.settings-usage-upsell-body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1 1 auto; }
.settings-usage-upsell-title { color: #fff; font-size: 0.92rem; }
.settings-usage-upsell-title strong {
    background: linear-gradient(90deg, #fbbf24, #f472b6, #a78bfa);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    font-weight: 800;
}
.settings-usage-upsell-sub { color: #a1a1aa; font-size: 0.78rem; line-height: 1.3; }
.settings-usage-upsell-arrow { flex: 0 0 auto; color: #fde68a; transition: transform 0.15s; }
.settings-usage-upsell:hover .settings-usage-upsell-arrow { transform: translateX(3px); }

.settings-uploads-status {
    min-height: 1.25rem;
    font-size: 0.85rem;
    color: var(--text-muted);
    margin: 0 0 0.5rem;
}
.settings-uploads-status[data-state="error"]   { color: var(--danger); }
.settings-uploads-status[data-state="working"] { color: var(--text-muted); }

.settings-uploads-empty {
    padding: 2rem 1rem;
    text-align: center;
    color: var(--text-muted);
    background: #0f0f0f;
    border: 1px dashed var(--border);
    border-radius: 0.5rem;
}

/* Grid of thumbnails + filename + delete. */
.settings-uploads-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
    gap: 0.75rem;
}

.settings-upload-item {
    position: relative;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.45rem;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: border-color 0.12s, transform 0.15s, opacity 0.2s;
}
.settings-upload-item:hover { border-color: var(--brand); }
.settings-upload-item.is-deleting { opacity: 0.35; pointer-events: none; }

.settings-upload-thumb {
    display: block;
    background: #050505;
    line-height: 0;
    aspect-ratio: 1 / 1;
}
.settings-upload-thumb img,
.settings-upload-thumb video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    background: #000;
}
.settings-upload-item { position: relative; }
.settings-upload-item.is-video .settings-upload-play {
    position: absolute;
    top: 35%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.settings-upload-meta {
    padding: 0.45rem 0.55rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

.settings-upload-name {
    font-size: 0.8rem;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.settings-upload-size {
    font-size: 0.7rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}

/* Hover-revealed action row: copy + delete buttons stacked
   horizontally in the top-right corner. The original .settings-
   upload-del rule is kept for backwards compatibility but the
   container handles positioning now. */
.settings-upload-actions {
    position: absolute;
    top: 0.35rem;
    right: 0.35rem;
    display: flex;
    gap: 0.25rem;
    opacity: 0;
    transform: translateY(-2px);
    transition: opacity 0.12s, transform 0.12s;
}
.settings-upload-item:hover .settings-upload-actions,
.settings-upload-actions:focus-within {
    opacity: 1;
    transform: none;
}
.settings-upload-copy,
.settings-upload-del {
    position: static;
    width: 1.75rem;
    height: 1.75rem;
    border: 0;
    border-radius: 0.3rem;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.12s;
}
.settings-upload-copy:hover { background: var(--brand); }
.settings-upload-del:hover  { background: var(--danger); }
.settings-upload-copy.is-ok {
    background: #16a34a;
    color: #fff;
}

/* On small viewports, collapse the sidebar into a top strip. */
@media (max-width: 640px) {
    .modal-card-settings {
        width: 100%;
        max-width: 100%;
        /* Use dynamic viewport height so the modal doesn't extend
           under the mobile URL bar. Respect the iOS home-bar inset. */
        max-height: calc(100dvh - 0.5rem - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
        height:     calc(100dvh - 0.5rem - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
        margin-top: env(safe-area-inset-top, 0px);
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }
    .settings-shell {
        flex: 1 1 auto;
        flex-direction: column;
        min-height: 0;
        max-height: none;
        overflow: hidden;
    }
    .settings-nav {
        width: auto;
        flex-direction: row;
        overflow-x: auto;
        overflow-y: hidden;
        padding: 0.5rem;
        border-right: 0;
        border-bottom: 1px solid var(--border);
        flex-shrink: 0;
        -webkit-overflow-scrolling: touch;
    }
    .settings-nav-title { display: none; }
    .settings-nav-item {
        white-space: nowrap;
        padding: 0.55rem 0.9rem;
    }
    .settings-content {
        flex: 1 1 auto;
        overflow-y: auto;
        overflow-x: hidden;
        min-height: 0;
        -webkit-overflow-scrolling: touch;
    }
    /* Inner panes often have their own grids that overflow on
       phones - clamp them. */
    .settings-pane,
    .settings-pane > * {
        max-width: 100%;
        min-width: 0;
    }
    .avatar-gallery {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* ---------- Avatar gallery ---------- */

.avatar-gallery {
    list-style: none;
    margin: 0 0 0.5rem;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0.5rem;
    transition: opacity 0.15s;
}

/* While a mutation is in flight, freeze the whole grid - no spam-clicking. */
.avatar-gallery.is-busy {
    opacity: 0.6;
    pointer-events: none;
    cursor: progress;
}

.avatar-tile {
    position: relative;
    aspect-ratio: 1 / 1;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.45rem;
    overflow: hidden;
    transition: border-color 0.12s, transform 0.15s, opacity 0.2s;
}
.avatar-tile:hover { border-color: var(--brand); }
.avatar-tile.is-deleting { opacity: 0.35; pointer-events: none; }

.avatar-tile.is-active {
    border-color: var(--brand);
    box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.5);
}

.avatar-tile-pic {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    overflow: hidden;
}
.avatar-tile-pic img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: transform 0.2s;
}
.avatar-tile:not(.is-active) .avatar-tile-pic:hover img { transform: scale(1.04); }
.avatar-tile.is-active .avatar-tile-pic { cursor: default; }

.avatar-tile-badge {
    position: absolute;
    left: 0.25rem;
    bottom: 0.25rem;
    padding: 0.1rem 0.4rem;
    font-size: 0.65rem;
    background: var(--brand);
    color: #fff;
    border-radius: 0.25rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.avatar-tile-del {
    position: absolute;
    top: 0.3rem;
    right: 0.3rem;
    width: 1.55rem;
    height: 1.55rem;
    border: 0;
    border-radius: 0.3rem;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    opacity: 0;
    transform: translateY(-2px);
    transition: opacity 0.12s, background 0.12s, transform 0.12s;
    z-index: 1;
}
.avatar-tile:hover .avatar-tile-del,
.avatar-tile-del:focus-visible {
    opacity: 1;
    transform: none;
}
.avatar-tile-del:hover { background: var(--danger); }

/* Empty slot - clickable "+" tile that opens the file picker. */
.avatar-tile.is-empty {
    border-style: dashed;
    background: #0a0a0a;
}
.avatar-tile-add {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    transition: color 0.12s, background 0.12s;
}
.avatar-tile-add:hover {
    color: #fff;
    background: rgba(124, 58, 237, 0.12);
}

/* Down to 3 columns on narrow screens. */
@media (max-width: 560px) {
    .avatar-gallery { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@keyframes modal-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@keyframes modal-pop {
    from { opacity: 0; transform: translateY(6px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}

/* ------------------------ Chat attachment lightbox ------------------------ */

html.chat-lightbox-open { overflow: hidden; }

.chat-lightbox {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    animation: modal-fade 0.15s ease-out;
}
.chat-lightbox[hidden] { display: none; }

.chat-lightbox-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.82);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    cursor: zoom-out;
}

.chat-lightbox-panel {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    max-width: 92vw;
    max-height: 92vh;
    animation: modal-pop 0.18s ease-out;
}

.chat-lightbox-close {
    position: absolute;
    top: -2.4rem;
    right: -0.25rem;
    width: 2rem;
    height: 2rem;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.1);
    color: #fff;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s;
}
.chat-lightbox-close:hover { background: rgba(255, 255, 255, 0.2); }

/* "3 / 7" image counter shown above the viewer when the gallery has more
   than one image. Stays hidden for single-image messages. */
.chat-lightbox-counter {
    color: rgba(255, 255, 255, 0.75);
    font-size: 0.8rem;
    font-variant-numeric: tabular-nums;
    background: rgba(0, 0, 0, 0.45);
    padding: 0.2rem 0.55rem;
    border-radius: 999px;
    pointer-events: none;
    user-select: none;
}
.chat-lightbox-counter[hidden] { display: none; }

/* Row that holds the prev button, image stage, next button, and the vertical
   zoom slider. */
.chat-lightbox-viewer {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    max-width: 92vw;
    max-height: calc(92vh - 4rem);
}

/* Prev / next nav buttons flanking the stage. Only rendered visible when the
   lightbox has >1 sibling image. */
.chat-lightbox-nav-prev,
.chat-lightbox-nav-next {
    flex-shrink: 0;
    width: 2.5rem;
    height: 2.5rem;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s, transform 0.12s;
}
.chat-lightbox-nav-prev:hover,
.chat-lightbox-nav-next:hover {
    background: rgba(124, 58, 237, 0.85);
    transform: scale(1.08);
}
.chat-lightbox-nav-prev[hidden],
.chat-lightbox-nav-next[hidden] { display: none; }

.chat-lightbox-stage {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 6rem;
    min-height: 6rem;
    /* Leave room for the zoom column (~3rem) so the stage shrinks on narrow
       viewports instead of overflowing the panel. */
    max-width: calc(92vw - 4rem);
    max-height: calc(92vh - 4rem);
    border-radius: 0.5rem;
    overflow: hidden;
    background: rgba(15, 15, 15, 0.6);
    /* Suppress native touch gestures so our pointer handlers own zoom/pan. */
    touch-action: none;
    user-select: none;
    cursor: default;
}
.chat-lightbox-stage.is-zoomed { cursor: grab; }
.chat-lightbox-stage.is-zoomed:active { cursor: grabbing; }

.chat-lightbox-img {
    display: block;
    max-width: calc(92vw - 4rem);
    max-height: calc(92vh - 4rem);
    object-fit: contain;
    opacity: 0;
    transform-origin: center center;
    /* Honor EXIF orientation on JPEGs from phones so portrait shots
       aren't rendered sideways even when the user never clicked the
       rotate button. Modern browsers default to this for <img>, but
       we make it explicit in case any UA defaults differ. */
    image-orientation: from-image;
    /* Only animate opacity - transform is driven live by pointer/wheel handlers
       and a transition would make every drag frame lag a beat behind. */
    transition: opacity 0.18s ease-out;
    /* Let pointer events hit the stage, not the <img>, so the whole area is
       draggable even when the scaled image overflows. */
    pointer-events: none;
    -webkit-user-drag: none;
}
/* In-message attachments inherit the same EXIF handling - otherwise a
   sideways portrait JPEG stays sideways in the bubble too. */
.chat-att img,
.chat-gallery-cell img,
.chat-message-attachments img,
.chat-message-content img {
    image-orientation: from-image;
}
.chat-lightbox-img[hidden] { display: none; }
.chat-lightbox-stage.is-loaded .chat-lightbox-img { opacity: 1; }

/* Video counterpart of the lightbox image. Keeps native controls so the
   user gets play/seek/volume/fullscreen. No zoom/pan on video - those
   only make sense for still images. */
.chat-lightbox-video {
    display: block;
    max-width: calc(92vw - 4rem);
    max-height: calc(92vh - 4rem);
    width: auto;
    height: auto;
    /* Transparent so the lightbox backdrop (already a near-opaque
       overlay) shows through letterbox gaps instead of a redundant
       hard black bar. */
    background: transparent;
    border-radius: 0.25rem;
}
.chat-lightbox-video[hidden] { display: none; }
/* Hide the zoom/pan bar + cursor affordances when viewing a video. */
.chat-lightbox-stage.is-video { cursor: default; touch-action: auto; }
.chat-lightbox-stage.is-video .chat-lightbox-spinner { display: none; }

/* Vertical zoom rail - sits centered to the right of the image. */
.chat-lightbox-zoom {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
    height: calc(92vh - 4rem);
    max-height: 26rem;
    min-height: 12rem;
    padding: 0.5rem 0.25rem;
    color: #fff;
    user-select: none;
}

.chat-lightbox-zoom-btn {
    width: 1.75rem;
    height: 1.75rem;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.1);
    color: #fff;
    font-size: 1.1rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.12s;
    flex-shrink: 0;
}
.chat-lightbox-zoom-btn:hover { background: rgba(255, 255, 255, 0.2); }
/* Rotate buttons pick up the same chrome as the +/- zoom buttons but
   render their SVG centered and a hair smaller so the toolbar stays
   visually balanced. */
.chat-lightbox-rotate-btn svg {
    width: 14px;
    height: 14px;
    display: block;
}

.chat-lightbox-zoom-slider {
    /* Vertical native range. Modern path is writing-mode + direction
       alone (HTML standard, works in current Chrome/Firefox/Safari).
       `appearance: slider-vertical` was a WebKit-only legacy hack;
       Chrome now spams a deprecation warning on every render and
       Firefox never honoured it. */
    writing-mode: vertical-lr;
    direction: rtl;
    width: 10px;
    flex: 1 1 auto;
    min-height: 6rem;
    accent-color: var(--brand);
    cursor: pointer;
    touch-action: none;
}

.chat-lightbox-zoom-label {
    min-width: 2.5rem;
    text-align: center;
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    color: rgba(255, 255, 255, 0.75);
    padding: 0.1rem 0.3rem;
    border-radius: 0.25rem;
    background: rgba(0, 0, 0, 0.35);
    flex-shrink: 0;
}

/* Too cramped on a phone - drop the rail. Cursor + pinch still work. */
@media (max-width: 500px) {
    .chat-lightbox-zoom { display: none; }
    .chat-lightbox-stage, .chat-lightbox-img { max-width: 92vw; }
}

/* Zoom rail doesn't apply to video - hide it when a video is the
   current frame. Video's native controls do all the zoom/fullscreen work. */
.chat-lightbox-viewer:has(.chat-lightbox-stage.is-video) .chat-lightbox-zoom {
    display: none;
}

.chat-lightbox-spinner {
    position: absolute;
    width: 2.5rem;
    height: 2.5rem;
    border: 3px solid rgba(255,255,255,0.15);
    border-top-color: var(--brand);
    border-radius: 50%;
    animation: settings-spin 0.8s linear infinite;
    pointer-events: none;
    transition: opacity 0.18s ease-out;
}
.chat-lightbox-stage.is-loaded .chat-lightbox-spinner,
.chat-lightbox-stage.is-error   .chat-lightbox-spinner { opacity: 0; }

.chat-lightbox-stage.is-error::after {
    content: 'Failed to load image';
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--danger);
    font-size: 0.9rem;
    background: rgba(26, 10, 10, 0.85);
}

.chat-lightbox-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    justify-content: center;
}

.chat-lightbox-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.5rem 1rem;
    text-decoration: none;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}

/* Secondary (outline) style for Copy buttons - keeps Download as the primary. */
.chat-lightbox-copy-img,
.chat-lightbox-copy-url {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
    border: 1px solid var(--border);
    cursor: pointer;
}
.chat-lightbox-copy-img:hover,
.chat-lightbox-copy-url:hover {
    background: rgba(255, 255, 255, 0.12);
    border-color: var(--brand);
}

/* Flash states after a click - confirm success or failure for ~1.4s. */
.chat-lightbox-btn.is-ok {
    background: #10b981;
    border-color: #10b981;
    color: #fff;
}
.chat-lightbox-btn.is-err {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}
.chat-lightbox-btn:disabled { cursor: default; opacity: 0.85; }

/* ------------------------ Drag-and-drop overlay ------------------------ */

.chat-main { position: relative; }

.chat-drop-overlay {
    position: absolute;
    inset: 0.5rem;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px dashed var(--brand);
    border-radius: 0.6rem;
    background: rgba(124, 58, 237, 0.1);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    pointer-events: none; /* let the drop event bubble from chat-main */
    animation: modal-fade 0.12s ease-out;
}
.chat-drop-overlay[hidden] { display: none; }

.chat-drop-inner {
    text-align: center;
    color: var(--text);
    padding: 1.5rem 2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
}
.chat-drop-inner svg { color: var(--brand); }

.chat-drop-title {
    font-size: 1.25rem;
    font-weight: 600;
}
.chat-drop-sub {
    font-size: 0.85rem;
    color: var(--text-muted);
}

/* ---------- Generic-file attachments in chat (non-image/video) ---------- */

/* Composer chip for a staged non-media file: document icon + uppercase
   extension label. Replaces the normal <img> preview inside .chat-attach-chip
   when the queued file isn't an image or video. */
.chat-attach-chip.is-file {
    background: #1a1a1a;
    display: flex;
    align-items: center;
    justify-content: center;
}
.chat-attach-fileicon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
}
.chat-attach-fileicon svg {
    display: block;
}
.chat-attach-fileext {
    position: absolute;
    left: 50%;
    top: 58%;
    transform: translate(-50%, 0);
    font-size: 0.55rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    background: var(--brand);
    color: #fff;
    padding: 0.05rem 0.3rem;
    border-radius: 0.2rem;
    pointer-events: none;
    max-width: calc(100% - 0.5rem);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Rendered file tile inside a message - Discord-style pill with the file
   icon on the left, filename + size in the middle, and a download arrow on
   the right. Clickable; intercepted by the download-warning delegate. */
.chat-message-files {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-top: 0.35rem;
}
.chat-att-file {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    max-width: min(28rem, 100%);
    padding: 0.6rem 0.85rem;
    background: #1a1a1a;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.12s, background 0.12s;
}
.chat-att-file:hover {
    border-color: var(--brand);
    background: #1f1f1f;
}
.chat-att-file-icon {
    position: relative;
    flex: 0 0 auto;
    width: 2.75rem;
    height: 2.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
}
.chat-att-file-ext {
    position: absolute;
    left: 50%;
    top: 58%;
    transform: translate(-50%, 0);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    background: var(--brand);
    color: #fff;
    padding: 0.08rem 0.35rem;
    border-radius: 0.22rem;
    pointer-events: none;
    max-width: calc(100% - 0.4rem);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-att-file-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
}
.chat-att-file-name {
    color: var(--text);
    font-size: 0.9rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.chat-att-file-size {
    color: var(--text-muted);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
}
.chat-att-file-dl {
    flex: 0 0 auto;
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: color 0.12s;
}
.chat-att-file:hover .chat-att-file-dl {
    color: var(--brand);
}

/* ---------- .txt inline preview ---------- */
.chat-att-txt-wrap {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    max-width: min(36rem, 100%);
}
.chat-att-txt-preview {
    background: #111;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    overflow: hidden;
}
.chat-att-txt-content {
    display: block;
    margin: 0;
    padding: 0.75rem 1rem;
    font-size: 0.82rem;
    font-family: ui-monospace, 'Cascadia Code', 'Fira Code', Consolas, monospace;
    line-height: 1.55;
    color: #d4d4d8;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-height: 300px;
    overflow-y: auto;
}
.chat-att-txt-truncated {
    padding: 0.35rem 1rem 0.5rem;
    font-size: 0.75rem;
    color: var(--text-muted);
    border-top: 1px solid var(--border);
}
.chat-att-txt-loading,
.chat-att-txt-error {
    display: block;
    padding: 0.6rem 1rem;
    font-size: 0.82rem;
    color: var(--text-muted);
}
.chat-att-txt-toobig {
    padding: 0.6rem 1rem;
    font-size: 0.82rem;
    color: var(--text-muted);
    background: #111;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
}
@media (max-width: 768px) {
    .chat-att-txt-wrap { max-width: 100%; }
    .chat-att-txt-content { font-size: 0.78rem; max-height: 220px; }
}

/* ---------- Settings → Uploads: Media / Files sub-tabs ---------- */

.settings-uploads-tabs {
    display: flex;
    gap: 0.35rem;
    margin: 0.75rem 0 0.75rem;
    border-bottom: 1px solid var(--border);
}
.settings-uploads-tab {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    padding: 0.5rem 0.85rem;
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    transition: color 0.12s, border-color 0.12s;
}
.settings-uploads-tab:hover { color: var(--text); }
.settings-uploads-tab.is-active {
    color: var(--text);
    border-bottom-color: var(--brand);
}
.settings-uploads-tab-count {
    min-width: 1.25rem;
    padding: 0 0.35rem;
    height: 1.1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #1a1a1a;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    font-size: 0.7rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
}
.settings-uploads-tab.is-active .settings-uploads-tab-count {
    background: var(--brand);
    color: #fff;
    border-color: var(--brand);
}

/* Settings upload tile: file (non-media) variant - mirrors the chip design
   but at the tile size. */
.settings-upload-item.is-file .settings-upload-thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    background: #1a1a1a;
}
.settings-upload-fileicon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
}
.settings-upload-fileext {
    position: absolute;
    left: 50%;
    top: 58%;
    transform: translate(-50%, 0);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    background: var(--brand);
    color: #fff;
    padding: 0.08rem 0.4rem;
    border-radius: 0.22rem;
    pointer-events: none;
    max-width: calc(100% - 0.4rem);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- Settings -> Listings pane ---------- */

.settings-listings {
    display: flex;
    flex-direction: column;
    gap: 0;
}

.settings-listings-filters {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
    border-bottom: 1px solid var(--border);
    padding-bottom: 0.75rem;
}

.settings-listings-tabs {
    display: flex;
    gap: 0.25rem;
}

.settings-listings-tab {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    padding: 0.35rem 0.8rem;
    font: inherit;
    font-size: 0.82rem;
    cursor: pointer;
    border-radius: 0.4rem;
    transition: background 0.12s, color 0.12s;
}
.settings-listings-tab:hover { background: rgba(255,255,255,0.06); color: var(--text); }
.settings-listings-tab.is-active {
    background: rgba(139,92,246,0.18);
    color: var(--brand);
    font-weight: 600;
}

.settings-listings-cat-select {
    appearance: none;
    background: var(--surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%236b7280'/%3E%3C/svg%3E") no-repeat right 0.6rem center;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    font-size: 0.82rem;
    padding: 0.35rem 1.8rem 0.35rem 0.65rem;
    cursor: pointer;
    transition: border-color 0.12s;
}
.settings-listings-cat-select:focus {
    outline: none;
    border-color: var(--brand);
}

.settings-listings-grid {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

.settings-listing-card {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.65rem 0.75rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    transition: border-color 0.12s;
}
.settings-listing-card:hover { border-color: rgba(139,92,246,0.35); }

.settings-listing-thumb-wrap {
    flex: 0 0 auto;
    display: block;
    width: 3.5rem;
    height: 3.5rem;
    border-radius: 0.35rem;
    overflow: hidden;
    background: #1a1a1a;
}
.settings-listing-thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.settings-listing-thumb-empty {
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #2a1a4a 0%, #1a1a2e 100%);
}

.settings-listing-info {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.settings-listing-title {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.settings-listing-meta {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    font-size: 0.78rem;
    color: var(--text-muted);
}
.settings-listing-cat {
    background: rgba(255,255,255,0.06);
    border-radius: 0.3rem;
    padding: 0.1rem 0.45rem;
}
.settings-listing-price { font-variant-numeric: tabular-nums; }

.settings-listing-side {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.4rem;
}

.settings-listing-badge {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 0.15rem 0.55rem;
    border-radius: 0.9rem;
}
.settings-listing-badge.is-live {
    background: rgba(52,211,153,0.15);
    color: #34d399;
    border: 1px solid rgba(52,211,153,0.3);
}
.settings-listing-badge.is-draft {
    background: rgba(113,113,122,0.15);
    color: #a1a1aa;
    border: 1px solid rgba(113,113,122,0.25);
}

.settings-listings-empty {
    text-align: center;
    padding: 2.5rem 1rem;
    color: var(--text-muted);
    font-size: 0.9rem;
}

/* Mobile: stack side actions below the info */
@media (max-width: 560px) {
    .settings-listing-card {
        flex-wrap: wrap;
    }
    .settings-listing-side {
        flex-direction: row;
        align-items: center;
        width: 100%;
        justify-content: flex-end;
    }
}


/* -------- GIF picker button + popover (Klipy) -------- */

.chat-gif-btn {
    flex-shrink: 0;
    height: 2.5rem;
    padding: 0 0.6rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.375rem;
    color: var(--text-muted);
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.chat-gif-btn:hover {
    color: var(--text);
    border-color: var(--brand);
    background: rgba(124, 58, 237, 0.08);
}

.chat-gif-popover {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 420;
    width: 26rem;
    max-width: 94vw;
    height: 28rem;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    background: #0a0a0a;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
    overflow: hidden;
}
.chat-gif-popover[hidden] { display: none; }
.chat-gif-popover.is-in-picker {
    position: static;
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    flex: 1 1 auto;
    min-height: 0;
}

.chat-gif-search-row {
    padding: 0.5rem;
    border-bottom: 1px solid var(--border);
}
.chat-gif-search {
    width: 100%;
    padding: 0.4rem 0.55rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    color: var(--text);
    font: inherit;
    font-size: 0.9rem;
}
.chat-gif-search:focus {
    outline: none;
    border-color: var(--brand);
}

.chat-gif-scroll {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

/* Search-result GIF: full-width, natural aspect ratio, stacked vertically
   so the reader can skim the feed without losing motion context. */
.chat-gif-tile {
    appearance: none;
    border: 1px solid transparent;
    background: #050505;
    border-radius: 0.35rem;
    padding: 0;
    overflow: hidden;
    cursor: pointer;
    line-height: 0;
    display: block;
    width: 100%;
    flex: 0 0 auto;
    transition: border-color 0.12s, transform 0.12s;
}
.chat-gif-tile:hover {
    border-color: var(--brand);
    transform: translateY(-1px);
}
.chat-gif-tile img {
    width: 100%;
    height: auto;
    display: block;
}

/* Category card: Discord-style wide banner - preview GIF fills the card,
   category name rendered in bold white text centred over a dimming overlay. */
.chat-gif-cat {
    position: relative;
    appearance: none;
    border: 1px solid transparent;
    background: #050505;
    border-radius: 0.35rem;
    padding: 0;
    overflow: hidden;
    cursor: pointer;
    display: block;
    width: 100%;
    height: 5rem;
    flex: 0 0 auto;
    transition: border-color 0.12s, transform 0.12s;
}
.chat-gif-cat:hover {
    border-color: var(--brand);
    transform: translateY(-1px);
}
.chat-gif-cat img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    filter: brightness(0.55);
    transition: filter 0.12s;
}
.chat-gif-cat:hover img { filter: brightness(0.75); }
.chat-gif-cat-label {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 1.15rem;
    font-weight: 800;
    letter-spacing: 0.02em;
    text-transform: capitalize;
    text-shadow: 0 1px 6px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}

/* Sticker + clip categories: Klipy serves identical GIF previews
   regardless of kind, so a 1:1 reuse of the GIF banner makes the
   tabs read identically. We render those as a denser grid of
   smaller tiles with pixelated rendering so the visual language
   is clearly distinct from GIF banners. */
.chat-gif-scroll[data-kind="sticker"][data-mode="categories"],
.chat-gif-scroll[data-kind="clip"][data-mode="categories"] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr));
    gap: 0.35rem;
    padding: 0.5rem;
}
.chat-gif-scroll[data-kind="sticker"][data-mode="categories"] .chat-gif-cat,
.chat-gif-scroll[data-kind="clip"][data-mode="categories"]    .chat-gif-cat {
    /* Definite height + visible <img> so the server-enriched real
       sticker / clip preview shows through behind the label. */
    width: 100%;
    height: 5.5rem;
    border-radius: 0.4rem;
}
.chat-gif-scroll[data-kind="sticker"][data-mode="categories"] .chat-gif-cat img,
.chat-gif-scroll[data-kind="clip"][data-mode="categories"]    .chat-gif-cat img {
    image-rendering: pixelated;
    image-rendering: -moz-crisp-edges;
    image-rendering: crisp-edges;
    filter: brightness(0.65) saturate(0.9);
}
.chat-gif-scroll[data-kind="sticker"][data-mode="categories"] .chat-gif-cat:hover img,
.chat-gif-scroll[data-kind="clip"][data-mode="categories"]    .chat-gif-cat:hover img {
    filter: brightness(0.85) saturate(1);
}
.chat-gif-scroll[data-kind="sticker"][data-mode="categories"] .chat-gif-cat-label,
.chat-gif-scroll[data-kind="clip"][data-mode="categories"]    .chat-gif-cat-label {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.95);
    padding: 0.2rem;
    text-align: center;
    line-height: 1.1;
}

/* GIF search / trending grid: 2 per row, fixed-height tiles. The
   default vertical flex stack reads like a long ribbon - the 2-col
   grid is denser and matches the sticker / clip layouts. */
.chat-gif-scroll[data-kind="gif"][data-mode="gifs"] {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.4rem;
    padding: 0.5rem;
}
.chat-gif-scroll[data-kind="gif"][data-mode="gifs"] .chat-gif-tile {
    /* Definite pixel height so the inner img's height: 100% has a
       concrete value (auto + aspect-ratio collapses the box). */
    width: 100%;
    height: 8rem;
    border-radius: 0.4rem;
    background: #050505;
    overflow: hidden;
}
.chat-gif-scroll[data-kind="gif"][data-mode="gifs"] .chat-gif-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Sticker search / trending grid: 3 per row, square tiles with the
   pixelated rendering treatment so stickers read as stickers, not
   GIFs. The default flex column is overridden to a 3-col grid. */
.chat-gif-scroll[data-kind="sticker"][data-mode="gifs"] {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.35rem;
    padding: 0.5rem;
}
.chat-gif-scroll[data-kind="sticker"][data-mode="gifs"] .chat-gif-tile {
    /* Definite pixel height so the inner img's height: 100% has
       something concrete to resolve against. aspect-ratio + auto
       collapsed the box in the previous pass. */
    width: 100%;
    height: 6rem;
    border-radius: 0.4rem;
    background: #050505;
    overflow: hidden;
}
.chat-gif-scroll[data-kind="sticker"][data-mode="gifs"] .chat-gif-tile img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    image-rendering: pixelated;
    image-rendering: -moz-crisp-edges;
    image-rendering: crisp-edges;
}

/* Clip search / trending grid: 2 per row, autoplay-muted-loop video
   tiles so the user can preview motion before posting. */
.chat-gif-scroll[data-kind="clip"][data-mode="gifs"] {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.4rem;
    padding: 0.5rem;
}
.chat-gif-scroll[data-kind="clip"][data-mode="gifs"] .chat-gif-tile {
    width: 100%;
    height: 8rem;
    border-radius: 0.4rem;
    background: #050505;
    overflow: hidden;
}
.chat-gif-scroll[data-kind="clip"][data-mode="gifs"] .chat-gif-tile video,
.chat-gif-scroll[data-kind="clip"][data-mode="gifs"] .chat-gif-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* Generic fallback: any tile that swapped its <img> for a <video>
   should render the same way as the img variant. Applies across
   gif / sticker / clip kinds. */
.chat-gif-tile video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}

/* Server lifecycle system rows (join / leave / kick / ban). Tone-
   tinted icon + matching link colour so the room reads at a glance. */
.chat-server-lifecycle .chat-call-system-icon { color: #a1a1aa; }
.chat-server-lifecycle.is-join  .chat-call-system-icon { color: #86efac; }
.chat-server-lifecycle.is-leave .chat-call-system-icon { color: #fda4af; }
.chat-server-lifecycle.is-kick  .chat-call-system-icon { color: #fdba74; }
.chat-server-lifecycle.is-ban   .chat-call-system-icon { color: #fca5a5; }
.chat-server-lifecycle-user {
    color: #c4b5fd;
    text-decoration: none;
    font-weight: 600;
}
.chat-server-lifecycle-user:hover { text-decoration: underline; }

/* Discord-style: when a message body is a bare media URL whose
   embed already renders below, the URL text gets hidden so only
   the rich preview shows. dataset.rawContent keeps the original
   for edit-mode / quotes. */
.chat-message-content.is-url-only {
    display: none;
}

.chat-gif-status {
    grid-column: 1 / -1;
    padding: 1.25rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.chat-gif-status.is-error { color: var(--danger, #ef4444); }

.chat-gif-attrib {
    padding: 0.35rem 0.6rem;
    border-top: 1px solid var(--border);
    font-size: 0.7rem;
    color: var(--text-muted);
    text-align: right;
    letter-spacing: 0.02em;
}

/* ---------- Settings -> Account pane ---------- */

.settings-account {
    padding: 1.5rem;
    max-width: 42rem;
}
.settings-account .settings-hint {
    margin: 0 0 1.25rem;
}

/* Card wrapper so the form feels like a self-contained unit, mirroring
   Discord's "Account" panel layout. Extra cards (2FA, sessions, delete
   account) can drop in as siblings and inherit the style. */
.settings-card {
    background: #141414;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    padding: 1.25rem 1.35rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.02) inset,
                0 6px 16px rgba(0, 0, 0, 0.25);
}

.settings-card-head {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
}
.settings-card-icon {
    flex: 0 0 auto;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 0.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(124, 58, 237, 0.14);
    border: 1px solid rgba(124, 58, 237, 0.35);
    color: var(--brand);
}
.settings-card-title h3 {
    margin: 0 0 0.2rem;
    font-size: 1rem;
    letter-spacing: -0.005em;
    color: var(--text);
}
.settings-card-title p {
    margin: 0;
    font-size: 0.82rem;
    color: var(--text-muted);
    line-height: 1.45;
}

/* Change-password form: reuses .signin-field / .signin-input. */
.settings-password-form {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}
.settings-password-form .signin-label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
}
.settings-password-actions {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    margin-top: 0.25rem;
    flex-wrap: wrap;
}
.settings-password-actions .settings-status {
    font-size: 0.85rem;
}
.settings-password-actions .settings-status[data-state="success"] { color: #6ee7b7; }
.settings-password-actions .settings-status[data-state="error"]   { color: var(--danger); }
.settings-password-actions .settings-status[data-state="working"] { color: var(--text-muted); }

/* Handle input with an @ prefix (matches the Add Friend field). */
.settings-handle-input-row {
    display: flex;
    align-items: stretch;
    gap: 0.4rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0 0.6rem;
}
.settings-handle-input-row:focus-within { border-color: var(--brand); }
.settings-handle-prefix {
    display: inline-flex;
    align-items: center;
    color: var(--text-muted);
    font-size: 0.95rem;
    font-weight: 600;
}
.settings-handle-input-row .settings-handle-input {
    flex: 1 1 auto;
    background: transparent;
    border: 0;
    padding: 0.55rem 0.2rem;
}
.settings-handle-input-row .settings-handle-input:focus { outline: none; }

/* Notifications toggle row. Uses a simple iOS-style switch so the state
   is visually obvious. */
.settings-notify-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

/* Discord webhook card body. */
.settings-webhook {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.settings-webhook-warn {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    padding: 0.6rem 0.8rem;
    background: rgba(220, 38, 38, 0.12);
    border: 1px solid rgba(220, 38, 38, 0.55);
    border-radius: 0.5rem;
    color: #fca5a5;
    font-size: 0.82rem;
    line-height: 1.45;
}
.settings-webhook-warn svg {
    flex: 0 0 auto;
    margin-top: 0.05rem;
    color: #f87171;
}

/* ---------- Discord webhook promo nudge (bottom-right) ---------- */
.webhook-promo {
    position: fixed;
    right: 1.25rem;
    bottom: 1.25rem;
    z-index: 9500;
    width: 340px;
    max-width: calc(100vw - 2rem);
    padding: 1.1rem 1.15rem 1rem;
    background: linear-gradient(160deg, #1c1c24 0%, #16161c 100%);
    border: 1px solid #34343f;
    border-radius: 0.9rem;
    box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
    color: var(--text, #e4e4e7);
    overflow: hidden;
    opacity: 0;
    transform: translateY(16px) scale(0.98);
    transition: opacity 0.28s ease, transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.webhook-promo.is-visible { opacity: 1; transform: translateY(0) scale(1); }
.webhook-promo[hidden] { display: none; }
/* Blurple accent glow bleeding in from the top edge. */
.webhook-promo-glow {
    position: absolute;
    inset: -40% -40% auto -40%;
    height: 140px;
    background: radial-gradient(60% 100% at 50% 0%, rgba(88, 101, 242, 0.45), transparent 70%);
    pointer-events: none;
}
.webhook-promo > *:not(.webhook-promo-glow) { position: relative; }
.webhook-promo-x {
    position: absolute;
    top: 0.55rem;
    right: 0.6rem;
    width: 1.8rem;
    height: 1.8rem;
    border: none;
    background: transparent;
    color: var(--text-muted, #a1a1aa);
    font-size: 1.3rem;
    line-height: 1;
    cursor: pointer;
    border-radius: 0.4rem;
}
.webhook-promo-x:hover { color: #fff; background: rgba(255, 255, 255, 0.06); }
.webhook-promo-head {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.55rem;
    margin: 0.15rem 0 0.55rem;
    text-align: center;
}
.webhook-promo-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    flex: 0 0 auto;
    border-radius: 0.85rem;
    background: rgba(88, 101, 242, 0.18);
    color: #aab4ff;
}
.webhook-promo-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
}
.webhook-promo-body {
    margin: 0 0 0.95rem;
    font-size: 0.86rem;
    line-height: 1.5;
    color: #c4c4cc;
    text-align: center;
}
.webhook-promo-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem 0.85rem;
}
.webhook-promo-setup { padding: 0.45rem 0.95rem; font-size: 0.88rem; }
.webhook-promo-link {
    border: none;
    background: transparent;
    color: var(--text-muted, #a1a1aa);
    font: inherit;
    font-size: 0.82rem;
    cursor: pointer;
    padding: 0.2rem 0;
}
.webhook-promo-link:hover { color: #e4e4e7; text-decoration: underline; }
.webhook-promo-never:hover { color: #fca5a5; }
@media (max-width: 560px) {
    .webhook-promo {
        right: 0.75rem;
        left: 0.75rem;
        bottom: 0.75rem;
        width: auto;
        max-width: none;
    }
}
.settings-webhook-events {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.settings-webhook-check {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: 0.88rem;
    color: var(--text, #e4e4e7);
    cursor: pointer;
    user-select: none;
}
.settings-webhook-check input[type="checkbox"] {
    width: 1rem;
    height: 1rem;
    flex-shrink: 0;
    accent-color: var(--brand, #8b5cf6);
    cursor: pointer;
}

/* iOS-style toggle built from a hidden checkbox + an explicit track and
   thumb span. Lives under its own class (.settings-switch) to avoid a
   CSS collision with the older .settings-toggle selector used by the
   Discord-tag-hidden checkbox. */
.settings-switch {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    cursor: pointer;
    user-select: none;
}
.settings-switch input {
    /* Visually hidden but still focusable for a11y. */
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}
.settings-switch-track {
    position: relative;
    width: 2.6rem;
    height: 1.4rem;
    background: #262626;
    border: 1px solid var(--border);
    border-radius: 999px;
    flex-shrink: 0;
    transition: background 0.15s, border-color 0.15s;
}
.settings-switch-thumb {
    position: absolute;
    top: 1px;
    left: 1px;
    width: 1.15rem;
    height: 1.15rem;
    background: #e5e5e5;
    border-radius: 50%;
    transition: transform 0.15s, background 0.15s;
}
.settings-switch input:checked + .settings-switch-track {
    background: var(--brand);
    border-color: var(--brand);
}
.settings-switch input:checked + .settings-switch-track .settings-switch-thumb {
    transform: translateX(1.2rem);
    background: #fff;
}
.settings-switch input:focus-visible + .settings-switch-track {
    box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.35);
}
.settings-switch input:disabled + .settings-switch-track {
    opacity: 0.5;
    cursor: not-allowed;
}
.settings-switch-label {
    color: var(--text);
    font-size: 0.9rem;
}

/* ---------- Unread badges (Discord-style red circles) ---------- */

.unread-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.1rem;
    height: 1.1rem;
    padding: 0 0.3rem;
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    background: #ef4444;
    border-radius: 999px;
    box-shadow: 0 0 0 2px var(--bg);
    font-variant-numeric: tabular-nums;
}
.unread-badge[hidden] { display: none !important; }

/* Rail: float the badge at the bottom-right corner of the squircle. */
.chat-rail-item {
    overflow: visible; /* counteract border-radius clipping of the badge */
}
.chat-rail-item .unread-badge {
    position: absolute;
    right: -0.15rem;
    bottom: -0.15rem;
    box-shadow: 0 0 0 2px #050505;
    pointer-events: none;
}

/* Channel + DM links: align badge to the right edge so #general and the
   count sit on the same row. */
.chat-channel-link,
.chat-dm-link {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.35rem;
}
.chat-channel-link .unread-badge,
.chat-dm-link .unread-badge {
    margin-left: auto;
    box-shadow: none;
}
/* Active channel already has its own purple background; the badge should
   still stand out but not clash. */
.chat-channel-link.is-active .unread-badge,
.chat-dm-link.is-active .unread-badge {
    background: #fff;
    color: var(--brand);
}

/* ============================================================
   Server rail (far-left column) - Madrigal Labs + Friends switch
   ============================================================ */

.chat-rail {
    background: #050505;
    border-right: 1px solid var(--border);
    padding: 0.6rem 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    overflow-y: auto;
    min-height: 0;
}

/* Bottom-left FAB column for non-chat pages. Mirrors the look of the
   .chat-rail-foot stack so the user sees the same icons in the same
   spot regardless of which page they're on. position:fixed so it
   floats over scrollable content; safe-area-inset-bottom respects the
   iOS home bar. */
.site-foot-fab {
    position: fixed;
    left: 0;
    bottom: 0;
    z-index: 60; /* above main content, below modals (which are 100+) */
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.45rem;
    padding: 0.65rem 0.5rem calc(0.65rem + env(safe-area-inset-bottom));
    background: #1f1f24;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    border-right: 1px solid rgba(255, 255, 255, 0.1);
    border-top-right-radius: 0.6rem;
    box-shadow: 0 -4px 18px -8px rgba(0, 0, 0, 0.55);
}
@media (max-width: 560px) {
    /* Compact horizontal pill on phones so the column doesn't eat the
       full left edge while content scrolls. */
    .site-foot-fab {
        flex-direction: row;
        gap: 0.35rem;
        padding: 0.4rem 0.6rem calc(0.4rem + env(safe-area-inset-bottom));
        border-radius: 0 0.7rem 0 0;
    }
    .site-foot-fab .chat-rail-foot-btn {
        width: 2.5rem;
        height: 2.5rem;
    }
    .site-foot-fab .chat-rail-foot-btn svg {
        width: 18px;
        height: 18px;
    }
}
/* Bottom-of-rail column carrying Settings + Sign out. `margin-top:
   auto` pins them to the bottom; vertical stack keeps them the same
   visual size as the server / friends pills above. */
.chat-rail-foot {
    margin-top: auto;
    padding-top: 0.65rem;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    align-items: center;
    justify-content: center;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    width: 100%;
    box-sizing: border-box;
}
/* Inner row holding the settings cog + sign-out form. Inherits the
   parent column's vertical stacking so the icons sit one above the
   other (matches the previous direct-child layout); a tiny gap keeps
   them visually distinct. The wrapper exists so the new help button
   can sit above this group without disturbing it. */
.chat-rail-foot-row {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.45rem;
}
.chat-rail-signout {
    display: inline-flex;
    margin: 0;
    padding: 0;
}
/* Help popover anchored to the bottom-left rail. Inherits the
   .rail-context-menu dark-pill look; only positioning differs (it's
   placed via JS using fixed coords measured from the help button's
   bounding rect). The min-width keeps the single Report/Contact
   item readable. */
.chat-help-menu {
    min-width: 12rem;
}
.chat-rail-foot-btn {
    appearance: none;
    background: #1a1a1a;
    border: 0;
    color: var(--text-muted);
    width: 3rem;
    height: 3rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-radius 0.18s, transform 0.1s;
    line-height: 0;
}
.chat-rail-foot-btn:hover,
.chat-rail-foot-btn:focus-visible {
    color: #fff;
    background: var(--brand);
    border-radius: 0.9rem;
    outline: none;
}
.chat-rail-foot-btn:active {
    transform: translateY(1px);
}
/* Sign-out specifically picks up a red accent on hover so users never
   accidentally think it's settings-adjacent. Matches the destructive-
   action cue (#ff6b6b) used elsewhere in context menus. */
.chat-rail-foot-signout:hover,
.chat-rail-foot-signout:focus-visible {
    color: #fff;
    background: #dc2626;
    border-radius: 0.9rem;
}
.chat-rail-foot-btn svg {
    width: 20px;
    height: 20px;
}
.chat-rail-item {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: 999px;
    color: var(--text);
    text-decoration: none;
    background: #1a1a1a;
    transition: border-radius 0.18s, background 0.15s, color 0.15s;
    cursor: pointer;
}
.chat-rail-item:hover {
    border-radius: 0.9rem;
    background: var(--brand);
    color: #fff;
}
.chat-rail-item.is-active {
    border-radius: 0.9rem;
    background: var(--brand);
    color: #fff;
}
/* Active pill on the left edge - Discord's "active server" indicator. */
.chat-rail-pill {
    position: absolute;
    left: -0.6rem;
    top: 50%;
    width: 0.3rem;
    height: 0.6rem;
    background: #fff;
    border-radius: 0 0.2rem 0.2rem 0;
    transform: translateY(-50%) scaleY(0);
    transition: transform 0.15s, height 0.15s;
}
.chat-rail-item:hover .chat-rail-pill { transform: translateY(-50%) scaleY(0.6); height: 1.2rem; }
.chat-rail-item.is-active .chat-rail-pill { transform: translateY(-50%) scaleY(1); height: 2rem; }

.chat-rail-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-weight: 700;
    letter-spacing: 0.04em;
}
.chat-rail-icon-server {
    font-size: 0.8rem;
    overflow: hidden;
    border-radius: inherit;
    /* Inherit the rail pill's dark background so a white/transparent
       logo stays visible. Previously this forced #fff and swallowed
       white logo marks whole. */
}

/* Soft white activity dot for the server rail - indicates "there's
   unread traffic, but nobody mentioned you specifically". Sits on
   the outer edge (left, near the active pill) so it doesn't stack
   on top of the red unread-badge on the right. */
.chat-rail-activity-dot {
    position: absolute;
    left: -0.15rem;
    top: 50%;
    transform: translateY(-50%);
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    background: #f4f4f5;
    box-shadow: 0 0 6px rgba(255, 255, 255, 0.55),
                0 0 0 2px rgba(0, 0, 0, 0.45);
    pointer-events: none;
    z-index: 2;
}
.chat-rail-activity-dot[hidden] { display: none; }

/* Per-server mute overlay - sits at the bottom-center of the rail
   icon as a small speaker-with-slash badge in red-ish. Hidden by
   default; revealed when the parent .chat-rail-item carries .is-muted
   (toggled live by refreshRailBadges via madrigalServerIsMuted). */
/* Visibility is governed entirely by the parent's `.is-muted`
   class - no `hidden` HTML attribute. Default state is hidden via
   `display: none !important`; the parent `.is-muted` flips it to
   `inline-flex !important` so no UA / specificity quirk can occlude
   the badge once a user mutes. */
.chat-rail-mute-overlay {
    position: absolute;
    /* Inside the rail item's bottom edge - the .chat-rail container
       has overflow-y: auto and was clipping the badge when it was
       positioned below the rail item's box. */
    bottom: 0.1rem;
    left: 50%;
    transform: translateX(-50%);
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
    background: #18181b;
    border: 2px solid #0a0a0d;
    color: #fca5a5;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    z-index: 5;
    display: none !important;
}
.chat-rail-item.is-muted .chat-rail-mute-overlay {
    display: inline-flex !important;
}
.chat-rail-item.is-muted .chat-rail-icon-server {
    /* gentle desaturation cue so the icon reads as silenced even
       before the eye registers the slashed-bell overlay. */
    filter: grayscale(0.55) opacity(0.75);
}

/* DM / group muted indicator. Mirrors the server-rail pattern:
   a small bell-with-slash glyph anchored to the bottom-right of
   the avatar, plus a subtle desaturation on the row text so the
   "silenced" state reads at a glance. The overlay node is injected
   lazily by refreshChannelMuteIndicators so unmuted rows pay no
   layout cost. */
.chat-dm-mute-overlay {
    position: absolute;
    right: -2px;
    bottom: -2px;
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    background: #18181b;
    border: 2px solid #0a0a0d;
    color: #fca5a5;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    z-index: 2;
    display: none;
}
.chat-dm-link.is-muted .chat-dm-mute-overlay { display: inline-flex; }
.chat-dm-link.is-muted .chat-dm-name,
.chat-dm-link.is-muted .chat-dm-sub {
    color: rgba(228, 228, 231, 0.55);
}
.chat-dm-link.is-muted.is-active .chat-dm-name,
.chat-dm-link.is-muted.is-active .chat-dm-sub {
    color: rgba(255, 255, 255, 0.85);
}
.chat-dm-link.is-muted .chat-dm-avatar {
    filter: grayscale(0.55) opacity(0.78);
}
/* Anchor the overlay to whichever avatar container the row uses -
   single-DM rows wrap the avatar in .chat-dm-avatar-wrap, group
   rows use .chat-dm-avatar-stack. Both are switched to relative
   positioning so the absolutely-positioned overlay sits flush. */
.chat-dm-avatar-wrap,
.chat-dm-avatar-stack { position: relative; }

/* Per-channel ambient activity dot - tells the user WHICH channel
   the rail-level white dot is chirping about. Appears on the right
   side of the channel row, just like the red mention badge, but
   smaller and white so it reads as "something happened here" rather
   than "you got pinged". Mutually exclusive with the red badge at
   runtime; chat-client hides one when the other is active. */
.chat-channel-activity-dot {
    margin-left: auto;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: #f4f4f5;
    box-shadow: 0 0 5px rgba(255, 255, 255, 0.5);
    flex: 0 0 auto;
    align-self: center;
    pointer-events: none;
}
.chat-channel-activity-dot[hidden] { display: none; }
.chat-rail-icon-server img {
    /* User-uploaded server icons fill the circle (cover) with no
       padding so a JPEG hits every edge. The built-in Madrigal logo
       overrides this below to preserve its aspect ratio + breathing
       room. */
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    image-rendering: auto;
    image-rendering: -webkit-optimize-contrast;
    -webkit-user-drag: none;
    user-drag: none;
    user-select: none;
    pointer-events: none;
}
/* Built-in Madrigal Labs logo: 624x689, transparent PNG with empty
   margins. `contain` + a hair of padding keeps it crisp. */
.chat-rail-item[data-rail-server-builtin="1"] .chat-rail-icon-server img {
    object-fit: contain;
    padding: 10%;
    box-sizing: border-box;
}
.chat-rail-icon-friends svg {
    width: 22px;
    height: 22px;
}

/* ============================================================
   Sidebar variants: .chat-sidebar reused, sections differ by view.
   ============================================================ */

.chat-sidebar-head {
    padding: 0.25rem 0.75rem 0.75rem;
    border-bottom: 1px solid var(--border);
    margin-bottom: 0.5rem;
}
.chat-sidebar-heading {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
    letter-spacing: -0.005em;
}
.chat-sidebar-subheading {
    font-size: 0.72rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
    margin-top: 0.15rem;
}

/* Friends-home button inside the Friends sidebar: route back from a DM
   to the friends list view. Sits above the "Direct Messages" heading. */
.chat-friends-home {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    width: calc(100% - 0.5rem);
    margin: 0.25rem;
    padding: 0.45rem 0.6rem;
    background: #1a1a1a;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    color: var(--text-muted);
    cursor: pointer;
    font: inherit;
    font-size: 0.9rem;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.chat-friends-home:hover {
    color: var(--text);
    border-color: var(--brand);
    background: rgba(124, 58, 237, 0.08);
}
.chat-friends-home.is-active {
    color: #fff;
    background: var(--brand);
    border-color: var(--brand);
}
.chat-friends-home .unread-badge {
    margin-left: auto;
    box-shadow: none;
}
.chat-friends-home.is-active .unread-badge {
    background: #fff;
    color: var(--brand);
}

/* DM list (Friends view sidebar) - one entry per DM channel. */
.chat-dm-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.chat-dm-link {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.35rem 0.5rem;
    margin: 0.15rem 0.25rem;
    border-radius: 0.35rem;
    text-decoration: none;
    color: var(--text-muted);
    transition: background 0.12s, color 0.12s;
}
.chat-dm-link:hover { background: #1a1a1a; color: var(--text); }
.chat-dm-link.is-active { background: var(--brand); color: #fff; }
.chat-dm-avatar-wrap {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
}
.chat-dm-avatar {
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    object-fit: cover;
    background: #050505;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.chat-dm-avatar-fallback {
    background: #1f1f1f;
    color: var(--text);
    font-weight: 700;
    font-size: 0.85rem;
}
.chat-dm-dot {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    border: 2px solid var(--surface);
    box-sizing: content-box;
}
.chat-dm-link.is-active .chat-dm-dot { border-color: var(--brand); }
.chat-dm-dot.is-online  { background: #22c55e; }
.chat-dm-dot.is-offline { background: #6b7280; }
.chat-dm-name {
    font-size: 0.95rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-dm-empty {
    padding: 0.5rem 0.75rem;
    font-size: 0.8rem;
    color: var(--text-muted);
    line-height: 1.4;
}

.chat-header-dm-at {
    color: var(--text-muted);
    margin-right: 0.1rem;
}
.chat-header-dm-link {
    color: inherit;
    text-decoration: none;
    border-radius: 0.3rem;
    padding: 0.05rem 0.2rem;
    margin: -0.05rem -0.2rem;
    transition: background 120ms ease, color 120ms ease;
}
.chat-header-dm-link:hover,
.chat-header-dm-link:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
    text-decoration: none;
    outline: none;
}

/* DM sidebar right-click menu (one-item "Show Profile"). Visually
   matches the message context menu but scoped to .chat-dm-context-menu
   so future items can diverge without stepping on message actions. */
.chat-dm-context-menu {
    position: fixed;
    z-index: 1200;
    min-width: 10rem;
    background: #111;
    border: 1px solid var(--border);
    border-radius: 0.45rem;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
    padding: 0.25rem;
    display: flex;
    flex-direction: column;
}
.chat-dm-context-menu .chat-context-item {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text);
    text-align: left;
    font-size: 0.9rem;
    padding: 0.45rem 0.7rem;
    border-radius: 0.3rem;
    cursor: pointer;
}
.chat-dm-context-menu .chat-context-item:hover,
.chat-dm-context-menu .chat-context-item:focus-visible {
    background: rgba(255, 255, 255, 0.08);
    outline: none;
}
.chat-dm-context-menu .chat-context-item.danger {
    color: #ff6b6b;
}
.chat-dm-context-menu .chat-context-item.danger:hover,
.chat-dm-context-menu .chat-context-item.danger:focus-visible {
    background: rgba(255, 107, 107, 0.12);
    color: #ff8a8a;
}

/* Right-click popover for group-owner actions on header member avatars
   (top-right member strip). Same visual vocabulary as the DM/message
   menus so the user gets a consistent "one-item floating menu" feel. */
.chat-member-context-menu {
    position: fixed;
    z-index: 1200;
    min-width: 11rem;
    background: #111;
    border: 1px solid var(--border);
    border-radius: 0.45rem;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
    padding: 0.25rem;
    display: flex;
    flex-direction: column;
}
.chat-member-context-menu[hidden] { display: none; }
.chat-member-context-item {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text);
    text-align: left;
    font-size: 0.9rem;
    padding: 0.45rem 0.7rem;
    border-radius: 0.3rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
}
.chat-member-context-item svg {
    flex-shrink: 0;
    color: var(--text-muted);
    transition: color 0.12s;
}
.chat-member-context-item:hover svg,
.chat-member-context-item:focus-visible svg {
    color: inherit;
}
.chat-member-context-item:hover,
.chat-member-context-item:focus-visible {
    background: rgba(255, 255, 255, 0.08);
    outline: none;
}
.chat-member-context-item.danger {
    color: #ff6b6b;
}
.chat-member-context-item.danger:hover,
.chat-member-context-item.danger:focus-visible {
    background: rgba(255, 107, 107, 0.12);
    color: #ff8a8a;
}

/* Archived DMs / groups: collapsed by default, muted presentation so
   they feel secondary to the active list. Rows share .chat-dm-link
   styling but dim with .is-archived. */
.chat-dm-archived {
    margin-top: 0.5rem;
    padding: 0 0.35rem;
}
.chat-dm-archived-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.55rem;
    color: var(--text-muted);
    font-size: 0.76rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    list-style: none;
    user-select: none;
    border-radius: 0.3rem;
}
.chat-dm-archived-head::-webkit-details-marker { display: none; }
.chat-dm-archived-head:hover { color: var(--text); background: rgba(255, 255, 255, 0.04); }
.chat-dm-archived-count {
    margin-left: auto;
    padding: 0 0.45rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.06);
    font-weight: 500;
    letter-spacing: 0.02em;
    color: var(--text-muted);
    font-size: 0.72rem;
}
.chat-dm-archived-chevron {
    transition: transform 0.18s ease;
    color: var(--text-muted);
}
.chat-dm-archived[open] .chat-dm-archived-chevron { transform: rotate(180deg); }
.chat-dm-list-archived { margin: 0.2rem 0 0; padding: 0; list-style: none; }
.chat-dm-link.is-archived {
    opacity: 0.6;
    filter: grayscale(0.2);
}
.chat-dm-link.is-archived:hover {
    opacity: 0.95;
    filter: grayscale(0);
}

/* ============================================================
   Friends view (main panel when no DM is open)
   ============================================================ */

.friends-view {
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: 100%;
    overflow: hidden;
}
.friends-header {
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    flex: 0 0 auto;
}
.friends-tabs {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.6rem 1rem;
    overflow-x: auto;
}
.friends-tab {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    padding: 0.4rem 0.75rem;
    font: inherit;
    font-size: 0.9rem;
    border-radius: 0.35rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    transition: color 0.12s, background 0.12s;
    white-space: nowrap;
}
.friends-tab:hover { color: var(--text); background: #1a1a1a; }
.friends-tab.is-active {
    color: #fff;
    background: rgba(124, 58, 237, 0.18);
}
.friends-tab-count {
    min-width: 1.3rem;
    padding: 0 0.35rem;
    height: 1.2rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #1a1a1a;
    border-radius: 0.5rem;
    font-size: 0.7rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
}
.friends-tab.is-active .friends-tab-count {
    background: var(--brand);
    color: #fff;
}
.friends-tab.friends-tab-add {
    margin-left: auto;
    color: #86efac;
    background: rgba(34, 197, 94, 0.08);
}
.friends-tab.friends-tab-add.is-active {
    color: #fff;
    background: #16a34a;
}

.friends-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 1rem 1.25rem 1.5rem;
}
.friends-pane { display: none; }
.friends-pane.is-active { display: block; }

.friends-pane-heading {
    margin: 0 0 0.4rem;
    font-size: 1rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.friends-pane-hint {
    margin: 0 0 0.75rem;
    color: var(--text-muted);
    font-size: 0.88rem;
}

/* --- Add Friend pane --- */
.friends-add-form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-width: 36rem;
}
.friends-add-row {
    display: flex;
    align-items: stretch;
    gap: 0.5rem;
    background: #141414;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.4rem 0.6rem;
}
.friends-add-row:focus-within { border-color: var(--brand); }
.friends-add-prefix {
    display: inline-flex;
    align-items: center;
    color: var(--text-muted);
    font-size: 1rem;
    font-weight: 600;
}
.friends-add-input {
    flex: 1 1 auto;
    background: transparent;
    border: 0;
    color: var(--text);
    font: inherit;
    font-size: 0.95rem;
    padding: 0.4rem 0.2rem;
    min-width: 0;
}
.friends-add-input:focus { outline: none; }
.friends-add-status {
    font-size: 0.85rem;
    min-height: 1.1rem;
}
.friends-add-status[data-state="success"] { color: #6ee7b7; }
.friends-add-status[data-state="error"]   { color: var(--danger); }
.friends-add-status[data-state="working"] { color: var(--text-muted); }

/* --- List pane (All / Online / Pending) --- */
.friends-list-root { max-width: 60rem; }

.friends-list-head {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-muted);
    padding: 0.5rem 0 0.35rem;
    border-bottom: 1px solid var(--border);
    margin: 1rem 0 0.25rem;
}
.friends-list-head:first-child { margin-top: 0; }

.friends-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.friends-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.75rem 0.5rem;
    border-bottom: 1px solid rgba(255,255,255,0.04);
    gap: 0.75rem;
    cursor: pointer;
}
.friends-row:hover {
    background: rgba(255,255,255,0.03);
    border-radius: 0.4rem;
    border-bottom-color: transparent;
}
.friends-row:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: -2px;
    border-radius: 0.4rem;
}
/* The avatar + name block is a profile-popup link, so keep it visually
   unchanged (no underline, inherit colours). */
.friends-row-main {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
    flex: 1 1 auto;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}
.friends-row-main:hover .friends-row-name { color: var(--text); }
.friends-row-avatar {
    position: relative;
    flex: 0 0 auto;
}
.friends-avatar {
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    object-fit: cover;
    background: #050505;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.friends-avatar-fallback {
    background: #1f1f1f;
    color: var(--text);
    font-weight: 700;
    font-size: 0.95rem;
}
.friends-dot {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
    border: 2px solid var(--surface);
}
.friends-dot-online  { background: #22c55e; }
.friends-dot-offline { background: #6b7280; }

.friends-row-ident {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.friends-row-name {
    color: var(--text);
    font-size: 0.95rem;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.friends-row-sub {
    color: var(--text-muted);
    font-size: 0.8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.friends-row-actions {
    display: flex;
    gap: 0.4rem;
    flex: 0 0 auto;
}
.friends-icon-btn {
    appearance: none;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    background: #1a1a1a;
    border: 0;
    color: var(--text);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.friends-icon-btn:hover { background: #262626; }
.friends-icon-btn.is-success:hover { background: #16a34a; color: #fff; }
.friends-icon-btn.is-danger:hover  { background: var(--danger); color: #fff; }

.friends-empty {
    padding: 2rem 1rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
}
.friends-empty.is-error { color: var(--danger); }

/* ============================================================
   Voice calls: phone button, modals, in-call bar, system rows.
   ============================================================ */

/* Header tweak: the DM title sits on the left, call button on the right. */
.chat-header {
    display: flex;
    align-items: center;
    gap: 1rem;
}
.chat-header-main { flex: 1 1 auto; min-width: 0; }

.chat-call-btn {
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: #1a1a1a;
    border: 1px solid var(--border);
    color: var(--text-muted);
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.chat-call-btn:hover {
    background: #16a34a;
    border-color: #16a34a;
    color: #fff;
}

/* Header pin button - opens the pinned-messages popover. */
.chat-pins-btn,
.chat-invite-btn {
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: #1a1a1a;
    border: 1px solid var(--border);
    color: var(--text-muted);
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
    margin-left: 0.5rem;
}
.chat-pins-btn:hover,
.chat-invite-btn:hover {
    background: #1f2937;
    border-color: #334155;
    color: var(--text);
}

/* Popover that drops out of the chat header. Positioned fixed via the
   anchor offset we compute... actually we anchor it absolutely within
   the chat column so scrolling the messages doesn't detach it from the
   header. */
.chat-pins-popover {
    position: fixed;
    z-index: 120;
    width: min(22rem, calc(100vw - 2rem));
    max-height: 32rem;
    display: flex;
    flex-direction: column;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
}
.chat-pins-popover[hidden] { display: none; }
.chat-pins-popover-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.7rem 0.9rem;
    border-bottom: 1px solid var(--border);
}
.chat-pins-popover-title {
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.chat-pins-popover-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.2rem;
    cursor: pointer;
    padding: 0 0.25rem;
}
.chat-pins-popover-close:hover { color: var(--text); }
.chat-pins-popover-body {
    overflow-y: auto;
    padding: 0.35rem;
}
.chat-pins-popover-loading,
.chat-pins-popover-empty {
    padding: 1.5rem 0.75rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.chat-pin-item {
    display: flex;
    gap: 0.6rem;
    padding: 0.55rem 0.6rem;
    border-radius: 0.4rem;
}
.chat-pin-item:hover { background: rgba(255, 255, 255, 0.035); }
.chat-pin-item-avatar {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}
.chat-pin-item-body { flex: 1; min-width: 0; }
.chat-pin-item-head {
    display: flex;
    align-items: baseline;
    gap: 0.45rem;
}
.chat-pin-item-author { font-weight: 600; color: var(--text); font-size: 0.88rem; }
.chat-pin-item-time   { color: var(--text-muted); font-size: 0.72rem; }
.chat-pin-item-content {
    margin-top: 0.15rem;
    font-size: 0.85rem;
    color: var(--text);
    word-break: break-word;
    max-height: 12rem;
    overflow-y: auto;
}
.chat-pin-item-content p       { margin: 0; }
.chat-pin-item-content p + p   { margin-top: 0.25rem; }
.chat-pin-item-content a       { color: #a78bfa; }
.chat-pin-item-content pre,
.chat-pin-item-content code    { font-size: 0.78rem; }

/* Media scope inside a pinned item. Everything here renders at roughly
   half the size of the normal chat attachment/embed so a long pinned
   image doesn't blow the popover up. Uses cascading max-width/-height
   overrides rather than transform:scale so layout/interaction stay sane
   (click targets, hover cursors, etc.). */
.chat-pin-item-media {
    margin-top: 0.35rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.chat-pin-item-media .chat-message-attachments,
.chat-pin-item-media .chat-message-embeds {
    margin-top: 0;
    max-width: 100%;
}
.chat-pin-item-media .chat-att,
.chat-pin-item-media .chat-att img,
.chat-pin-item-media .chat-att video,
.chat-pin-item-media .chat-att-wrap {
    max-width: 11rem;
    max-height: 8rem;
}
.chat-pin-item-media .chat-embed {
    max-width: 18rem;
}
.chat-pin-item-media .chat-embed-thumb,
.chat-pin-item-media .chat-embed-thumb img,
.chat-pin-item-media .chat-embed-image,
.chat-pin-item-media .chat-embed-image img,
.chat-pin-item-media .chat-embed-video,
.chat-pin-item-media .chat-embed-video video,
.chat-pin-item-media .chat-embed-media {
    max-width: 14rem;
    max-height: 10rem;
}
.chat-pin-item-media .chat-embed-iframe,
.chat-pin-item-media .chat-embed-iframe iframe {
    max-width: 18rem;
    max-height: 10rem;
}
/* Non-image file tile (generic upload). Already a small chip; just
   don't let it stretch wider than the popover column. */
.chat-pin-item-media .chat-att-file {
    max-width: 18rem;
}
.chat-pin-item-actions {
    display: flex;
    gap: 0.45rem;
    margin-top: 0.35rem;
}
.chat-pin-item-jump,
.chat-pin-item-unpin {
    appearance: none;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-muted);
    padding: 0.2rem 0.55rem;
    border-radius: 0.3rem;
    cursor: pointer;
    font-size: 0.75rem;
}
.chat-pin-item-jump:hover  { background: rgba(255, 255, 255, 0.06); color: var(--text); }
.chat-pin-item-unpin:hover { background: var(--danger); border-color: var(--danger); color: #fff; }

/* Inline pin indicator next to the timestamp on a pinned message row. */
.chat-message-pinned-badge {
    display: inline-flex;
    align-items: center;
    margin-left: 0.35rem;
    color: rgba(250, 204, 21, 0.9); /* amber so it stands out vs "edited" text */
}
.chat-message.is-pinned {
    /* Faint left accent bar to reinforce the pinned state in-flow. */
    box-shadow: inset 3px 0 0 rgba(250, 204, 21, 0.55);
}
.chat-message.is-pinned:hover {
    background: rgba(250, 204, 21, 0.04);
}

/* Pin system-message row reuses the call-system centred layout. The
   entire row is clickable (click -> scroll to pinned message), so give
   it a pointer cursor and a subtle amber tint on hover. */
.chat-pin-system { cursor: pointer; }
.chat-pin-system .chat-call-system-icon          { color: rgba(250, 204, 21, 0.9); }
.chat-pin-system.is-unpin .chat-call-system-icon { color: var(--text-muted); }
.chat-pin-system:hover .chat-call-system-text    { color: var(--text); }

/* Full-screen outgoing + incoming modals (Discord-style). */
.call-modal {
    position: fixed;
    inset: 0;
    z-index: 480;
    display: flex;
    align-items: center;
    justify-content: center;
}
.call-modal[hidden] { display: none; }
.call-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.72);
    backdrop-filter: blur(6px);
}
.call-modal-card {
    position: relative;
    background: #111;
    border: 1px solid var(--border);
    border-radius: 0.7rem;
    padding: 2rem 2.5rem;
    text-align: center;
    max-width: 24rem;
    box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
}
.call-modal-avatar {
    width: 5rem;
    height: 5rem;
    border-radius: 50%;
    object-fit: cover;
    margin-bottom: 0.5rem;
    border: 2px solid var(--brand);
}
.call-modal-title {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--text-muted);
}
.call-modal-name {
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--text);
    margin: 0.1rem 0;
}
.call-modal-handle {
    margin: -0.15rem 0 0.1rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-variant: normal;
}
/* Initial-letter disc shown when the caller has no avatar URL. */
.call-modal-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 5rem;
    height: 5rem;
    border-radius: 50%;
    background: #1f1f1f;
    color: var(--text);
    font-size: 2rem;
    font-weight: 700;
    border: 2px solid var(--brand);
    margin-bottom: 0.5rem;
}
.call-modal-status {
    font-size: 0.85rem;
    color: var(--text-muted);
    margin-bottom: 0.9rem;
    animation: call-ring-pulse 1.4s ease-in-out infinite;
}
@keyframes call-ring-pulse {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 1; }
}

/* ------------------- Ringing phone animation -------------------
   The block sits at the top of the incoming-call card. A stack of
   three concentric rings scales outward while fading, giving the
   illusion of sound waves leaving the handset. The handset itself
   wiggles on a short cycle like a vibrating phone. */
.call-phone-ringing {
    position: relative;
    width: 5.5rem;
    height: 5.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 0.4rem;
}
.call-phone-ringing .call-phone-icon {
    position: relative;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3.2rem;
    height: 3.2rem;
    border-radius: 50%;
    background: linear-gradient(135deg, #10b981 0%, #059669 100%);
    color: #fff;
    box-shadow:
        0 6px 18px rgba(16, 185, 129, 0.45),
        inset 0 1px 0 rgba(255, 255, 255, 0.3);
    transform-origin: 50% 55%;
    animation: call-phone-wiggle 1s ease-in-out infinite;
}
.call-phone-ringing .call-phone-ring {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 3.2rem;
    height: 3.2rem;
    margin: -1.6rem 0 0 -1.6rem;
    border-radius: 50%;
    border: 2px solid rgba(16, 185, 129, 0.55);
    opacity: 0;
    transform: scale(1);
    animation: call-phone-ring 2s ease-out infinite;
    pointer-events: none;
}
.call-phone-ringing .call-phone-ring-2 { animation-delay: 0.65s; }
.call-phone-ringing .call-phone-ring-3 { animation-delay: 1.3s; }

@keyframes call-phone-wiggle {
    0%, 100%         { transform: rotate(0deg); }
    10%, 30%, 50%    { transform: rotate(-14deg); }
    20%, 40%, 60%    { transform: rotate(14deg); }
    65%              { transform: rotate(0deg); }
}
@keyframes call-phone-ring {
    0%   { transform: scale(0.75); opacity: 0.75; border-width: 3px; }
    70%  { opacity: 0.1; }
    100% { transform: scale(2.1);  opacity: 0;    border-width: 1px; }
}
@media (prefers-reduced-motion: reduce) {
    .call-phone-ringing .call-phone-icon,
    .call-phone-ringing .call-phone-ring {
        animation: none;
    }
}
.call-modal-actions {
    display: flex;
    gap: 0.75rem;
    margin-top: 0.75rem;
}
.call-modal-hangup { margin-top: 0.5rem; }

/* Persistent in-call bar. Pinned to the top-right of the viewport so it
   overlays the DM header area where the peer's name is rendered - the
   place a Discord user instinctively looks for call controls. Survives
   view changes (friends-home, other DMs, server channels) so users can
   always see they're connected + hang up from anywhere. */
.call-bar {
    position: fixed;
    top: 4.5rem;                /* below the site-header */
    right: 1.25rem;
    z-index: 460;
    display: inline-flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.6rem 0.85rem 0.6rem 0.6rem;
    background: linear-gradient(180deg, #0f766e 0%, #065f46 100%);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0.7rem;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55),
                inset 0 1px 0 rgba(255, 255, 255, 0.06);
    font-size: 0.92rem;
    min-width: 22rem;
}
.call-bar-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
    line-height: 1.2;
}
.call-bar-name {
    font-weight: 700;
    font-size: 0.95rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.call-bar-timer {
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    color: rgba(255, 255, 255, 0.75);
}
.call-bar[hidden] { display: none; }

/* Draggable surface: grab cursor everywhere on the bar EXCEPT the
   interactive controls. Those keep their own pointer cursor via
   being `button` elements. Text-select is disabled while dragging so
   a hold + slide doesn't accidentally select the peer's name. */
.call-bar {
    cursor: grab;
    user-select: none;
    -webkit-user-select: none;
    /* `none` on the bar so touch drags go through our pointer
       handler without the browser stealing them for scrolling.
       Buttons inside override back to `manipulation` so taps still
       land without a 300 ms delay. */
    touch-action: none;
}
.call-bar.is-dragging {
    cursor: grabbing;
}
.call-bar button,
.call-bar a,
.call-bar input,
.call-bar [data-no-drag] {
    cursor: pointer;
    touch-action: manipulation;
}

/* Avatars row. Layout: [you] [peer, peer, peer...] - the `.call-bar-peers`
   container wraps the variable-length list of remote participants so we
   can lay them out independently of the "me" avatar (DM = 1 peer, group
   = up to 9). The ".is-speaking" class toggles a green pulse ring so
   it's obvious who's talking at any moment. Triggered by the voice-
   activity analysers in call.js. */
.call-bar-avatars {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
    row-gap: 0.4rem;
}
.call-bar-avatar {
    position: relative;
    width: 2.6rem;
    height: 2.6rem;
    border-radius: 50%;
    background: #1f1f1f;
    border: 2px solid rgba(255, 255, 255, 0.12);
    box-sizing: border-box;
    transition: border-color 0.12s, box-shadow 0.12s;
    flex-shrink: 0;
}

/* Owner crown inside the in-call bar. Mirrors the header crown: a
   small gold icon sitting above the avatar circle. Owner avatars
   need overflow:visible so the crown pokes out of the border-radius
   clip; other avatars stay clipped as before. */
.call-bar-avatar.is-owner { overflow: visible; }
/* Lock out the native drag-and-drop of avatar images (the browser's
   default "drag a copy of this image" gesture). Users should be
   able to click / right-click the avatar without grabbing a ghost
   image. Combined with `draggable="false"` on the <img>, this is
   belt + braces across browsers. */
.call-bar-avatar img {
    -webkit-user-drag: none;
    user-drag: none;
    -webkit-user-select: none;
    user-select: none;
    pointer-events: none;
}

/* Per-peer volume popover. Opened by right-clicking a peer avatar
   in the in-call bar. Sets remoteSpeakerGain.gain.value per peer
   and persists to localStorage so the setting survives reloads +
   future calls with the same user. */
.call-bar-volume-menu {
    position: fixed;
    z-index: 600;
    min-width: 14rem;
    padding: 0.65rem 0.8rem 0.75rem;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    color: var(--text);
}
.call-bar-volume-menu[hidden] { display: none; }
.call-bar-volume-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 0.78rem;
}
.call-bar-volume-title {
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.call-bar-volume-value {
    font-weight: 600;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.call-bar-volume-slider {
    width: 100%;
    cursor: pointer;
    accent-color: #10b981;
}
.call-bar-volume-reset {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-size: 0.75rem;
    padding: 0.3rem 0.55rem;
    border-radius: 0.3rem;
    cursor: pointer;
    align-self: flex-start;
}
.call-bar-volume-reset:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.05);
}
.call-bar-crown {
    position: absolute;
    top: -0.55rem;
    left: 50%;
    transform: translateX(-50%) rotate(-8deg);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 0.95rem;
    height: 0.95rem;
    color: #facc15;
    filter:
        drop-shadow(0 0 2px rgba(250, 204, 21, 0.7))
        drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
    pointer-events: none;
    z-index: 2;
}

/* N-peer grid: up to ~8 visible peers in a horizontal overlapping row.
   Beyond 8 we let flex clip the overflow - the "+N" chip would be a
   nice-to-have but isn't wired yet (the bar title already shows the
   total peer count for groups). */
.call-bar-peers {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
    row-gap: 0.4rem;
}
.call-bar-avatar > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    border-radius: 50%;
}

/* Peer mute badges - circular overlays at top-right / bottom-right of
   the peer avatar. Driven by the classes `peer-mic-muted` / `peer-
   speaker-muted` toggled by the call.state frame handler. */
.call-bar-badge {
    position: absolute;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
    background: #dc2626;
    color: #fff;
    display: none;
    align-items: center;
    justify-content: center;
    border: 2px solid #0f766e;    /* match call-bar background for a cut-out look */
    box-sizing: content-box;
    pointer-events: none;
}
.call-bar-badge-mic     { top:    -2px; right: -2px; }
.call-bar-badge-speaker { bottom: -2px; right: -2px; }
.call-bar-avatar.peer-mic-muted     .call-bar-badge-mic     { display: inline-flex; }
.call-bar-avatar.peer-speaker-muted .call-bar-badge-speaker { display: inline-flex; }
.call-bar-avatar.is-speaking {
    border-color: #34d399;
    box-shadow: 0 0 0 2px rgba(52, 211, 153, 0.5),
                0 0 14px rgba(52, 211, 153, 0.85);
    animation: call-speak-pulse 0.9s ease-in-out infinite;
    z-index: 1;            /* pulse sits above the neighbour in the stack */
}
@keyframes call-speak-pulse {
    0%, 100% {
        box-shadow: 0 0 0 2px rgba(52, 211, 153, 0.5),
                    0 0 6px  rgba(52, 211, 153, 0.5);
    }
    50% {
        box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.75),
                    0 0 16px rgba(52, 211, 153, 0.95);
    }
}
.call-bar-name   { font-weight: 600; }
.call-bar-timer  { font-variant-numeric: tabular-nums; color: rgba(255,255,255,0.85); }
.call-bar-hangup {
    appearance: none;
    background: #dc2626;
    color: #fff;
    border: 0;
    padding: 0.35rem 0.8rem;
    border-radius: 999px;
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
    transition: background 0.12s;
}
.call-bar-hangup:hover { background: #b91c1c; }

/* Mic + speaker mute toggles: circular icon buttons sitting just to
   the left of Stop Call. Unmuted = subtle white-on-translucent. Muted
   = solid red so you immediately see "I'm silenced" at a glance. */
.call-bar-toggle {
    appearance: none;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.14);
    border: 0;
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
    flex-shrink: 0;
}
.call-bar-toggle svg { width: 18px; height: 18px; }
.call-bar-toggle:hover { background: rgba(255, 255, 255, 0.2); }
.call-bar-toggle.is-muted {
    background: #dc2626;
    color: #fff;
}
.call-bar-toggle.is-muted:hover { background: #b91c1c; }

/* Solo mode: amber bar so the user sees at a glance that the call is
   stale and counting down. Peer avatar fades to 40% so the still-lit
   own-avatar reads as "you're here, they're not". */
.call-bar.is-solo {
    background: linear-gradient(180deg, #b45309 0%, #78350f 100%);
}
.call-bar .call-bar-avatar.is-dim {
    opacity: 0.4;
    filter: grayscale(0.6);
}
.call-bar .call-bar-avatar.is-dim.is-speaking { animation: none; box-shadow: none; border-color: rgba(255,255,255,0.12); }

/* Rejoin button - visible only in solo mode. Green so it reads as
   "reconnect" (positive action) vs the red Stop Call next to it.
   Mute toggles are hidden in solo because they're meaningless without
   an active peer connection. */
.call-bar-rejoin {
    display: none;
    appearance: none;
    background: #16a34a;
    color: #fff;
    border: 0;
    padding: 0.35rem 0.95rem;
    border-radius: 999px;
    font: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.12s, opacity 0.12s;
    flex-shrink: 0;
}
.call-bar-rejoin:hover { background: #15803d; }
.call-bar.is-solo      .call-bar-rejoin  { display: inline-flex; }
.call-bar.is-solo      .call-bar-toggle  { display: none; }
.call-bar.is-rejoining .call-bar-rejoin  { opacity: 0.55; pointer-events: none; }

/* Mobile push-to-talk button. Hidden on every device by default;
   revealed only on coarse-pointer (touch) input when PTT is enabled
   on the user's profile. Sized larger than the round mic/speaker
   toggles so a thumb can comfortably hold it during a call - it's
   the primary touch surface, not a secondary control. The touch-
   action lockout keeps a long press from triggering text selection
   or page scroll while held. The .is-pressed state lights the
   button green so the user can see the gate is open even if they
   can't hear themselves. */
.call-bar-ptt {
    display: none;
    touch-action: none;
    -webkit-user-select: none;
    user-select: none;
    position: relative;
    /* Override the round 2.1rem .call-bar-toggle into a wider pill so
       there's room for the icon + "Hold to talk" label. Min-height
       hits Apple's 44px tap target. */
    width: auto;
    min-height: 2.6rem;
    border-radius: 1.3rem;
    padding: 0 0.85rem;
    gap: 0.4rem;
    font: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    background: rgba(255, 255, 255, 0.18);
}
.call-bar-ptt svg { width: 22px; height: 22px; flex-shrink: 0; }
.call-bar-ptt-label { white-space: nowrap; }
.call-bar-ptt:hover { background: rgba(255, 255, 255, 0.24); }
.call-bar-ptt.is-pressed {
    background: #16a34a;
    box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.45),
                0 0 18px rgba(34, 197, 94, 0.55);
}
.call-bar-ptt.is-pressed:hover { background: #15803d; }
@media (pointer: coarse) {
    .call-bar.is-ptt-on .call-bar-ptt { display: inline-flex; }
    /* On real phones (narrow viewports) make it even chunkier - the
       call bar wraps to multiple rows here so we can spend the room
       on a comfortable thumb target. */
    @media (max-width: 768px) {
        .call-bar-ptt {
            min-height: 3rem;
            padding: 0 1.1rem;
            font-size: 0.85rem;
            border-radius: 1.5rem;
        }
        .call-bar-ptt svg { width: 26px; height: 26px; }
    }
}
/* In solo mode (waiting alone) the .call-bar-toggle base hides every
   toggle including the PTT button. Keep that behaviour - PTT only
   matters with peers actually listening. */

/* "Go to channel" link - shown only when the bar is floating (i.e.
   the user is on a different channel than the live call). Tapping
   it SPA-navigates to that channel; the bar then auto-docks inline
   under the chat-header via reparentCallBar(). Hidden inline because
   you're already there. */
.call-bar-goto {
    appearance: none;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.14);
    border: 0;
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
    flex-shrink: 0;
    text-decoration: none;
}
.call-bar-goto svg { width: 18px; height: 18px; }
.call-bar-goto:hover  { background: rgba(255, 255, 255, 0.2); }
.call-bar-goto:focus  { outline: none; background: rgba(255, 255, 255, 0.2); }
.call-bar.is-inline .call-bar-goto { display: none; }

/* Leaver state: we hung up. Stop Call is gone (we already stopped our
   side); only Rejoin + the countdown remain. The stayer side keeps
   Stop Call so they can fully end their own waiting session. */
.call-bar.is-solo.is-leaver .call-bar-hangup { display: none; }

/* Call-lifecycle system rows in the chat log (start / decline / timeout /
   ended). Centred, subdued, distinct from normal messages. */
.chat-message.chat-call-system {
    display: flex;
    justify-content: center;
    /* Add side padding so the pill never sits flush against the
       message-list edge on mobile. min-width: 0 lets the inner pill
       shrink below its content's intrinsic width when needed. */
    padding: 0.45rem 0.6rem;
    min-width: 0;
}
.chat-call-system-inner {
    display: inline-flex;
    align-items: center;
    /* row-gap kicks in only when wrap engages (long usernames at
       narrow widths); column gap stays at 0.5rem for the icon -> text
       -> time line. */
    gap: 0.35rem 0.5rem;
    flex-wrap: wrap;
    justify-content: center;
    padding: 0.35rem 0.85rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--border);
    /* On narrow viewports the pill wraps to multiple lines (long
       display name + "joined the server." + timestamp can easily run
       past 360 px). 999px would cap a wrapped pill into a stadium
       shape that looks broken; 1rem keeps the rounded look on
       single-line and reads cleanly when wrapped. max-width pins
       the pill inside its row so the right edge no longer spills. */
    border-radius: 1rem;
    max-width: 100%;
    min-width: 0;
    color: var(--text-muted);
    font-size: 0.82rem;
    text-align: center;
    box-sizing: border-box;
}
/* Long display names + interpolated text need to wrap inside the
   pill instead of forcing it past the viewport. anywhere covers
   ULID-like opaque tokens too. */
.chat-call-system-text {
    min-width: 0;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.chat-call-system-icon {
    display: inline-flex;
    color: #16a34a;
}
/* Call-ended + membership-leave/kick rows render with a red icon to
   visually contrast with the green "call started" marker. */
.chat-call-system-ended .chat-call-system-icon {
    color: #dc2626;
}
/* Rename rows get their own accent - a warm amber that doesn't
   collide with the red (destructive) or green (call started) cues. */
.chat-call-system-rename .chat-call-system-icon {
    color: #f59e0b;
}
/* Promotion / ownership transfer rows share the same gold as the
   crown icon elsewhere in the UI. */
.chat-call-system-promote .chat-call-system-icon {
    color: #facc15;
    filter: drop-shadow(0 0 2px rgba(250, 204, 21, 0.65));
}
/* Invite rows pick up a teal accent - distinct from the red (kick /
   leave) and the gold (promote) cues. */
.chat-call-system-invite .chat-call-system-icon {
    color: #2dd4bf;
}
.chat-call-system-time {
    font-variant-numeric: tabular-nums;
    opacity: 0.7;
}

/* ============================================================
   Settings -> Voice Chat pane: sliders + VU meter.
   ============================================================ */

/* Native <select> inside a .signin-input gets a custom caret. */
.signin-input:is(select) {
    appearance: none;
    background-image:
        linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
        linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
    background-position:
        calc(100% - 1rem) 55%,
        calc(100% - 0.7rem) 55%;
    background-size: 0.3rem 0.3rem;
    background-repeat: no-repeat;
    padding-right: 2rem;
    cursor: pointer;
}
.signin-input:is(select):disabled { opacity: 0.5; cursor: not-allowed; }

/* Slider rows. Label + numeric value right-aligned; slider beneath. */
.settings-voice-slider {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-bottom: 0.35rem;
}

/* Push-to-talk binder. Row reveals when PTT is enabled. The bind
   button switches to a pulsing "press a key" prompt while in capture
   mode (driven by .is-capturing on the button itself). */
.settings-ptt-row { margin-top: 0.5rem; }
.settings-ptt-row[hidden] { display: none; }
.settings-ptt-binder {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.settings-ptt-hint {
    margin-top: 0.4rem;
    font-size: 0.78rem;
    color: var(--text-muted);
}
#settings-ptt-bind-btn.is-capturing {
    background: rgba(124, 58, 237, 0.22);
    border-color: var(--brand);
    color: #fff;
    animation: pttPulse 1.2s ease-in-out infinite;
}
@keyframes pttPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(124, 58, 237, 0.4); }
    50%      { box-shadow: 0 0 0 6px rgba(124, 58, 237, 0); }
}
.settings-voice-slider label {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 0.82rem;
    color: var(--text-muted);
    letter-spacing: 0.02em;
}
.settings-voice-slider-value {
    color: var(--text);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}
.settings-voice-slider input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 0.4rem;
    background: #262626;
    border: 1px solid var(--border);
    border-radius: 999px;
    outline: none;
    cursor: pointer;
}
.settings-voice-slider input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    background: var(--brand);
    border: 2px solid #fff;
    cursor: pointer;
    transition: transform 0.1s;
}
.settings-voice-slider input[type="range"]::-webkit-slider-thumb:hover {
    transform: scale(1.15);
}
.settings-voice-slider input[type="range"]::-moz-range-thumb {
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    background: var(--brand);
    border: 2px solid #fff;
    cursor: pointer;
}

/* Mic-test row: button + VU meter side by side. */
.settings-voice-test-row {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    flex-wrap: wrap;
}
.settings-voice-meter {
    flex: 1 1 auto;
    min-width: 8rem;
    height: 0.75rem;
    background: #111;
    border: 1px solid var(--border);
    border-radius: 999px;
    overflow: hidden;
    position: relative;
}
.settings-voice-meter-fill {
    display: block;
    width: 0%;
    height: 100%;
    background: linear-gradient(90deg, #16a34a 0%, #eab308 75%, #ef4444 100%);
    transition: width 0.06s linear;
}

/* ============================================================
   Group chats: 3-dot menu, Create Group modal, stacked group
   sidebar rows. Kept at the bottom so this whole feature stays
   easy to lift or tweak in one block.
   ============================================================ */

/* Chat header 3-dot button. Matches .chat-call-btn / .chat-pins-btn
   so the three controls line up as a row. */
.chat-dm-menu-btn {
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: #1a1a1a;
    border: 1px solid var(--border);
    color: var(--text-muted);
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
    margin-left: 0.5rem;
}
.chat-dm-menu-btn:hover {
    background: #1f2937;
    border-color: #334155;
    color: var(--text);
}

/* Small dropdown anchored under the 3-dot button. Patterned after
   .chat-dm-context-menu so floating menus feel consistent. */
.chat-dm-menu-popover {
    position: fixed;
    z-index: 1200;
    min-width: 10rem;
    background: #111;
    border: 1px solid var(--border);
    border-radius: 0.45rem;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
    padding: 0.25rem;
    display: flex;
    flex-direction: column;
}
.chat-dm-menu-popover[hidden] { display: none; }
.chat-dm-menu-item {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text);
    text-align: left;
    font-size: 0.9rem;
    padding: 0.45rem 0.7rem;
    border-radius: 0.3rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
}
.chat-dm-menu-item:hover,
.chat-dm-menu-item:focus-visible {
    background: rgba(255, 255, 255, 0.08);
    outline: none;
}

/* @mention suggester popover. Rendered at body level and positioned
   above the composer textarea by chat-client.js. One row per
   candidate user with avatar + display name + handle; keyboard
   navigation highlights the current pick. */
.chat-mention-popover {
    position: fixed;
    z-index: 1200;
    /* min-width was 14rem (224 px). On a 360 px viewport (22.5 rem)
       with safe-area-inset, that left only ~8 rem of breathing room.
       Clamp it so the popover can shrink below 14 rem on small screens
       and the max-width still wins. */
    min-width: min(14rem, calc(100vw - 1rem));
    max-width: min(22rem, calc(100vw - 1rem));
    max-height: min(18rem, 60vh);
    background: #0e0e10;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
    overflow-y: auto;
    padding: 0.25rem;
}
.chat-mention-popover[hidden] { display: none; }
.chat-mention-row {
    display: grid;
    grid-template-columns: 1.75rem auto 1fr;
    grid-template-areas: "av dot ident";
    gap: 0.5rem;
    align-items: center;
    padding: 0.4rem 0.55rem;
    border-radius: 0.35rem;
    cursor: pointer;
    color: var(--text);
}
.chat-mention-row:hover,
.chat-mention-row.is-active {
    background: rgba(124, 58, 237, 0.22);
}
.chat-mention-avatar {
    grid-area: av;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    object-fit: cover;
    background: #1f1f1f;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text);
}
.chat-mention-dot {
    grid-area: dot;
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: #3f3f46;
}
.chat-mention-dot.is-online  { background: #22c55e; box-shadow: 0 0 4px rgba(34, 197, 94, 0.7); }
.chat-mention-dot.is-offline { background: #52525b; }
.chat-mention-ident {
    grid-area: ident;
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.2;
}
.chat-mention-name {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.chat-mention-handle {
    font-size: 0.78rem;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* @everyone row: amber-tinted avatar chip + the usual ident cell. */
.chat-mention-row-everyone .chat-mention-avatar-everyone {
    background: rgba(251, 191, 36, 0.22);
    color: #fbbf24;
    border: 1px solid rgba(251, 191, 36, 0.45);
}
.chat-mention-row-everyone .chat-mention-name {
    color: #fbbf24;
}
.chat-mention-row-everyone.is-active,
.chat-mention-row-everyone:hover {
    background: rgba(251, 191, 36, 0.18);
}

/* Group rename modal. Single-input dialog, mirrors the visual
   vocabulary of the URL-insert prompt but with its own header +
   character counter + subtle entrance animation. */
.chat-rename-modal { z-index: 320; }
.chat-rename-card {
    width: min(26rem, calc(100vw - 2rem));
    padding: 0;
    overflow: hidden;
    border: 1px solid var(--border);
    background:
        radial-gradient(120% 60% at 0% 0%, rgba(99, 102, 241, 0.18), transparent 60%),
        radial-gradient(120% 60% at 100% 100%, rgba(236, 72, 153, 0.14), transparent 60%),
        #0c0c10;
    box-shadow: 0 22px 60px rgba(0, 0, 0, 0.55),
                0 0 0 1px rgba(255, 255, 255, 0.03);
    animation: chat-rename-pop 160ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
@keyframes chat-rename-pop {
    from { opacity: 0; transform: translateY(6px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0)   scale(1); }
}
.chat-rename-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.85rem 1rem 0.7rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.chat-rename-icon {
    width: 2rem;
    height: 2rem;
    border-radius: 0.5rem;
    background: linear-gradient(135deg, rgba(99, 102, 241, 0.35), rgba(236, 72, 153, 0.25));
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.chat-rename-title {
    margin: 0;
    font-size: 1.02rem;
    font-weight: 600;
    color: var(--text);
    flex: 1 1 auto;
}
.chat-rename-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.45rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.1rem 0.5rem;
    border-radius: 0.3rem;
}
.chat-rename-close:hover,
.chat-rename-close:focus-visible {
    color: var(--text);
    background: rgba(255, 255, 255, 0.06);
    outline: none;
}
.chat-rename-body {
    padding: 0.9rem 1rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}
.chat-rename-message {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.86rem;
    line-height: 1.4;
}
.chat-rename-input {
    appearance: none;
    background: #0a0a0d;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0.45rem;
    color: var(--text);
    padding: 0.6rem 0.75rem;
    font-size: 0.98rem;
    width: 100%;
    box-sizing: border-box;
    transition: border-color 0.12s, box-shadow 0.12s;
}
.chat-rename-input:focus {
    outline: none;
    border-color: rgba(99, 102, 241, 0.65);
    box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.18);
}
.chat-rename-meta {
    display: flex;
    justify-content: flex-end;
    font-size: 0.72rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    margin-top: -0.3rem;
}
.chat-rename-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 0.35rem;
}

/* Create Group modal. Reuses .modal / .modal-card / .modal-backdrop
   from the shared modal system; just adds the inner layout + the
   scrollable friend picker. */
.chat-group-modal { z-index: 310; }
.chat-group-modal-card {
    width: min(28rem, calc(100vw - 2rem));
    display: flex;
    flex-direction: column;
    max-height: min(36rem, calc(100vh - 2rem));
    max-height: min(36rem, calc(100dvh - 2rem));
    overflow: hidden;
}
.chat-group-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.9rem 1rem 0.65rem;
    border-bottom: 1px solid var(--border);
}
.chat-group-modal-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--text);
}
.chat-group-modal-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.5rem;
    border-radius: 0.3rem;
}
.chat-group-modal-close:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.05);
}
.chat-group-modal-body {
    padding: 0.75rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    min-height: 0;
    flex: 1 1 auto;
}
.chat-group-modal-label {
    font-size: 0.8rem;
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.chat-group-modal-name {
    appearance: none;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    padding: 0.55rem 0.7rem;
    font-size: 0.95rem;
    width: 100%;
    box-sizing: border-box;
}
.chat-group-modal-name:focus-visible {
    outline: none;
    border-color: var(--brand);
}
.chat-group-modal-pick-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 0.25rem;
}
.chat-group-modal-pick-heading {
    font-size: 0.8rem;
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.chat-group-modal-counter {
    font-size: 0.8rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.chat-group-modal-friends {
    flex: 1 1 auto;
    min-height: 8rem;
    max-height: 18rem;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    background: #0f0f10;
    padding: 0.25rem;
}
.chat-group-modal-loading,
.chat-group-modal-empty {
    padding: 1.5rem 1rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
}
.chat-group-pick-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.4rem 0.55rem;
    border-radius: 0.35rem;
    cursor: pointer;
    color: var(--text);
    transition: background 0.12s;
}
.chat-group-pick-row:hover { background: rgba(255, 255, 255, 0.05); }
.chat-group-pick-row.is-checked { background: rgba(56, 189, 248, 0.08); }
.chat-group-pick-row.is-locked {
    cursor: not-allowed;
    opacity: 0.85;
}
.chat-group-pick-box {
    width: 1rem;
    height: 1rem;
    accent-color: var(--brand);
    flex: 0 0 auto;
    margin: 0;
}
.chat-group-pick-avatar-wrap {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
}
.chat-group-pick-avatar {
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    object-fit: cover;
    background: #050505;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.chat-group-pick-avatar-fallback {
    background: #1f1f1f;
    color: var(--text);
    font-weight: 700;
    font-size: 0.85rem;
}
.chat-group-pick-dot {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    border: 2px solid var(--surface);
    box-sizing: content-box;
}
.chat-group-pick-dot.is-online  { background: #22c55e; }
.chat-group-pick-dot.is-offline { background: #6b7280; }
.chat-group-pick-ident {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}
.chat-group-pick-name {
    font-size: 0.95rem;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-group-pick-handle {
    font-size: 0.78rem;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-group-pick-lock {
    font-size: 0.7rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: 0.15rem 0.4rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    flex: 0 0 auto;
}
.chat-group-modal-error {
    color: #f87171;
    font-size: 0.85rem;
    padding: 0.4rem 0.55rem;
    background: rgba(248, 113, 113, 0.1);
    border: 1px solid rgba(248, 113, 113, 0.3);
    border-radius: 0.35rem;
}
.chat-group-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.65rem 1rem 0.9rem;
    border-top: 1px solid var(--border);
}

/* Report modal. Reuses .modal / .modal-card / .modal-backdrop from the
   shared modal system; adds a tinted icon badge in the header, a
   target-preview card with a kind-pill, an info-styled reassurance
   block, and a gradient submit button. Built lazily by site.js so
   pages without a report entry-point pay no DOM cost. */
.report-modal { z-index: 310; }
.report-modal .modal-backdrop {
    /* A touch heavier blur than the default .modal-backdrop so the
       page behind feels muted and the moderation action gets weight. */
    background: rgba(0, 0, 0, 0.62);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.report-modal-card {
    position: relative;
    width: min(30rem, calc(100vw - 2rem));
    display: flex;
    flex-direction: column;
    max-height: min(40rem, calc(100dvh - 2rem));
    overflow: hidden;
    border-radius: 0.85rem;
    /* Soft drop shadow + faint inner edge highlight for depth. */
    box-shadow:
        0 24px 70px rgba(0, 0, 0, 0.55),
        0 0 0 1px rgba(255, 255, 255, 0.02) inset;
    animation: report-modal-pop 0.18s cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes report-modal-pop {
    from { opacity: 0; transform: translateY(8px) scale(0.96); }
    to   { opacity: 1; transform: none; }
}
.report-modal-head {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.4rem;
    padding: 1.4rem 1.25rem 1rem;
    border-bottom: 1px solid var(--border);
    /* Subtle red-orange wash behind the icon to reinforce "this is a
       moderation action" without screaming about it. */
    background:
        radial-gradient(circle at 50% 0%, rgba(244, 63, 94, 0.10), transparent 60%),
        radial-gradient(circle at 50% 0%, rgba(249, 115, 22, 0.06), transparent 70%);
}
.report-modal-icon {
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fda4af;
    background: linear-gradient(135deg, rgba(244, 63, 94, 0.18), rgba(249, 115, 22, 0.18));
    border: 1px solid rgba(244, 63, 94, 0.45);
    box-shadow:
        0 0 0 6px rgba(244, 63, 94, 0.06),
        0 0 24px rgba(244, 63, 94, 0.20);
    margin-bottom: 0.15rem;
}
.report-modal-title {
    margin: 0;
    font-size: 1.2rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
}
.report-modal-subtitle {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
}
.report-modal-close {
    position: absolute;
    top: 0.55rem;
    right: 0.65rem;
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.5rem;
    border-radius: 0.35rem;
    min-width: 2.25rem;
    min-height: 2.25rem;
    z-index: 2;
}
.report-modal-close:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.05);
}
.report-modal-body {
    padding: 0.95rem 1.1rem 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    min-height: 0;
    flex: 1 1 auto;
    overflow-y: auto;
}
.report-modal-preview {
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.6rem 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}
.report-modal-preview-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
}
.report-modal-preview-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    font-weight: 600;
}
.report-modal-preview-badge {
    display: inline-block;
    padding: 0.15rem 0.55rem;
    font-size: 0.72rem;
    font-weight: 600;
    border-radius: 999px;
    background: rgba(124, 58, 237, 0.15);
    color: #c4b5fd;
    border: 1px solid rgba(124, 58, 237, 0.45);
    letter-spacing: 0.01em;
    text-transform: uppercase;
}
.report-modal-preview-badge[data-kind="user"] {
    background: rgba(59, 130, 246, 0.15);
    color: #93c5fd;
    border-color: rgba(59, 130, 246, 0.45);
}
.report-modal-preview-badge[data-kind="server"] {
    background: rgba(244, 114, 182, 0.15);
    color: #f9a8d4;
    border-color: rgba(244, 114, 182, 0.45);
}
.report-modal-preview-text {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
    max-height: 5.5rem;
    overflow: hidden;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-wrap: anywhere;
}
.report-modal-label {
    font-size: 0.78rem;
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-top: 0.2rem;
}
.report-modal-reason {
    appearance: none;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--text);
    padding: 0.6rem 0.75rem;
    font: inherit;
    font-size: 0.95rem;
    line-height: 1.5;
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
    min-height: 6rem;
    max-height: 18rem;
    transition: border-color 0.15s, box-shadow 0.15s;
}
.report-modal-reason:focus,
.report-modal-reason:focus-visible {
    outline: none;
    border-color: var(--brand);
    /* Purple accent ring matching the project's --brand. */
    box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.25);
}
.report-modal-reason::placeholder {
    color: #5a5a5a;
}
.report-modal-foot-row {
    display: flex;
    justify-content: flex-end;
    margin-top: -0.35rem;
}
.report-modal-counter {
    font-size: 0.78rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.report-modal-error {
    padding: 0.5rem 0.7rem;
    background: rgba(244, 63, 94, 0.08);
    border: 1px solid rgba(244, 63, 94, 0.35);
    color: #fecaca;
    border-radius: 0.5rem;
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}
/* Reassurance block: muted info-blue background, info-circle icon,
   a short bulleted list explaining what happens after submission. */
.report-modal-reassure {
    display: flex;
    gap: 0.6rem;
    padding: 0.65rem 0.75rem;
    background: rgba(59, 130, 246, 0.06);
    border: 1px solid rgba(59, 130, 246, 0.22);
    border-radius: 0.5rem;
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.45;
}
.report-modal-reassure-icon {
    flex: 0 0 auto;
    color: #60a5fa;
    margin-top: 0.05rem;
}
.report-modal-reassure-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}
.report-modal-reassure-list li {
    position: relative;
    padding-left: 0.85rem;
}
.report-modal-reassure-list li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.55rem;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: rgba(96, 165, 250, 0.7);
}
/* Warning callout: same shape as the blue reassurance block but tinted
   with the red/rose palette from the submit-button gradient. Used to
   make the "false reports may be punished" warning more prominent than
   a buried bullet. */
.report-modal-warn {
    display: flex;
    gap: 0.6rem;
    padding: 0.65rem 0.75rem;
    background: rgba(244, 63, 94, 0.07);
    border: 1px solid rgba(244, 63, 94, 0.32);
    border-radius: 0.5rem;
    color: #fecaca;
    font-size: 0.82rem;
    line-height: 1.45;
}
.report-modal-warn-icon {
    flex: 0 0 auto;
    color: #fb7185;
    margin-top: 0.05rem;
}
.report-modal-warn-text {
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
}
.report-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.55rem;
    padding: 0.75rem 1.1rem 0.95rem;
    border-top: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.015);
}
.report-modal-actions .btn {
    min-height: 2.4rem;
}
/* Submit button: red-to-orange gradient that mirrors the icon badge.
   Disabled state drops back to a muted slate so it doesn't shout. */
.report-modal-submit {
    color: #fff;
    border: 1px solid rgba(244, 63, 94, 0.5);
    background: linear-gradient(135deg, #f43f5e 0%, #fb7185 50%, #fb923c 100%);
    box-shadow: 0 4px 14px rgba(244, 63, 94, 0.30);
    font-weight: 600;
}
.report-modal-submit:hover:not(:disabled) {
    filter: brightness(1.06);
    box-shadow: 0 6px 18px rgba(244, 63, 94, 0.38);
}
.report-modal-submit:active:not(:disabled) {
    filter: brightness(0.96);
}
.report-modal-submit:disabled {
    background: #2a2a2a;
    border-color: var(--border);
    color: #6b6b6b;
    box-shadow: none;
    cursor: not-allowed;
}
/* Tighter footer + larger touch targets at narrow widths so the modal
   stays usable around the iPhone safe-area + small Androids. */
@media (max-width: 420px) {
    .report-modal-card {
        width: calc(100vw - 1rem);
        max-height: calc(100dvh - 1rem);
        border-radius: 0.7rem;
    }
    .report-modal-head { padding: 1.05rem 1rem 0.85rem; }
    .report-modal-icon { width: 2.6rem; height: 2.6rem; }
    .report-modal-icon svg { width: 22px; height: 22px; }
    .report-modal-title { font-size: 1.05rem; }
    .report-modal-subtitle { font-size: 0.8rem; }
    .report-modal-body { padding: 0.8rem 0.9rem 0.5rem; gap: 0.6rem; }
    .report-modal-reassure { padding: 0.55rem 0.65rem; font-size: 0.78rem; }
    .report-modal-reassure-list { gap: 0.25rem; }
    .report-modal-warn { padding: 0.55rem 0.65rem; font-size: 0.78rem; }
    .report-modal-actions { flex-direction: column-reverse; padding: 0.7rem 0.9rem 0.85rem; }
    .report-modal-actions .btn { width: 100%; }
}

/* Generic note-prompt modal: title + optional message + textarea.
   Used by the staff reports page to collect an optional resolution
   note. Same DOM frame as the report modal so the look is consistent. */
.note-prompt-modal { z-index: 320; }
.note-prompt-card {
    width: min(26rem, calc(100vw - 2rem));
    display: flex;
    flex-direction: column;
    max-height: min(28rem, calc(100dvh - 2rem));
    overflow: hidden;
}
.note-prompt-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.85rem 1rem 0.55rem;
    border-bottom: 1px solid var(--border);
}
.note-prompt-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
}
.note-prompt-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.5rem;
    border-radius: 0.3rem;
    min-width: 2.25rem;
    min-height: 2.25rem;
}
.note-prompt-close:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.05);
}
.note-prompt-body {
    padding: 0.75rem 1rem 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-height: 0;
    flex: 1 1 auto;
}
.note-prompt-msg {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.88rem;
    line-height: 1.4;
}
.note-prompt-text {
    appearance: none;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    padding: 0.55rem 0.7rem;
    font: inherit;
    font-size: 0.95rem;
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
    min-height: 5rem;
    max-height: 14rem;
}
.note-prompt-text:focus-visible {
    outline: none;
    border-color: var(--brand);
}
.note-prompt-foot-row {
    display: flex;
    justify-content: flex-end;
}
.note-prompt-counter {
    font-size: 0.78rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.note-prompt-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.6rem 1rem 0.85rem;
    border-top: 1px solid var(--border);
}
.note-prompt-actions .btn {
    min-height: 2.4rem;
}
@media (max-width: 420px) {
    .note-prompt-card {
        width: calc(100vw - 1rem);
        max-height: calc(100dvh - 1rem);
    }
    .note-prompt-actions { flex-direction: column-reverse; }
    .note-prompt-actions .btn { width: 100%; }
}

/* ---------------- Seller-role application modal ----------------
   Lazy-built modal opened by the "Request Seller Role" pill on the
   marketplace browse page. Reuses the global .modal frame; the inner
   .seller-apply-* classes are scoped so they don't leak into other
   dialogs sharing the modal infrastructure. Fields stack vertically
   so the form scales cleanly to mobile widths (390 / 360 px). */
.seller-apply-card {
    width: min(34rem, calc(100vw - 2rem));
    max-height: min(48rem, calc(100dvh - 2rem));
    display: flex;
    flex-direction: column;
    overflow: hidden;
    position: relative;
}
.seller-apply-close {
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.5rem;
    border-radius: 0.3rem;
    /* 40 px touch target per the project's mobile-first rules. */
    min-width: 2.5rem;
    min-height: 2.5rem;
    z-index: 1;
}
.seller-apply-close:hover,
.seller-apply-close:focus-visible,
.seller-apply-close:active { color: var(--text); background: rgba(255,255,255,0.05); }
.seller-apply-head {
    padding: 1rem 1rem 0.4rem;
    border-bottom: 1px solid var(--border);
}
.seller-apply-title {
    margin: 0 2rem 0.4rem 0;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text);
}
.seller-apply-intro {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.45;
}
.seller-apply-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.85rem 1rem 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-height: 0;
}
.seller-apply-agreements {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    padding: 0.7rem 0.85rem;
    border-radius: 0.5rem;
    background: rgba(251, 191, 36, 0.06);
    border: 1px solid rgba(251, 191, 36, 0.25);
}
.seller-apply-check {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    color: var(--text);
    font-size: 0.9rem;
    line-height: 1.4;
    cursor: pointer;
    user-select: none;
}
.seller-apply-check input[type="checkbox"] {
    margin-top: 0.18rem;
    width: 1rem;
    height: 1rem;
    accent-color: #fbbf24;
    flex: 0 0 auto;
}
.seller-apply-check a {
    color: #fbbf24;
    text-decoration: underline;
}
.seller-apply-check a:hover { color: #fcd34d; }
.seller-apply-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.seller-apply-label {
    color: var(--text);
    font-weight: 600;
    font-size: 0.92rem;
}
/* Red asterisk on required-field labels. Tight letter-spacing so the
   star sits against the label text without a gap, but with enough
   padding-left to feel separate. The aria-label on the wrapper marks
   it for screen readers. */
.seller-apply-required-star {
    color: #f87171;
    font-weight: 700;
    margin-left: 0.05rem;
}
/* Helper line under a required label, e.g. "Min 20 characters." Sits
   between the label and the input itself so the user reads the
   constraint before typing. */
.seller-apply-required-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.78rem;
    line-height: 1.3;
}
/* "Required:" heading above the agreement checkboxes. Same font scale
   as a field label so the visual hierarchy matches. */
.seller-apply-agreements-heading {
    margin: 0 0 0.25rem;
    color: var(--text);
    font-weight: 600;
    font-size: 0.92rem;
}
.seller-apply-field-body {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.seller-apply-textarea,
.seller-apply-input {
    appearance: none;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    padding: 0.55rem 0.7rem;
    font: inherit;
    font-size: 0.95rem;
    width: 100%;
    box-sizing: border-box;
}
.seller-apply-textarea {
    resize: vertical;
    min-height: 4.5rem;
    max-height: 14rem;
}
.seller-apply-textarea:focus-visible,
.seller-apply-input:focus-visible {
    outline: none;
    border-color: var(--brand);
}
.seller-apply-counter {
    align-self: flex-end;
    font-size: 0.78rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.seller-apply-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.4;
}
.seller-apply-radio-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.seller-apply-radio {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    background: #0f0f10;
    color: var(--text);
    font-size: 0.88rem;
    cursor: pointer;
    user-select: none;
    min-height: 2.4rem;
}
.seller-apply-radio:has(input:checked) {
    border-color: var(--brand);
    background: rgba(124, 58, 237, 0.18);
    color: #fff;
}
.seller-apply-radio input[type="radio"] {
    accent-color: var(--brand);
    margin: 0;
}
.seller-apply-count-wrap {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    margin-top: 0.2rem;
    padding-top: 0.5rem;
    border-top: 1px dashed var(--border);
}
.seller-apply-count-label {
    color: var(--text-muted);
    font-size: 0.85rem;
}
.seller-apply-error {
    background: rgba(220, 38, 38, 0.08);
    border: 1px solid rgba(220, 38, 38, 0.4);
    border-radius: 0.4rem;
    color: #fca5a5;
    padding: 0.55rem 0.75rem;
    font-size: 0.88rem;
}
.seller-apply-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.7rem 1rem 0.9rem;
    border-top: 1px solid var(--border);
}
.seller-apply-actions .btn { min-height: 2.5rem; }
.seller-apply-submit {
    background: #fbbf24;
    color: #1f1700;
    border: 1px solid #fbbf24;
    font-weight: 700;
}
.seller-apply-submit:hover:not(:disabled),
.seller-apply-submit:focus:not(:disabled) {
    background: #fcd34d; border-color: #fcd34d;
}
.seller-apply-submit:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}
@media (max-width: 560px) {
    .seller-apply-card {
        width: calc(100vw - 1rem);
        max-height: calc(100dvh - 1rem);
    }
    .seller-apply-actions { flex-direction: column-reverse; }
    .seller-apply-actions .btn { width: 100%; }
    .seller-apply-radio { flex: 1 1 30%; justify-content: center; }
}

/* ---------------- Message-link chips ----------------
   Inline pill rendered in place of any pasted Madrigal message URL
   (chat-client.js's processMessageLinkChips post-processor). Sits
   inside .chat-message-content so it inherits the surrounding line
   height and wraps naturally with adjacent text. State variants:
     is-loading  - shimmer + lock icon, neutral colour.
     ok          - default purple-tinted accent (matches md-link).
     deleted     - dimmed grey, lock icon, click navigates to channel.
     locked      - amber-tinted, lock icon, click pops the dialog.
     error       - same shape as locked, neutral grey-red. */
.msg-link-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    max-width: 100%;
    min-height: 1.6rem;
    padding: 0.15rem 0.55rem;
    margin: 0 0.05rem;
    border: 1px solid rgba(167, 139, 250, 0.35);
    border-radius: 0.45rem;
    background: rgba(167, 139, 250, 0.10);
    color: #c4b5fd;
    text-decoration: none;
    font-size: 0.86rem;
    font-weight: 500;
    line-height: 1.25;
    vertical-align: baseline;
    cursor: pointer;
    overflow-wrap: anywhere;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.msg-link-chip:hover {
    background: rgba(167, 139, 250, 0.18);
    border-color: rgba(167, 139, 250, 0.55);
    color: #ddd6fe;
    text-decoration: none;
}
.msg-link-chip:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}
.msg-link-chip-icon {
    flex: 0 0 auto;
    opacity: 0.85;
}
.msg-link-chip-channel,
.msg-link-chip-server {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    min-width: 0;
}
.msg-link-chip-channel > span:not(.msg-link-chip-icon),
.msg-link-chip-server > span:not(.msg-link-chip-server-icon) {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.msg-link-chip-server-icon {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    border-radius: 4px;
    object-fit: cover;
    background: #2a2a2a;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 700;
    color: #fff;
    line-height: 1;
}
.msg-link-chip-server-icon-fb {
    background: linear-gradient(135deg, #3b3b6e 0%, #5b3b6e 100%);
    color: #ddd6fe;
}
.msg-link-chip-sep {
    flex: 0 0 auto;
    color: var(--text-muted);
    opacity: 0.7;
}
.msg-link-chip-deleted-prefix {
    color: var(--text-muted);
    margin-right: 0.15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Loading shimmer: muted grey label that pulses gently until the
   /api/chat/message-link fetch resolves. Avoids any layout shift -
   chip dimensions match the resolved variants. */
.msg-link-chip.is-loading {
    border-color: rgba(255, 255, 255, 0.10);
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-muted);
    cursor: default;
}
.msg-link-chip-loading {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    animation: msg-link-chip-pulse 1.4s ease-in-out infinite;
}
@keyframes msg-link-chip-pulse {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 1; }
}
/* Deleted state: muted grey, lock icon. Click still navigates to the
   channel root so the user lands somewhere useful. */
.msg-link-chip[data-state="deleted"] {
    border-color: rgba(148, 163, 184, 0.30);
    background: rgba(148, 163, 184, 0.08);
    color: var(--text-muted);
}
.msg-link-chip[data-state="deleted"]:hover {
    background: rgba(148, 163, 184, 0.14);
    border-color: rgba(148, 163, 184, 0.50);
    color: #cbd5e1;
}
/* Locked state: amber tint, lock icon front and centre. */
.msg-link-chip[data-state="locked"] {
    border-color: rgba(251, 191, 36, 0.40);
    background: rgba(251, 191, 36, 0.10);
    color: #fcd34d;
}
.msg-link-chip[data-state="locked"]:hover {
    background: rgba(251, 191, 36, 0.18);
    border-color: rgba(251, 191, 36, 0.60);
    color: #fde68a;
}
/* Error state: neutral grey-red, mostly read-only fallback. */
.msg-link-chip[data-state="error"] {
    border-color: rgba(244, 63, 94, 0.30);
    background: rgba(244, 63, 94, 0.05);
    color: #fca5a5;
}
.msg-link-chip[data-state="error"]:hover {
    background: rgba(244, 63, 94, 0.10);
    border-color: rgba(244, 63, 94, 0.50);
}
@media (max-width: 420px) {
    .msg-link-chip {
        font-size: 0.82rem;
        gap: 0.3rem;
        min-height: 2rem;
        padding: 0.2rem 0.5rem;
    }
    .msg-link-chip-server-icon {
        width: 14px;
        height: 14px;
    }
}

/* ---------------- Channel-mention chips ----------------
   `<#01XXX>` tokens render as a Discord-style channel pill. Visual
   weight matches `.md-mention` (small inline pill with hash glyph),
   distinct from the fatter `.msg-link-chip` so users can tell
   #channel apart from a forwarded message link at a glance.

   States:
     is-loading - muted grey with pulsing "#…" placeholder.
     ok         - blue-tinted pill, click navigates via chat-spa.js.
     locked     - amber-tinted with lock icon, click pops dialog.
     error      - rose-tinted, click pops dialog reason='error'. */
.chat-mention-channel {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    max-width: 100%;
    padding: 0 0.35rem;
    border-radius: 0.3rem;
    background: rgba(99, 102, 241, 0.18);
    color: #a5b4fc;
    text-decoration: none;
    font-weight: 500;
    line-height: 1.35;
    cursor: pointer;
    overflow-wrap: anywhere;
    vertical-align: baseline;
    transition: background 0.12s ease, color 0.12s ease;
}
.chat-mention-channel:hover {
    background: rgba(99, 102, 241, 0.32);
    color: #e0e7ff;
    text-decoration: none;
}
.chat-mention-channel:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}
.chat-mention-channel-icon {
    flex: 0 0 auto;
    opacity: 0.85;
}
.chat-mention-channel-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.chat-mention-channel-server {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    opacity: 0.85;
    font-weight: 500;
}
.chat-mention-channel-sep {
    flex: 0 0 auto;
    opacity: 0.7;
    font-size: 0.95em;
    line-height: 1;
}
.chat-mention-channel.is-loading {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-muted);
    cursor: default;
}
.chat-mention-channel-loading {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    animation: chat-mention-channel-pulse 1.4s ease-in-out infinite;
}
@keyframes chat-mention-channel-pulse {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 1; }
}
.chat-mention-channel[data-state="locked"] {
    background: rgba(251, 191, 36, 0.18);
    color: #fcd34d;
}
.chat-mention-channel[data-state="locked"]:hover {
    background: rgba(251, 191, 36, 0.30);
    color: #fde68a;
}
.chat-mention-channel[data-state="error"] {
    background: rgba(244, 63, 94, 0.14);
    color: #fca5a5;
}
.chat-mention-channel[data-state="error"]:hover {
    background: rgba(244, 63, 94, 0.24);
    color: #fecaca;
}

/* Suggester popover row icon: re-uses .chat-mention-avatar slot but
   recolours so the hash glyph reads as "channel" instead of "person". */
.chat-channel-mention-icon {
    background: rgba(99, 102, 241, 0.20);
    color: #a5b4fc;
    border: 1px solid rgba(99, 102, 241, 0.35);
}
.chat-channel-mention-row.is-active,
.chat-channel-mention-row:hover {
    background: rgba(99, 102, 241, 0.22);
}
.chat-channel-mention-row .chat-mention-name {
    color: #c7d2fe;
}

@media (max-width: 420px) {
    .chat-mention-channel {
        font-size: 0.82rem;
        gap: 0.2rem;
        padding: 0.1rem 0.3rem;
    }
}

/* ---------------- Locked-message dialog ----------------
   Fancy "Message unavailable" popup shown when a chip click hits a
   target the viewer can't access. Header with tinted padlock icon
   badge + bold title + muted subtitle, body in a card-styled block,
   single OK button. Mirrors the report-modal energy with an amber /
   red-orange palette so it reads as informational-but-locked, not
   destructive. */
.locked-msg-modal { z-index: 315; }
.locked-msg-modal .modal-backdrop {
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.locked-msg-card {
    position: relative;
    width: min(28rem, calc(100vw - 2rem));
    max-height: calc(100dvh - 2rem);
    display: flex;
    flex-direction: column;
    border-radius: 0.95rem;
    overflow: hidden;
    /* Soft drop shadow + faint inner edge highlight, same recipe as
       the report dialog so the two feel like family. */
    box-shadow:
        0 24px 70px rgba(0, 0, 0, 0.55),
        0 0 0 1px rgba(255, 255, 255, 0.02) inset;
    animation: locked-msg-pop 0.2s cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes locked-msg-pop {
    from { opacity: 0; transform: translateY(8px) scale(0.96); }
    to   { opacity: 1; transform: none; }
}
.locked-msg-head {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.45rem;
    padding: 1.5rem 1.25rem 1.05rem;
    border-bottom: 1px solid var(--border);
    /* Subtle amber-rose wash behind the icon to reinforce "locked /
       informational" without screaming about it. */
    background:
        radial-gradient(circle at 50% 0%, rgba(251, 191, 36, 0.10), transparent 60%),
        radial-gradient(circle at 50% 0%, rgba(244, 63, 94, 0.06), transparent 70%);
}
.locked-msg-icon {
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fcd34d;
    background: linear-gradient(135deg, rgba(251, 191, 36, 0.20), rgba(244, 63, 94, 0.18));
    border: 1px solid rgba(251, 191, 36, 0.45);
    box-shadow:
        0 0 0 6px rgba(251, 191, 36, 0.06),
        0 0 24px rgba(251, 191, 36, 0.18);
    margin-bottom: 0.15rem;
}
.locked-msg-title {
    margin: 0;
    font-size: 1.2rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
}
.locked-msg-subtitle {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
    overflow-wrap: anywhere;
}
.locked-msg-close {
    position: absolute;
    top: 0.55rem;
    right: 0.65rem;
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.5rem;
    border-radius: 0.35rem;
    min-width: 2.25rem;
    min-height: 2.25rem;
    z-index: 2;
}
.locked-msg-close:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.05);
}
.locked-msg-body {
    padding: 0.95rem 1.1rem 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}
.locked-msg-text {
    margin: 0;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    padding: 0.75rem 0.85rem;
    color: var(--text);
    font-size: 0.92rem;
    line-height: 1.5;
    overflow-wrap: anywhere;
}
.locked-msg-actions {
    display: flex;
    justify-content: center;
    gap: 0.55rem;
    padding: 0.9rem 1.1rem 1.15rem;
}
.locked-msg-actions .btn {
    min-height: 2.4rem;
    min-width: 6rem;
}
@media (max-width: 420px) {
    .locked-msg-card {
        width: calc(100vw - 1rem);
        max-height: calc(100dvh - 1rem);
        border-radius: 0.75rem;
    }
    .locked-msg-head { padding: 1.15rem 0.95rem 0.85rem; }
    .locked-msg-icon { width: 2.75rem; height: 2.75rem; }
    .locked-msg-icon svg { width: 24px; height: 24px; }
    .locked-msg-title { font-size: 1.05rem; }
    .locked-msg-subtitle { font-size: 0.82rem; }
    .locked-msg-body { padding: 0.8rem 0.9rem 0.3rem; }
    .locked-msg-text { font-size: 0.86rem; padding: 0.65rem 0.75rem; }
    .locked-msg-actions { padding: 0.75rem 0.9rem 0.95rem; }
    .locked-msg-actions .btn { width: 100%; }
}

/* Profile "Report user" action sits below the friend strip with no
   border-top so it doesn't read as a new section, just a quieter
   tertiary action. Same pattern on profile-popup + full profile. */
.profile-actions-report {
    margin-top: 0.4rem;
}
.profile-actions-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    justify-content: center;
    align-items: center;
}
.profile-action-block-wrap[hidden] { display: none; }
.profile-action-report {
    color: var(--text-muted);
    border: 1px solid var(--border);
}
.profile-action-report:hover {
    color: #fecaca;
    border-color: rgba(244, 63, 94, 0.45);
    background: rgba(244, 63, 94, 0.08);
}

/* Block / Unblock action - small ghost-style button placed under the
   friend strip alongside Report. Blocking is rare; should NEVER read
   as a primary CTA. The icon picks up currentColor via .lucide. */
.profile-actions-block {
    margin-top: 0.4rem;
}
.profile-action-block {
    color: var(--text-muted);
    border: 1px solid var(--border);
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 40px; /* mobile-first: comfortable tap target */
}
.profile-action-block:hover {
    color: #fecaca;
    border-color: rgba(244, 63, 94, 0.45);
    background: rgba(244, 63, 94, 0.08);
}
.profile-action-block.is-blocked {
    color: #a7f3d0;
    border-color: rgba(34, 197, 94, 0.45);
    background: rgba(34, 197, 94, 0.08);
}
.profile-action-block.is-blocked:hover {
    color: #d1fae5;
    border-color: rgba(34, 197, 94, 0.6);
    background: rgba(34, 197, 94, 0.14);
}

/* Blocked-author placeholder row. Replaces the avatar+bubble layout
   for messages whose author the viewer has blocked. Suppresses
   author name, attachments, embeds, reactions - the row is just a
   single muted italic line so the conversation shape (timeline +
   day dividers) stays intact without surfacing blocked content. */
.chat-message.is-blocked-author {
    opacity: 0.55;
    padding: 0.15rem 0.6rem;
    grid-template-columns: none;
    display: block;
}
.chat-message-blocked {
    color: var(--text-muted);
    font-style: italic;
    padding: 0.4rem 0.6rem;
    font-size: 0.88rem;
}

/* Settings - Blocked users pane. Avatar + name/handle + Unblock
   button on a single row; reason (when present) sits underneath in
   muted italic. Mobile-first: column flips to stacked at narrow
   widths so the unblock button gets its own row instead of being
   crushed. */
.settings-blocked-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.settings-blocked-row {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.6rem 0.7rem;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border);
    border-radius: 10px;
}
.settings-blocked-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
    background: rgba(255, 255, 255, 0.05);
}
.settings-blocked-meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}
.settings-blocked-name {
    color: var(--text);
    text-decoration: none;
    font-weight: 600;
    overflow-wrap: anywhere;
}
.settings-blocked-name:hover {
    text-decoration: underline;
}
.settings-blocked-handle {
    color: var(--text-muted);
    font-size: 0.82rem;
    overflow-wrap: anywhere;
}
.settings-blocked-reason {
    margin: 0.2rem 0 0;
    color: var(--text-muted);
    font-style: italic;
    font-size: 0.82rem;
    overflow-wrap: anywhere;
}
.settings-blocked-unblock {
    flex: 0 0 auto;
    min-height: 40px;
    padding: 0.4rem 0.85rem;
}
.settings-blocked-empty {
    color: var(--text-muted);
    font-style: italic;
    margin: 0.5rem 0 0;
}
.settings-blocked-foot {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    margin-top: 0.7rem;
}
@media (max-width: 560px) {
    .settings-blocked-row {
        flex-wrap: wrap;
    }
    .settings-blocked-unblock {
        flex: 1 1 100%;
        margin-top: 0.3rem;
    }
}

/* Group rows in the DM sidebar: first-3-member avatars stacked with
   slight overlap, followed by group name + member count. Reuses the
   .chat-dm-link selection/active/hover; .is-group tweaks the layout. */
.chat-dm-link.is-group {
    align-items: center;
    gap: 0.5rem;
}
.chat-dm-avatar-stack {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
    /* Width computed for three 1.3rem avatars overlapped by -0.55rem each:
       1.3 + 0.75 + 0.75 = 2.8rem. Includes the outlining borders so the
       third avatar doesn't clip into the name column. */
    width: 2.8rem;
    height: 1.75rem;
    align-items: center;
    margin-right: 0.15rem;
}
.chat-dm-avatar-stack .chat-dm-avatar-stacked {
    width: 1.3rem;
    height: 1.3rem;
    border: 2px solid var(--surface);
    font-size: 0.65rem;
    margin-left: -0.55rem;
    flex: 0 0 auto;
    box-sizing: content-box;
}
.chat-dm-avatar-stack .chat-dm-avatar-stacked:first-child { margin-left: 0; }
.chat-dm-link.is-group.is-active .chat-dm-avatar-stacked { border-color: var(--brand); }
.chat-dm-ident {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
}
.chat-dm-ident .chat-dm-name {
    font-size: 0.95rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-dm-sub {
    font-size: 0.75rem;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-dm-link.is-group.is-active .chat-dm-sub {
    color: rgba(255, 255, 255, 0.78);
}

/* =========================================================
   Mobile layout (Discord-style sliding sidebar)
   =========================================================
   Default desktop uses a 3-column grid: rail | sidebar | main.
   On narrow viewports the rail + sidebar slide in from the left
   as a single overlay panel, freeing the full width for the chat.
   A hamburger in the header toggles. Backdrop + Escape + tapping
   a channel closes. */

/* Hamburger - hidden on desktop. */
.chat-mobile-toggle {
    display: none;
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    padding: 0.35rem;
    margin-right: 0.5rem;
    border-radius: 0.35rem;
    cursor: pointer;
    flex-shrink: 0;
}
.chat-mobile-toggle:hover { color: var(--text); background: rgba(255, 255, 255, 0.04); }

/* Backdrop - hidden on desktop. */
.chat-sidebar-backdrop {
    display: none;
}

@media (max-width: 768px) {
    /* Hamburger visible, aligned on the left of the header row. */
    .chat-mobile-toggle { display: inline-flex; align-items: center; }
    .friends-header .chat-mobile-toggle { align-self: center; }

    /* Collapse the grid: main takes the whole viewport; rail +
       sidebar float on top when revealed. The :has() variant must be
       explicit because it has higher specificity than the bare .chat
       rule and the desktop variant reserves columns for rail + sidebar
       even though they are position:fixed at this breakpoint, which
       leaves the chat-main column squeezed against a wide blank strip. */
    .chat,
    .chat:has(.chat-members-panel) {
        grid-template-columns: 1fr;
        position: relative;
    }

    /* Rail: fixed-position, 4.5rem wide, off-screen by default. */
    .chat-rail {
        position: fixed;
        top: 0; bottom: 0; left: 0;
        width: 4.5rem;
        z-index: 210;
        transform: translateX(-100%);
        transition: transform 0.22s ease;
        padding-top: env(safe-area-inset-top, 0);
        padding-bottom: env(safe-area-inset-bottom, 0);
    }

    /* Sidebar: sits to the right of the rail when open. */
    .chat-sidebar {
        position: fixed;
        top: 0; bottom: 0; left: 4.5rem;
        width: min(18rem, calc(100vw - 4.5rem));
        z-index: 205;
        transform: translateX(calc(-100% - 4.5rem));
        transition: transform 0.22s ease;
        padding-top: env(safe-area-inset-top, 0);
        padding-bottom: env(safe-area-inset-bottom, 0);
    }

    .chat.is-sidebar-open .chat-rail,
    .chat.is-sidebar-open .chat-sidebar {
        transform: translateX(0);
    }

    /* Dim the chat behind the drawer when open. */
    .chat-sidebar-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        background: rgba(0, 0, 0, 0.55);
        z-index: 200;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.2s ease;
    }
    .chat.is-sidebar-open .chat-sidebar-backdrop {
        opacity: 1;
        pointer-events: auto;
    }

    /* Lock body scroll while drawer is open so iOS Safari doesn't
       rubber-band the page behind the overlay. */
    body.chat-drawer-open { overflow: hidden; }

    /* Tighten the chat header so the hamburger fits. */
    .chat-header {
        padding: 0.75rem 0.9rem;
        display: flex;
        align-items: center;
        gap: 0.4rem;
    }
    .chat-header-main {
        min-width: 0;
        flex: 1 1 auto;
    }
    .chat-header-main h1 {
        font-size: 1.05rem;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .chat-topic {
        font-size: 0.78rem;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    /* Message bubbles use the full viewport width. */
    .chat-messages {
        padding-left: 0.6rem;
        padding-right: 0.6rem;
    }

    /* Attachments: never blow past the viewport. */
    .chat-att,
    .chat-att img,
    .chat-att video,
    .chat-att-wrap {
        max-width: calc(100vw - 2rem);
    }

    /* Composer: reduce breathing room so the keyboard doesn't push
       content off-screen. The 1.25rem bottom padding on desktop is
       wasted space on mobile - it reads as a scrollable gap below
       the input. Respect iOS home-bar safe area instead. */
    .chat-composer,
    .chat-input {
        padding: 0.55rem 0.65rem calc(0.55rem + env(safe-area-inset-bottom, 0px));
    }

    /* Composer layout on mobile: textarea gets the full first row
       (so it can breathe + grow as the user types); the tool
       buttons (attach, gif, emoji) AND Send share row 2 - Send is
       pinned to the right edge, everything else to the left. On
       360-390 px devices all four buttons sit on one short row
       without any of them getting cut off, and the text area keeps
       its full width regardless. */
    /* Composer layout on mobile: textarea fills row 1; attach, gif,
       emoji, Send flow left-to-right on row 2, flush left so Send
       sits RIGHT NEXT TO the emoji button (no gap column). Flex-wrap
       with a 100%-basis textarea forces the break; order values
       guarantee the button sequence regardless of DOM order. */
    .chat-input-row {
        display: flex !important;
        flex-wrap: wrap;
        gap: 0.4rem;
        align-items: flex-end;
        width: 100%;
        max-width: 100%;
        min-width: 0;
    }
    .chat-input-row .chat-textbox-stack {
        order: 1;
        flex: 1 1 100%;
        min-width: 0;
        width: 100%;
    }
    .chat-input-row #chat-plus-btn      { order: 2; flex: 0 0 auto; }
    .chat-input-row #chat-gif-btn       { order: 3; flex: 0 0 auto; }
    .chat-input-row #chat-emoji-btn     { order: 4; flex: 0 0 auto; }
    .chat-input-row .chat-send-btn      {
        order: 5;
        flex: 0 0 auto;
        min-width: 3.5rem !important;
        width: auto !important;
    }
    .chat-input-row #chat-attach-input  { display: none; }

    /* Header button row stays compact. */
    .chat-call-btn, .chat-pins-btn, .chat-dm-menu-btn {
        width: 2rem;
        height: 2rem;
    }

    /* In-call bar: span almost full viewport by default so controls
       stay tappable. Previously both left AND top used `!important`
       which defeated the drag code entirely on mobile (JS sets
       style.left/top; `!important` rules win). Now apply the sizing
       only to the INITIAL (non-dragged) position, and omit !important
       so a deliberate drag actually moves the bar. */
    .call-bar:not(.is-dragged) {
        left: 0.5rem;
        right: 0.5rem;
        top: 0.5rem;
        max-width: calc(100vw - 1rem);
    }
    .call-bar {
        flex-wrap: wrap;
        row-gap: 0.4rem;
    }

    /* Pins popover: fall back to full-width on tiny screens. */
    .chat-pins-popover {
        width: min(22rem, calc(100vw - 1rem));
    }

    /* Friends view header gets tight too. */
    .friends-header {
        padding: 0.75rem 0.9rem;
        gap: 0.4rem;
    }
}

/* Even narrower phones: drawer is almost the full width. */
@media (max-width: 420px) {
    .chat-sidebar {
        width: calc(100vw - 4.5rem);
    }
}

/* =========================================================
   "A new version is available" toast, managed by version-watch.js.
   Fixed bottom-right on desktop, bottom edge on mobile. High
   z-index so it sits above modals. Auto-sized to content. */
.app-update-toast {
    position: fixed;
    bottom: 1.5rem;
    right: 1.5rem;
    z-index: 500;
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 0.95rem 1.15rem;
    background:
        linear-gradient(135deg, rgba(124, 58, 237, 0.18) 0%, rgba(37, 99, 235, 0.12) 100%),
        #0f0f13;
    color: var(--text);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0.8rem;
    box-shadow:
        0 20px 60px rgba(0, 0, 0, 0.65),
        0 0 0 1px rgba(124, 58, 237, 0.35),
        0 0 30px rgba(124, 58, 237, 0.4);
    max-width: min(26rem, calc(100vw - 1.5rem * 2));
    animation:
        app-update-toast-in 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.4),
        app-update-toast-glow 2.4s ease-in-out infinite;
    overflow: hidden;
    isolation: isolate;
}
.app-update-toast::before {
    /* Subtle animated shimmer strip along the top edge */
    content: '';
    position: absolute;
    top: 0; left: 0;
    height: 2px;
    width: 100%;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(168, 85, 247, 0.9) 20%,
        rgba(59, 130, 246, 0.9) 50%,
        rgba(168, 85, 247, 0.9) 80%,
        transparent 100%);
    background-size: 200% 100%;
    animation: app-update-toast-shimmer 3s linear infinite;
    pointer-events: none;
}
.app-update-toast[hidden] { display: none; }
@keyframes app-update-toast-in {
    from { opacity: 0; transform: translateY(1.25rem) scale(0.94); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes app-update-toast-glow {
    0%, 100% {
        box-shadow:
            0 20px 60px rgba(0, 0, 0, 0.65),
            0 0 0 1px rgba(124, 58, 237, 0.35),
            0 0 30px rgba(124, 58, 237, 0.4);
    }
    50% {
        box-shadow:
            0 20px 60px rgba(0, 0, 0, 0.65),
            0 0 0 1px rgba(59, 130, 246, 0.5),
            0 0 40px rgba(59, 130, 246, 0.55);
    }
}
@keyframes app-update-toast-shimmer {
    0%   { background-position: 200% 0%; }
    100% { background-position: -200% 0%; }
}
.app-update-toast-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.4rem;
    border-radius: 50%;
    background: linear-gradient(135deg, #a855f7 0%, #3b82f6 100%);
    color: #fff;
    flex-shrink: 0;
    box-shadow:
        0 0 16px rgba(168, 85, 247, 0.55),
        inset 0 1px 0 rgba(255, 255, 255, 0.3);
    animation: app-update-toast-icon-pulse 1.6s ease-in-out infinite;
}
@keyframes app-update-toast-icon-pulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.08); }
}
.app-update-toast-body { min-width: 0; flex: 1 1 auto; }
.app-update-toast-title {
    font-weight: 700;
    font-size: 0.98rem;
    line-height: 1.2;
    color: #fff;
    letter-spacing: 0.01em;
}
.app-update-toast-desc {
    margin-top: 0.2rem;
    font-size: 0.82rem;
    color: rgba(255, 255, 255, 0.72);
    line-height: 1.25;
}
.app-update-toast-refresh {
    appearance: none;
    background: linear-gradient(135deg, #a855f7 0%, #3b82f6 100%);
    color: #fff;
    border: 0;
    padding: 0.5rem 1rem;
    border-radius: 0.45rem;
    cursor: pointer;
    font-size: 0.85rem;
    font-weight: 700;
    flex-shrink: 0;
    box-shadow: 0 4px 14px rgba(168, 85, 247, 0.5);
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.app-update-toast-refresh:hover {
    transform: translateY(-1px);
    box-shadow: 0 6px 18px rgba(168, 85, 247, 0.65);
}
.app-update-toast-refresh:active { transform: translateY(0); }
.app-update-toast-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: rgba(255, 255, 255, 0.55);
    cursor: pointer;
    font-size: 1.35rem;
    line-height: 1;
    padding: 0.25rem 0.4rem;
    flex-shrink: 0;
    border-radius: 0.3rem;
    transition: color 0.12s, background 0.12s;
}
.app-update-toast-close:hover {
    color: #fff;
    background: rgba(255, 255, 255, 0.08);
}

@media (max-width: 560px) {
    .app-update-toast {
        bottom: 0.85rem;
        right:  0.85rem;
        left:   0.85rem;
        max-width: none;
        padding: 0.85rem 1rem;
        gap: 0.7rem;
    }
    .app-update-toast-icon { width: 2rem; height: 2rem; }
    .app-update-toast-title { font-size: 0.92rem; }
    .app-update-toast-desc { font-size: 0.78rem; }
}

/* =========================================================
   "Call in progress" banner inside a channel's chat-main. Rendered
   by call.js when the currently-viewed channel has a live call but
   the user isn't in that call yet. Clicking Join attempts to join
   it. Distinct from the .call-bar (which is the global in-call
   indicator visible even when navigating around). */
.chat-call-ongoing-banner {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.55rem 0.9rem;
    background: linear-gradient(90deg, rgba(16, 185, 129, 0.12), rgba(16, 185, 129, 0.04));
    border-bottom: 1px solid rgba(16, 185, 129, 0.35);
    font-size: 0.85rem;
    color: var(--text);
}
.chat-call-ongoing-banner[hidden] { display: none; }
.chat-call-ongoing-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    background: #10b981;
    color: #fff;
    animation: call-phone-wiggle 1.4s ease-in-out infinite;
}
.chat-call-ongoing-text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-call-ongoing-text strong { color: var(--text); }
.chat-call-ongoing-join {
    appearance: none;
    border: 0;
    background: #10b981;
    color: #fff;
    font-weight: 600;
    font-size: 0.82rem;
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    cursor: pointer;
    flex-shrink: 0;
}
.chat-call-ongoing-join:hover { filter: brightness(1.08); }
.chat-call-ongoing-join:disabled { opacity: 0.6; cursor: not-allowed; }

/* Always-visible member avatars in the group-chat header. Rendered
   by chat.phtml from $activeChannel.allMembers and sits next to the
   group name. When a call is active and this is the current channel,
   call.js additionally overlays speaking / mute state on these same
   elements so the same set of avatars conveys both membership and
   per-peer voice status. */
.chat-header-members {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.2rem;
    min-width: 0;
    padding-right: 0.3rem;
}
/* Group header title with inline rename pen. */
.chat-group-title {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
}
.chat-group-rename-btn {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0.25rem;
    border-radius: 0.3rem;
    color: var(--text-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
}
.chat-group-rename-btn:hover,
.chat-group-rename-btn:focus-visible {
    color: var(--text);
    background: rgba(255, 255, 255, 0.06);
    outline: none;
}

.chat-header-member {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.7rem;
    height: 1.7rem;
    border-radius: 50%;
    /* overflow stays visible so the hover tooltip (::after) isn't
       clipped by the circular frame. Circular clipping is delegated
       to the inner img / fallback which both carry border-radius:50%. */
    overflow: visible;
    background: #1f1f1f;
    border: 2px solid transparent;
    flex-shrink: 0;
    cursor: pointer;
    text-decoration: none;
    color: inherit;
}
.chat-header-member > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    border-radius: 50%;
    -webkit-user-drag: none;
    user-drag: none;
    pointer-events: none;
    user-select: none;
}
.chat-header-member > .chat-header-member-fallback {
    border-radius: 50%;
}

/* Presence dot on each group-header avatar. Green when online, grey when
   offline. Sits at the bottom-right of the circular frame with a ring so
   it reads against any avatar. Toggled live by site.js __mlApplyPresence
   via the .is-online / .is-offline class on the parent .chat-header-member. */
.chat-header-member-dot {
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    border: 2px solid var(--bg, #0a0a0a);
    background: #6b7280; /* grey = offline (default) */
    pointer-events: none;
    box-sizing: border-box;
}
.chat-header-member.is-online > .chat-header-member-dot {
    background: #22c55e; /* green = online */
}

/* Header-member tooltips are rendered by the portal controller in
   site.js; the CSS ::after pill is no longer needed. */
.chat-header-member-fallback {
    width: 100%;
    height: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text);
}
.chat-header-member.is-speaking {
    border-color: #34d399;
    box-shadow: 0 0 0 2px rgba(52, 211, 153, 0.5),
                0 0 12px rgba(52, 211, 153, 0.5);
}
.chat-header-member.is-muted::after {
    /* Mic-muted slash overlay. */
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(circle, rgba(220, 38, 38, 0.35) 30%, transparent 70%);
    pointer-events: none;
}
.chat-header-member.is-in-call {
    border-color: rgba(255, 255, 255, 0.35);
}
.chat-header-member.is-absent {
    opacity: 0.45;
    filter: grayscale(0.3);
}
/* Group owner: a small gold crown sits on top-center of the avatar,
   slightly overlapping the upper border. overflow on the parent has
   to stay visible for the crown to poke out, so we rely on the
   circular clip being on the img itself (border-radius:50% above). */
.chat-header-member.is-owner {
    overflow: visible;
}
.chat-header-member.is-owner > img,
.chat-header-member.is-owner > .chat-header-member-fallback {
    border-radius: 50%;
}
.chat-header-member-crown {
    position: absolute;
    top: -0.55rem;
    left: 50%;
    transform: translateX(-50%) rotate(-8deg);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 0.95rem;
    height: 0.95rem;
    color: #facc15; /* amber/gold */
    filter:
        drop-shadow(0 0 2px rgba(250, 204, 21, 0.65))
        drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
    pointer-events: none;
}

/* Slot that hosts the inline call-bar under the chat-header. Its
   data-channel-id matches the header's active channel; call.js
   uses that to decide whether to reparent the floating call-bar
   into this slot (same channel as the call -> inline) or leave it
   floating (viewing a different channel). */
.chat-inline-voice-slot {
    display: block;
}
.chat-inline-voice-slot:empty {
    display: none;
}

/* Inline variant of .call-bar. Same controls, same handlers, but
   flowed into .chat-main under the header instead of fixed-position
   floating. Drag is disabled when this class is set. */
.call-bar.is-inline {
    position: static !important;
    top: auto !important;
    left: auto !important;
    right: auto !important;
    bottom: auto !important;
    margin: 0;
    max-width: 100%;
    width: 100%;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    border-top: 0;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
    cursor: default;
}
.call-bar.is-inline.is-dragging { cursor: default; }

/* Sidebar indicator: DM / group row glows green while a call is live
   on that channel. Painted by refreshSidebarCallIndicators() in
   call.js whenever _channelCalls changes. */
.chat-dm-link.is-in-call {
    position: relative;
    background: linear-gradient(90deg,
        rgba(16, 185, 129, 0.18) 0%,
        rgba(16, 185, 129, 0.04) 100%);
    box-shadow: inset 3px 0 0 #10b981;
}
.chat-dm-link.is-in-call .chat-dm-name {
    color: var(--text);
    font-weight: 600;
}
.chat-dm-link.is-in-call::after {
    /* Tiny pulsing dot on the right edge. */
    content: '';
    position: absolute;
    right: 0.55rem;
    top: 50%;
    margin-top: -0.3rem;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    background: #10b981;
    box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.6);
    animation: chat-dm-call-pulse 1.6s ease-out infinite;
    pointer-events: none;
}
@keyframes chat-dm-call-pulse {
    0%   { box-shadow: 0 0 0 0   rgba(16, 185, 129, 0.65); }
    70%  { box-shadow: 0 0 0 8px rgba(16, 185, 129, 0);    }
    100% { box-shadow: 0 0 0 0   rgba(16, 185, 129, 0);    }
}

/* ======================================================================
 * Premium: nav CTA, settings hero, preset picker.
 * ====================================================================== */

/* Header icon-button (Help / Settings / Sign out). Lives in the
   global site header so these actions are reachable from every page,
   not just /chat. Sized to match the visual scale of the premium pill
   + avatar while still meeting the 40 px touch target on mobile via a
   generous tap area. The wrapping <form> for sign-out gets its margin
   stripped so the icon aligns with its siblings. */
.site-nav-icon-btn {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    cursor: pointer;
    line-height: 0;
    flex-shrink: 0;
    position: relative; /* anchor for the announcements numeric badge */
    transition: background 0.15s, color 0.15s, border-radius 0.18s, transform 0.1s;
}
/* Announcements numeric badge. Renders inside both the layout-header
   .site-nav-icon-btn AND the chat-rail .chat-rail-foot-btn anchors, so
   the chat-rail variant also needs `position: relative` (it already is
   via .chat-rail-foot-btn, but defensively applied below). */
.site-nav-icon-badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: #ef4444;
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    pointer-events: none;
    border: 1px solid var(--bg);
    box-sizing: border-box;
}
.site-nav-icon-badge[hidden] { display: none; }
/* The chat-rail mail button is bigger (3rem vs 2.25rem); nudge the
   badge so it sits on the icon edge rather than floating in the
   corner. */
.chat-rail-foot-btn { position: relative; }
.chat-rail-foot-btn .site-nav-icon-badge {
    top: 4px;
    right: 4px;
    min-width: 18px;
    height: 18px;
    font-size: 0.72rem;
}
.site-nav-icon-btn:hover,
.site-nav-icon-btn:focus-visible {
    color: #fff;
    background: var(--brand);
    border-radius: 0.7rem;
    outline: none;
}
.site-nav-icon-btn:active {
    transform: translateY(1px);
}
/* Sign-out specifically picks up a red accent on hover so users never
   accidentally think it's settings-adjacent. Mirrors the rail-footer
   destructive cue. */
.site-nav-icon-signout:hover,
.site-nav-icon-signout:focus-visible {
    color: #fff;
    background: #dc2626;
    border-radius: 0.7rem;
}
.site-nav-icon-btn svg {
    width: 18px;
    height: 18px;
}
.site-nav-signout {
    display: inline-flex;
    margin: 0;
    padding: 0;
}
@media (max-width: 768px) {
    /* Bigger tap target on touch devices without making the visual
       footprint balloon - the icon stays 18 px but the hit zone grows
       to >= 40 px tall to satisfy the mobile touch-target rule. */
    .site-nav-icon-btn { width: 2.5rem; height: 2.5rem; }
}
@media (max-width: 480px) {
    /* Tighter spacing on the smallest phones so the row of icons +
       premium pill + avatar still fits without horizontal scroll at
       360 px. Premium pill already drops its label below 640 px. */
    .site-header nav { gap: 0.35rem; }
    .site-nav-icon-btn { width: 2.4rem; height: 2.4rem; }
    .site-nav-icon-btn svg { width: 17px; height: 17px; }
}

/* Top-nav "Purchase Premium" button. Sits next to .nav-user. Rainbow
   test-tube icon with an animated hue shift; whole pill has a subtle
   border glow and a shimmer sweep on hover. */
.nav-premium-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.35rem 0.85rem 0.35rem 0.55rem;
    margin-right: 0.75rem;
    border-radius: 999px;
    background: linear-gradient(90deg, rgba(244,114,182,0.12), rgba(34,211,238,0.12), rgba(251,191,36,0.12));
    border: 1px solid rgba(251, 191, 36, 0.35);
    color: #fde68a;
    font-weight: 600;
    font-size: 0.82rem;
    text-decoration: none;
    position: relative;
    overflow: hidden;
    transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
}
.nav-premium-cta:hover {
    transform: translateY(-1px);
    border-color: #fbbf24;
    box-shadow: 0 4px 14px -6px rgba(251, 191, 36, 0.6);
}
/* Active (already premium): softer gold border + "active" emerald hint on the label. */
.nav-premium-cta.is-active {
    border-color: rgba(134, 239, 172, 0.5);
    background: linear-gradient(90deg, rgba(134,239,172,0.1), rgba(251,191,36,0.1), rgba(134,239,172,0.1));
    color: #bbf7d0;
}
.nav-premium-cta.is-active:hover {
    border-color: #86efac;
    box-shadow: 0 4px 14px -6px rgba(134, 239, 172, 0.5);
}
.nav-premium-cta::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(110deg, transparent 30%, rgba(255,255,255,0.3) 50%, transparent 70%);
    transform: translateX(-130%);
    transition: transform 0.55s ease;
    pointer-events: none;
}
.nav-premium-cta:hover::after { transform: translateX(130%); }
.nav-premium-tube {
    display: inline-flex;
    width: 20px; height: 20px;
    animation: premium-tube-pulse 3s ease-in-out infinite;
}
.nav-premium-tube svg { width: 100%; height: 100%; }
@keyframes premium-tube-pulse {
    0%, 100% { filter: drop-shadow(0 0 2px rgba(251, 191, 36, 0.6)); transform: scale(1); }
    50%      { filter: drop-shadow(0 0 6px rgba(251, 191, 36, 0.9)); transform: scale(1.06); }
}
@media (max-width: 640px) {
    .nav-premium-label { display: none; }
    .nav-premium-cta   { padding: 0.35rem 0.55rem; }
}

/* Settings left-rail tab label for Premium. Gold-tinted. */
.settings-nav-tube { display: inline-flex; width: 16px; height: 16px; align-items: center; justify-content: center; animation: premium-tube-pulse 4s ease-in-out infinite; }
.settings-nav-tube svg { width: 100%; height: 100%; }
.settings-nav-premium {
    color: #fde68a !important;
}

/* Hero banner at the top of the Premium pane. */
.premium-pane { display: flex; flex-direction: column; gap: 1.25rem; }
.premium-hero {
    position: relative;
    padding: 1.25rem 1.25rem 1.5rem;
    border-radius: 0.85rem;
    overflow: hidden;
    isolation: isolate;
    text-align: center;
    border: 1px solid rgba(251, 191, 36, 0.25);
    background: linear-gradient(140deg, rgba(244,114,182,0.15), rgba(124,58,237,0.1) 40%, rgba(34,211,238,0.12) 70%, rgba(251,191,36,0.12));
}
.premium-hero-bg {
    position: absolute; inset: 0; z-index: -1;
    background:
        radial-gradient(circle at 20% 20%, rgba(244,114,182,0.35), transparent 45%),
        radial-gradient(circle at 80% 80%, rgba(34,211,238,0.3),  transparent 45%),
        radial-gradient(circle at 50% 50%, rgba(251,191,36,0.2),  transparent 55%);
    animation: premium-hero-drift 18s ease-in-out infinite alternate;
    filter: blur(18px);
}
@keyframes premium-hero-drift {
    from { transform: translate3d(0, 0, 0) scale(1);    }
    to   { transform: translate3d(3%, -2%, 0) scale(1.08);}
}
.premium-hero-tube {
    display: block;
    width: 78px;
    height: 88px;
    margin: 0 auto 0.55rem;
    filter: drop-shadow(0 6px 18px rgba(251, 191, 36, 0.45));
    animation: premium-hero-bob 3.8s ease-in-out infinite;
}
.premium-hero-tube svg { width: 100%; height: 100%; }
@keyframes premium-hero-bob {
    0%, 100% { transform: translateY(0)    rotate(-4deg); }
    50%      { transform: translateY(-6px) rotate( 4deg); }
}
.premium-hero-title {
    margin: 0;
    font-size: 1.7rem;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: #fff;
}
.premium-hero-title-grad {
    background: linear-gradient(90deg, #f472b6, #a78bfa, #22d3ee, #86efac, #fbbf24, #f87171);
    background-size: 200% 100%;
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    animation: premium-hero-gradient 6s linear infinite;
}
@keyframes premium-hero-gradient {
    from { background-position:   0% 50%; }
    to   { background-position: 200% 50%; }
}
.premium-hero-sub { margin: 0.25rem 0 0; color: #a1a1aa; font-size: 0.92rem; }

/* "Your premium status" card under the hero. Shown to everyone; the
   data-state attribute drives the colour treatment. */
.settings-premium-card {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    padding: 0.85rem 1rem;
    border-radius: 0.7rem;
    border: 1px solid rgba(251, 191, 36, 0.25);
    background: linear-gradient(140deg, rgba(251, 191, 36, 0.08), rgba(24, 24, 27, 0.7));
}
.settings-premium-card[data-state="inactive"] {
    border-color: rgba(113, 113, 122, 0.3);
    background: linear-gradient(140deg, rgba(63, 63, 70, 0.18), rgba(24, 24, 27, 0.6));
}
.settings-premium-card[data-state="loading"] {
    border-color: rgba(63, 63, 70, 0.3);
    background: rgba(24, 24, 27, 0.5);
}
.settings-premium-card[data-state="permanent"] {
    border-color: rgba(251, 191, 36, 0.5);
    background: linear-gradient(140deg, rgba(251, 191, 36, 0.18), rgba(24, 24, 27, 0.6));
}
.settings-premium-card-icon {
    flex: 0 0 auto;
    width: 2rem;
    height: 2rem;
    display: grid;
    place-items: center;
    filter: drop-shadow(0 4px 10px rgba(251, 191, 36, 0.35));
}
.settings-premium-card-icon svg { width: 100%; height: 100%; }
.settings-premium-card[data-state="inactive"] .settings-premium-card-icon { filter: grayscale(0.65) opacity(0.7); }
.settings-premium-card-body { min-width: 0; flex: 1 1 auto; }
.settings-premium-card-title {
    color: #fde68a;
    font-weight: 700;
    font-size: 0.95rem;
    line-height: 1.35;
    word-break: break-word;
}
.settings-premium-card[data-state="inactive"] .settings-premium-card-title,
.settings-premium-card[data-state="loading"]  .settings-premium-card-title { color: #d4d4d8; }
.settings-premium-card-meta {
    margin-top: 0.2rem;
    color: #a1a1aa;
    font-size: 0.82rem;
    font-variant-numeric: tabular-nums;
}
.settings-premium-card[data-state="permanent"] .settings-premium-card-meta { color: #fde68a; }
@media (max-width: 560px) {
    .settings-premium-card { padding: 0.75rem 0.85rem; gap: 0.65rem; }
    .settings-premium-card-icon { width: 1.75rem; height: 1.75rem; }
    .settings-premium-card-title { font-size: 0.9rem; }
}

.premium-section-heading { margin: 0 0 0.25rem; color: #fff; font-size: 1.05rem; }

/* Purchase CTA card (non-premium). */
.premium-cta-card {
    background: linear-gradient(140deg, #18181b, #0f0f13);
    border: 1px solid rgba(251, 191, 36, 0.35);
    border-radius: 0.7rem;
    padding: 1rem 1.1rem;
}
.premium-cta-card h3 { margin: 0 0 0.35rem; color: #fff; font-size: 1.05rem; }
.premium-cta-card p  { margin: 0 0 0.75rem; color: #a1a1aa; font-size: 0.88rem; line-height: 1.5; }
.premium-cta-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.65rem 1.15rem;
    border-radius: 0.5rem;
    border: 0;
    font: inherit;
    font-weight: 700;
    color: #0a0a0d;
    background: linear-gradient(90deg, #fbbf24, #f472b6, #22d3ee, #fbbf24);
    background-size: 200% 100%;
    cursor: not-allowed;
    opacity: 0.8;
    animation: premium-hero-gradient 5s linear infinite;
}
.premium-cta-foot { margin: 0.6rem 0 0; color: #71717a; font-size: 0.78rem; font-style: italic; }

.premium-perks { list-style: none; padding: 0; margin: 0.5rem 0 0.75rem; display: flex; flex-direction: column; gap: 0.35rem; }
.premium-perks li { position: relative; padding-left: 1.2rem; color: #d4d4d8; font-size: 0.85rem; line-height: 1.45; }
.premium-perks li::before { content: "✦"; position: absolute; left: 0; top: 0; color: #fbbf24; }
/* Small subordinate notice attached to a perk - smaller, muted, no
   bullet, indented slightly so it reads as a footnote to the line above. */
.premium-perks-note {
    display: block;
    margin-top: 0.25rem;
    font-size: 0.74rem;
    color: #a1a1aa;
    line-height: 1.4;
    font-style: italic;
}
.premium-perks strong { color: #fff; }

.premium-perks-box {
    background: rgba(251,191,36,0.07);
    border: 1px solid rgba(251,191,36,0.28);
    border-radius: 0.55rem;
    padding: 0.75rem 0.9rem;
    margin-bottom: 1rem;
}
.premium-perks-box > strong {
    display: block;
    color: #fde68a;
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin-bottom: 0.2rem;
}

/* Preset picker grid (both active + teaser use the same layout). */
.premium-preset-grid { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
.premium-preset-card {
    background: #18181b;
    border: 1px solid #27272a;
    border-radius: 0.55rem;
    padding: 0.75rem;
    cursor: pointer;
    text-align: left;
    color: inherit;
    font: inherit;
    transition: border-color 0.15s, background 0.15s, transform 0.1s;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    position: relative;
}
.premium-preset-card:hover:not(.is-locked) { border-color: #52525b; background: #1f1f23; transform: translateY(-1px); }
.premium-preset-card.is-active { border-color: #a855f7; box-shadow: 0 0 0 1px rgba(168,85,247,0.45) inset; }
.premium-preset-card.is-locked { cursor: not-allowed; opacity: 0.7; }
.premium-preset-card.is-locked:hover { background: #18181b; }
.premium-preset-preview { background: #0a0a0d; border: 1px solid #1f1f23; border-radius: 0.4rem; padding: 0.7rem 0.9rem; min-height: 2.4rem; display: flex; align-items: center; font-size: 0.95rem; font-weight: 600; }
.premium-preset-label { color: #fff; font-weight: 600; font-size: 0.92rem; }
.premium-preset-hint  { color: #a1a1aa; font-size: 0.78rem; }
.premium-preset-lock  { margin-top: 0.25rem; color: #fde68a; font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; }
.premium-teaser-grid  { opacity: 0.9; }

/* Ban / restriction status banner on the full profile page. Shown to
 * everyone who visits the profile (not just staff) so the lockout state
 * is obvious. Style matches the tone of the corresponding mod button. */
.profile-modstate {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.35rem 0.75rem;
    margin: 0 0 0.6rem;
    border-radius: 0.4rem;
    font-size: 0.82rem;
    letter-spacing: 0.04em;
}
.profile-modstate strong { letter-spacing: 0.12em; }
.profile-modstate-banned     { background: rgba(239,68,68,0.14);  border: 1px solid rgba(239,68,68,0.4);  color: #fca5a5; }
.profile-modstate-restricted { background: rgba(148,163,184,0.12); border: 1px solid rgba(148,163,184,0.4); color: #cbd5e1; }

/* Inline ban form inside the profile popup. */
.profile-mod-actions { display: flex; flex-direction: column; gap: 0.5rem; margin: 0 0 0.85rem; }
.profile-action-ban   { color: #fca5a5; border-color: rgba(239,68,68,0.35); }
.profile-action-ban:hover { color: #fff; background: rgba(239,68,68,0.15); }
.profile-action-unban { color: #86efac; border-color: rgba(16,185,129,0.35); }
.profile-action-unban:hover { color: #fff; background: rgba(16,185,129,0.15); }
.profile-action-restrict   { color: #cbd5e1; border-color: rgba(148,163,184,0.35); }
.profile-action-restrict:hover { color: #fff; background: rgba(148,163,184,0.15); }
.profile-action-unrestrict { color: #86efac; border-color: rgba(16,185,129,0.35); }
.profile-action-unrestrict:hover { color: #fff; background: rgba(16,185,129,0.15); }
.profile-restrict-form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.75rem 0.8rem;
    background: rgba(148,163,184,0.06);
    border: 1px solid rgba(148,163,184,0.3);
    border-radius: 0.5rem;
    text-align: left;
}
.profile-ban-form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.75rem 0.8rem;
    background: rgba(239,68,68,0.06);
    border: 1px solid rgba(239,68,68,0.3);
    border-radius: 0.5rem;
    text-align: left;
}
.profile-ban-field { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.82rem; color: #a1a1aa; }
.profile-ban-field input {
    background: #0a0a0d; color: #fff;
    border: 1px solid #27272a;
    border-radius: 0.35rem;
    padding: 0.5rem 0.6rem;
    font: inherit; font-size: 0.88rem;
}
.profile-ban-field input:focus { outline: none; border-color: #ef4444; box-shadow: 0 0 0 2px rgba(239,68,68,0.25); }
.profile-ban-actions { display: flex; gap: 0.4rem; justify-content: flex-end; }
.profile-ban-status { margin: 0; font-size: 0.82rem; color: #a1a1aa; }
.profile-ban-status[data-state="error"]   { color: #fca5a5; }
.profile-ban-status[data-state="working"] { color: #fcd34d; }

/* =================================================================
 * Banned lockout page.
 * ================================================================= */
.ban-body {
    background: #07070a;
    color: #e5e7eb;
    min-height: 100dvh;
    margin: 0;
    font: 15px/1.55 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}
.ban-stage {
    position: relative;
    min-height: 100dvh;
    display: grid;
    place-items: center;
    padding: 2rem 1rem;
    isolation: isolate;
    overflow: hidden;
}
.ban-aurora {
    position: absolute; inset: -20%;
    z-index: -1;
    pointer-events: none;
    filter: blur(120px);
    opacity: 0.45;
}
.ban-aurora-orb { position: absolute; width: 55vmax; height: 55vmax; border-radius: 50%; }
.ban-aurora-orb-a { background: radial-gradient(circle, rgba(239,68,68,0.55), transparent 65%); top: -18%; left: -8%; animation: ban-orb-a 22s ease-in-out infinite alternate; }
.ban-aurora-orb-b { background: radial-gradient(circle, rgba(124,58,237,0.5),  transparent 65%); bottom: -22%; right: -15%; animation: ban-orb-b 26s ease-in-out infinite alternate; }
@keyframes ban-orb-a { from { transform: translate3d(0,0,0) scale(1); } to { transform: translate3d(6vw,4vh,0) scale(1.12); } }
@keyframes ban-orb-b { from { transform: translate3d(0,0,0) scale(1); } to { transform: translate3d(-5vw,-3vh,0) scale(1.1); } }

.ban-card {
    max-width: 34rem;
    width: 100%;
    padding: 2.25rem 2rem 2rem;
    border: 1px solid rgba(239, 68, 68, 0.35);
    border-radius: 1rem;
    background: linear-gradient(160deg, rgba(127,29,29,0.22), rgba(24,24,27,0.85));
    box-shadow: 0 30px 80px -30px rgba(239, 68, 68, 0.35);
    text-align: center;
    animation: ban-card-in 0.55s cubic-bezier(.2,.8,.2,1) both;
}
@keyframes ban-card-in {
    from { opacity: 0; transform: translateY(14px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0)    scale(1);     }
}
.ban-icon { color: #fca5a5; display: grid; place-items: center; margin: 0 auto 0.75rem; filter: drop-shadow(0 0 20px rgba(239,68,68,0.5)); animation: ban-icon-pulse 3.2s ease-in-out infinite; }
@keyframes ban-icon-pulse {
    0%, 100% { transform: scale(1);    filter: drop-shadow(0 0 16px rgba(239,68,68,0.4)); }
    50%      { transform: scale(1.06); filter: drop-shadow(0 0 28px rgba(239,68,68,0.8)); }
}
.ban-eyebrow { margin: 0 0 0.25rem; color: #fca5a5; font-size: 0.72rem; letter-spacing: 0.22em; text-transform: uppercase; }
.ban-title   { margin: 0 0 0.35rem; color: #fff; font-size: clamp(1.6rem, 3vw, 2rem); font-weight: 800; letter-spacing: -0.02em; }
.ban-lead    { margin: 0 0 1.25rem; color: #d4d4d8; }
.ban-facts {
    display: grid;
    gap: 0.6rem;
    margin: 0 0 1.4rem;
    padding: 1rem 1.1rem;
    background: rgba(255,255,255,0.02);
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 0.6rem;
    text-align: left;
}
.ban-facts > div { display: grid; grid-template-columns: 7rem 1fr; gap: 0.75rem; align-items: start; }
.ban-facts dt { color: #71717a; font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase; }
.ban-facts dd { margin: 0; color: #fff; font-size: 0.92rem; word-break: break-word; }
.ban-reason { white-space: pre-wrap; }
.ban-permanent { color: #fca5a5; font-weight: 600; }
.ban-remaining { color: #a1a1aa; font-size: 0.82rem; }
.ban-appeal { margin: 0; color: #a1a1aa; font-size: 0.88rem; line-height: 1.55; }
.ban-appeal a { color: #c4b5fd; text-decoration: none; }
.ban-appeal a:hover { text-decoration: underline; }
@media (max-width: 520px) {
    .ban-facts > div { grid-template-columns: 1fr; gap: 0.15rem; }
}

/* =================================================================
 * Role name styling.
 * Applied anywhere a username renders. Solid color comes from inline
 * `style="color:..."`. Gradients replace the color with a clipped
 * gradient (also inline). Effect classes (.role-fx-*) layer animations
 * on top. Sparkle dots are an optional child element.
 * ================================================================= */

/* Inline display (not inline-flex) so background-clip:text renders the
   gradient correctly across browsers. Sparkle dots use vertical-align
   instead of flexbox alignment. */
.role-name-fx {
    /* inline-block rather than inline: keeps block-level host elements
       (e.g. <h2 class="profile-name">) on their own line instead of
       collapsing them inline with their siblings, which was breaking the
       avatar-and-name vertical stack on the profile modal. Gradient
       text-clip + absolute FX children still work on inline-block. */
    display: inline-block;
    font-weight: 600;
    position: relative;
    white-space: nowrap;
    /* Keep vertical alignment natural when the host element is an inline
       flow neighbour (e.g. chat-dm-name next to siblings on one line). */
    vertical-align: baseline;
    /* Create a stacking context so child effects with z-index: -1 (like
       lightning) stay behind the text but inside the name element. */
    isolation: isolate;
}
.role-name-fx > .role-sparkle {
    display: inline-block;
    vertical-align: middle;
    margin-left: 0.2rem;
}

/* Pause every animation on a role-styled element + its FX children
   while it's outside the viewport. role-paint's IntersectionObserver
   sets / removes `data-role-offscreen="1"` based on visibility, so
   off-screen messages stop burning frames. The user never sees a
   freeze because the observer has 200 px of root-margin headroom. */
[data-role-offscreen="1"],
[data-role-offscreen="1"] *,
[data-role-offscreen="1"]::before,
[data-role-offscreen="1"]::after {
    animation-play-state: paused !important;
}
/* Pausing animations alone still leaves expensive composited layers
   alive (matrix rain has 80 sub-spans, firestorm has 18, wind has 7
   SVGs, etc). Hide the decorative FX children entirely while the
   row is offscreen so the browser drops their paint/composite work. */
[data-role-offscreen="1"] > .role-name-text > .role-sparkle,
[data-role-offscreen="1"] > .role-name-text > .role-orbit,
[data-role-offscreen="1"] > .role-name-text > .role-snow,
[data-role-offscreen="1"] > .role-name-text > .role-laser,
[data-role-offscreen="1"] > .role-name-text > .role-crown,
[data-role-offscreen="1"] > .role-name-text > .role-lightning,
[data-role-offscreen="1"] > .role-name-text > .role-hearts,
[data-role-offscreen="1"] > .role-name-text > .role-bubbles,
[data-role-offscreen="1"] > .role-name-text > .role-stars,
[data-role-offscreen="1"] > .role-name-text > .role-wind,
[data-role-offscreen="1"] > .role-name-text > .role-bonfire,
[data-role-offscreen="1"] > .role-name-text > .role-firestorm,
[data-role-offscreen="1"] > .role-name-text > .role-matrix {
    display: none !important;
}
/* Skip the per-letter wave shimmer + glitch ghost copies while offscreen
   (these emit text-shadow / filter chains that re-paint on every frame). */
[data-role-offscreen="1"] .role-name-text.role-fx-glitch::before,
[data-role-offscreen="1"] .role-name-text.role-fx-glitch::after {
    display: none !important;
}
/* Bottle the layout cost of offscreen messages: tell the browser it can
   skip layout + paint work entirely until they re-enter the viewport.
   `contain-intrinsic-size: auto 80px` gives a sensible placeholder
   height so the scroll position doesn't snap when offscreen rows are
   skipped (browser remembers actual size after first paint). */
.chat-messages > .chat-message {
    content-visibility: auto;
    contain-intrinsic-size: auto 80px;
}
/* Inner text span created by role-paint. Carries the gradient / bg-clip
   styling + FX classes so the text paints as regular inline content -
   that's what lets z-index:-1 FX children (hearts, orbit, lightning...)
   actually sit behind the letters. Kept inline-block to match the host's
   layout rules. */
.role-name-text {
    display: inline-block;
    /* isolation here would create a new stacking context and trap the
       z-index:-1 FX children below it, re-breaking the whole point of
       this wrapper. Override the value inherited from .role-name-fx. */
    isolation: auto;
    /* Positioned so that absolutely-placed FX children (sparkle, orbit,
       laser, hearts, bubbles, bonfire, ...) anchor to the text's
       bounding box instead of the host's. That keeps the decorations
       wrapped tightly around the NAME and stops them from bleeding
       over the role icons rendered to the left of it. */
    position: relative;
}

/* Glow: soft halo around the name. Per-effect tint via --role-fx-glow-color. */
.role-name-fx.role-fx-glow {
    text-shadow:
        0 0 6px  var(--role-fx-glow-color, var(--role-c1, currentColor)),
        0 0 14px var(--role-fx-glow-color, var(--role-c1, currentColor));
}

/* Pulse: brightness breathing. Intense enough that the effect reads
   across the whole name - brightness pops to 2x and we add a synchronised
   glow so the peak feels energetic rather than a faint wobble. */
.role-name-fx.role-fx-pulse {
    animation: role-name-pulse 1.4s ease-in-out infinite;
}
@keyframes role-name-pulse {
    0%, 100% { filter: brightness(0.85) drop-shadow(0 0 0   transparent); }
    50%      { filter: brightness(2)   drop-shadow(0 0 10px var(--role-c1, currentColor)); }
}

/* Shimmer: sharper, faster sweep. The synthesized gradient in JS now
   plants a bright white highlight between two stops of the base color
   so the shimmer reads as a concentrated glint travelling across the
   text. background-size 400% widens the canvas so the highlight moves
   across the visible letters more dramatically. */
.role-name-fx.role-fx-shimmer {
    background-size: 400% 100% !important;
    animation: role-name-shimmer 2.2s linear infinite;
}
@keyframes role-name-shimmer {
    0%   { background-position: 100% 50%; }
    100% { background-position:  -100% 50%; }
}

/* Rainbow: overrides any solid color with a cycling hue gradient. */
.role-name-fx.role-fx-rainbow {
    background-image: linear-gradient(90deg, #f87171, #fbbf24, #86efac, #22d3ee, #a78bfa, #f472b6, #f87171) !important;
    background-size: 400% 100% !important;
    -webkit-background-clip: text !important;
            background-clip: text !important;
    color: transparent !important;
    -webkit-text-fill-color: transparent !important;
    animation: role-name-rainbow 5s linear infinite;
}
@keyframes role-name-rainbow {
    0%   { background-position:   0% 50%; }
    100% { background-position: 400% 50%; }
}

/* ---- ROLE ICONS: one small SVG glyph per role that has an icon
       configured. Rendered as a flex wrapper inserted before the
       name text (outside the gradient-clip on the text), so a user
       with multiple roles shows ALL their icons stacked left of
       their name. Each icon inherits its colour from the DB via
       inline style on the wrapper span. */
.role-icons {
    display: inline-flex;
    align-items: center;
    gap: 0.15em;
    margin-right: 0.28em;
    vertical-align: middle;
    /* Icons are decorative - don't let a gradient text-clip on the
       name swallow them. */
    -webkit-text-fill-color: initial;
}
.role-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 0.95em;
    height: 0.95em;
    line-height: 1;
    /* Default fallback colour so icons are visible even if nothing is
       set on the individual span (e.g. icon column without icon_color). */
    color: #e5e7eb;
    filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.4));
}
.role-icon svg {
    width: 100%;
    height: 100%;
    display: block;
    /* SVG paths fill with currentColor, so setting color on the
       wrapper tints the whole glyph. */
}
/* Bitmap icons (e.g. Madrigal logo PNG) use <img> instead of <svg>.
   object-fit: contain keeps the non-square source in its aspect ratio
   inside the square role-icon box; icon_color has no effect here. */
.role-icon img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
}

/* Profile surfaces (modal popup + full profile page) already show the
   user's roles as pills with their own styling, so suppress the
   name-level role-icons stripe there - the same glyphs are instead
   rendered INSIDE each pill, next to the role name. */
.profile-card .role-icons,
.profile .role-icons,
#profile-modal .role-icons {
    display: none !important;
}
/* Icon inside a role pill: smaller than the default role-icon (which
   matches the font-size), snug against the role label. The pill's
   flex layout keeps the icon + text aligned. */
.profile-role .role-icon {
    width: 0.95em;
    height: 0.95em;
    margin-right: 0.25em;
    filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.35));
    /* Transparent-text gradient clip on the sibling .profile-role-text
       doesn't affect us here - we're not inside that span. */
}
.profile-role .role-icon-pill {
    /* Pills already carry their own padding; drop the default width/
       height constraint and let the pill size to its text. */
    width: auto;
    height: auto;
    margin-right: 0.28em;
    font-size: 0.75em;
    padding: 0.08em 0.38em;
    letter-spacing: 0.08em;
}
.profile-role {
    /* Keep icon + text on one line with a tight vertical alignment. */
    display: inline-flex;
    align-items: center;
}

/* Pill-style role icons: "BANNED" / "RESTRICTED" (and any future pill
   kinds). Same rendering shape as the old banned_tag / restricted_tag
   effects - red / slate rounded rectangle with white uppercase text. */
.role-icon-pill {
    width: auto;
    height: auto;
    padding: 0.15em 0.45em;
    border-radius: 0.3em;
    font-size: 0.55em;
    font-weight: 800;
    letter-spacing: 0.1em;
    line-height: 1.25;
    /* Pills force white text regardless of any inherited gradient
       clip on an ancestor. */
    color: #fff !important;
    -webkit-text-fill-color: #fff !important;
    text-transform: uppercase;
    white-space: nowrap;
    vertical-align: middle;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25);
    /* Background colour is set inline by role-paint (icon_color or
       the per-kind default). drop-shadow in the parent rule softens
       the edge. */
}

/* Sparkles: tiny four-pointed stars that twinkle AROUND the name. The
   wrapper is absolutely positioned and overflows the name bounding box
   slightly so the stars appear to orbit the text without adding any
   extra width/height to the inline flow. Each star sits at a different
   scatter point and twinkles on its own offset. */
.role-sparkle {
    position: absolute;
    inset: -0.25em -0.15em;
    pointer-events: none;
    z-index: 1;
}
.role-sparkle i {
    position: absolute;
    background: var(--role-fx-sparkles-color, var(--role-c1, #fde68a));
    clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
    filter: drop-shadow(0 0 2px var(--role-fx-sparkles-color, var(--role-c1, #fde68a)));
    opacity: 0;
    /* Each sparkle has its own hop keyframe that both teleports it
       between 4 anchor spots (via top/left) and handles the twinkle
       scale+rotate. Opacity dips to 0 between hops so the teleport
       itself is invisible; visually the sparkle pops in and out of
       different positions around the name. */
    width: 0.38rem; height: 0.38rem;
}
.role-sparkle i:nth-child(1) { animation: role-sparkle-hop-a 4.4s ease-in-out infinite; }
.role-sparkle i:nth-child(2) { animation: role-sparkle-hop-b 4.4s ease-in-out infinite; animation-delay: -1.3s; width: 0.28rem; height: 0.28rem; }
.role-sparkle i:nth-child(3) { animation: role-sparkle-hop-c 4.4s ease-in-out infinite; animation-delay: -2.8s; width: 0.32rem; height: 0.32rem; }

@keyframes role-sparkle-hop-a {
    0%   { top: -0.1em;  left: -0.05em; opacity: 0;   transform: scale(0.4) rotate(0deg);   }
    12%  { opacity: 1;                  transform: scale(1.2) rotate(30deg); }
    24%  { opacity: 0;                  transform: scale(0.4) rotate(60deg); }
    25%  { top:  0.15em; left:  30%;    }
    37%  { opacity: 1;                  transform: scale(1.1) rotate(20deg); }
    49%  { opacity: 0;                  transform: scale(0.4) rotate(0deg);  }
    50%  { top: -0.05em; left:  calc(100% - 0.4em); }
    62%  { opacity: 1;                  transform: scale(1.25) rotate(45deg);}
    74%  { opacity: 0;                  transform: scale(0.4) rotate(0deg);  }
    75%  { top:  0.45em; left:  15%;    }
    87%  { opacity: 1;                  transform: scale(1.1)  rotate(-20deg);}
    100% { top: -0.1em;  left: -0.05em; opacity: 0;   transform: scale(0.4) rotate(0deg);  }
}

@keyframes role-sparkle-hop-b {
    0%   { top:  0.3em;  left: calc(100% - 0.3em); opacity: 0; transform: scale(0.4) rotate(0deg);}
    12%  { opacity: 1;                  transform: scale(1.15) rotate(-30deg);}
    24%  { opacity: 0;                  transform: scale(0.4) rotate(0deg);   }
    25%  { top: -0.1em;  left:  60%;    }
    37%  { opacity: 1;                  transform: scale(1)   rotate(30deg);  }
    49%  { opacity: 0;                  transform: scale(0.4) rotate(0deg);   }
    50%  { top:  0.5em;  left:  calc(100% - 0.5em); }
    62%  { opacity: 1;                  transform: scale(1.2) rotate(15deg);  }
    74%  { opacity: 0;                  transform: scale(0.4) rotate(0deg);   }
    75%  { top:  0.05em; left:  5%;     }
    87%  { opacity: 1;                  transform: scale(1.1) rotate(-15deg); }
    100% { top:  0.3em;  left: calc(100% - 0.3em); opacity: 0; transform: scale(0.4) rotate(0deg);}
}

@keyframes role-sparkle-hop-c {
    0%   { bottom: -0.1em; left: 55%; opacity: 0; transform: scale(0.4) rotate(0deg); }
    12%  { opacity: 1;                 transform: scale(1.1)  rotate(45deg); }
    24%  { opacity: 0;                 transform: scale(0.4) rotate(0deg);   }
    25%  { bottom:  0.15em; left: 10%; }
    37%  { opacity: 1;                 transform: scale(1.2)  rotate(-25deg);}
    49%  { opacity: 0;                 transform: scale(0.4) rotate(0deg);   }
    50%  { bottom:  0.35em; left: 80%; }
    62%  { opacity: 1;                 transform: scale(1)    rotate(25deg); }
    74%  { opacity: 0;                 transform: scale(0.4) rotate(0deg);   }
    75%  { bottom: -0.05em; left: 30%; }
    87%  { opacity: 1;                 transform: scale(1.15) rotate(-45deg);}
    100% { bottom: -0.1em;  left: 55%; opacity: 0; transform: scale(0.4) rotate(0deg); }
}

/* ---- NEON: stronger multi-layer glow. Reads as "sign above a bar". */
.role-name-fx.role-fx-neon {
    text-shadow:
        0 0 2px  #fff,
        0 0 4px  var(--role-fx-neon-color, var(--role-c1, currentColor)),
        0 0 10px var(--role-fx-neon-color, var(--role-c1, currentColor)),
        0 0 22px var(--role-fx-neon-color, var(--role-c1, currentColor)),
        0 0 34px var(--role-fx-neon-color, var(--role-c1, currentColor));
}

/* ---- FLICKER: unstable neon-sign blink. Pairs naturally with neon. */
.role-name-fx.role-fx-flicker {
    animation: role-name-flicker 3s steps(1) infinite;
}
@keyframes role-name-flicker {
    0%, 18%, 22%, 25%, 53%, 57%, 100% { opacity: 1;   filter: brightness(1);   }
    20%, 24%, 55%                     { opacity: 0.4; filter: brightness(0.55);}
    40%                                { opacity: 0.8; filter: brightness(0.8); }
}

/* ---- OUTLINED / STROKE: transparent fill, outline in the role color. */
.role-name-fx.role-fx-stroke {
    -webkit-text-stroke: 1px var(--role-fx-stroke-color, var(--role-c1, #fff));
    color: transparent !important;
    -webkit-text-fill-color: transparent !important;
    background-image: none !important;
}

/* ---- WAVE: per-letter bob. JS wraps each character in .role-letter.
       If the span hasn't been wrapped (e.g., server-rendered-but-unpainted),
       the whole span bobs together - still visible, just not per-letter. */
.role-name-fx.role-fx-wave { animation: role-name-wave 1.4s ease-in-out infinite; }
.role-name-fx.role-fx-wave .role-letter {
    display: inline-block;
    animation: role-name-wave 1.4s ease-in-out infinite;
    /* Parent uses -webkit-background-clip:text + transparent fill for
       its gradient. Child spans inherit the transparent fill but not the
       background image, which left wave'd names invisible. Restore
       visibility: inherit the gradient + re-apply background-clip so each
       letter paints its own slice, and fall back to the primary colour
       when there's no gradient image to inherit. */
    background: inherit;
    -webkit-background-clip: text;
            background-clip: text;
    color: var(--role-c1, currentColor);
    -webkit-text-fill-color: var(--role-c1, currentColor);
}
/* If the parent actually has a gradient, let the clipped-to-text version
   win over the solid fallback above. */
.role-name-fx.role-fx-wave[style*="linear-gradient"] .role-letter {
    color: transparent;
    -webkit-text-fill-color: transparent;
}
@keyframes role-name-wave {
    0%, 100% { transform: translateY(0);     }
    50%      { transform: translateY(-0.18em); }
}

/* ---- CURSOR: blinking terminal bar after the name. */
.role-name-fx.role-fx-cursor::after {
    content: "";
    display: inline-block;
    width: 0.12em;
    height: 0.95em;
    margin-left: 0.12em;
    vertical-align: -0.05em;
    background: var(--role-fx-cursor-color, var(--role-c1, currentColor));
    animation: role-name-cursor 1s steps(1) infinite;
}
@keyframes role-name-cursor {
    0%, 49%   { opacity: 1; }
    50%, 100% { opacity: 0; }
}

/* ---- ORBIT: two dots circling around the name's center. Each dot
       rotates independently using a translate-radius trick, so the
       "orbit" is a true circle (~0.75em radius) rather than tracing the
       rectangular bounds of the wrapper. */
.role-orbit {
    position: absolute;
    top: 50%;  left: 50%;
    width: 0;  height: 0;           /* anchor point at the name's centre */
    pointer-events: none;
    /* Parent .role-name-fx has isolation:isolate so z-index:-1 parks the
       orbiting dots BEHIND the name text while still inside the name box. */
    z-index: -1;
}
.role-orbit i {
    position: absolute;
    top:  -0.175rem;
    left: -0.175rem;
    width:  0.35rem;
    height: 0.35rem;
    border-radius: 50%;
    background: var(--role-fx-orbit-color, var(--role-c1, #fde68a));
    filter: drop-shadow(0 0 3px var(--role-fx-orbit-color, var(--role-c1, #fde68a)));
    animation: role-orbit-dot 3.2s linear infinite;
}
/* Second dot orbits offset by half a cycle so they sit opposite. */
.role-orbit i:nth-child(2) { animation-delay: -1.6s; }
@keyframes role-orbit-dot {
    from { transform: rotate(0deg)   translateX(0.75em) rotate(0deg);   }
    to   { transform: rotate(360deg) translateX(0.75em) rotate(-360deg);}
}

/* ---- SNOW: small falling particles. Each particle drifts from above
       the name down past it, fading in/out. */
.role-snow {
    position: absolute;
    inset: -0.6em -0.2em -0.2em;
    pointer-events: none;
    overflow: hidden;
}
.role-snow i {
    position: absolute;
    top: -0.3em;
    width: 3px; height: 3px;
    border-radius: 50%;
    background: var(--role-fx-snow-color, var(--role-c1, #e5e7eb));
    opacity: 0;
    animation: role-snow-fall 2.8s linear infinite;
}
.role-snow i:nth-child(1) { left:  8%; animation-delay: 0s;   }
.role-snow i:nth-child(2) { left: 28%; animation-delay: -0.6s; width: 2px; height: 2px; }
.role-snow i:nth-child(3) { left: 52%; animation-delay: -1.3s; }
.role-snow i:nth-child(4) { left: 72%; animation-delay: -0.9s; width: 2px; height: 2px; }
.role-snow i:nth-child(5) { left: 88%; animation-delay: -1.8s; }
@keyframes role-snow-fall {
    0%   { transform: translateY(0)    translateX(0);    opacity: 0;   }
    10%  { opacity: 0.9; }
    80%  { opacity: 0.9; }
    100% { transform: translateY(1.6em) translateX(0.15em); opacity: 0; }
}

/* ---- LASER: thin sweeping line under the name. */
.role-laser {
    position: absolute;
    left: 0; right: 0;
    bottom: -0.1em;
    height: 2px;
    pointer-events: none;
    overflow: hidden;
}
.role-laser::before {
    content: "";
    position: absolute;
    top: 0; left: -40%;
    width: 40%; height: 100%;
    background: linear-gradient(90deg, transparent, var(--role-fx-laser-color, var(--role-c1, #fde68a)), transparent);
    filter: drop-shadow(0 0 4px var(--role-fx-laser-color, var(--role-c1, #fde68a)));
    animation: role-laser-sweep 2s linear infinite;
}
@keyframes role-laser-sweep {
    from { transform: translateX(0);   }
    to   { transform: translateX(350%);}
}

/* ---- CROWN: gold crown pinned above the last letter. Static by default;
       a tiny bob animation gives it life. */
.role-crown {
    position: absolute;
    right: -0.25em;
    top: -0.75em;
    width: 0.85em;
    height: 0.7em;
    pointer-events: none;
    animation: role-crown-bob 2.4s ease-in-out infinite;
}
.role-crown svg { width: 100%; height: 100%; display: block; color: var(--role-fx-crown-color, #fcd34d); filter: drop-shadow(0 0 2px color-mix(in srgb, var(--role-fx-crown-color, #fcd34d) 60%, transparent)); }
@keyframes role-crown-bob {
    0%, 100% { transform: translateY(0);    }
    50%      { transform: translateY(-2px); }
}

/* ---- FLAME: the name IS the fire. Warm vertical gradient (yellow top,
       red bottom) clipped to the text, plus multi-layer warm glow that
       flickers to simulate flame motion. Overrides any role color - when
       a role is on fire, it is on fire. */
.role-name-fx.role-fx-flame {
    background-image: linear-gradient(to top,
        #7f1d1d 0%,
        #dc2626 25%,
        #f97316 55%,
        #fbbf24 80%,
        #fef3c7 100%) !important;
    -webkit-background-clip: text !important;
            background-clip: text !important;
    color: transparent !important;
    -webkit-text-fill-color: transparent !important;
    animation: role-name-flame 0.18s steps(2) infinite;
    filter:
        drop-shadow(0 0 2px  rgba(251, 146, 60, 0.9))
        drop-shadow(0 0 6px  rgba(249, 115, 22, 0.7))
        drop-shadow(0 0 14px rgba(220, 38, 38, 0.55));
}
@keyframes role-name-flame {
    0%   { transform: translateY(0)     skewX(0);   }
    33%  { transform: translateY(-0.5px) skewX(-1deg);}
    66%  { transform: translateY(0.5px) skewX(1deg); }
    100% { transform: translateY(-0.5px) skewX(0);   }
}

/* ---- LIGHTNING: zap flash behind the text. Uses a yellow bolt that
       pops in once per cycle. */
.role-lightning {
    position: absolute;
    inset: -0.15em -0.2em;
    pointer-events: none;
    z-index: -1;
    opacity: 0;
    color: var(--role-fx-lightning-color, #fde047);
    filter: drop-shadow(0 0 6px var(--role-fx-lightning-color, #fde047));
    animation: role-lightning-zap 3s steps(1) infinite;
}
.role-lightning svg { width: 100%; height: 100%; display: block; }
@keyframes role-lightning-zap {
    0%, 5%       { opacity: 0;   transform: scale(0.9); }
    6%           { opacity: 1;   transform: scale(1);   }
    9%           { opacity: 0;   }
    11%          { opacity: 0.8; }
    14%, 100%    { opacity: 0;   }
}

/* ---- HEARTS: actual heart-shaped SVGs floating up behind the name.
       Each <i> wraps a heart SVG (injected by role-paint) so the shape
       is unmistakable. Colour follows --role-fx-hearts-color via
       currentColor. */
.role-hearts {
    position: absolute;
    inset: -0.2em -0.3em;
    pointer-events: none;
    overflow: visible;
    z-index: -1;
}
.role-hearts i {
    position: absolute;
    bottom: 0;
    width: 0.7em;
    height: 0.65em;
    opacity: 0;
    color: var(--role-fx-hearts-color, var(--role-c1, #fb7185));
    filter: drop-shadow(0 0 3px currentColor);
    animation: role-hearts-rise 3.4s ease-in infinite;
}
.role-hearts i svg { width: 100%; height: 100%; display: block; }
.role-hearts i:nth-child(1) { left: 10%; animation-delay:    0s; --hearts-scale: 1;    }
.role-hearts i:nth-child(2) { left: 35%; animation-delay: -0.9s; --hearts-scale: 0.75; }
.role-hearts i:nth-child(3) { left: 62%; animation-delay: -1.8s; --hearts-scale: 0.9;  }
.role-hearts i:nth-child(4) { left: 85%; animation-delay: -2.6s; --hearts-scale: 0.65; }
@keyframes role-hearts-rise {
    0%   { transform: translate(0, 0)         scale(var(--hearts-scale, 1));        opacity: 0;   }
    15%  { opacity: 0.95; }
    60%  { transform: translate(0.3em, -1.2em) scale(calc(var(--hearts-scale, 1) * 0.9)); opacity: 0.7; }
    100% { transform: translate(-0.2em, -2em)  scale(calc(var(--hearts-scale, 1) * 0.4)); opacity: 0;   }
}

/* ---- BUBBLES: rising translucent bubbles. Some sit behind the name
       text and some in front, so the name feels like it's IN the bubble
       stream rather than just parked on a backdrop. The parent has no
       z-index; each bubble chooses its own layer via nth-child. */
.role-bubbles {
    position: absolute;
    inset: -0.2em -0.25em;
    pointer-events: none;
    overflow: hidden;
}
.role-bubbles i {
    position: absolute;
    bottom: -0.2em;
    width: 0.4em;
    height: 0.4em;
    border-radius: 50%;
    opacity: 0;
    background:
        radial-gradient(circle at 30% 30%, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.15) 35%, transparent 60%),
        var(--role-fx-bubbles-color, var(--role-c1, #7dd3fc));
    box-shadow: 0 0 4px color-mix(in srgb, var(--role-fx-bubbles-color, var(--role-c1, #7dd3fc)) 70%, transparent);
    animation: role-bubbles-rise 3s linear infinite;
}
/* Odd children float BEHIND the name, even children IN FRONT. Parent
   .role-name-fx creates an isolate context so z-index:1 sits above
   text (z~0) and z-index:-1 sits below. Front bubbles get a touch of
   extra translucency so they don't overwhelm the text they cross. */
.role-bubbles i:nth-child(odd)  { z-index: -1; }
.role-bubbles i:nth-child(even) { z-index:  1; mix-blend-mode: screen; }
.role-bubbles i:nth-child(1) { left:  8%; animation-delay:    0s; }
.role-bubbles i:nth-child(2) { left: 24%; animation-delay: -0.8s; width: 0.28em; height: 0.28em; }
.role-bubbles i:nth-child(3) { left: 42%; animation-delay: -1.6s; width: 0.5em;  height: 0.5em;  }
.role-bubbles i:nth-child(4) { left: 58%; animation-delay: -0.4s; width: 0.32em; height: 0.32em; }
.role-bubbles i:nth-child(5) { left: 76%; animation-delay: -2.2s; width: 0.45em; height: 0.45em; }
.role-bubbles i:nth-child(6) { left: 92%; animation-delay: -1.1s; width: 0.3em;  height: 0.3em;  }
@keyframes role-bubbles-rise {
    0%   { transform: translateY(0)     scale(0.6); opacity: 0;   }
    15%  { opacity: 0.85; }
    85%  { opacity: 0.85; }
    100% { transform: translateY(-1.8em) scale(1);   opacity: 0;   }
}

/* ---- STARS: tiny twinkling plus-shaped stars scattered around the
       name. Positions are hand-picked to look random while staying
       close to the text (inset tightened so they don't float half a
       line above/below). Each star scales + rotates on its own phase. */
.role-stars {
    position: absolute;
    inset: -0.15em -0.2em;
    pointer-events: none;
    overflow: visible;
    z-index: -1;
}
.role-stars i {
    position: absolute;
    width: 0.3em;
    height: 0.3em;
    color: var(--role-fx-stars-color, var(--role-c1, #fef08a));
    animation: role-stars-twinkle 2.4s ease-in-out infinite;
}
.role-stars i::before,
.role-stars i::after {
    content: "";
    position: absolute;
    inset: 0;
    background: currentColor;
    filter: drop-shadow(0 0 3px currentColor);
}
.role-stars i::before { clip-path: polygon(45% 0, 55% 0, 55% 100%, 45% 100%); }
.role-stars i::after  { clip-path: polygon(0 45%, 100% 45%, 100% 55%, 0 55%); }
/* Seven stars, picked to feel random: stagger column + row + size +
   phase so no two land close to each other or blink in sync. */
.role-stars i:nth-child(1) { top:  18%; left:  7%; animation-delay:    0s; --star-s: 0.9; }
.role-stars i:nth-child(2) { top:  62%; left: 19%; animation-delay: -0.7s; --star-s: 0.6; }
.role-stars i:nth-child(3) { top:   8%; left: 34%; animation-delay: -1.4s; --star-s: 0.75;}
.role-stars i:nth-child(4) { top:  74%; left: 46%; animation-delay: -0.3s; --star-s: 1;   }
.role-stars i:nth-child(5) { top:  24%; left: 61%; animation-delay: -2.0s; --star-s: 0.55;}
.role-stars i:nth-child(6) { top:  58%; left: 78%; animation-delay: -1.1s; --star-s: 0.85;}
.role-stars i:nth-child(7) { top:  12%; left: 92%; animation-delay: -1.7s; --star-s: 0.7; }
@keyframes role-stars-twinkle {
    0%, 100% { opacity: 0;   transform: scale(calc(var(--star-s, 1) * 0.35)) rotate(0);    }
    50%      { opacity: 1;   transform: scale(var(--star-s, 1))              rotate(45deg); }
}

/* ---- WIND: Zelda-Wind-Waker-style curly wind trails. Each streak is a
       short segment that "draws itself" along a swirly SVG path and then
       erases, produced with a stroke-dash animation rather than a sliding
       transform. Because the SVG fills the container at 100% width, the
       trails stay inside the name's horizontal bounds instead of flying
       off-screen. pathLength=100 on each path normalises the dash math. */
.role-wind {
    position: absolute;
    /* Tight to the name box. overflow:hidden clips any drop-shadow bleed
       so streaks can't escape the element bounds horizontally. */
    inset: -0.1em 0;
    pointer-events: none;
    overflow: hidden;
    z-index: -1;
}
.role-wind i {
    position: absolute;
    left: 0;
    right: 0;
    height: 0.5em;
    display: block;
    color: var(--role-fx-wind-color, var(--role-c1, #bae6fd));
}
.role-wind i svg { width: 100%; height: 100%; display: block; overflow: visible; }
.role-wind i svg path {
    /* 18 units of visible dash travelling along 100 units of path, with a
       large gap so only one segment is ever on-screen per streak. */
    stroke-dasharray: 18 200;
    stroke-dashoffset: 218;
    animation: role-wind-trail 2.6s cubic-bezier(0.4, 0, 0.25, 1) infinite;
}
/* Seven streaks scattered across varied tops / heights / opacities.
   Durations are intentionally co-prime (2.3, 2.9, 7.1, 2.6, 3.1, 7.7,
   2.5s) so their combined pattern doesn't repeat - streaks keep arriving
   at seemingly random offsets. The two loop streaks (3rd and 6th) carry
   long durations + a bigger dash gap so they show up only every 7-8s. */
.role-wind i:nth-child(1)          { top:   3%; height: 0.55em; }
.role-wind i:nth-child(1) svg path { animation-delay:    0s;   animation-duration: 2.3s; }
.role-wind i:nth-child(2)          { top:  20%; height: 0.45em; opacity: 0.85; }
.role-wind i:nth-child(2) svg path { animation-delay: -0.9s;  animation-duration: 2.9s; }
/* Loop streak 1 - rare, slow, bigger dash gap so the loop only draws
   itself once in a while. Visible part is longer (24 units) so the
   loop reads clearly when it does appear. */
.role-wind i:nth-child(3)          { top:  38%; height: 0.7em;  }
.role-wind i:nth-child(3) svg path { stroke-dasharray: 24 500; stroke-dashoffset: 524;
                                     animation-delay: -3.2s;  animation-duration: 7.1s; }
.role-wind i:nth-child(4)          { top:  52%; height: 0.45em; opacity: 0.9; }
.role-wind i:nth-child(4) svg path { animation-delay: -1.7s;  animation-duration: 2.6s; }
.role-wind i:nth-child(5)          { top:  70%; height: 0.5em;  }
.role-wind i:nth-child(5) svg path { animation-delay: -2.4s;  animation-duration: 3.1s; }
/* Loop streak 2 - same rare treatment, different top + phase so the two
   loops never fire at the same time. */
.role-wind i:nth-child(6)          { top:  82%; height: 0.7em;  }
.role-wind i:nth-child(6) svg path { stroke-dasharray: 24 500; stroke-dashoffset: 524;
                                     animation-delay: -6.0s;  animation-duration: 7.7s; }
.role-wind i:nth-child(7)          { top:  95%; height: 0.45em; opacity: 0.75; }
.role-wind i:nth-child(7) svg path { animation-delay: -0.4s;  animation-duration: 2.5s; }
@keyframes role-wind-trail {
    0%   { stroke-dashoffset: 218; opacity: 0;   }
    10%  { opacity: 1;   }
    85%  { opacity: 1;   }
    100% { stroke-dashoffset: -40; opacity: 0;   }
}
/* Loop streaks override the start/end offsets so the dash actually
   completes its bigger gap before showing up again. */
.role-wind i:nth-child(3) svg path,
.role-wind i:nth-child(6) svg path {
    animation-name: role-wind-loop-trail;
}
@keyframes role-wind-loop-trail {
    0%   { stroke-dashoffset: 524; opacity: 0;   }
    8%   { opacity: 1;   }
    22%  { opacity: 1;   }
    30%  { opacity: 0;   }
    100% { stroke-dashoffset: -40; opacity: 0;   }
}

/* ---- BONFIRE: quick little embers popping off the bottom of the name.
       Tight rise (doesn't climb far above the cap-height), fast
       durations, warm radial-gradient dots that flicker + fade. Three
       embers ride mix-blend-mode: screen in front of the text so they
       brighten letters as they drift past. */
.role-bonfire {
    position: absolute;
    /* Shorter top extension than the earlier "ashes" design so embers
       don't climb half a line above the name. */
    inset: -0.25em -0.15em 0;
    pointer-events: none;
    overflow: visible;
}
.role-bonfire i {
    position: absolute;
    bottom: -0.1em;
    width: 0.22em;
    height: 0.22em;
    border-radius: 50%;
    opacity: 0;
    background: radial-gradient(circle at 50% 40%,
        #fff7ed               0%,
        var(--role-fx-bonfire-color, #fb923c) 40%,
        color-mix(in srgb, var(--role-fx-bonfire-color, #fb923c) 45%, transparent) 75%,
        transparent           100%);
    box-shadow: 0 0 4px color-mix(in srgb, var(--role-fx-bonfire-color, #fb923c) 75%, transparent),
                0 0 10px color-mix(in srgb, var(--role-fx-bonfire-color, #fb923c) 35%, transparent);
    filter: blur(0.3px);
    animation: role-bonfire-rise 1.8s ease-out infinite;
    z-index: -1;
}
/* Tuned per-particle: left-start, horizontal drift (--bf-dx), rise
   height (--bf-dy ~ -1 to -1.3em so they stay close to the name),
   size + co-prime durations (1.5-2.5s) for fast-yet-natural stagger. */
.role-bonfire i:nth-child(1)  { left:  6%; width: 0.25em; height: 0.25em; --bf-dx: 0.12em; --bf-dy: -1.2em; animation-delay:    0s; animation-duration: 1.7s; }
.role-bonfire i:nth-child(2)  { left: 14%; width: 0.16em; height: 0.16em; --bf-dx:-0.08em; --bf-dy: -1.0em; animation-delay: -0.5s; animation-duration: 2.1s; }
.role-bonfire i:nth-child(3)  { left: 24%; width: 0.22em; height: 0.22em; --bf-dx: 0.06em; --bf-dy: -1.3em; animation-delay: -0.9s; animation-duration: 1.9s; }
.role-bonfire i:nth-child(4)  { left: 34%; width: 0.28em; height: 0.28em; --bf-dx:-0.14em; --bf-dy: -1.1em; animation-delay: -1.3s; animation-duration: 2.3s; z-index: 1; mix-blend-mode: screen; }
.role-bonfire i:nth-child(5)  { left: 43%; width: 0.18em; height: 0.18em; --bf-dx: 0.16em; --bf-dy: -1.2em; animation-delay: -0.2s; animation-duration: 2.2s; }
.role-bonfire i:nth-child(6)  { left: 52%; width: 0.2em;  height: 0.2em;  --bf-dx:-0.04em; --bf-dy: -1.1em; animation-delay: -0.7s; animation-duration: 1.6s; }
.role-bonfire i:nth-child(7)  { left: 62%; width: 0.24em; height: 0.24em; --bf-dx: 0.1em;  --bf-dy: -1.3em; animation-delay: -1.5s; animation-duration: 2.5s; z-index: 1; mix-blend-mode: screen; }
.role-bonfire i:nth-child(8)  { left: 72%; width: 0.17em; height: 0.17em; --bf-dx:-0.12em; --bf-dy: -1.0em; animation-delay: -0.3s; animation-duration: 1.8s; }
.role-bonfire i:nth-child(9)  { left: 82%; width: 0.22em; height: 0.22em; --bf-dx: 0.05em; --bf-dy: -1.25em; animation-delay: -1.1s; animation-duration: 2.0s; z-index: 1; mix-blend-mode: screen; }
.role-bonfire i:nth-child(10) { left: 92%; width: 0.14em; height: 0.14em; --bf-dx:-0.04em; --bf-dy: -0.95em; animation-delay: -0.1s; animation-duration: 2.4s; }
@keyframes role-bonfire-rise {
    0%   { transform: translate(0, 0.2em) scale(0.4);                                                         opacity: 0;    }
    12%  {                                                                                                    opacity: 1;    }
    40%  { transform: translate(calc(var(--bf-dx, 0.1em) * 0.5), calc(var(--bf-dy, -1.1em) * 0.4))  scale(1); opacity: 0.95; }
    55%  {                                                                                                   opacity: 1;    }
    75%  { transform: translate(calc(var(--bf-dx, 0.1em) * 0.95),calc(var(--bf-dy, -1.1em) * 0.8))  scale(0.7); opacity: 0.5; }
    100% { transform: translate(var(--bf-dx, 0.1em),             var(--bf-dy, -1.1em))             scale(0.25);opacity: 0;    }
}

/* ---- FIRESTORM: ember + spark cloud rising off a fire that's being
       whipped by wind. The swarm is a mix of tall flame tongues and
       tiny round sparks, all climbing a curvy offset-path while the
       flame axis stays UP (offset-rotate: 0deg - particles don't
       "tumble" with the path tangent, which looked synthetic). The
       path just tosses them side to side as they rise. Sway is
       ±4-6 px which reads as turbulent wind, and a per-particle
       rotation wobble ±7deg gives the flames that flickering look.
       Path coords are px (offset-path doesn't accept em), sized so
       the tallest trail reaches ~22 px above the cap - about one chat
       row-height and no more. */
.role-firestorm {
    position: absolute;
    inset: -0.5em -0.3em -0.1em;
    pointer-events: none;
    overflow: visible;
}
.role-firestorm i {
    position: absolute;
    /* Shift the whole swarm down by 0.3em so the rise starts below the
       baseline rather than at the cap-height - keeps all other timing,
       curves, and motion unchanged. */
    bottom: -0.3em;
    margin-left: -0.05em;
    width: 0.09em;
    height: 0.4em;
    border-radius: 0.09em;
    opacity: 0;
    background: linear-gradient(to top,
        transparent                                                             0%,
        color-mix(in srgb, var(--role-fx-firestorm-color, #dc2626) 60%, transparent) 30%,
        var(--role-fx-firestorm-color, #dc2626)                                    65%,
        #fde68a                                                                  100%);
    box-shadow: 0 0 4px color-mix(in srgb, var(--role-fx-firestorm-color, #dc2626) 75%, transparent),
                0 0 9px color-mix(in srgb, var(--role-fx-firestorm-color, #dc2626) 40%, transparent);
    filter: blur(0.25px);
    /* Flames stay UP. offset-path just translates them along a curve -
       no auto-rotate, no tumbling. Rotation comes from the keyframe
       below (a small flicker wobble). */
    offset-rotate: 0deg;
    offset-distance: 0%;
    transform-origin: 50% 100%;
    transform: rotate(0deg) scale(1);
    animation: role-firestorm-rise 1.8s cubic-bezier(0.25, 0.6, 0.35, 1) infinite;
    z-index: -1;
}
/* Every 3rd particle is a tiny round SPARK instead of a flame tongue -
   breaks up the "rows of matchsticks" look and adds the bright-dot
   embers you see in real footage of burning forests. */
.role-firestorm i:nth-child(3n+2) {
    width: 0.14em;
    height: 0.14em;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 40%,
        #fff7ed                                                                     0%,
        var(--role-fx-firestorm-color, #dc2626)                                    45%,
        color-mix(in srgb, var(--role-fx-firestorm-color, #dc2626) 35%, transparent) 80%,
        transparent                                                               100%);
}
/* 6 different curvy updraft paths with stronger sideways sway (±4-6 px
   instead of ±2). Each rises to a varied top between -16 and -22 px so
   some embers die sooner than others. */
.role-firestorm i:nth-child(6n+1) { offset-path: path("M0 0 Q -4 -6, 0 -12 Q 5 -16, 0 -22"); }
.role-firestorm i:nth-child(6n+2) { offset-path: path("M0 0 C 3 -5, 6 -11, 2 -17 C -2 -20, 1 -21, 0 -21"); }
.role-firestorm i:nth-child(6n+3) { offset-path: path("M0 0 Q -5 -5, -3 -11 Q 1 -16, 2 -19"); }
.role-firestorm i:nth-child(6n+4) { offset-path: path("M0 0 Q 4 -4, 1 -9 Q -4 -13, 0 -17 Q 3 -20, 0 -22"); }
.role-firestorm i:nth-child(6n+5) { offset-path: path("M0 0 C 2 -6, -3 -11, 1 -16 C 4 -19, 1 -20, 0 -20"); }
.role-firestorm i:nth-child(6n)   { offset-path: path("M0 0 Q 3 -5, -2 -10 Q -5 -14, 1 -18 Q 2 -20, 0 -22"); }

/* 18 particles. Varied start columns, heights, co-prime durations
   (1.3-2.1s), and ~half ride mix-blend-mode: screen in front of the
   text so the fire wraps around the letters. */
.role-firestorm i:nth-child(1)  { left:  5%; height: 0.5em;  animation-delay:    0s;   animation-duration: 1.45s; }
.role-firestorm i:nth-child(2)  { left: 12%;                 animation-delay: -0.35s; animation-duration: 1.85s; z-index: 1; mix-blend-mode: screen; }
.role-firestorm i:nth-child(3)  { left: 19%; height: 0.42em; animation-delay: -0.8s;  animation-duration: 1.55s; }
.role-firestorm i:nth-child(4)  { left: 27%; height: 0.46em; animation-delay: -1.25s; animation-duration: 1.95s; z-index: 1; mix-blend-mode: screen; }
.role-firestorm i:nth-child(5)  { left: 34%;                 animation-delay: -0.2s;  animation-duration: 1.65s; }
.role-firestorm i:nth-child(6)  { left: 41%; height: 0.38em; animation-delay: -1.5s;  animation-duration: 1.35s; z-index: 1; mix-blend-mode: screen; }
.role-firestorm i:nth-child(7)  { left: 48%; height: 0.52em; animation-delay: -0.55s; animation-duration: 1.75s; }
.role-firestorm i:nth-child(8)  { left: 54%;                 animation-delay: -1.05s; animation-duration: 1.4s;  z-index: 1; mix-blend-mode: screen; }
.role-firestorm i:nth-child(9)  { left: 61%; height: 0.44em; animation-delay: -0.1s;  animation-duration: 2.05s; }
.role-firestorm i:nth-child(10) { left: 68%; height: 0.48em; animation-delay: -1.3s;  animation-duration: 1.5s;  z-index: 1; mix-blend-mode: screen; }
.role-firestorm i:nth-child(11) { left: 74%;                 animation-delay: -0.45s; animation-duration: 1.7s;  }
.role-firestorm i:nth-child(12) { left: 80%; height: 0.4em;  animation-delay: -0.9s;  animation-duration: 1.9s;  z-index: 1; mix-blend-mode: screen; }
.role-firestorm i:nth-child(13) { left: 86%; height: 0.5em;  animation-delay: -0.25s; animation-duration: 1.55s; }
.role-firestorm i:nth-child(14) { left: 92%;                 animation-delay: -1.7s;  animation-duration: 1.8s;  z-index: 1; mix-blend-mode: screen; }
.role-firestorm i:nth-child(15) { left: 96%; height: 0.42em; animation-delay: -0.65s; animation-duration: 1.35s; }
.role-firestorm i:nth-child(16) { left:  9%;                 animation-delay: -1.15s; animation-duration: 1.95s; z-index: 1; mix-blend-mode: screen; }
.role-firestorm i:nth-child(17) { left: 44%; height: 0.46em; animation-delay: -0.75s; animation-duration: 1.6s;  }
.role-firestorm i:nth-child(18) { left: 78%;                 animation-delay: -0.4s;  animation-duration: 2.0s;  z-index: 1; mix-blend-mode: screen; }
@keyframes role-firestorm-rise {
    0%   { offset-distance:   0%; transform: rotate(0deg)  scale(0.4); opacity: 0;    }
    10%  {                        transform: rotate(-4deg) scale(1);   opacity: 1;    }
    /* Heat flicker: quick sputter mid-rise. Rotation wobbles slightly,
       scale breathes, opacity stammers. Flame still points UP. */
    35%  {                        transform: rotate(6deg)  scale(0.95);opacity: 0.85; }
    55%  {                        transform: rotate(-3deg) scale(1.05);opacity: 1;    }
    75%  {                        transform: rotate(4deg)  scale(0.8); opacity: 0.5;  }
    100% { offset-distance: 100%; transform: rotate(0deg)  scale(0.3); opacity: 0;    }
}

/* ---- MATRIX RAIN: columns of 0s and 1s cascading behind the name.
       Each column is an <i> stacking block-level <span> glyphs; the
       column translates downward while the container mask-images the
       head/tail so the stream fades in at the top and out at the bottom
       (the classic Matrix dropper). */
.role-matrix {
    position: absolute;
    inset: 0 -0.05em;
    pointer-events: none;
    overflow: hidden;
    z-index: -1;
    /* Head bright + fully opaque, tail fades out - same gradient on
       mask so every column gets the fade for free regardless of speed. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 20%, #000 80%, transparent 100%);
            mask-image: linear-gradient(to bottom, transparent 0%, #000 20%, #000 80%, transparent 100%);
}
.role-matrix i {
    position: absolute;
    top: 0;
    display: block;
    font-family: ui-monospace, 'Courier New', monospace;
    font-weight: 700;
    font-size: 0.45em;
    line-height: 0.95;
    letter-spacing: 0;
    color: var(--role-fx-matrix-color, #22c55e);
    text-shadow: 0 0 3px color-mix(in srgb, var(--role-fx-matrix-color, #22c55e) 80%, transparent),
                 0 0 6px color-mix(in srgb, var(--role-fx-matrix-color, #22c55e) 40%, transparent);
    animation: role-matrix-fall 2.4s linear infinite;
}
.role-matrix i span {
    display: block;
    opacity: 0.55;
}
/* Brightest glyph at the head of each stream - pure white with a
   strong coloured glow so the leading digit pops. */
.role-matrix i span:last-child {
    color: #ecfdf5;
    opacity: 1;
    text-shadow: 0 0 5px var(--role-fx-matrix-color, #22c55e),
                 0 0 9px var(--role-fx-matrix-color, #22c55e);
}
/* Trailing glyphs get progressively more transparent so the tail
   reads as "fading memory of data". */
.role-matrix i span:nth-last-child(2) { opacity: 0.85; }
.role-matrix i span:nth-last-child(3) { opacity: 0.7;  }
.role-matrix i span:nth-last-child(4) { opacity: 0.55; }
.role-matrix i span:nth-last-child(5) { opacity: 0.4;  }
.role-matrix i span:nth-last-child(6) { opacity: 0.3;  }
.role-matrix i span:nth-last-child(n+7) { opacity: 0.2; }
/* 8 columns spaced across the name width, each with a co-prime duration
   + staggered delay so the streams don't visibly sync. Two columns
   ride mix-blend-mode: screen in front of the text for that "data
   bleeding through" hacker terminal vibe. */
.role-matrix i:nth-child(1) { left:  5%; animation-duration: 2.3s; animation-delay:    0s; }
.role-matrix i:nth-child(2) { left: 17%; animation-duration: 2.9s; animation-delay: -0.8s; }
.role-matrix i:nth-child(3) { left: 29%; animation-duration: 2.1s; animation-delay: -1.5s; z-index: 1; mix-blend-mode: screen; }
.role-matrix i:nth-child(4) { left: 41%; animation-duration: 3.1s; animation-delay: -0.4s; }
.role-matrix i:nth-child(5) { left: 53%; animation-duration: 2.5s; animation-delay: -2.0s; }
.role-matrix i:nth-child(6) { left: 65%; animation-duration: 2.7s; animation-delay: -1.2s; z-index: 1; mix-blend-mode: screen; }
.role-matrix i:nth-child(7) { left: 77%; animation-duration: 3.3s; animation-delay: -0.6s; }
.role-matrix i:nth-child(8) { left: 89%; animation-duration: 2.4s; animation-delay: -1.8s; }
@keyframes role-matrix-fall {
    /* Column starts above the name box (its last-child head glyph just
       about to enter from the top) and finishes well below (tail
       drained out). 100% = translate by the column's own height plus
       the name height, which is roughly 200% of the column's own box. */
    0%   { transform: translateY(-100%); }
    100% { transform: translateY(200%);  }
}

/* ---- AURORA: smooth flowing multi-colour sweep clipped to the text.
       Cycles teal -> violet -> rose indefinitely. Overrides any solid
       role colour (same rule as rainbow) and plays well with glow. */
.role-name-fx.role-fx-aurora {
    background-image: linear-gradient(110deg,
        #22d3ee 0%, #8b5cf6 30%, #ec4899 55%, #8b5cf6 80%, #22d3ee 100%) !important;
    background-size: 300% 100% !important;
    -webkit-background-clip: text !important;
            background-clip: text !important;
    color: transparent !important;
    -webkit-text-fill-color: transparent !important;
    animation: role-name-aurora 7s ease-in-out infinite;
}
@keyframes role-name-aurora {
    0%, 100% { background-position:   0% 50%; }
    50%      { background-position: 100% 50%; }
}

/* ---- HOLOGRAM: scan-line overlay + a subtle chromatic ghost. The
       ghost uses text-shadow in cyan/magenta at small offsets. */
.role-name-fx.role-fx-hologram {
    text-shadow:
         1px 0 0 rgba(0,255,255,0.45),
        -1px 0 0 rgba(255,0,128,0.45) !important;
    /* Overrides any solid/gradient color set inline so the stripes win. */
    background-image:
        linear-gradient(var(--role-c1, #c4b5fd), var(--role-c1, #c4b5fd)),
        repeating-linear-gradient(
            to bottom,
            transparent 0,
            transparent 2px,
            rgba(255,255,255,0.35) 2px,
            rgba(255,255,255,0.35) 3px
        ) !important;
    background-blend-mode: overlay;
    -webkit-background-clip: text !important;
            background-clip: text !important;
    -webkit-text-fill-color: transparent !important;
    color: transparent !important;
    animation: role-name-hologram 5s linear infinite;
}
@keyframes role-name-hologram {
    from { background-position: 0 0; }
    to   { background-position: 0 20px; }
}

/* ---- GLITCH: text briefly offsets into red/cyan ghosts. Uses
       pseudo-elements that read the text from data-text (set by JS). */
.role-name-fx.role-fx-glitch { position: relative; }
.role-name-fx.role-fx-glitch::before,
.role-name-fx.role-fx-glitch::after {
    content: attr(data-text);
    position: absolute;
    inset: 0;
    pointer-events: none;
    mix-blend-mode: screen;
    background: transparent;
    -webkit-background-clip: initial;
            background-clip: initial;
    -webkit-text-fill-color: initial;
}
.role-name-fx.role-fx-glitch::before {
    color: #22d3ee;
    animation: role-name-glitch-a 4.2s steps(1) infinite;
}
.role-name-fx.role-fx-glitch::after {
    color: #f472b6;
    animation: role-name-glitch-b 4.2s steps(1) infinite;
}
/* One glitch burst per cycle (~85% -> 91%). Longer 4.2s cycle + single
   burst reads as an occasional stutter instead of a constant seizure. */
@keyframes role-name-glitch-a {
    0%, 84%, 91%, 100% { transform: translate(0);         opacity: 0;   }
    85%                { transform: translate(-2px, 1px); opacity: 0.9; }
    88%                { transform: translate( 2px,-1px); opacity: 0.7; }
}
@keyframes role-name-glitch-b {
    0%, 84%, 91%, 100% { transform: translate(0);         opacity: 0;   }
    85%                { transform: translate( 2px,-1px); opacity: 0.9; }
    88%                { transform: translate(-2px, 1px); opacity: 0.7; }
}

/* ---- FROST: icy blue-white fringe. Layers of cool-toned text-shadow. */
.role-name-fx.role-fx-frost {
    text-shadow:
        0 0 1px  color-mix(in srgb, var(--role-fx-frost-color, #38bdf8) 20%, #ffffff),
        0 0 4px  color-mix(in srgb, var(--role-fx-frost-color, #38bdf8) 70%, #ffffff),
        0 0 8px  var(--role-fx-frost-color, #38bdf8),
        0 0 14px color-mix(in srgb, var(--role-fx-frost-color, #38bdf8) 50%, transparent);
}

/* ---- CRT: subtle vertical jitter + faint noise-looking overlay. */
.role-name-fx.role-fx-crt {
    animation: role-name-crt 0.18s steps(2) infinite;
}
.role-name-fx.role-fx-crt::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        repeating-linear-gradient(
            to bottom,
            transparent 0,
            transparent 1px,
            rgba(255,255,255,0.05) 1px,
            rgba(255,255,255,0.05) 2px
        );
    mix-blend-mode: overlay;
}
@keyframes role-name-crt {
    0%   { transform: translateY(0);   }
    50%  { transform: translateY(0.5px); }
    100% { transform: translateY(-0.5px);}
}

@media (prefers-reduced-motion: reduce) {
    .role-name-fx.role-fx-pulse,
    .role-name-fx.role-fx-shimmer,
    .role-name-fx.role-fx-rainbow,
    .role-name-fx.role-fx-flicker,
    .role-name-fx.role-fx-wave,
    .role-name-fx.role-fx-wave .role-letter,
    .role-name-fx.role-fx-cursor::after,
    .role-name-fx.role-fx-hologram,
    .role-name-fx.role-fx-glitch::before,
    .role-name-fx.role-fx-glitch::after,
    .role-name-fx.role-fx-crt,
    .role-sparkle i,
    .role-orbit,
    .role-snow i,
    .role-laser::before,
    .role-crown,
    .role-flame,
    .role-lightning,
    .role-hearts i,
    .role-bubbles i,
    .role-stars i,
    .role-wind i svg path,
    .role-bonfire i,
    .role-firestorm i,
    .role-matrix i,
    .role-name-fx.role-fx-aurora { animation: none !important; opacity: 0.7; }
}

/* =================================================================
 * Reduce-motion preference (Settings > Website > Reduce animations).
 * -----------------------------------------------------------------
 * When the user opts out of motion (or the toggle defaults from the
 * OS-level prefers-reduced-motion setting), kill every looping
 * animation and transition site-wide. This is the CPU relief valve
 * for older devices and browsers without GPU acceleration, where the
 * site's many infinite keyframe animations (glows, shimmers, pulses)
 * are rendered by the CPU instead of the graphics card.
 *
 * The toggle sets `.reduce-motion` on <html> (also applied pre-paint
 * by an inline bootstrap in layout.phtml). We don't fold this into the
 * @media (prefers-reduced-motion) blocks above because this is an
 * explicit in-app choice that must win regardless of the OS setting.
 *
 * Technique: collapse durations to ~0 rather than `animation: none` so
 * any element that relies on an animation's END state (not its first
 * keyframe) still settles correctly. The in-canvas game runs its own
 * JS rAF loop and is unaffected by these CSS rules.
 * ================================================================= */
html.reduce-motion *,
html.reduce-motion *::before,
html.reduce-motion *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
}

/* =================================================================
 * Home page (/) - animated landing stage.
 * Built to sit inside <main class="container">; everything is scoped
 * under .home-stage so it can't leak into chat/settings pages.
 * ================================================================= */

.home-stage {
    position: relative;
    isolation: isolate;
    min-height: calc(100dvh - 9rem);
    padding: clamp(1.5rem, 4vw, 3rem) 0 4rem;
    overflow: hidden;
    /* Break out of the <main class="container"> max-width so the aurora
       reaches the edges of the viewport on wide screens. */
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
}

/* --- aurora backdrop ---------------------------------------------- */
.home-aurora {
    position: absolute;
    inset: -10% -5%;
    z-index: -1;
    pointer-events: none;
    filter: saturate(1.1);
}
.home-aurora-orb {
    position: absolute;
    width: 46vmax;
    height: 46vmax;
    border-radius: 50%;
    filter: blur(90px);
    opacity: 0.55;
    mix-blend-mode: screen;
    will-change: transform;
}
.home-aurora-orb-a {
    background: radial-gradient(circle, rgba(124,58,237,0.75), rgba(124,58,237,0) 65%);
    top: -12%; left: -10%;
    animation: home-orb-a 22s ease-in-out infinite alternate;
}
.home-aurora-orb-b {
    background: radial-gradient(circle, rgba(34,211,238,0.55), rgba(34,211,238,0) 60%);
    top: 30%; right: -18%;
    animation: home-orb-b 28s ease-in-out infinite alternate;
}
.home-aurora-orb-c {
    background: radial-gradient(circle, rgba(244,114,182,0.5), rgba(244,114,182,0) 60%);
    bottom: -20%; left: 15%;
    animation: home-orb-c 26s ease-in-out infinite alternate;
}
@keyframes home-orb-a {
    from { transform: translate3d(0, 0, 0)   scale(1);    }
    to   { transform: translate3d(8vw, 4vh, 0) scale(1.15); }
}
@keyframes home-orb-b {
    from { transform: translate3d(0, 0, 0)   scale(1);    }
    to   { transform: translate3d(-6vw, -5vh, 0) scale(1.18); }
}
@keyframes home-orb-c {
    from { transform: translate3d(0, 0, 0)   scale(1);    }
    to   { transform: translate3d(4vw, -7vh, 0) scale(1.1); }
}
.home-aurora-grid {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(255,255,255,0.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px);
    background-size: 48px 48px;
    mask-image: radial-gradient(circle at 50% 35%, #000 0%, #000 35%, transparent 80%);
    opacity: 0.55;
}

/* --- hero ---------------------------------------------------------- */
.home-hero {
    position: relative;
    text-align: center;
    max-width: 54rem;
    margin: 0 auto;
    padding: 0 1rem;
}

.home-logo-wrap {
    position: relative;
    width: clamp(180px, 34vw, 260px);
    margin: 0 auto 1.25rem;
    aspect-ratio: 1 / 1;
    display: grid;
    place-items: center;
    transform: translate3d(var(--tx, 0), var(--ty, 0), 0);
    transition: transform 0.35s cubic-bezier(.2,.8,.2,1);
    animation: home-logo-pop 0.9s cubic-bezier(.2,.8,.2,1) both,
               home-logo-float 7s ease-in-out 0.9s infinite alternate;
}
@keyframes home-logo-pop {
    from { opacity: 0; transform: scale(.85) translateY(14px); }
    to   { opacity: 1; transform: scale(1)  translateY(0); }
}
@keyframes home-logo-float {
    from { transform: translate3d(var(--tx,0), calc(var(--ty,0px) - 6px), 0); }
    to   { transform: translate3d(var(--tx,0), calc(var(--ty,0px) + 6px), 0); }
}

.home-logo {
    position: relative;
    z-index: 1;
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: drop-shadow(0 10px 35px rgba(124,58,237,0.4));
    user-select: none;
}
.home-logo-glow {
    position: absolute;
    inset: -18%;
    z-index: 0;
    background:
        radial-gradient(circle at 50% 50%,
            rgba(124,58,237,0.55) 0%,
            rgba(244,114,182,0.32) 35%,
            transparent 70%);
    filter: blur(36px);
    animation: home-glow-pulse 4.5s ease-in-out infinite alternate;
}
@keyframes home-glow-pulse {
    from { opacity: 0.7; transform: scale(0.95); }
    to   { opacity: 1;   transform: scale(1.08); }
}

.home-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 1rem;
    padding: 0.3rem 0.85rem;
    border-radius: 999px;
    background: rgba(124,58,237,0.12);
    border: 1px solid rgba(124,58,237,0.35);
    color: #d6c2ff;
    font-size: 0.75rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    animation: home-fade-up 0.7s 0.15s cubic-bezier(.2,.8,.2,1) both;
}
.home-eyebrow-dot {
    width: 8px; height: 8px;
    border-radius: 50%;
    background: #a855f7;
    box-shadow: 0 0 12px #a855f7;
    animation: home-dot-pulse 2s ease-in-out infinite;
}
@keyframes home-dot-pulse {
    0%, 100% { opacity: 1;   transform: scale(1);    }
    50%      { opacity: 0.45;transform: scale(0.65); }
}

.home-title {
    font-size: clamp(2rem, 5vw, 3.75rem);
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.05;
    margin: 0 0 1rem;
    color: #fff;
}
.home-title-line {
    display: block;
    animation: home-fade-up 0.8s cubic-bezier(.2,.8,.2,1) both;
}
.home-title-line:nth-child(1) { animation-delay: 0.25s; }
.home-title-line:nth-child(2) { animation-delay: 0.4s; }

.home-title-gradient {
    background: linear-gradient(90deg, #c4b5fd 0%, #f472b6 45%, #22d3ee 100%);
    background-size: 200% 100%;
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
    animation: home-fade-up 0.8s 0.4s cubic-bezier(.2,.8,.2,1) both,
               home-gradient-slide 8s ease-in-out 1.2s infinite alternate;
}
@keyframes home-gradient-slide {
    from { background-position:   0% 50%; }
    to   { background-position: 100% 50%; }
}

.home-tagline {
    max-width: 38rem;
    margin: 0.75rem auto 1.25rem;
    font-size: clamp(1.05rem, 1.8vw, 1.3rem);
    line-height: 1.4;
    font-weight: 700;
    background: linear-gradient(120deg, #c4b5fd 0%, #67e8f9 50%, #f472b6 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
    animation: home-fade-up 0.8s 0.5s cubic-bezier(.2,.8,.2,1) both;
}

.home-lead {
    max-width: 42rem;
    margin: 0 auto 2rem;
    color: #a1a1aa;
    font-size: clamp(1rem, 1.5vw, 1.15rem);
    line-height: 1.6;
    animation: home-fade-up 0.8s 0.55s cubic-bezier(.2,.8,.2,1) both;
}

.home-actions {
    display: flex;
    gap: 0.75rem;
    justify-content: center;
    flex-wrap: wrap;
    animation: home-fade-up 0.8s 0.7s cubic-bezier(.2,.8,.2,1) both;
}

.home-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.8rem 1.4rem;
    border-radius: 0.55rem;
    border: 1px solid transparent;
    font: inherit;
    font-size: 0.95rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: transform 0.2s cubic-bezier(.2,.8,.2,1), box-shadow 0.2s, background 0.2s, border-color 0.2s, color 0.2s;
    overflow: hidden;
    will-change: transform;
}
.home-btn svg { transition: transform 0.2s cubic-bezier(.2,.8,.2,1); }
.home-btn:hover { transform: translateY(-2px); }
.home-btn:hover svg { transform: translateX(3px); }

/* Discord join button: blurple, brand-logo lead. The logo shouldn't
   slide on hover (it's a mark, not a directional arrow). */
.home-btn-discord {
    color: #fff;
    background: #5865F2;
    box-shadow: 0 8px 24px -10px rgba(88,101,242,0.8), 0 0 0 1px rgba(255,255,255,0.08) inset;
}
.home-btn-discord:hover { background: #4752c4; }
.home-btn-discord:hover svg { transform: none; }

.home-btn-primary {
    color: #fff;
    background: linear-gradient(135deg, #7c3aed 0%, #ec4899 100%);
    background-size: 200% 200%;
    box-shadow: 0 8px 24px -10px rgba(124,58,237,0.7), 0 0 0 1px rgba(255,255,255,0.08) inset;
    animation: home-btn-shimmer 6s ease-in-out infinite alternate;
}
.home-btn-primary::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(110deg, transparent 30%, rgba(255,255,255,0.35) 50%, transparent 70%);
    transform: translateX(-120%);
    transition: transform 0.6s ease;
}
.home-btn-primary:hover::after { transform: translateX(120%); }
.home-btn-primary:hover { box-shadow: 0 12px 32px -8px rgba(236,72,153,0.55), 0 0 0 1px rgba(255,255,255,0.12) inset; }
@keyframes home-btn-shimmer {
    from { background-position:   0% 50%; }
    to   { background-position: 100% 50%; }
}

.home-btn-ghost {
    color: #e5e7eb;
    background: rgba(255,255,255,0.04);
    border-color: rgba(255,255,255,0.12);
    backdrop-filter: blur(6px);
}
.home-btn-ghost:hover {
    background: rgba(255,255,255,0.08);
    border-color: rgba(255,255,255,0.25);
}
.home-btn-pulse {
    width: 8px; height: 8px;
    border-radius: 50%;
    background: #10b981;
    box-shadow: 0 0 0 0 rgba(16,185,129,0.65);
    animation: chat-dm-call-pulse 2.4s ease-out infinite;
    flex: 0 0 auto;
}

/* Passive service-health indicator. Sits in the hero CTA row but is
   not a button - just a coloured dot + label. Defaults to a neutral
   "checking" state on first paint; flips green on a successful
   /api/health probe and red on any failure. */
.home-health {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.95rem;
    border-radius: 0.55rem;
    border: 1px solid rgba(255,255,255,0.10);
    background: rgba(255,255,255,0.04);
    color: var(--text-muted, #a1a1aa);
    font-size: 0.88rem;
    font-weight: 500;
    user-select: none;
    cursor: default;
}
.home-health-dot {
    width: 9px; height: 9px;
    border-radius: 50%;
    background: #71717a;
    box-shadow: 0 0 0 0 rgba(113,113,122,0);
    flex: 0 0 auto;
}
.home-health[data-state="ok"] {
    color: #d1fae5;
    border-color: rgba(16,185,129,0.35);
    background: rgba(16,185,129,0.08);
}
.home-health[data-state="ok"] .home-health-dot {
    background: #10b981;
    box-shadow: 0 0 10px rgba(16,185,129,0.7);
    animation: chat-dm-call-pulse 2.4s ease-out infinite;
}
.home-health[data-state="down"] {
    color: #fecaca;
    border-color: rgba(239,68,68,0.35);
    background: rgba(239,68,68,0.10);
}
.home-health[data-state="down"] .home-health-dot {
    background: #ef4444;
    box-shadow: 0 0 10px rgba(239,68,68,0.7);
}

/* --- construction notice ------------------------------------------ */
.home-construction {
    margin: 2.25rem auto 0;
    max-width: 42rem;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.7rem 1rem;
    border-radius: 0.55rem;
    background: rgba(251,191,36,0.08);
    border: 1px dashed rgba(251,191,36,0.4);
    color: #fcd34d;
    font-size: 0.85rem;
    line-height: 1.5;
    text-align: left;
    animation: home-fade-up 0.8s 0.85s cubic-bezier(.2,.8,.2,1) both;
}

/* "Why Madrigal Labs?" trigger pill - sits above the construction
   notice in the hero. Renders as a glassy capsule with a slowly-
   shimmering accent dot to draw the eye without screaming for it. */
.home-why-trigger {
    margin: 1.5rem auto 0;
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.55rem 1.05rem;
    background:
        linear-gradient(120deg, rgba(167,139,250,0.18), rgba(56,189,248,0.18));
    border: 1px solid rgba(167,139,250,0.40);
    border-radius: 999px;
    color: #fff;
    font: inherit;
    font-size: 0.92rem;
    font-weight: 600;
    cursor: pointer;
    backdrop-filter: blur(6px);
    transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
    animation: home-fade-up 0.8s 0.7s cubic-bezier(.2,.8,.2,1) both;
}
.home-why-trigger:hover {
    transform: translateY(-1px);
    border-color: rgba(167,139,250,0.65);
    box-shadow: 0 14px 32px -16px rgba(167,139,250,0.7);
}
.home-why-trigger:focus-visible { outline: 2px solid #a78bfa; outline-offset: 2px; }
.home-why-spark {
    width: 7px; height: 7px;
    border-radius: 50%;
    background: #a78bfa;
    box-shadow: 0 0 10px rgba(167,139,250,0.85);
    animation: home-why-spark 1.6s ease-in-out infinite;
}
@keyframes home-why-spark {
    0%, 100% { transform: scale(1);   opacity: 0.65; }
    50%      { transform: scale(1.4); opacity: 1;    }
}

/* The modal itself. Full-viewport overlay + a centered card with the
   same purple/cyan rim treatment as .home-privacy-shell. Body scrolls
   inside the card so very long copy doesn't push the close button off
   screen on small viewports. */
.home-why-modal {
    position: fixed;
    inset: 0;
    z-index: 9200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(0.75rem, 3vw, 2rem);
    overflow: hidden;
}
.home-why-modal[hidden] { display: none; }
.home-why-backdrop {
    position: absolute;
    inset: 0;
    background: radial-gradient(120% 120% at 50% 30%, rgba(15,15,22,0.85), rgba(0,0,0,0.95));
    backdrop-filter: blur(6px);
}
.home-why-card {
    position: relative;
    z-index: 1;
    width: min(820px, 100%);
    max-height: calc(100dvh - clamp(1.5rem, 6vw, 4rem));
    /* overflow-y forces overflow-x to compute as `auto` if not pinned;
       the rim ::before peeks out by 2px which would summon a horizontal
       scrollbar across the whole card. Pinning x to hidden suppresses
       that without breaking vertical scrolling. */
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    background:
        radial-gradient(110% 80% at 0% 0%, rgba(167,139,250,0.18), transparent 60%),
        radial-gradient(110% 80% at 100% 100%, rgba(56,189,248,0.16), transparent 65%),
        rgba(13,13,18,0.94);
    border: 1px solid rgba(167,139,250,0.30);
    border-radius: 1.25rem;
    box-shadow: 0 30px 90px -25px rgba(124,58,237,0.65);
    padding: clamp(1.25rem, 3vw, 2rem);
    color: #d4d4d8;
    animation: home-why-pop 0.32s cubic-bezier(.2,.8,.2,1);
}
@keyframes home-why-pop {
    from { opacity: 0; transform: translateY(14px) scale(0.985); }
    to   { opacity: 1; transform: translateY(0)    scale(1);     }
}
.home-why-rim {
    position: absolute;
    inset: 0;          /* was -2px - bled outside the card and triggered a horizontal scrollbar */
    border-radius: inherit;
    padding: 2px;
    background: conic-gradient(from 0deg,
        rgba(167,139,250,0.0)   0deg,
        rgba(167,139,250,0.5)  60deg,
        rgba(56,189,248,0.5)  120deg,
        rgba(167,139,250,0.0) 200deg,
        rgba(244,114,182,0.45)280deg,
        rgba(167,139,250,0.0) 360deg);
    -webkit-mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    pointer-events: none;
    opacity: 0.5;
    animation: home-privacy-rim 14s linear infinite;
}
.home-why-head {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    margin-bottom: 1.25rem;
}
.home-why-head > div { min-width: 0; flex: 1; }
.home-why-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.5rem;
    padding: 0.18rem 0.65rem;
    border-radius: 999px;
    background: rgba(167,139,250,0.12);
    border: 1px solid rgba(167,139,250,0.32);
    color: #c4b5fd;
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-weight: 700;
}
.home-why-eyebrow-dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: #a78bfa;
    box-shadow: 0 0 8px rgba(167,139,250,0.9);
    animation: home-privacy-dot 1.8s ease-in-out infinite;
}
.home-why-title {
    margin: 0 0 0.55rem;
    font-size: clamp(1.4rem, 3.6vw, 1.95rem);
    line-height: 1.15;
    color: #fff;
    font-weight: 800;
}
.home-why-title-grad {
    background: linear-gradient(90deg, #c4b5fd 0%, #67e8f9 50%, #f472b6 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
}
.home-why-lead {
    margin: 0;
    color: #d4d4d8;
    line-height: 1.6;
    max-width: 60ch;
    font-size: 0.95rem;
}
.home-why-close {
    flex-shrink: 0;
    width: 2rem; height: 2rem;
    background: transparent;
    border: 0;
    color: var(--text-muted, #a1a1aa);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    border-radius: 0.4rem;
}
.home-why-close:hover { color: #fff; background: rgba(255,255,255,0.06); }

.home-why-list {
    list-style: none;
    margin: 0 0 1.25rem;
    padding: 0;
    display: grid;
    gap: 0.7rem;
}
.home-why-item {
    display: grid;
    grid-template-columns: 42px 1fr;
    gap: 0.85rem;
    align-items: flex-start;
    padding: 0.85rem 1rem;
    background: rgba(20,20,28,0.65);
    border: 1px solid rgba(255,255,255,0.07);
    border-radius: 0.85rem;
    transition: border-color 0.18s ease, transform 0.18s ease;
}
.home-why-item:hover {
    border-color: rgba(167,139,250,0.45);
    transform: translateY(-1px);
}
.home-why-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px; height: 42px;
    border-radius: 0.6rem;
    color: #c4b5fd;
    background: rgba(167,139,250,0.10);
    border: 1px solid rgba(167,139,250,0.22);
    flex-shrink: 0;
}
.home-why-item h3 {
    margin: 0 0 0.25rem;
    font-size: 1rem;
    font-weight: 700;
    color: #fff;
}
.home-why-item p {
    margin: 0;
    font-size: 0.88rem;
    line-height: 1.55;
    color: #d4d4d8;
}

.home-why-foot {
    display: flex;
    gap: 0.6rem;
    flex-wrap: wrap;
    justify-content: flex-end;
    padding-top: 0.5rem;
    border-top: 1px solid rgba(255,255,255,0.07);
    margin-top: 0.25rem;
}
.home-why-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.55rem 1rem;
    border-radius: 0.55rem;
    background: linear-gradient(120deg, #a78bfa, #67e8f9);
    color: #1a1a22;
    font-weight: 700;
    font-size: 0.92rem;
    border: 0;
    cursor: pointer;
    text-decoration: none;
    transition: transform 0.15s ease, filter 0.15s ease;
}
.home-why-cta:hover { transform: translateY(-1px); filter: brightness(1.06); }
.home-why-cta-ghost {
    background: transparent;
    color: var(--text-muted);
    border: 1px solid rgba(255,255,255,0.14);
}
.home-why-cta-ghost:hover { color: #fff; }

@media (max-width: 560px) {
    .home-why-item { grid-template-columns: 36px 1fr; padding: 0.75rem 0.85rem; }
    .home-why-icon { width: 36px; height: 36px; }
}
.home-construction strong { color: #fde68a; }
.home-construction svg { flex: 0 0 auto; }

/* --- feature cards ------------------------------------------------- */
.home-features {
    margin: clamp(3rem, 6vw, 4.5rem) auto 0;
    max-width: 64rem;
    padding: 0 1rem;
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.home-feature {
    position: relative;
    padding: 1.25rem 1.1rem;
    border-radius: 0.85rem;
    background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.015));
    border: 1px solid rgba(255,255,255,0.08);
    overflow: hidden;
    isolation: isolate;
    transition: transform 0.3s cubic-bezier(.2,.8,.2,1), border-color 0.3s, background 0.3s;
    animation: home-fade-up 0.8s var(--home-delay, 0s) cubic-bezier(.2,.8,.2,1) both;
}
.home-feature::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: radial-gradient(380px circle at var(--mx, 50%) var(--my, 0%),
                                rgba(124,58,237,0.22), transparent 60%);
    opacity: 0;
    transition: opacity 0.3s;
}
.home-feature:hover {
    transform: translateY(-3px);
    border-color: rgba(168,85,247,0.45);
    background: linear-gradient(180deg, rgba(168,85,247,0.08), rgba(255,255,255,0.02));
}
.home-feature:hover::before { opacity: 1; }

.home-feature-icon {
    width: 2.4rem; height: 2.4rem;
    display: grid; place-items: center;
    border-radius: 0.5rem;
    background: linear-gradient(135deg, rgba(124,58,237,0.25), rgba(236,72,153,0.18));
    color: #e9d5ff;
    margin-bottom: 0.8rem;
    box-shadow: 0 0 0 1px rgba(168,85,247,0.25) inset;
}
.home-feature h3 {
    margin: 0 0 0.3rem;
    color: #fff;
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}
.home-feature p {
    margin: 0;
    color: #a1a1aa;
    font-size: 0.88rem;
    line-height: 1.5;
}

/* Mouse-follow glow: handled via a tiny listener registered the first
   time the user hovers (using CSS custom props on hover). */
@media (hover: hover) and (pointer: fine) {
    .home-feature { cursor: default; }
}

/* The "...and way more soon" card is a forward-looking teaser - mark
   it with a dashed border + subtle shimmer so users read it as a
   placeholder, not a shipped feature. */
.home-feature-more {
    border-style: dashed;
    border-color: rgba(168,85,247,0.35);
    background:
        linear-gradient(180deg, rgba(168,85,247,0.10), rgba(236,72,153,0.05));
}
.home-feature-more h3 {
    background: linear-gradient(90deg, #c4b5fd, #f472b6);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
}
.home-feature-more .home-feature-icon svg circle {
    animation: home-more-dot 1.4s ease-in-out infinite;
}
.home-feature-more .home-feature-icon svg circle:nth-child(2) { animation-delay: 0.18s; }
.home-feature-more .home-feature-icon svg circle:nth-child(3) { animation-delay: 0.36s; }
@keyframes home-more-dot {
    0%, 100% { opacity: 0.4; transform: translateY(0);     }
    50%      { opacity: 1;   transform: translateY(-2px);  }
}

/* --- shared fade-up keyframe -------------------------------------- */
@keyframes home-fade-up {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0);     }
}

/* --- mobile polish ------------------------------------------------- */
@media (max-width: 640px) {
    .home-stage { min-height: calc(100dvh - 7rem); }
    .home-logo-wrap { width: min(58vw, 200px); }
    .home-construction { font-size: 0.8rem; }
    .home-title { font-size: clamp(1.9rem, 8vw, 2.4rem); }
}

/* Honor reduced motion - kill every infinite/loop animation.
   Entrance fade-ups are kept (one-shot, subtle). */
@media (prefers-reduced-motion: reduce) {
    .home-aurora-orb,
    .home-aurora-stars,
    .home-logo-wrap,
    .home-logo-glow,
    .home-btn-primary,
    .home-title-gradient,
    .home-eyebrow-dot,
    .home-btn-pulse,
    .home-privacy-shield-glow,
    .home-privacy-eyebrow-dot,
    .home-why-spark,
    .home-why-eyebrow-dot,
    .home-why-rim {
        animation: none !important;
    }
}

/* --- starry overlay for the aurora (extra fancy) ----------------- */
.home-aurora-stars {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image:
        radial-gradient(1px 1px at 12% 18%, rgba(255,255,255,0.55), transparent 60%),
        radial-gradient(1px 1px at 32% 64%, rgba(255,255,255,0.40), transparent 60%),
        radial-gradient(1.4px 1.4px at 51% 22%, rgba(255,255,255,0.55), transparent 60%),
        radial-gradient(1px 1px at 70% 80%, rgba(255,255,255,0.35), transparent 60%),
        radial-gradient(1px 1px at 84% 38%, rgba(255,255,255,0.55), transparent 60%),
        radial-gradient(1px 1px at 18% 86%, rgba(255,255,255,0.45), transparent 60%),
        radial-gradient(1.2px 1.2px at 92% 12%, rgba(255,255,255,0.5), transparent 60%);
    opacity: 0.55;
    animation: home-stars-twinkle 8s ease-in-out infinite alternate;
}
@keyframes home-stars-twinkle {
    0%   { opacity: 0.35; }
    50%  { opacity: 0.7;  }
    100% { opacity: 0.45; }
}

/* --- hero stat row ----------------------------------------------- */
.home-stats {
    list-style: none;
    margin: 1.75rem auto 0;
    padding: 0;
    display: flex;
    gap: 0.85rem;
    justify-content: center;
    flex-wrap: wrap;
    max-width: 640px;
}
.home-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    padding: 0.7rem 1.1rem;
    border: 1px solid rgba(255,255,255,0.08);
    background: rgba(15,15,20,0.55);
    backdrop-filter: blur(6px);
    border-radius: 0.75rem;
    min-width: 7rem;
    opacity: 0;
    animation: home-fade-up 0.7s ease-out forwards;
    animation-delay: var(--home-stat-delay, 0s);
}
.home-stat-num {
    font-size: 1.55rem;
    font-weight: 800;
    background: linear-gradient(120deg, #c4b5fd 0%, #67e8f9 50%, #f472b6 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
    font-variant-numeric: tabular-nums;
}
.home-stat-suffix {
    display: inline-block;
    margin-left: 0.1rem;
    font-size: 0.95rem;
    color: var(--text-muted);
    font-weight: 700;
    vertical-align: middle;
}
.home-stat-label {
    font-size: 0.72rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* --- privacy band -------------------------------------------------- */
.home-privacy {
    margin: 4rem auto 3rem;
    max-width: 1140px;
    padding: 0 clamp(1rem, 3vw, 2rem);
    opacity: 0;
    transform: translateY(28px);
    transition: opacity 0.7s ease, transform 0.7s ease;
}
.home-privacy.is-revealed {
    opacity: 1;
    transform: none;
}
.home-privacy-shell {
    position: relative;
    display: grid;
    grid-template-columns: auto 1fr;
    gap: clamp(1rem, 3vw, 2.25rem);
    align-items: flex-start;
    padding: clamp(1.5rem, 4vw, 2.5rem);
    background:
        radial-gradient(120% 140% at 0% 0%, rgba(167,139,250,0.18), transparent 60%),
        radial-gradient(120% 140% at 100% 100%, rgba(56,189,248,0.16), transparent 65%),
        rgba(13,13,18,0.78);
    border: 1px solid rgba(167,139,250,0.30);
    border-radius: 1.25rem;
    box-shadow:
        0 25px 80px -25px rgba(124,58,237,0.45),
        inset 0 0 0 1px rgba(255,255,255,0.04);
    overflow: hidden;
}
.home-privacy-shell::before {
    /* Animated rim-light around the band. Pure CSS conic gradient
       rotation for a subtle "this is the headline" feeling. */
    content: '';
    position: absolute;
    inset: -2px;
    border-radius: inherit;
    padding: 2px;
    background: conic-gradient(from 0deg,
        rgba(167,139,250,0.0) 0deg,
        rgba(167,139,250,0.55) 60deg,
        rgba(56,189,248,0.55)  120deg,
        rgba(167,139,250,0.0) 200deg,
        rgba(244,114,182,0.45) 280deg,
        rgba(167,139,250,0.0) 360deg);
    -webkit-mask:
        linear-gradient(#000 0 0) content-box,
        linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
            mask-composite: exclude;
    pointer-events: none;
    opacity: 0.45;
    animation: home-privacy-rim 12s linear infinite;
}
@keyframes home-privacy-rim {
    to { transform: rotate(360deg); }
}
.home-privacy-shield {
    position: relative;
    width: 88px;
    height: 88px;
    color: #c4b5fd;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    filter: drop-shadow(0 0 22px rgba(167,139,250,0.55));
}
.home-privacy-shield-glow {
    position: absolute;
    inset: -25%;
    border-radius: 50%;
    background: radial-gradient(closest-side, rgba(167,139,250,0.35), transparent 70%);
    animation: home-shield-pulse 4s ease-in-out infinite;
}
@keyframes home-shield-pulse {
    0%, 100% { opacity: 0.55; transform: scale(1);     }
    50%      { opacity: 0.95; transform: scale(1.08);  }
}
.home-privacy-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0 0 0.55rem;
    padding: 0.2rem 0.7rem;
    border-radius: 999px;
    background: rgba(167,139,250,0.12);
    border: 1px solid rgba(167,139,250,0.32);
    color: #c4b5fd;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-weight: 700;
}
.home-privacy-eyebrow-dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: #a78bfa;
    box-shadow: 0 0 8px rgba(167,139,250,0.9);
    animation: home-privacy-dot 1.8s ease-in-out infinite;
}
@keyframes home-privacy-dot {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 1;    }
}
.home-privacy-title {
    margin: 0 0 0.6rem;
    font-size: clamp(1.45rem, 3.6vw, 2rem);
    font-weight: 800;
    line-height: 1.15;
    color: #fff;
    letter-spacing: 0.005em;
}
.home-privacy-title em {
    font-style: italic;
    background: linear-gradient(90deg, #c4b5fd 0%, #67e8f9 50%, #f472b6 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
}
.home-privacy-lead {
    margin: 0 0 1.3rem;
    color: #d4d4d8;
    font-size: 0.98rem;
    line-height: 1.62;
    max-width: 60ch;
}
.home-privacy-lead strong { color: #fff; }

.home-privacy-pillars {
    list-style: none;
    margin: 0 0 1.25rem;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 0.85rem;
}
.home-privacy-pillar {
    --mx: 50%;
    --my: 50%;
    position: relative;
    padding: 1rem 1.1rem;
    background:
        radial-gradient(120% 100% at var(--mx) var(--my), rgba(167,139,250,0.18), transparent 60%),
        rgba(20,20,28,0.72);
    border: 1px solid rgba(255,255,255,0.07);
    border-radius: 0.9rem;
    transition: transform 0.18s ease, border-color 0.18s ease;
    opacity: 0;
    animation: home-fade-up 0.7s ease-out forwards;
    animation-delay: var(--home-pillar-delay, 0s);
}
.home-privacy-pillar:hover {
    transform: translateY(-3px);
    border-color: rgba(167,139,250,0.45);
}
.home-privacy-pillar-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px; height: 38px;
    border-radius: 0.6rem;
    color: #c4b5fd;
    background: rgba(167,139,250,0.10);
    border: 1px solid rgba(167,139,250,0.22);
    margin-bottom: 0.5rem;
}
.home-privacy-pillar h4 {
    margin: 0 0 0.25rem;
    font-size: 0.98rem;
    color: #fff;
    font-weight: 700;
}
.home-privacy-pillar p {
    margin: 0;
    font-size: 0.85rem;
    color: #c5c5cb;
    line-height: 1.5;
}

.home-privacy-foot {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.home-privacy-link {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.95rem;
    border-radius: 0.55rem;
    color: #fff;
    background: linear-gradient(120deg, rgba(167,139,250,0.30), rgba(56,189,248,0.30));
    border: 1px solid rgba(167,139,250,0.4);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.88rem;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.home-privacy-link:hover {
    transform: translateY(-1px);
    box-shadow: 0 8px 24px -8px rgba(167,139,250,0.55);
}
.home-privacy-link-ghost {
    background: transparent;
    color: var(--text-muted);
    border: 1px solid rgba(255,255,255,0.10);
}
.home-privacy-link-ghost:hover { color: #fff; box-shadow: none; }

/* Stack the shield above the copy on narrow screens. */
@media (max-width: 720px) {
    .home-privacy-shell {
        grid-template-columns: 1fr;
        text-align: left;
    }
    .home-privacy-shield {
        width: 64px; height: 64px;
        margin-bottom: 0.25rem;
    }
}

/* --- features + privacy reveal --------------------------------------- */
[data-reveal] {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity 0.7s ease, transform 0.7s ease;
}
[data-reveal].is-revealed {
    opacity: 1;
    transform: none;
}

/* --- closing CTA strip ------------------------------------------- */
.home-cta {
    margin: 4rem auto 1rem;
    max-width: 840px;
    padding: 0 clamp(1rem, 3vw, 2rem);
}
.home-cta-card {
    --mx: 50%;
    --my: 50%;
    position: relative;
    padding: clamp(1.75rem, 4vw, 2.75rem);
    text-align: center;
    background:
        radial-gradient(120% 140% at var(--mx) var(--my), rgba(244,114,182,0.18), transparent 60%),
        radial-gradient(120% 140% at 0% 0%, rgba(167,139,250,0.16), transparent 65%),
        rgba(13,13,18,0.78);
    border: 1px solid rgba(244,114,182,0.30);
    border-radius: 1.25rem;
    box-shadow: 0 25px 80px -25px rgba(244,114,182,0.40);
}
.home-cta-title {
    margin: 0 0 0.5rem;
    font-size: clamp(1.4rem, 3.6vw, 1.85rem);
    font-weight: 800;
    color: #fff;
}
.home-cta-lead {
    margin: 0 auto 1.25rem;
    max-width: 56ch;
    color: #d4d4d8;
    line-height: 1.55;
}
.home-cta-btn {
    display: inline-flex;
}

/* =================================================================
 * Multi-server: rail extras (create-server "+" button, fallback
 * initial when a server has no icon yet) + server modals.
 * ============================================================== */

.chat-rail-create {
    /* Same dimensions as a normal rail item but rendered as a dashed
       circle so it visually reads as "you can add one here". */
    background: transparent;
    border: 0;
    cursor: pointer;
    color: #a1a1aa;
}

/* Slim divider between the always-on top items (Madrigal + Friends)
   and the user's own custom servers below. Pure cosmetics - the
   block of customs handles drag reorder. */
.chat-rail-divider {
    display: block;
    width: 60%;
    height: 2px;
    background: #27272a;
    border-radius: 999px;
    margin: 0.35rem auto;
}
.chat-rail-customs {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
}
/* Dragging visual cues: the lifted item dims, the hovered target
   shows a single insertion bar above OR below indicating where the
   dropped icon will land. */
.chat-rail [data-rail-draggable] { cursor: grab; position: relative; }
.chat-rail [data-rail-draggable].is-dragging { opacity: 0.35; }
.chat-rail [data-rail-draggable].is-drop-above::before,
.chat-rail [data-rail-draggable].is-drop-below::after {
    content: '';
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: 70%;
    height: 3px;
    border-radius: 999px;
    background: linear-gradient(90deg, transparent 0%, #a78bfa 20%, #7c3aed 50%, #a78bfa 80%, transparent 100%);
    box-shadow: 0 0 8px rgba(167, 139, 250, 0.7);
    pointer-events: none;
    z-index: 5;
}
.chat-rail [data-rail-draggable].is-drop-above::before { top: -7px; }
.chat-rail [data-rail-draggable].is-drop-below::after  { bottom: -7px; }
/* Tiny chevron at the bar's midpoint pointing in the direction the
   dragged icon will move. Uses a separate pseudo-stack so we keep
   the bar pure-CSS without extra DOM. */
.chat-rail [data-rail-draggable].is-drop-above,
.chat-rail [data-rail-draggable].is-drop-below { overflow: visible; }

/* ---- Rail right-click context menu ---- */
.rail-context-menu {
    position: fixed;
    z-index: 1300;
    min-width: 11rem;
    background: #15151a;
    border: 1px solid #27272a;
    border-radius: 0.5rem;
    box-shadow: 0 16px 32px rgba(0, 0, 0, 0.55);
    padding: 0.3rem;
    display: flex;
    flex-direction: column;
}
.rail-context-menu[hidden] { display: none; }
.rail-ctx-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    background: transparent;
    border: 0;
    color: #d4d4d8;
    cursor: pointer;
    padding: 0.45rem 0.6rem;
    border-radius: 0.4rem;
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    text-decoration: none;
}
.rail-ctx-item:hover,
.rail-ctx-item:focus-visible {
    background: rgba(124, 58, 237, 0.18);
    color: #fff;
    outline: none;
}
.rail-ctx-item[hidden] { display: none; }
.rail-ctx-label { flex: 1 1 auto; }
.rail-ctx-arrow { color: #71717a; font-size: 1rem; line-height: 1; margin-left: auto; }
.rail-ctx-item-danger { color: #fca5a5; }
.rail-ctx-item-danger:hover,
.rail-ctx-item-danger:focus-visible {
    background: rgba(239, 68, 68, 0.18);
    color: #fecaca;
}
.rail-mute-status {
    padding: 0.45rem 0.6rem;
    font-size: 0.78rem;
    color: #a1a1aa;
    border-bottom: 1px solid #1f1f23;
}
.rail-mute-status[hidden] { display: none; }
.rail-mute-explainer {
    padding: 0.5rem 0.6rem 0.55rem;
    font-size: 0.74rem;
    line-height: 1.35;
    color: #a1a1aa;
    border-bottom: 1px solid #1f1f23;
    max-width: 16rem;
    white-space: normal;
}
.rail-mute-sep {
    border: 0;
    border-top: 1px solid #1f1f23;
    margin: 0.25rem 0;
}
.rail-mute-sep[hidden] { display: none; }
.chat-rail-create .chat-rail-icon-create {
    width: 2.6rem;
    height: 2.6rem;
    border-radius: 999px;
    background: rgba(124, 58, 237, 0.12);
    border: 1.5px dashed rgba(124, 58, 237, 0.5);
    color: #c4b5fd;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.18s, color 0.18s, transform 0.12s;
}
.chat-rail-create:hover .chat-rail-icon-create {
    background: rgba(124, 58, 237, 0.25);
    color: #fff;
    border-style: solid;
    transform: translateY(-1px);
}
.chat-rail-create:active .chat-rail-icon-create {
    transform: translateY(0);
}

/* Fallback initial when a server has no icon yet. */
.chat-rail-icon-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(135deg, #6366f1, #a855f7);
    color: #fff;
    font-weight: 700;
    font-size: 1.1rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

/* Sidebar header gets a settings gear next to the title for owners. */
.chat-sidebar-heading-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}
.chat-sidebar-edit {
    background: transparent;
    border: 1px solid #27272a;
    color: #a1a1aa;
    cursor: pointer;
    padding: 0.3rem;
    border-radius: 0.4rem;
    line-height: 0;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.chat-sidebar-edit:hover {
    background: rgba(124, 58, 237, 0.18);
    border-color: rgba(124, 58, 237, 0.6);
    color: #fff;
}

/* ------ Server create / settings modals (fancy) ------ */

.server-modal-card {
    /* Wider than the default confirm-modal card and dressed with a
       gradient header flair. Same rounding + shadow tokens as other
       modals in the app so the system feels consistent. */
    width: min(28rem, calc(100vw - 1.5rem));
    padding: 0;
    overflow: hidden;
    border: 1px solid #27272a;
    background: #0e0e10;
    /* Cap to the viewport + flow as a column so long panes (Members,
       Emojis, Audit log) scroll inside the card instead of pushing it
       past the screen. The flair, head, and footer stay flex-shrink:0
       so they never lose space; only the active pane gets to scroll. */
    max-height: calc(100dvh - 2rem);
    display: flex;
    flex-direction: column;
}
.server-modal-card > .server-modal-flair,
.server-modal-card > .server-modal-head,
.server-modal-card > .server-modal-actions {
    flex-shrink: 0;
}
.server-modal-card > .server-modal-form {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}
.server-modal-flair {
    /* Top decorative band - rainbow gradient that lightly hints the
       Madrigal premium tube without copying it. Pinches in at both
       ends via mask so it feels like a ribbon rather than a stripe. */
    height: 0.45rem;
    background: linear-gradient(90deg,
        #f472b6 0%,
        #a78bfa 25%,
        #22d3ee 50%,
        #86efac 75%,
        #fbbf24 100%);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.server-modal-head {
    padding: 1.1rem 1.4rem 0.4rem;
}
.server-modal-title {
    margin: 0;
    font-size: 1.15rem;
    font-weight: 700;
    color: #fff;
    letter-spacing: 0.01em;
}
.server-modal-sub {
    margin: 0.25rem 0 0;
    color: #a1a1aa;
    font-size: 0.85rem;
}
.server-modal-form {
    padding: 0.9rem 1.4rem 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.server-modal-field {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.server-modal-label {
    font-size: 0.78rem;
    color: #a1a1aa;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
.server-modal-required {
    color: #f87171;
    text-transform: none;
    letter-spacing: 0;
}
.server-modal-optional {
    color: #71717a;
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.72rem;
}
.server-modal-hint {
    font-size: 0.78rem;
    color: #71717a;
}
/* Read-only numeric field (e.g. role priority - now set by drag
   order so the value is informational). Same neutral pill style
   as the editable inputs but greyed and not focusable. */
.server-modal-form input[readonly] {
    color: #a1a1aa;
    background: #0c0c0f;
    border-color: #1f1f23;
    cursor: default;
}
.server-modal-form input[type="text"],
.server-modal-form textarea {
    background: #0a0a0d;
    color: #fff;
    border: 1px solid #27272a;
    border-radius: 0.5rem;
    padding: 0.65rem 0.8rem;
    font: inherit;
    font-size: 0.9rem;
    resize: vertical;
    min-height: 2.4rem;
    transition: border-color 0.12s, box-shadow 0.12s;
}
.server-modal-form input[type="text"]:focus,
.server-modal-form textarea:focus {
    outline: none;
    border-color: #7c3aed;
    box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.25);
}
.server-modal-error {
    margin: 0;
    padding: 0.45rem 0.6rem;
    background: rgba(239, 68, 68, 0.12);
    border: 1px solid rgba(239, 68, 68, 0.4);
    color: #fca5a5;
    border-radius: 0.4rem;
    font-size: 0.82rem;
}
.server-modal-builtin-note {
    margin: 0;
    padding: 0.45rem 0.6rem;
    background: rgba(124, 58, 237, 0.12);
    border: 1px solid rgba(124, 58, 237, 0.4);
    color: #c4b5fd;
    border-radius: 0.4rem;
    font-size: 0.82rem;
}
.server-modal-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.85rem 1.4rem 1.1rem;
    border-top: 1px solid #1f1f23;
    background: #0a0a0c;
}
.server-modal-spacer {
    flex: 1 1 auto;
}
.server-modal-delete {
    margin-right: auto;
}

/* Icon preview + buttons in the settings modal. */
.server-modal-icon-row {
    display: flex;
    gap: 0.85rem;
    align-items: flex-start;
}
.server-modal-icon-preview {
    width: 4.5rem;
    height: 4.5rem;
    flex: 0 0 auto;
    border-radius: 1rem;
    overflow: hidden;
    background: #0a0a0d;
    border: 1px solid #27272a;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}
.server-modal-icon-preview img {
    width: 100%;
    height: 100%;
    /* Fill the slot - matches what users see on the rail. */
    object-fit: cover;
    display: block;
}
.server-modal-icon-fallback {
    font-size: 1.6rem;
    font-weight: 700;
    background: linear-gradient(135deg, #6366f1, #a855f7);
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
}
.server-modal-icon-actions {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.server-modal-icon-btns {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.25rem;
}
.btn-sm {
    padding: 0.35rem 0.7rem;
    font-size: 0.82rem;
}

/* Wider modal card for the multi-tab settings modal so the roles +
   members tabs aren't cramped. */
.server-modal-card-wide {
    width: min(36rem, calc(100vw - 1.5rem));
}

/* Tab bar at the top of the settings modal. */
.server-modal-tabs {
    display: flex;
    gap: 0.25rem;
    margin-top: 0.85rem;
    border-bottom: 1px solid #1f1f23;
}
.server-modal-tab {
    background: transparent;
    border: 0;
    color: #a1a1aa;
    cursor: pointer;
    padding: 0.55rem 0.9rem;
    font: inherit;
    font-size: 0.88rem;
    font-weight: 600;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color 0.12s, border-color 0.12s;
}
.server-modal-tab:hover { color: #e4e4e7; }
.server-modal-tab.is-active {
    color: #fff;
    border-bottom-color: #7c3aed;
}

/* ---- Roles tab ---- */
.server-roles-pane,
.server-members-pane {
    max-height: 60vh;
    overflow-y: auto;
}
.server-roles-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 0.85rem;
}
.server-roles-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.server-role-row-card {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.6rem 0.8rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.5rem;
    transition: border-color 0.12s, background 0.12s;
}
.server-role-row-card.is-builtin {
    background: linear-gradient(90deg, rgba(251,191,36,0.06), rgba(251,191,36,0.02));
    border-color: rgba(251,191,36,0.35);
}
.server-role-row-card:hover { border-color: #3f3f46; background: #111114; }
.server-role-row-card.is-builtin:hover { border-color: rgba(251,191,36,0.55); }
.server-role-dot {
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 50%;
    flex: 0 0 auto;
    box-shadow: 0 0 0 1px rgba(0,0,0,0.45) inset;
}
.server-role-name {
    color: #fff;
    font-weight: 600;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.server-role-meta {
    color: #71717a;
    font-size: 0.78rem;
}
.server-role-pill {
    background: rgba(124, 58, 237, 0.18);
    color: #c4b5fd;
    border-radius: 999px;
    padding: 0.1rem 0.5rem;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.server-roles-empty {
    color: #71717a;
    text-align: center;
    padding: 1rem;
    font-size: 0.85rem;
}

/* ---- Drag-to-reorder handle (server roles list) ----
   Mirrors the staff-panel role list. Owner / everyone rows show a
   placeholder span that occupies the same width so the rest of the
   row alignment stays grid-perfect. */
.server-role-drag {
    background: transparent;
    border: 0;
    color: #52525b;
    cursor: grab;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.2rem;
    border-radius: 0.3rem;
    flex: 0 0 auto;
    transition: color 0.12s, background 0.12s;
}
.server-role-drag:hover,
.server-role-drag:focus-visible {
    color: #d4d4d8;
    background: rgba(255,255,255,0.05);
    outline: none;
}
.server-role-drag:active { cursor: grabbing; }
.server-role-drag-placeholder {
    display: inline-block;
    width: 1.4rem;
    height: 1.4rem;
    flex: 0 0 auto;
}
.server-role-row-card.is-draggable[draggable="true"] { cursor: grab; }
.server-role-row-card.is-dragging {
    opacity: 0.5;
    border-color: rgba(168,85,247,0.55);
}
.server-role-row-card.is-drop-before {
    box-shadow: 0 -2px 0 0 #a855f7 inset;
}
.server-role-row-card.is-drop-after {
    box-shadow: 0 2px 0 0 #a855f7 inset;
}

/* ---- Inline role editor ---- */
.server-role-editor {
    margin-top: 0.85rem;
    padding: 0.85rem;
    border: 1px solid #27272a;
    border-radius: 0.6rem;
    background: linear-gradient(180deg, #111114, #0a0a0d);
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}
.server-role-editor-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.server-role-editor-title {
    margin: 0;
    color: #fff;
    font-size: 1rem;
    font-weight: 700;
}
.server-role-row {
    display: flex;
    gap: 0.65rem;
}
.server-role-perms {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 0.4rem 0.85rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.5rem;
    padding: 0.7rem;
}
.server-role-perm {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    cursor: pointer;
    padding: 0.25rem 0;
    color: #d4d4d8;
}
.server-role-perm input[type="checkbox"] {
    margin-top: 0.2rem;
    accent-color: #7c3aed;
}
.server-role-perm-name {
    display: block;
    color: #fff;
    font-weight: 600;
    font-size: 0.85rem;
}
.server-role-perm-hint {
    display: block;
    color: #a1a1aa;
    font-size: 0.78rem;
    line-height: 1.35;
}
/* ---- Emojis tab (server settings) ---- */
.server-emojis-toolbar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.5rem 0;
}
.server-emojis-counter {
    color: #a1a1aa;
    font-size: 0.78rem;
}
.server-emojis-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.server-emoji-row {
    display: grid;
    grid-template-columns: 48px 1fr auto;
    gap: 0.75rem;
    align-items: center;
    padding: 0.5rem 0.6rem;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 0.5rem;
}
.server-emoji-img {
    width: 40px;
    height: 40px;
    object-fit: contain;
    image-rendering: -webkit-optimize-contrast;
    background: rgba(255, 255, 255, 0.04);
    border-radius: 0.35rem;
}
.server-emoji-meta {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}
.server-emoji-name {
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0.35rem;
    color: #e2e8f0;
    padding: 0.3rem 0.5rem;
    font-size: 0.85rem;
    font-family: inherit;
    width: 100%;
    max-width: 280px;
}
.server-emoji-name:read-only {
    border-color: transparent;
    background: transparent;
    cursor: default;
}
.server-emoji-token {
    font-size: 0.75rem;
    color: #a1a1aa;
    font-family: ui-monospace, "Cascadia Code", "JetBrains Mono", Menlo, monospace;
}
.server-emoji-anim {
    color: #c4b5fd;
    font-weight: 600;
    margin-left: 0.4rem;
}
.server-emoji-delete { color: #fca5a5; }

/* Pending upload row + spinner. Shown while the server is resizing
   + re-encoding a freshly-uploaded emoji image so users get visual
   feedback for the round-trip (which can take a couple of seconds
   for big animated GIFs). */
.server-emoji-row-pending {
    opacity: 0.85;
    background: rgba(124, 58, 237, 0.08);
    border-color: rgba(124, 58, 237, 0.25);
}
.server-emoji-pending-spinner {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.12);
    border-top-color: #c4b5fd;
    animation: server-emoji-spin 0.7s linear infinite;
    margin: 4px;
}
.server-emoji-pending-name {
    font-size: 0.9rem;
    color: #e2e8f0;
}
.server-emoji-pending-hint {
    font-size: 0.78rem;
    color: #a1a1aa;
}
.server-emoji-row-pending .server-emoji-meta {
    gap: 0.15rem;
}
@keyframes server-emoji-spin {
    to { transform: rotate(360deg); }
}
#server-emojis-upload-btn.is-busy {
    position: relative;
    cursor: wait;
    opacity: 0.85;
}
#server-emojis-upload-btn.is-busy::after {
    content: '';
    display: inline-block;
    width: 12px;
    height: 12px;
    margin-left: 0.5rem;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.25);
    border-top-color: #fff;
    animation: server-emoji-spin 0.7s linear infinite;
    vertical-align: -2px;
}

/* Drag-and-drop upload overlay for the Emojis tab. The pane itself
   becomes positioned so the overlay can pin to it; the overlay sits
   above the list and toolbar with a dashed border + centered hint. */
.server-emojis-pane { position: relative; }
.server-emoji-drop-overlay {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 15, 18, 0.82);
    border: 2px dashed rgba(124, 58, 237, 0.55);
    border-radius: 0.6rem;
    pointer-events: none;
    backdrop-filter: blur(2px);
}
.server-emoji-drop-overlay[hidden] { display: none; }
.server-emoji-drop-inner {
    text-align: center;
    color: #e4e4e7;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.45rem;
}
.server-emoji-drop-inner svg { color: #c4b5fd; }
.server-emoji-drop-title {
    font-weight: 700;
    font-size: 1.05rem;
}
.server-emoji-drop-sub {
    color: #a1a1aa;
    font-size: 0.82rem;
}

/* Inline custom emojis inside rendered messages. Match Twemoji's
   inline-icon sizing so a row of mixed regular + custom emojis
   reads at a consistent height. Jumbo when the message is emoji-
   only, just like Twemoji + Discord. */
.chat-emoji-custom {
    display: inline-block;
    height: 1.4em;
    width: auto;
    max-height: 1.4em;
    vertical-align: -0.3em;
    object-fit: contain;
    user-select: none;
    -webkit-user-drag: none;
}
.chat-message-content.is-jumbomoji .chat-emoji-custom {
    height: 3rem;
    max-height: 3rem;
    width: auto;
    margin: 0.1rem 0.15rem;
    vertical-align: middle;
}
/* Pending placeholders (text chips before /api/emojis/:id resolves)
   should also bulk up so the row height doesn't pop when the real
   <img> swaps in. */
.chat-message-content.is-jumbomoji .chat-emoji-custom-pending {
    font-size: 1.5rem;
    padding: 0.15rem 0.5rem;
}
.chat-emoji-custom-missing {
    color: #94a3b8;
    background: rgba(148, 163, 184, 0.12);
    padding: 0 0.2rem;
    border-radius: 0.25rem;
    font-size: 0.85em;
    font-family: ui-monospace, "Cascadia Code", "JetBrains Mono", Menlo, monospace;
}
/* Placeholder used between renderMarkdown emitting the chip and
   /api/emojis/:id returning. Shorter-lived than .missing - nearly
   always promoted to a real <img> within ~100ms. */
.chat-emoji-custom-pending {
    color: #c4b5fd;
    background: rgba(124, 58, 237, 0.10);
    padding: 0 0.2rem;
    border-radius: 0.25rem;
    font-size: 0.85em;
    font-family: ui-monospace, "Cascadia Code", "JetBrains Mono", Menlo, monospace;
    opacity: 0.85;
}
.chat-emoji-cell-custom .chat-emoji-cell-img {
    width: 24px;
    height: 24px;
    object-fit: contain;
    pointer-events: none;
    -webkit-user-drag: none;
    user-select: none;
}
.chat-emoji-custom,
.chat-emoji-custom-mirror {
    -webkit-user-drag: none;
    user-select: none;
}
/* Mirror flavour: match Twemoji `img.emoji` sizing exactly so the
   inline IMG occupies the same horizontal slot as a unicode emoji
   character would. The textarea's underlying single PUA marker char
   renders as a ~1em "missing glyph" box, and Twemoji's IMG is
   1.2em wide with a small margin - the result is the caret sits
   right at the IMG's right edge instead of inside it (the bug the
   user hit when our custom IMG was 1.4em wide vs the marker's 1em). */
.chat-emoji-custom-mirror {
    height: 1.2em !important;
    width:  1.2em !important;
    max-height: 1.2em;
    margin: 0 0.05em;
    vertical-align: -0.25em;
    display: inline-block;
    object-fit: contain;
}

/* Custom-emoji rows inside the existing :shortcode autocomplete.
   The unicode rows have a Twemoji glyph; for custom emojis we
   replace the glyph with the inline image and hang the server name
   on the right so duplicate-name shortcodes (`:nero:` from Server
   A AND Server B) read as distinct rows. */
.chat-emoji-ac-item-custom .chat-emoji-ac-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    object-fit: contain;
    image-rendering: -webkit-optimize-contrast;
}
.chat-emoji-ac-glyph-bg {
    /* CSS background-image flavour of the glyph slot. Used for
       custom emojis instead of <img> so the browser can't initiate
       a drag of the picture (which would drop `src="/uploads/..."`
       text into the textarea on click). */
    width: 24px;
    height: 24px;
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    flex: 0 0 auto;
    pointer-events: none;
    user-select: none;
}
.chat-emoji-ac-server {
    margin-left: auto;
    padding-left: 0.6rem;
    font-size: 0.72rem;
    color: var(--c-muted, #94a3b8);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 50%;
}

.server-role-perm-section + .server-role-perm-section {
    margin-top: 0.85rem;
}
.server-role-perm-section-head {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #a1a1aa;
    padding: 0.4rem 0 0.35rem;
    border-bottom: 1px solid #27272a;
    margin-bottom: 0.25rem;
}
.server-role-editor-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* ---- Members tab ---- */
.server-members-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.server-member-row {
    display: grid;
    grid-template-columns: 2.4rem 1fr;
    gap: 0.6rem 0.7rem;
    align-items: start;
    padding: 0.55rem 0.7rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.5rem;
    min-width: 0;
}
.server-member-row > .server-member-av,
.server-member-row > .server-member-av-fb {
    align-self: center;
}
.server-member-body {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    min-width: 0;
}
.server-member-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.6rem;
    flex-wrap: wrap;
    min-width: 0;
}
.server-member-actions {
    display: flex;
    gap: 0.3rem;
    flex-shrink: 0;
    flex-wrap: wrap;
    justify-content: flex-end;
}
.server-member-actions .server-member-ban { color: #fca5a5; }
.server-member-actions .server-member-ban:hover { background: rgba(239, 68, 68, 0.15); color: #fecaca; }
.server-bans-section { margin-top: 1rem; padding-top: 0.85rem; border-top: 1px solid #27272a; }
.server-bans-head { display: flex; flex-direction: column; gap: 0.15rem; margin-bottom: 0.5rem; }
.server-audit-reason { color: #71717a; font-size: 0.78rem; }
.server-audit-msgdiff {
    margin-top: 0.4rem;
    padding: 0.5rem 0.6rem;
    background: rgba(15, 15, 18, 0.6);
    border: 1px solid #27272a;
    border-radius: 0.4rem;
    font-size: 0.78rem;
    color: #d4d4d8;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.server-audit-msgdiff-label {
    color: #71717a;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 700;
    margin-right: 0.3rem;
}
.server-audit-msgdiff-old {
    color: #fca5a5;
    text-decoration: line-through;
    text-decoration-color: rgba(239, 68, 68, 0.5);
    overflow-wrap: anywhere;
}
.server-audit-msgdiff-new {
    color: #86efac;
    overflow-wrap: anywhere;
}
.server-audit-window {
    color: #fda4af;
    font-size: 0.75rem;
    margin-left: 0.25rem;
    background: rgba(239, 68, 68, 0.1);
    padding: 0.05rem 0.35rem;
    border-radius: 0.3rem;
    border: 1px solid rgba(239, 68, 68, 0.25);
}
.server-ban-reason-rel { color: #71717a; }
.server-ban-reason-permanent .server-ban-reason-label { color: #fda4af; }

/* ---- Composer mute banner with live countdown ---- */
.chat-mute-banner {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.55rem 0.8rem;
    margin: 0 0 0.45rem;
    background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(15, 15, 18, 0.6) 100%);
    border: 1px solid rgba(245, 158, 11, 0.4);
    border-radius: 0.55rem;
    color: #fde68a;
    font-size: 0.85rem;
}
.chat-mute-banner svg { flex-shrink: 0; color: #fbbf24; }
.chat-mute-banner-text { flex: 1 1 auto; display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.chat-mute-banner-title { font-weight: 700; color: #fde68a; }
.chat-mute-banner-meta  { color: #d4d4d8; font-size: 0.78rem; overflow-wrap: anywhere; }
.chat-mute-banner-countdown {
    font-family: ui-monospace, 'Courier New', monospace;
    font-size: 0.85rem;
    font-weight: 600;
    color: #fde68a;
    background: rgba(245, 158, 11, 0.18);
    border: 1px solid rgba(245, 158, 11, 0.4);
    padding: 0.15rem 0.55rem;
    border-radius: 0.45rem;
    white-space: nowrap;
}
.chat-input.is-muted .chat-textbox,
.chat-input.is-muted .chat-textbox-stack { opacity: 0.55; }

/* Inline notice rendered above the composer when the C# chat-server
   rejects a `message.send` with a blocked_relationship error. Persistent
   for the lifetime of this chat-client instance (re-rendered on each
   channel switch via the SPA), so the user keeps seeing it after the
   transient toast fades. */
.chat-composer-blocked-banner {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.8rem;
    margin: 0 0 0.4rem;
    background: rgba(239, 68, 68, 0.1);
    border: 1px solid rgba(239, 68, 68, 0.3);
    border-radius: 0.55rem;
    color: #fca5a5;
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}
.chat-composer-blocked-banner svg { flex-shrink: 0; color: #f87171; }
.chat-composer-blocked-banner span { flex: 1 1 auto; min-width: 0; }

/* ---- Mute variant of removal modal ---- */
#server-removal-modal.is-mute .server-removal-card {
    border-color: rgba(245, 158, 11, 0.5);
    box-shadow: 0 24px 60px -20px rgba(245, 158, 11, 0.55);
}
#server-removal-modal.is-mute .server-removal-flair {
    background: linear-gradient(90deg, #fde68a 0%, #fbbf24 50%, #d97706 100%);
    box-shadow: 0 0 18px rgba(245, 158, 11, 0.55);
}
#server-removal-modal.is-mute .server-removal-icon {
    color: #fde68a;
    background: radial-gradient(circle at 30% 25%, rgba(245, 158, 11, 0.32) 0%, rgba(15, 15, 18, 0.0) 75%);
    border-color: rgba(245, 158, 11, 0.45);
    box-shadow: 0 0 18px rgba(245, 158, 11, 0.35), inset 0 0 14px rgba(245, 158, 11, 0.2);
}
#server-removal-modal.is-mute .server-removal-title { color: #fde68a; }
#server-removal-modal.is-mute .server-removal-server {
    background: linear-gradient(135deg, rgba(245, 158, 11, 0.08) 0%, rgba(15, 15, 18, 0.6) 100%);
    border-color: rgba(245, 158, 11, 0.3);
}
#server-removal-modal.is-mute .server-removal-server-meta { color: #fde68a; }
#server-removal-modal.is-mute .server-removal-reason {
    background: rgba(245, 158, 11, 0.07);
    border-color: rgba(245, 158, 11, 0.3);
}
#server-removal-modal.is-mute .server-removal-reason-label { color: #fde68a; }

/* Unmute variant: positive green tone. */
#server-removal-modal.is-unmute .server-removal-card {
    border-color: rgba(34, 197, 94, 0.45);
    box-shadow: 0 24px 60px -20px rgba(34, 197, 94, 0.5);
}
#server-removal-modal.is-unmute .server-removal-flair {
    background: linear-gradient(90deg, #86efac 0%, #22c55e 50%, #15803d 100%);
    box-shadow: 0 0 18px rgba(34, 197, 94, 0.55);
}
#server-removal-modal.is-unmute .server-removal-icon {
    color: #86efac;
    background: radial-gradient(circle at 30% 25%, rgba(34, 197, 94, 0.32) 0%, rgba(15, 15, 18, 0.0) 75%);
    border-color: rgba(34, 197, 94, 0.45);
    box-shadow: 0 0 18px rgba(34, 197, 94, 0.35), inset 0 0 14px rgba(34, 197, 94, 0.2);
}
#server-removal-modal.is-unmute .server-removal-title { color: #86efac; }
#server-removal-modal.is-unmute .server-removal-server {
    background: linear-gradient(135deg, rgba(34, 197, 94, 0.08) 0%, rgba(15, 15, 18, 0.6) 100%);
    border-color: rgba(34, 197, 94, 0.3);
}
#server-removal-modal.is-unmute .server-removal-server-meta { color: #86efac; }

/* Warn-tone context-menu item (mute action). */
.rail-ctx-item-warn { color: #fcd34d; }
.rail-ctx-item-warn:hover,
.rail-ctx-item-warn:focus-visible {
    background: rgba(245, 158, 11, 0.18);
    color: #fde68a;
}
.server-ban-reason {
    display: block;
    color: #d4d4d8;
    font-size: 0.8rem;
    margin-top: 0.25rem;
    overflow-wrap: anywhere;
}
.server-ban-reason-label {
    color: #fda4af;
    font-weight: 600;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.server-ban-reason-empty { color: #71717a; font-style: italic; }
.server-member-av {
    width: 2.4rem;
    height: 2.4rem;
    border-radius: 50%;
    object-fit: cover;
    background: #1f1f23;
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
}
.server-member-av-fb {
    background: linear-gradient(135deg, #6366f1, #a855f7);
}
.server-member-ident {
    min-width: 0;
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}
.server-member-name {
    color: #fff;
    font-weight: 600;
    display: block;
    background: transparent;
    border: 0;
    padding: 0;
    text-align: left;
    font: inherit;
    cursor: pointer;
    overflow-wrap: anywhere;
    word-break: break-word;
    max-width: 100%;
}
.server-member-name:hover,
.server-member-name:focus-visible { color: #c4b5fd; outline: none; }
.server-member-handle {
    color: #71717a;
    font-size: 0.78rem;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.server-member-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    justify-content: flex-start;
    align-items: center;
    min-width: 0;
}
.server-member-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.45rem;
    background: rgba(124,58,237,0.15);
    border: 1px solid rgba(124,58,237,0.3);
    color: #e4e4e7;
    border-radius: 999px;
    font-size: 0.78rem;
    max-width: 100%;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.server-member-chip-x {
    background: transparent;
    border: 0;
    color: #a1a1aa;
    cursor: pointer;
    line-height: 1;
    font-size: 0.95rem;
    padding: 0 0.15rem;
}
.server-member-chip-x:hover { color: #fca5a5; }
.server-member-add {
    background: #0f0f13;
    color: #d4d4d8;
    border: 1px solid #27272a;
    border-radius: 0.4rem;
    padding: 0.3rem 0.5rem;
    font: inherit;
    font-size: 0.82rem;
}
.server-members-transfer {
    margin-top: 1rem;
    padding: 0.85rem;
    background: rgba(239,68,68,0.06);
    border: 1px solid rgba(239,68,68,0.4);
    border-radius: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.server-members-transfer-row {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.35rem;
}
.server-transfer-select {
    flex: 1;
    background: #0f0f13;
    color: #d4d4d8;
    border: 1px solid #27272a;
    border-radius: 0.4rem;
    padding: 0.4rem 0.6rem;
    font: inherit;
    font-size: 0.85rem;
}

/* ---- Invites tab ---- */
.server-invites-pane {
    max-height: 60vh;
    overflow-y: auto;
}
.server-invites-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 0.85rem;
}
.server-invites-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}
.server-invite-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 0.7rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.5rem;
}
.server-invite-row-main {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    flex: 1 1 18rem;
    min-width: 0;
}
.server-invite-code {
    font-family: ui-monospace, 'Courier New', monospace;
    font-size: 0.82rem;
    color: #c4b5fd;
    background: rgba(124,58,237,0.1);
    padding: 0.2rem 0.45rem;
    border-radius: 0.35rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.server-invite-meta {
    font-size: 0.75rem;
    color: #71717a;
}
.server-invite-author {
    background: transparent;
    border: 0;
    padding: 0;
    font: inherit;
    color: #c4b5fd;
    cursor: pointer;
}
.server-invite-author:hover,
.server-invite-author:focus-visible {
    text-decoration: underline;
    color: #ddd6fe;
    outline: none;
}
.server-invite-row-actions {
    display: flex;
    gap: 0.35rem;
    flex-wrap: wrap;
}

/* ---- Create-invite form (Invites tab + My-Invites modal) ---- */
.server-invite-create-card {
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.55rem;
    padding: 0.75rem 0.85rem;
}
.server-invite-create-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.6rem;
}
.server-invite-create-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    flex: 1 1 9rem;
    min-width: 9rem;
}
.server-invite-create-field .server-modal-label {
    font-size: 0.72rem;
    color: #a1a1aa;
}
.server-invite-create-field select,
.server-invite-create-field input {
    background: #050507;
    border: 1px solid #27272a;
    border-radius: 0.4rem;
    color: #e4e4e7;
    padding: 0.45rem 0.55rem;
    font-size: 0.85rem;
    height: 2.2rem;
}
.server-invite-create-field select:focus,
.server-invite-create-field input:focus {
    border-color: #7c3aed;
    outline: none;
}
.server-invite-max-input {
    width: 100%;
}
.server-invite-create-row .btn {
    height: 2.2rem;
    align-self: flex-end;
}

/* ---- Joiners-list modal ("View joiners (N)") ---- */
.server-invite-joiners-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    max-height: 26rem;
    overflow-y: auto;
}
.server-invite-joiner-row {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.45rem 0.6rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.45rem;
}
.server-invite-joiner-name {
    background: transparent;
    border: 0;
    padding: 0;
    font: inherit;
    color: #e4e4e7;
    cursor: pointer;
    text-align: left;
    font-weight: 600;
}
.server-invite-joiner-name:hover,
.server-invite-joiner-name:focus-visible {
    color: #c4b5fd;
    outline: none;
}
.server-invite-joiner-when {
    font-size: 0.72rem;
    color: #71717a;
    margin-left: auto;
    white-space: nowrap;
}
.server-invite-joiner-line {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    flex-wrap: wrap;
}
.server-invite-rejoin-pill {
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 700;
    color: #fcd34d;
    background: rgba(245, 158, 11, 0.14);
    border: 1px solid rgba(245, 158, 11, 0.4);
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
}
.server-invite-rejoin-pill.is-first {
    color: #86efac;
    background: rgba(34, 197, 94, 0.12);
    border-color: rgba(34, 197, 94, 0.35);
}
.server-invite-code-inline {
    font-family: ui-monospace, 'Courier New', monospace;
    font-size: 0.78rem;
    color: #c4b5fd;
    background: rgba(124, 58, 237, 0.12);
    padding: 0.05rem 0.3rem;
    border-radius: 0.3rem;
}

/* ---- Member-picker modal (transfer ownership search) ---- */
.server-member-search-field input {
    background: #050507;
    border: 1px solid #27272a;
    border-radius: 0.45rem;
    color: #e4e4e7;
    padding: 0.55rem 0.7rem;
    font-size: 0.9rem;
}
.server-member-search-field input:focus {
    border-color: #7c3aed;
    outline: none;
}
.server-member-pick-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    max-height: 24rem;
    overflow-y: auto;
}
.server-member-pick-row {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 100%;
    padding: 0.55rem 0.7rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.5rem;
    color: #e4e4e7;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.12s ease, background 0.12s ease;
}
.server-member-pick-row:hover,
.server-member-pick-row:focus-visible {
    border-color: #7c3aed;
    background: rgba(124, 58, 237, 0.08);
    outline: none;
}
.server-member-pick-av {
    width: 2.2rem;
    height: 2.2rem;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid #27272a;
    background: #18181b;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    color: #e4e4e7;
}
.server-member-pick-ident {
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    min-width: 0;
}
.server-member-pick-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.server-member-pick-handle {
    font-size: 0.78rem;
    color: #a1a1aa;
}

/* ---- Audit-log tab ---- */
.server-audit-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    max-height: 28rem;
    overflow-y: auto;
}
.server-audit-row {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.5rem 0.65rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.45rem;
}
.server-audit-av {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    border: 1px solid #27272a;
    background: #18181b;
    color: #e4e4e7;
    text-align: center;
    line-height: 1.95rem;
    font-size: 0.85rem;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.server-audit-av.server-audit-av-fb { display: inline-flex; }
.server-audit-body { flex: 1 1 auto; min-width: 0; }
.server-audit-line {
    color: #d4d4d8;
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}
.server-audit-line strong { color: #e4e4e7; font-weight: 600; }
.server-audit-when {
    color: #71717a;
    font-size: 0.72rem;
    margin-top: 0.15rem;
}
.server-audit-actor {
    background: transparent;
    border: 0;
    padding: 0;
    font: inherit;
    color: #c4b5fd;
    cursor: pointer;
    font-weight: 600;
}
.server-audit-actor:hover,
.server-audit-actor:focus-visible {
    color: #ddd6fe;
    text-decoration: underline;
    outline: none;
}
.server-audit-actor-system { cursor: default; color: #71717a; }
.server-audit-code {
    font-family: ui-monospace, 'Courier New', monospace;
    font-size: 0.78rem;
    color: #c4b5fd;
    background: rgba(124, 58, 237, 0.12);
    padding: 0.05rem 0.3rem;
    border-radius: 0.3rem;
}

/* Title-line suffix on the My-Invites modal so the server name reads
   inline with the heading. */
.server-modal-sub-inline {
    font-size: 0.85rem;
    color: #a1a1aa;
    font-weight: 500;
    margin-left: 0.35rem;
}

/* ---- Send-to-friend share modal ---- */
.server-invite-share-url {
    font-family: ui-monospace, 'Courier New', monospace;
    font-size: 0.82rem;
    color: #c4b5fd;
    background: #0a0a0d;
    border: 1px solid #27272a;
    padding: 0.5rem 0.7rem;
    border-radius: 0.4rem;
    overflow-x: auto;
    white-space: nowrap;
}
.server-invite-share-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    max-height: 22rem;
    overflow-y: auto;
}
.server-invite-share-row {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    width: 100%;
    padding: 0.45rem 0.6rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.45rem;
    color: inherit;
    cursor: pointer;
    text-align: left;
    transition: background 0.12s, border-color 0.12s;
}
.server-invite-share-row:hover {
    background: rgba(124, 58, 237, 0.12);
    border-color: rgba(124, 58, 237, 0.4);
}
.server-invite-share-av {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    object-fit: cover;
    background: #1f1f23;
}
.server-invite-share-ident { display: flex; flex-direction: column; min-width: 0; }
.server-invite-share-name { color: #fff; font-weight: 600; font-size: 0.9rem; }
.server-invite-share-handle { color: #71717a; font-size: 0.78rem; }

/* ---- Chat invite embed (server card under a message bubble) ---- */
.chat-invite-embed {
    margin-top: 0.45rem;
    background: linear-gradient(180deg, #14141a, #0a0a0d);
    border: 1px solid #27272a;
    border-left: 3px solid #7c3aed;
    border-radius: 0.55rem;
    padding: 0.7rem 0.85rem;
    max-width: 26rem;
}
.chat-invite-embed.is-loading,
.chat-invite-embed.is-broken {
    color: #71717a;
    font-size: 0.85rem;
    border-left-color: #3f3f46;
}
.chat-invite-eyebrow {
    color: #a1a1aa;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-bottom: 0.4rem;
}
.chat-invite-body {
    display: grid;
    grid-template-columns: 3rem 1fr auto;
    gap: 0.7rem;
    align-items: center;
}
.chat-invite-icon {
    width: 3rem;
    height: 3rem;
    border-radius: 0.6rem;
    object-fit: cover;
    background: #1f1f23;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.chat-invite-icon-fb {
    background: linear-gradient(135deg, #6366f1, #a855f7);
    color: #fff;
    font-weight: 700;
    font-size: 1.3rem;
}
.chat-invite-text { min-width: 0; }
.chat-invite-name {
    color: #fff;
    font-weight: 700;
    font-size: 0.95rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-invite-meta {
    color: #a1a1aa;
    font-size: 0.78rem;
    margin-top: 0.1rem;
}
.chat-invite-uses {
    color: #c4b5fd;
    font-size: 0.75rem;
    margin-top: 0.15rem;
    font-weight: 500;
}
.chat-invite-uses-left {
    color: #a1a1aa;
    font-weight: 400;
}
.chat-invite-inviter {
    color: #a1a1aa;
    font-size: 0.78rem;
    margin-top: 0.15rem;
}
.chat-invite-inviter-link {
    background: transparent;
    border: 0;
    padding: 0;
    font: inherit;
    color: #c4b5fd;
    font-weight: 600;
    cursor: pointer;
}
.chat-invite-inviter-link:hover,
.chat-invite-inviter-link:focus-visible {
    text-decoration: underline;
    color: #ddd6fe;
    outline: none;
}
.chat-invite-desc {
    color: #d4d4d8;
    font-size: 0.82rem;
    margin: 0.3rem 0 0;
    line-height: 1.35;
    /* Two-line clamp keeps the card tidy on long descriptions. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.chat-invite-cta { display: flex; align-items: center; }

/* ---- /invite/:code landing page ---- */
.invite-landing {
    min-height: calc(100dvh - 6rem);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1rem;
}
.invite-card {
    width: min(28rem, calc(100vw - 1.5rem));
    background: linear-gradient(180deg, #15151b, #0a0a0d);
    border: 1px solid #27272a;
    border-radius: 1rem;
    overflow: hidden;
    box-shadow: 0 24px 60px rgba(0,0,0,0.55);
}
.invite-flair {
    height: 0.5rem;
    background: linear-gradient(90deg, #f472b6, #a78bfa, #22d3ee, #86efac, #fbbf24);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.invite-card-body {
    padding: 1.6rem;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
}
.invite-card-icon-wrap {
    width: 5.5rem;
    height: 5.5rem;
    border-radius: 1.4rem;
    overflow: hidden;
    background: #0a0a0d;
    border: 2px solid #1f1f23;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 0.4rem;
    box-shadow: 0 12px 24px rgba(0,0,0,0.5);
}
.invite-card-icon {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.invite-card-icon-fb {
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #6366f1, #a855f7);
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 2rem;
    font-weight: 700;
}
.invite-card-eyebrow {
    color: #a1a1aa;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin: 0;
}
.invite-card-title {
    color: #fff;
    font-size: 1.45rem;
    font-weight: 800;
    margin: 0;
    letter-spacing: -0.01em;
}
.invite-card-sub {
    color: #d4d4d8;
    font-size: 0.95rem;
    margin: 0;
    line-height: 1.45;
}
.invite-card-meta {
    color: #a1a1aa;
    font-size: 0.85rem;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0.5rem 0 1rem;
}
.invite-card-meta-dot {
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: #86efac;
    box-shadow: 0 0 6px rgba(134, 239, 172, 0.7);
}
.invite-card-cta {
    min-width: 12rem;
}
.invite-card-mini {
    color: #71717a;
    font-size: 0.78rem;
    margin: 0.5rem 0 0;
}
.invite-card-banned {
    margin: 1rem 0 0.5rem;
    padding: 0.85rem 0.95rem;
    background: rgba(239, 68, 68, 0.08);
    border: 1px solid rgba(239, 68, 68, 0.35);
    border-radius: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    text-align: left;
}
.invite-card-banned strong { color: #fecaca; font-size: 0.95rem; }
.reason-prompt-input {
    width: 100%;
    background: #050507;
    border: 1px solid #27272a;
    border-radius: 0.45rem;
    color: #e4e4e7;
    padding: 0.55rem 0.7rem;
    font: inherit;
    font-size: 0.9rem;
    resize: vertical;
    min-height: 3.5rem;
    margin: 0.4rem 0 0.3rem;
}
.reason-prompt-input:focus { border-color: #ef4444; outline: none; }
.reason-prompt-hint { color: #71717a; font-size: 0.72rem; margin: 0 0 0.4rem; }
.invite-card-banned-reason-label {
    color: #fda4af;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 700;
}
.invite-card-banned-reason-text { color: #e4e4e7; font-size: 0.88rem; overflow-wrap: anywhere; }

/* ---- Ownership-received celebration modal ---- */
.ownership-received-card {
    max-width: 30rem;
    overflow: hidden;
    border: 1px solid rgba(167, 139, 250, 0.3);
    box-shadow: 0 24px 60px -20px rgba(124, 58, 237, 0.55);
}
.ownership-received-flair {
    height: 5px;
    background: linear-gradient(90deg, #fbbf24 0%, #f97316 35%, #c084fc 75%, #7c3aed 100%);
    box-shadow: 0 0 18px rgba(192, 132, 252, 0.65);
}
.ownership-received-head {
    text-align: center;
    padding: 1.4rem 1.4rem 0.6rem;
}
.ownership-received-crown {
    width: 4.4rem;
    height: 4.4rem;
    margin: 0.2rem auto 0.55rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fde68a;
    background: radial-gradient(circle at 30% 25%, rgba(251, 191, 36, 0.35) 0%, rgba(124, 58, 237, 0.18) 65%, rgba(15, 15, 18, 0.0) 100%);
    border: 1px solid rgba(251, 191, 36, 0.45);
    box-shadow: 0 0 18px rgba(251, 191, 36, 0.35), inset 0 0 14px rgba(251, 191, 36, 0.15);
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));
}
.ownership-received-title {
    margin: 0.4rem 0 0.2rem;
    font-size: 1.35rem;
    font-weight: 700;
    color: #fde68a;
    letter-spacing: 0.01em;
}
.ownership-received-sub {
    margin: 0;
    color: #d4d4d8;
    font-size: 0.92rem;
}
.ownership-received-body {
    padding: 0.6rem 1.4rem 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.ownership-received-server {
    display: flex;
    gap: 0.85rem;
    align-items: flex-start;
    background: linear-gradient(135deg, rgba(124, 58, 237, 0.12) 0%, rgba(15, 15, 18, 0.6) 100%);
    border: 1px solid rgba(124, 58, 237, 0.35);
    border-radius: 0.7rem;
    padding: 0.85rem 0.9rem;
}
.ownership-received-icon {
    width: 3rem;
    height: 3rem;
    border-radius: 0.55rem;
    object-fit: cover;
    background: #18181b;
    border: 1px solid #27272a;
    flex-shrink: 0;
    color: #e4e4e7;
    text-align: center;
    line-height: 2.95rem;
    font-size: 1.2rem;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.ownership-received-icon-fb { background: linear-gradient(135deg, #7c3aed, #a855f7); color: #fff; }
.ownership-received-server-text {
    flex: 1 1 auto;
    min-width: 0;
}
.ownership-received-server-name {
    font-weight: 700;
    color: #f4f4f5;
    font-size: 1.05rem;
    overflow-wrap: anywhere;
}
.ownership-received-server-meta {
    color: #a78bfa;
    font-size: 0.78rem;
    margin-top: 0.1rem;
    font-weight: 500;
}
.ownership-received-desc {
    color: #d4d4d8;
    font-size: 0.85rem;
    margin: 0.45rem 0 0;
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.ownership-received-desc-empty { color: #71717a; font-style: italic; }
.ownership-received-from {
    display: flex;
    align-items: center;
    gap: 0.65rem;
}
.ownership-received-from-label {
    color: #a1a1aa;
    font-size: 0.78rem;
    flex-shrink: 0;
}
.ownership-received-from-card {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.55rem;
    padding: 0.5rem 0.7rem;
    color: #e4e4e7;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.12s ease, background 0.12s ease;
}
.ownership-received-from-card:hover,
.ownership-received-from-card:focus-visible {
    border-color: #7c3aed;
    background: rgba(124, 58, 237, 0.08);
    outline: none;
}
.ownership-received-from-av {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid #27272a;
    background: #18181b;
    color: #e4e4e7;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 0.85rem;
}
.ownership-received-from-text {
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    min-width: 0;
}
.ownership-received-from-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ownership-received-from-handle {
    font-size: 0.75rem;
    color: #a1a1aa;
}
.ownership-received-foot {
    color: #a1a1aa;
    font-size: 0.78rem;
    margin: 0.2rem 0 0;
    line-height: 1.4;
}
.ownership-received-actions {
    display: flex;
    justify-content: flex-end;
    padding: 0.5rem 1.4rem 1.2rem;
    border-top: 1px solid rgba(39, 39, 42, 0.6);
    margin-top: 0.4rem;
}
.ownership-received-actions .btn { min-width: 10rem; }

/* ---- Server removal (kicked / banned) modal ---- */
.server-removal-card {
    max-width: 30rem;
    overflow: hidden;
    border: 1px solid rgba(239, 68, 68, 0.3);
    box-shadow: 0 24px 60px -20px rgba(239, 68, 68, 0.45);
}
#server-removal-modal.is-ban .server-removal-card {
    border-color: rgba(239, 68, 68, 0.55);
    box-shadow: 0 24px 60px -20px rgba(239, 68, 68, 0.65);
}
.server-removal-flair {
    height: 5px;
    background: linear-gradient(90deg, #f87171 0%, #ef4444 50%, #b91c1c 100%);
    box-shadow: 0 0 18px rgba(239, 68, 68, 0.55);
}
#server-removal-modal.is-kick .server-removal-flair {
    background: linear-gradient(90deg, #fbbf24 0%, #f97316 50%, #b91c1c 100%);
    box-shadow: 0 0 18px rgba(249, 115, 22, 0.5);
}
.server-removal-head {
    text-align: center;
    padding: 1.4rem 1.4rem 0.6rem;
}
.server-removal-icon {
    width: 4.4rem;
    height: 4.4rem;
    margin: 0.2rem auto 0.55rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fecaca;
    background: radial-gradient(circle at 30% 25%, rgba(239, 68, 68, 0.32) 0%, rgba(15, 15, 18, 0.0) 75%);
    border: 1px solid rgba(239, 68, 68, 0.45);
    box-shadow: 0 0 18px rgba(239, 68, 68, 0.35), inset 0 0 14px rgba(239, 68, 68, 0.2);
    filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));
}
#server-removal-modal.is-kick .server-removal-icon {
    color: #fed7aa;
    background: radial-gradient(circle at 30% 25%, rgba(249, 115, 22, 0.32) 0%, rgba(15, 15, 18, 0.0) 75%);
    border-color: rgba(249, 115, 22, 0.45);
    box-shadow: 0 0 18px rgba(249, 115, 22, 0.35), inset 0 0 14px rgba(249, 115, 22, 0.2);
}
.server-removal-title {
    margin: 0.4rem 0 0.2rem;
    font-size: 1.35rem;
    font-weight: 700;
    color: #fecaca;
    letter-spacing: 0.01em;
}
#server-removal-modal.is-kick .server-removal-title { color: #fed7aa; }
.server-removal-sub {
    margin: 0;
    color: #d4d4d8;
    font-size: 0.92rem;
}
.server-removal-body {
    padding: 0.6rem 1.4rem 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}
.server-removal-server {
    display: flex;
    gap: 0.85rem;
    align-items: flex-start;
    background: linear-gradient(135deg, rgba(239, 68, 68, 0.08) 0%, rgba(15, 15, 18, 0.6) 100%);
    border: 1px solid rgba(239, 68, 68, 0.28);
    border-radius: 0.7rem;
    padding: 0.85rem 0.9rem;
}
#server-removal-modal.is-kick .server-removal-server {
    background: linear-gradient(135deg, rgba(249, 115, 22, 0.08) 0%, rgba(15, 15, 18, 0.6) 100%);
    border-color: rgba(249, 115, 22, 0.28);
}
.server-removal-server-icon {
    width: 3rem;
    height: 3rem;
    border-radius: 0.55rem;
    object-fit: cover;
    background: #18181b;
    border: 1px solid #27272a;
    flex-shrink: 0;
    color: #e4e4e7;
    font-size: 1.2rem;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.server-removal-server-icon-fb {
    background: linear-gradient(135deg, #b91c1c, #ef4444);
    color: #fff;
}
#server-removal-modal.is-kick .server-removal-server-icon-fb {
    background: linear-gradient(135deg, #b45309, #f97316);
}
.server-removal-server-text {
    flex: 1 1 auto;
    min-width: 0;
}
.server-removal-server-name {
    font-weight: 700;
    color: #f4f4f5;
    font-size: 1.05rem;
    overflow-wrap: anywhere;
}
.server-removal-server-meta {
    color: #fda4af;
    font-size: 0.78rem;
    margin-top: 0.1rem;
    font-weight: 500;
}
#server-removal-modal.is-kick .server-removal-server-meta { color: #fdba74; }
.server-removal-server-desc {
    color: #d4d4d8;
    font-size: 0.85rem;
    margin: 0.45rem 0 0;
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.server-removal-server-desc-empty { color: #71717a; font-style: italic; }
.server-removal-reason {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.65rem 0.8rem;
    background: rgba(239, 68, 68, 0.07);
    border: 1px solid rgba(239, 68, 68, 0.25);
    border-radius: 0.55rem;
}
.server-removal-reason-row { display: flex; flex-direction: column; gap: 0.2rem; }
.server-removal-reason-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #fda4af;
    font-weight: 700;
}
.server-removal-reason-text {
    color: #e4e4e7;
    font-size: 0.88rem;
    overflow-wrap: anywhere;
}
.server-removal-actor {
    display: flex;
    align-items: center;
    gap: 0.65rem;
}
.server-removal-actor-label {
    color: #a1a1aa;
    font-size: 0.78rem;
    flex-shrink: 0;
}
.server-removal-actor-card {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.55rem;
    padding: 0.5rem 0.7rem;
    color: #e4e4e7;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.12s ease, background 0.12s ease;
}
.server-removal-actor-card:hover,
.server-removal-actor-card:focus-visible {
    border-color: #7c3aed;
    background: rgba(124, 58, 237, 0.08);
    outline: none;
}
.server-removal-actor-av {
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid #27272a;
    background: #18181b;
    color: #e4e4e7;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 0.85rem;
}
.server-removal-actor-text {
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    min-width: 0;
}
.server-removal-actor-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.server-removal-actor-handle {
    font-size: 0.75rem;
    color: #a1a1aa;
}
.server-removal-foot {
    color: #a1a1aa;
    font-size: 0.78rem;
    margin: 0.3rem 0 0;
    line-height: 1.4;
}
.server-removal-actions {
    display: flex;
    justify-content: flex-end;
    padding: 0.5rem 1.4rem 1.2rem;
    border-top: 1px solid rgba(39, 39, 42, 0.6);
    margin-top: 0.4rem;
}
.server-removal-actions .btn { min-width: 10rem; }

/* ---- Server right-side member list ---- */
.chat-members-panel {
    border-left: 1px solid var(--border);
    background: var(--surface);
    display: flex;
    flex-direction: column;
    min-width: 0;
    overflow: hidden;
}
.chat-members-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.85rem 0.95rem 0.55rem;
    border-bottom: 1px solid var(--border);
}
.chat-members-title {
    margin: 0;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #a1a1aa;
    font-weight: 700;
}
.chat-members-count {
    font-size: 0.75rem;
    color: #71717a;
    background: #0a0a0d;
    border: 1px solid #27272a;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
}
.chat-members-list {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.5rem 0.6rem 1rem;
}
.chat-members-empty {
    color: #71717a;
    font-size: 0.85rem;
    padding: 1rem 0.5rem;
    text-align: center;
}
.chat-members-group + .chat-members-group { margin-top: 0.85rem; }
.chat-members-group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.25rem 0.4rem 0.2rem;
    color: #a1a1aa;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
}
.chat-members-group-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}
.chat-member-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.3rem 0.45rem;
    border-radius: 0.4rem;
    transition: background 0.12s ease;
    /* Skip layout/paint/animation work for off-screen rows. The browser
       caches the layout box (intrinsic-size hint keeps the scrollbar
       correct) and resumes painting once the row scrolls back in.
       Combined with role-paint's lazy data-role-name-pending promotion,
       a 500-member channel only pays FX cost for ~the visible window. */
    content-visibility: auto;
    contain-intrinsic-size: auto 36px;
}
.chat-member-row:hover { background: rgba(124, 58, 237, 0.08); }
.chat-member-row.is-viewer { background: rgba(167, 139, 250, 0.07); }
.chat-member-avwrap {
    position: relative;
    flex-shrink: 0;
}
.chat-member-av {
    width: 1.85rem;
    height: 1.85rem;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid #27272a;
    background: #18181b;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #e4e4e7;
    font-size: 0.78rem;
    font-weight: 600;
}
.chat-member-presence {
    position: absolute;
    right: -2px;
    bottom: -2px;
    width: 0.65rem;
    height: 0.65rem;
    border-radius: 50%;
    background: #52525b;
    border: 2px solid var(--surface);
    box-shadow: 0 0 0 1px rgba(0,0,0,0.4);
}
.chat-member-presence.is-online {
    background: #34d399;
    box-shadow: 0 0 6px rgba(52, 211, 153, 0.6);
}
.chat-member-name {
    background: transparent;
    border: 0;
    padding: 0;
    color: #d4d4d8;
    font: inherit;
    font-size: 0.88rem;
    font-weight: 500;
    cursor: pointer;
    flex: 1 1 auto;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-member-name:hover,
.chat-member-name:focus-visible { color: #fff; outline: none; }

/* Tablet (769-1024px): hide the right panel so the chat column gets
   the room. Members can still be reached via the settings -> Members
   tab on small screens. Below 769px the rail + sidebar become a
   drawer overlay, so we leave grid collapse to the mobile media query
   to avoid this rule (which has higher specificity than the bare
   .chat collapse) winning on phones and squeezing the chat column. */
@media (min-width: 769px) and (max-width: 1024px) {
    .chat:has(.chat-members-panel) {
        grid-template-columns: 4.5rem 16rem 1fr;
    }
}
@media (max-width: 1024px) {
    .chat-members-panel { display: none; }
}

/* -------------------------------------------------------------------
   Discord-style two-pane role-permission editor (channels-admin.js).
   Left rail = role list; right pane = full-width permission rows
   (label + hint + tri-state pill). Stacks on narrow screens.
   ------------------------------------------------------------------- */
.channel-perms-card { width: min(820px, 94vw); max-width: 820px; }
.channel-perms-body {
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
    max-height: 70vh;
    /* No overflow:hidden on the body itself - the pane below owns
       its own scroll. Keeping overflow visible here lets the pane
       use a real flexbox-sized region (min-height:0) so the inner
       detail panel can scroll consistently for both channel and
       category modals. */
    min-height: 0;
    padding: 0.25rem 0;
}
.channel-perms-loading {
    color: var(--c-muted, #94a3b8);
    font-size: 0.85rem;
    padding: 1rem 0;
    text-align: center;
}

.channel-perms-pane {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 0;
    border-radius: 0.55rem;
    border: 1px solid rgba(255, 255, 255, 0.06);
    background: rgba(0, 0, 0, 0.18);
    /* Fill remaining body space and clip; children scroll on their
       own (roles list + detail panel). Without flex:1 + min-height:0
       a shorter category-edit body wasn't constraining the pane and
       the detail panel had no upper bound to scroll against. */
    flex: 1 1 auto;
    min-height: 320px;
    max-height: 60vh;
    overflow: hidden;
}
.channel-perms-roles {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.4rem 0.35rem;
    overflow-y: auto;
    border-right: 1px solid rgba(255, 255, 255, 0.06);
    background: rgba(0, 0, 0, 0.18);
}
.channel-perms-role {
    appearance: none;
    border: none;
    background: transparent;
    color: var(--c-text, #e2e8f0);
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.45rem 0.55rem;
    border-radius: 0.4rem;
    cursor: pointer;
    text-align: left;
    font-size: 0.85rem;
    width: 100%;
    min-height: 36px;
    transition: background 0.1s;
}
.channel-perms-role:hover {
    background: rgba(255, 255, 255, 0.04);
}
.channel-perms-role.is-active {
    background: rgba(99, 102, 241, 0.22);
}
.channel-perms-rolecolor {
    width: 0.65rem;
    height: 0.65rem;
    border-radius: 50%;
    flex: 0 0 auto;
}
.channel-perms-rolecolor-empty {
    background: rgba(255, 255, 255, 0.22);
}
.channel-perms-rolename {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.channel-perms-role-badge {
    flex: 0 0 auto;
    background: rgba(99, 102, 241, 0.45);
    color: #e0e7ff;
    font-size: 0.7rem;
    font-weight: 700;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 0.4rem;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.channel-perms-detail {
    overflow-y: auto;
    /* Generous side padding so the label/hint text doesn't kiss the
       divider on the left. */
    padding: 0.75rem 1.25rem 0.75rem 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    min-height: 0;
}
.channel-perms-roles {
    min-height: 0;
}
.channel-perms-detail-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.2rem 0 0.6rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    margin-bottom: 0.4rem;
    font-weight: 600;
}
.channel-perms-detail-rolename {
    font-size: 1rem;
}
.channel-perms-empty {
    color: var(--c-muted, #94a3b8);
    font-size: 0.85rem;
    padding: 1rem 0;
    text-align: center;
}
.channel-perms-permlist {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.channel-perms-section + .channel-perms-section {
    margin-top: 0.85rem;
}
.channel-perms-section-head {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--c-muted, #94a3b8);
    padding: 0.4rem 0 0.35rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    margin-bottom: 0.25rem;
}
.channel-perms-permrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6rem 0.25rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.channel-perms-permrow:last-child { border-bottom: none; }
.channel-perms-permmeta {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}
.channel-perms-permlabel {
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--c-text, #e2e8f0);
}
.channel-perms-permhint {
    font-size: 0.78rem;
    color: var(--c-muted, #94a3b8);
    line-height: 1.35;
}

.channel-perms-pillgroup {
    display: inline-flex;
    background: rgba(0, 0, 0, 0.35);
    border-radius: 999px;
    padding: 0.18rem;
    gap: 0.1rem;
    border: 1px solid rgba(255, 255, 255, 0.06);
    flex: 0 0 auto;
}
.channel-perms-pill {
    appearance: none;
    border: none;
    background: transparent;
    color: var(--c-muted, #94a3b8);
    cursor: pointer;
    width: 32px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    transition: background 0.1s, color 0.1s;
}
.channel-perms-pill:hover { color: #e2e8f0; }
.channel-perms-pill.is-active.channel-perms-pill-inherit {
    background: rgba(148, 163, 184, 0.30);
    color: #e2e8f0;
}
.channel-perms-pill.is-active.channel-perms-pill-allow {
    background: rgba(34, 197, 94, 0.30);
    color: #86efac;
}
.channel-perms-pill.is-active.channel-perms-pill-deny {
    background: rgba(239, 68, 68, 0.30);
    color: #fca5a5;
}

@media (max-width: 720px) {
    .channel-perms-pane {
        grid-template-columns: 1fr;
        max-height: 60vh;
    }
    .channel-perms-roles {
        flex-direction: row;
        flex-wrap: wrap;
        max-height: 110px;
        border-right: none;
        border-bottom: 1px solid rgba(255, 255, 255, 0.06);
    }
    .channel-perms-role { width: auto; min-height: 36px; }
    .channel-perms-permrow {
        flex-direction: column;
        align-items: stretch;
        gap: 0.45rem;
    }
    .channel-perms-pill { width: 36px; height: 32px; }
}

/* "Edit channel" modal section divider (above the role permission grid). */
.channel-edit-section {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    margin-top: 0.5rem;
    padding-top: 0.65rem;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.channel-edit-section-head {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--c-muted, #94a3b8);
    font-weight: 600;
}
.channel-edit-section-hint {
    font-size: 0.78rem;
    color: var(--c-muted, #94a3b8);
}

/* =========================================================
   Screen-share UI (call.js).

   - .call-bar-share / .call-bar-share-audio: extra toggle buttons in the
     in-call bar, sharing the .call-bar-toggle base styles. .is-sharing
     swaps the share button to a "live" red look so the user immediately
     sees they're broadcasting.
   - .chat-screenshare-panel: a strip mounted into .chat-main above the
     message list, holding one tile per active sharer (self + peers).
     Mobile-first: at <= 768 px the panel becomes full-width and tiles
     stack vertically; at desktop they sit side-by-side. */
.call-bar-toggle.call-bar-share.is-sharing {
    background: #dc2626;
    color: #fff;
}
.call-bar-toggle.call-bar-share.is-sharing:hover { background: #b91c1c; }

.chat-screenshare-panel {
    display: flex;
    flex-direction: column;
    background: rgba(15, 118, 110, 0.08);
    box-sizing: border-box;
    overflow: hidden;
}
.chat-screenshare-panel[hidden] { display: none; }

/* Docked: lives as a flex sibling above #chat-messages inside .chat-main.
   Takes ~50% of the column height (capped to 60vh on tall screens) so the
   message list keeps a usable bottom half. */
.chat-screenshare-panel[data-layout="docked"] {
    flex: 0 0 auto;
    height: min(50%, 60vh);
    max-height: min(50%, 60vh);
    border-bottom: 1px solid var(--border);
    border-radius: 0;
}

/* Popout: pinned to the bottom-right of the viewport as a small floating
   tile so the chat regains full height. Above the call-bar's z-index but
   below modals + the SPA drawer. */
.chat-screenshare-panel[data-layout="popout"] {
    position: fixed;
    right: max(1rem, env(safe-area-inset-right, 0px));
    bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    width: 22rem;
    max-width: calc(100vw - 2rem);
    height: 14rem;
    max-height: 50dvh;
    z-index: 950;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
    background: #0d0d0d;
}

.chat-screenshare-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.35rem 0.6rem;
    background: rgba(0, 0, 0, 0.35);
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
    flex: 0 0 auto;
}
.chat-screenshare-head-title {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-muted);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-screenshare-layout {
    appearance: none;
    border: 0;
    background: rgba(255, 255, 255, 0.08);
    color: #fff;
    border-radius: 0.4rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    flex-shrink: 0;
    transition: background 0.12s;
}
.chat-screenshare-layout:hover { background: rgba(255, 255, 255, 0.18); }

.chat-screenshare-tiles {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.55rem 0.75rem;
    overflow: hidden;
    box-sizing: border-box;
}

.chat-screenshare-tile {
    position: relative;
    flex: 1 1 22rem;
    min-width: 0;
    min-height: 0;
    max-width: 100%;
    background: #000;
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 0.55rem;
    overflow: hidden;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
}

/* Docked single tile fills the panel; with 2+ tiles the wrap kicks in
   and they share rows. The aspect-ratio fallback only applies when the
   panel is in popout mode where height is fixed. */
.chat-screenshare-panel[data-layout="docked"] .chat-screenshare-tile {
    height: 100%;
}
.chat-screenshare-panel[data-layout="popout"] .chat-screenshare-tile {
    height: 100%;
    flex: 1 1 100%;
}

.chat-screenshare-video {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
    background: #000;
    cursor: pointer;
}
.chat-screenshare-overlay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.55rem;
    padding-bottom: calc(0.4rem + env(safe-area-inset-bottom, 0px));
    background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.65) 100%);
    color: #fff;
    pointer-events: none;
}
.chat-screenshare-overlay > * { pointer-events: auto; }
.chat-screenshare-name {
    font-size: 0.85rem;
    font-weight: 600;
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}
.chat-screenshare-mute,
.chat-screenshare-fs,
.chat-screenshare-stop {
    appearance: none;
    border: 0;
    color: #fff;
    background: rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s, color 0.12s;
    flex-shrink: 0;
    min-width: 2.5rem;
    height: 2.5rem;
    padding: 0 0.6rem;
    font: inherit;
    font-size: 0.78rem;
    font-weight: 600;
}
.chat-screenshare-mute:hover,
.chat-screenshare-fs:hover { background: rgba(255, 255, 255, 0.3); }
.chat-screenshare-mute.is-muted {
    background: #dc2626;
    color: #fff;
}
.chat-screenshare-mute.is-muted:hover { background: #b91c1c; }
.chat-screenshare-stop {
    background: #dc2626;
}
.chat-screenshare-stop:hover { background: #b91c1c; }

/* Popout shrinks the overlay buttons so they fit the smaller tile. */
.chat-screenshare-panel[data-layout="popout"] .chat-screenshare-mute,
.chat-screenshare-panel[data-layout="popout"] .chat-screenshare-fs,
.chat-screenshare-panel[data-layout="popout"] .chat-screenshare-stop {
    min-width: 2rem;
    height: 2rem;
    padding: 0 0.45rem;
    font-size: 0.7rem;
}
.chat-screenshare-panel[data-layout="popout"] .chat-screenshare-name {
    font-size: 0.75rem;
}

@media (max-width: 768px) {
    .chat-screenshare-panel[data-layout="docked"] {
        height: 45dvh;
        max-height: 45dvh;
        flex: 0 0 45dvh;
    }
    .chat-screenshare-panel[data-layout="popout"] {
        width: 14rem;
        height: 10rem;
        right: max(0.6rem, env(safe-area-inset-right, 0px));
        bottom: calc(0.6rem + env(safe-area-inset-bottom, 0px));
    }
    .chat-screenshare-tiles {
        padding: 0.45rem 0.5rem;
        gap: 0.45rem;
    }
    .chat-screenshare-tile {
        flex: 1 1 100%;
        max-width: 100%;
    }
    .chat-screenshare-mute,
    .chat-screenshare-fs,
    .chat-screenshare-stop {
        min-width: 2.75rem;
        height: 2.75rem;
    }
    .chat-screenshare-panel[data-layout="popout"] .chat-screenshare-mute,
    .chat-screenshare-panel[data-layout="popout"] .chat-screenshare-fs,
    .chat-screenshare-panel[data-layout="popout"] .chat-screenshare-stop {
        min-width: 2.25rem;
        height: 2.25rem;
    }
}

/* When a video element is in the browser fullscreen API, take the whole
   screen with black bars so the user can actually watch the share. */
.chat-screenshare-video:fullscreen,
.chat-screenshare-video:-webkit-full-screen {
    width: 100vw;
    height: 100dvh;
    object-fit: contain;
    background: #000;
}

/* ================================================================
   Forwarded-message card.

   Rendered inside the message body when the DTO carries
   `forwardedFrom`. Layout:
     [arrow] Forwarded   [#channel - server]   ........  2:14 PM
     +---------------------------------------------+
     | snapshot body                               |
     | [attachments grid]                          |
     | [embed cards]                               |
     +---------------------------------------------+
   The original author's identity is intentionally not rendered
   anywhere - we mirror Discord's privacy parity.
   ================================================================ */
.chat-message-forward-card {
    margin-top: 0.4rem;
    padding: 0.55rem 0.7rem 0.55rem 0.8rem;
    background: rgba(124, 58, 237, 0.045);
    border-left: 3px solid rgba(124, 58, 237, 0.55);
    border-radius: 0 0.5rem 0.5rem 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    /* Clamp width so a wide attachment grid never overflows the
       message bubble on narrow phones. */
    max-width: 100%;
    min-width: 0;
    overflow-wrap: anywhere;
}
.chat-forward-head {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    flex-wrap: wrap;
    color: var(--text-muted);
    font-size: 0.78rem;
    line-height: 1.2;
    min-width: 0;
}
.chat-forward-arrow {
    display: inline-flex;
    align-items: center;
    color: #c4b5fd;
    flex: 0 0 auto;
}
.chat-forward-label {
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #c4b5fd;
    font-size: 0.7rem;
    flex: 0 0 auto;
}
.chat-forward-source-wrap {
    display: inline-flex;
    align-items: center;
    min-width: 0;
    /* Allow the chip to shrink within the header without forcing the
       row to wrap mid-chip on narrow widths. */
    flex: 0 1 auto;
    max-width: 100%;
}
.chat-forward-source-wrap .msg-link-chip {
    /* Slightly smaller chip than the inline message-body version so
       it nests cleanly inside the header strip. */
    font-size: 0.78rem;
}
.chat-forward-time {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    flex: 0 0 auto;
    white-space: nowrap;
}
.chat-forward-body {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}
.chat-forward-content {
    color: var(--text);
    font-size: 0.92rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    word-break: break-word;
    /* Empty content (media-only forward) collapses cleanly. */
}
.chat-forward-content:empty {
    display: none;
}
/* Defensive empty-state inside a forwarded card. Rendered only when
   the forward has no comment, no source content, and no source
   media on either side - keeps the row from looking broken. */
.chat-forward-empty {
    color: var(--text-muted);
    font-size: 0.85rem;
    font-style: italic;
    opacity: 0.85;
}
/* Inner attachments + embeds stay full-width relative to the
   forwarded card. The base `.chat-message-attachments` rules already
   cap to the bubble width on mobile, so we don't need overrides
   beyond ensuring nothing forces an explicit min-width. */
.chat-message-forward-card .chat-message-attachments,
.chat-message-forward-card .chat-message-embeds {
    margin: 0;
    max-width: 100%;
    min-width: 0;
}
@media (max-width: 560px) {
    .chat-message-forward-card {
        padding: 0.5rem 0.55rem 0.5rem 0.65rem;
    }
    .chat-forward-content { font-size: 0.88rem; }
    .chat-forward-time    { font-size: 0.68rem; }
    .chat-forward-label   { font-size: 0.66rem; }
}
@media (max-width: 420px) {
    .chat-message-forward-card { border-radius: 0 0.4rem 0.4rem 0; }
    .chat-forward-head { gap: 0.35rem; }
    .chat-forward-time {
        /* On the narrowest phones the chip + time can overflow if
           the chip text is long. Drop the timestamp to its own line
           by removing the auto margin so flex-wrap takes over. */
        margin-left: 0;
        flex-basis: 100%;
        text-align: right;
    }
}

/* ================================================================
   Forward-To dialog (Discord-style picker).

   Multi-select destination list with chips above + optional comment
   below. Reuses .modal / .modal-card-sm infrastructure for backdrop
   + animation. Submit button uses the brand purple gradient (matches
   the `forwarded` accent so the two surfaces feel related).
   ================================================================ */
.forward-modal-card {
    position: relative;
    width: min(32rem, calc(100vw - 2rem));
    display: flex;
    flex-direction: column;
    max-height: min(40rem, calc(100dvh - 2rem));
    overflow: hidden;
    border-radius: 0.85rem;
    box-shadow:
        0 24px 70px rgba(0, 0, 0, 0.55),
        0 0 0 1px rgba(255, 255, 255, 0.02) inset;
    animation: forward-modal-pop 0.18s cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes forward-modal-pop {
    from { opacity: 0; transform: translateY(8px) scale(0.96); }
    to   { opacity: 1; transform: none; }
}
.forward-modal-head {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.4rem;
    padding: 1.4rem 1.25rem 1rem;
    border-bottom: 1px solid var(--border);
    background:
        radial-gradient(circle at 50% 0%, rgba(124, 58, 237, 0.12), transparent 60%),
        radial-gradient(circle at 50% 0%, rgba(59, 130, 246, 0.06), transparent 70%);
}
.forward-modal-icon {
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #c4b5fd;
    background: linear-gradient(135deg, rgba(124, 58, 237, 0.20), rgba(59, 130, 246, 0.20));
    border: 1px solid rgba(124, 58, 237, 0.45);
    box-shadow:
        0 0 0 6px rgba(124, 58, 237, 0.06),
        0 0 24px rgba(124, 58, 237, 0.20);
    margin-bottom: 0.15rem;
}
.forward-modal-title {
    margin: 0;
    font-size: 1.2rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
}
.forward-modal-subtitle {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
}
.forward-modal-preview {
    margin-top: 0.5rem;
    width: 100%;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.5rem 0.65rem;
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.45;
    text-align: left;
    max-height: 4rem;
    overflow: hidden;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-wrap: anywhere;
}
.forward-modal-close {
    position: absolute;
    top: 0.55rem;
    right: 0.65rem;
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.5rem;
    border-radius: 0.35rem;
    min-width: 2.25rem;
    min-height: 2.25rem;
    z-index: 2;
}
.forward-modal-close:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.05);
}
.forward-modal-body {
    padding: 0.85rem 1rem 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-height: 0;
    flex: 1 1 auto;
    overflow-y: auto;
}
.forward-modal-search-row {
    position: relative;
    display: flex;
    align-items: center;
}
.forward-modal-search-icon {
    position: absolute;
    left: 0.65rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-muted);
    pointer-events: none;
}
.forward-modal-search {
    appearance: none;
    width: 100%;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--text);
    padding: 0.55rem 0.75rem 0.55rem 2rem;
    font: inherit;
    font-size: 0.92rem;
    line-height: 1.4;
    box-sizing: border-box;
    min-height: 2.5rem;
}
.forward-modal-search:focus,
.forward-modal-search:focus-visible {
    outline: none;
    border-color: var(--brand);
    box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.25);
}
.forward-modal-search::placeholder { color: #5a5a5a; }
.forward-modal-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding: 0.4rem 0.5rem;
    background: rgba(124, 58, 237, 0.05);
    border: 1px solid rgba(124, 58, 237, 0.25);
    border-radius: 0.5rem;
}
.forward-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.45rem 0.2rem 0.55rem;
    background: rgba(124, 58, 237, 0.18);
    color: #ddd6fe;
    border: 1px solid rgba(124, 58, 237, 0.5);
    border-radius: 999px;
    font-size: 0.78rem;
    line-height: 1.2;
    max-width: 100%;
    min-width: 0;
}
.forward-chip-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 12rem;
}
.forward-chip-remove {
    appearance: none;
    background: transparent;
    border: 0;
    color: #c4b5fd;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    width: 1.1rem;
    height: 1.1rem;
    min-width: 1.1rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
}
.forward-chip-remove:hover {
    background: rgba(124, 58, 237, 0.25);
    color: #fff;
}
.forward-modal-list {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    background: #0d0d0e;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    overflow-y: auto;
    /* Responsive: list scrolls inside; outer card already capped to
       40rem max-height so the comment + footer stay visible. */
    max-height: 16rem;
    min-height: 6rem;
    padding: 0.25rem;
}
.forward-modal-empty {
    padding: 1.1rem 0.85rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    text-align: center;
}
/* Loading + load-error fallbacks shown inside the destinations
   list while the /api/chat/forward-destinations request is in
   flight or has just failed. Both fit at 360px. */
.forward-modal-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.55rem;
    padding: 1.4rem 0.85rem;
    color: var(--text-muted);
    font-size: 0.9rem;
}
.forward-modal-spinner {
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    border: 2px solid rgba(124, 58, 237, 0.30);
    border-top-color: rgba(124, 58, 237, 0.95);
    animation: forward-modal-spin 0.85s linear infinite;
    flex: 0 0 auto;
}
@keyframes forward-modal-spin {
    to { transform: rotate(360deg); }
}
.forward-modal-loading-text { min-width: 0; }
.forward-modal-load-error {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.65rem;
    padding: 1.1rem 0.85rem;
    text-align: center;
}
.forward-modal-load-error-msg {
    color: var(--text-muted);
    font-size: 0.88rem;
}
.forward-modal-retry { min-height: 2.4rem; padding: 0.4rem 1rem; }
/* Server-icon avatar variant for cross-server public channels.
   Mirrors the DM avatar mask (round image fill) but keeps a soft
   ring so the icon reads against the row background. */
.forward-row-avatar-server {
    border-radius: 0.45rem;
    background: #1a1a1a;
    border: 1px solid rgba(124, 58, 237, 0.30);
    overflow: hidden;
}
.forward-row-avatar-server img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.forward-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.55rem;
    border-radius: 0.4rem;
    cursor: pointer;
    user-select: none;
    /* >= 40 px tap target on mobile. */
    min-height: 2.5rem;
    transition: background-color 0.12s;
}
.forward-row:hover { background: rgba(255, 255, 255, 0.03); }
.forward-row.is-selected {
    background: rgba(124, 58, 237, 0.10);
}
.forward-row.is-selected:hover {
    background: rgba(124, 58, 237, 0.16);
}
.forward-row.is-disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
.forward-row.is-disabled:hover { background: transparent; }
.forward-row-check {
    flex: 0 0 1.15rem;
    width: 1.15rem;
    height: 1.15rem;
    border: 1.5px solid #3a3a3a;
    border-radius: 0.3rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    background: transparent;
    transition: background-color 0.12s, border-color 0.12s;
}
.forward-row.is-selected .forward-row-check {
    background: var(--brand);
    border-color: var(--brand);
}
.forward-row-avatar {
    flex: 0 0 1.85rem;
    width: 1.85rem;
    height: 1.85rem;
    border-radius: 50%;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #1a1a1a;
    color: var(--text-muted);
    font-size: 0.78rem;
    font-weight: 600;
}
.forward-row-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.forward-row-avatar-fb {
    background: linear-gradient(135deg, #2a2a2a, #1a1a1a);
    color: #cfcfcf;
}
.forward-row-avatar-group {
    border-radius: 0.45rem;
    background: linear-gradient(135deg, rgba(244, 114, 182, 0.20), rgba(124, 58, 237, 0.20));
    color: #f9a8d4;
    border: 1px solid rgba(244, 114, 182, 0.35);
}
.forward-row-avatar-channel {
    border-radius: 0.45rem;
    background: rgba(124, 58, 237, 0.10);
    color: #c4b5fd;
    border: 1px solid rgba(124, 58, 237, 0.30);
}
.forward-row-ident {
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    min-width: 0;
    flex: 1 1 auto;
}
.forward-row-name {
    color: var(--text);
    font-size: 0.92rem;
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.forward-row-sub {
    color: var(--text-muted);
    font-size: 0.76rem;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.forward-modal-limit-note {
    color: #fbbf24;
    font-size: 0.78rem;
    padding: 0.2rem 0.1rem;
}
.forward-modal-label {
    font-size: 0.78rem;
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-top: 0.15rem;
}
.forward-modal-comment {
    appearance: none;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--text);
    padding: 0.55rem 0.7rem;
    font: inherit;
    font-size: 0.92rem;
    line-height: 1.5;
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
    min-height: 4rem;
    max-height: 12rem;
    transition: border-color 0.15s, box-shadow 0.15s;
}
.forward-modal-comment:focus,
.forward-modal-comment:focus-visible {
    outline: none;
    border-color: var(--brand);
    box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.25);
}
.forward-modal-comment::placeholder { color: #5a5a5a; }
.forward-modal-foot-row {
    display: flex;
    justify-content: flex-end;
    margin-top: -0.25rem;
}
.forward-modal-counter {
    font-size: 0.78rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.forward-modal-error {
    padding: 0.5rem 0.7rem;
    background: rgba(244, 63, 94, 0.08);
    border: 1px solid rgba(244, 63, 94, 0.35);
    color: #fecaca;
    border-radius: 0.5rem;
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}
.forward-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.55rem;
    padding: 0.75rem 1rem 0.95rem;
    border-top: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.015);
}
.forward-modal-actions .btn { min-height: 2.4rem; }
.forward-modal-submit {
    color: #fff;
    border: 1px solid rgba(124, 58, 237, 0.55);
    background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 50%, #4f46e5 100%);
    box-shadow: 0 4px 14px rgba(124, 58, 237, 0.30);
    font-weight: 600;
}
.forward-modal-submit:hover:not(:disabled) {
    filter: brightness(1.06);
    box-shadow: 0 6px 18px rgba(124, 58, 237, 0.38);
}
.forward-modal-submit:active:not(:disabled) { filter: brightness(0.96); }
.forward-modal-submit:disabled {
    background: #2a2a2a;
    border-color: var(--border);
    color: #6b6b6b;
    box-shadow: none;
    cursor: not-allowed;
}
@media (max-width: 420px) {
    .forward-modal-card {
        width: calc(100vw - 1rem);
        max-height: calc(100dvh - 1rem);
        border-radius: 0.7rem;
    }
    .forward-modal-head { padding: 1.05rem 1rem 0.85rem; }
    .forward-modal-icon { width: 2.6rem; height: 2.6rem; }
    .forward-modal-icon svg { width: 22px; height: 22px; }
    .forward-modal-title    { font-size: 1.05rem; }
    .forward-modal-subtitle { font-size: 0.8rem; }
    .forward-modal-body     { padding: 0.7rem 0.8rem 0.45rem; gap: 0.45rem; }
    .forward-modal-list     { max-height: 14rem; }
    .forward-row            { min-height: 2.65rem; }
    .forward-modal-actions  { flex-direction: column-reverse; padding: 0.7rem 0.85rem 0.85rem; }
    .forward-modal-actions .btn { width: 100%; }
}

/* ============================================================
   Marketplace product editor
   ------------------------------------------------------------
   Mounts into <div id="marketplace-editor-root"> from
   templates/marketplace-edit.phtml. Driven by
   /assets/js/marketplace-editor.js. Mobile-first: stacks to one
   column under 900 px, sticky action bar on phones so Save is
   always reachable, gallery uses auto-fill grid that re-flows
   from 2 cols (360 px) up to 5+ cols on desktop.
   ============================================================ */
.mp-editor {
    display: block;
    color: var(--text);
}

.mp-editor-heading {
    margin: 0 0 1.25rem;
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.mp-editor-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.25rem;
    align-items: start;
}
@media (min-width: 900px) {
    .mp-editor-grid {
        grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
        gap: 1.5rem;
    }
}

.mp-editor-col {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: 0; /* lets long URLs / images shrink in CSS grid cells */
}

/* ---- Generic field block ---- */
.mp-field {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    position: relative;
}
.mp-field-label {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text);
    letter-spacing: 0.01em;
}
.mp-field-counter {
    align-self: flex-end;
    font-size: 0.75rem;
    color: var(--text-muted);
}
.mp-field-error {
    margin: 0.35rem 0 0;
    color: #fca5a5;
    font-size: 0.82rem;
    background: rgba(244, 63, 94, 0.08);
    border: 1px solid rgba(244, 63, 94, 0.35);
    border-radius: 0.35rem;
    padding: 0.4rem 0.6rem;
}
.mp-field.is-invalid .mp-input,
[data-field].is-invalid .mp-input,
[data-field].is-invalid .mp-select-btn,
[data-field].is-invalid .mp-desc-textarea {
    border-color: rgba(244, 63, 94, 0.65);
}

/* ---- Inputs ---- */
.mp-input {
    width: 100%;
    padding: 0.65rem 0.85rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    line-height: 1.4;
    transition: border-color 0.12s, background 0.12s;
    min-height: 2.6rem;
}
.mp-input:focus {
    outline: none;
    border-color: var(--brand);
    background: #131313;
}
.mp-input::placeholder { color: #5d5d63; }
.mp-input-title {
    font-size: 1rem;
    font-weight: 500;
}

/* ---- Custom category select ---- */
.mp-select-btn {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    width: 100%;
    min-height: 2.6rem;
    padding: 0.55rem 0.75rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.12s, background 0.12s;
}
.mp-select-btn:hover  { border-color: #3a3a3a; }
.mp-select-btn:focus  { outline: none; border-color: var(--brand); background: #131313; }
.mp-select-btn[aria-expanded="true"] { border-color: var(--brand); background: #131313; }

.mp-select-btn-inner {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mp-select-icon { color: var(--text); flex-shrink: 0; }
.mp-select-icon-empty { color: var(--text-muted); }
.mp-select-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mp-select-name-empty { color: var(--text-muted); }
.mp-select-chevron { color: var(--text-muted); flex-shrink: 0; }

.mp-select-popup {
    position: fixed;
    z-index: 200;
    background: #18181b;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
    padding: 0.35rem;
    max-height: min(60vh, 420px);
    overflow-y: auto;
    min-width: 220px;
}
.mp-select-option {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    min-height: 2.4rem;
    padding: 0.45rem 0.6rem;
    background: transparent;
    border: 0;
    color: var(--text);
    font: inherit;
    text-align: left;
    border-radius: 0.35rem;
    cursor: pointer;
}
.mp-select-option:hover,
.mp-select-option:focus { background: rgba(124, 58, 237, 0.16); outline: none; }
.mp-select-option.is-selected { background: rgba(124, 58, 237, 0.22); }

/* ---- Description editor ---- */
.mp-desc-field {
    gap: 0.5rem;
}
.mp-desc-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    padding: 0.35rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-bottom: 0;
    border-radius: 0.4rem 0.4rem 0 0;
}
.mp-desc-tool {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.2rem;
    height: 2.2rem;
    min-height: 40px;
    min-width:  40px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.3rem;
    color: var(--text-muted);
    cursor: pointer;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.mp-desc-tool:hover  { color: var(--text); background: #1c1c20; border-color: var(--border); }
.mp-desc-tool:active { color: var(--text); background: rgba(124, 58, 237, 0.18); border-color: var(--brand); }
.mp-desc-tool-icon { display: block; }

/* Generic markdown toolbar (window.mountMarkdownToolbar in
   markdown-toolbar.js). Used by the staff announcements composer
   and any future textarea that wants the same formatting buttons. */
.md-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    margin-bottom: 0.4rem;
    padding: 0.35rem;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border);
    border-radius: 0.4rem;
}
.md-toolbar-btn {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.3rem;
    padding: 0.35rem 0.45rem;
    color: var(--text-muted);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 32px;
    min-height: 32px;
    transition: color 0.12s, background 0.12s, border-color 0.12s;
}
.md-toolbar-btn:hover,
.md-toolbar-btn:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
    outline: none;
    border-color: var(--border);
}
.md-toolbar-btn:active {
    background: rgba(124, 58, 237, 0.18);
    border-color: var(--brand);
    color: var(--text);
}
.md-toolbar-icon { display: block; }
@media (max-width: 560px) {
    .md-toolbar-btn { min-width: 36px; min-height: 36px; }
}

/* Floating selection popover variant of the markdown toolbar
   (mountMarkdownToolbar mode 'floating' / 'both'). Anchored above the
   text selection caret in viewport coordinates by markdown-toolbar.js;
   `left` / `top` are written inline. The translate keeps the popover
   centred horizontally and lifted above the caret line by 8px. The
   `.is-below` modifier flips it below when the caret is in the top
   60px of the viewport. Uses position:fixed because we measure caret
   coords in viewport space. */
.md-toolbar-floating {
    position: fixed;
    z-index: 1000;
    display: none;
    gap: 0.25rem;
    padding: 0.4rem;
    margin: 0;
    background: #2a2a30;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
    transform: translate(-50%, -100%) translateY(-8px);
    pointer-events: auto;
    flex-wrap: nowrap;
    max-width: min(420px, calc(100vw - 16px));
}
.md-toolbar-floating.is-visible { display: inline-flex; }
.md-toolbar-floating::after {
    content: '';
    position: absolute;
    bottom: -6px;
    left: 50%;
    transform: translateX(-50%);
    width: 0;
    height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-top: 6px solid var(--border);
}
.md-toolbar-floating.is-below {
    transform: translate(-50%, 0) translateY(8px);
}
.md-toolbar-floating.is-below::after {
    bottom: auto;
    top: -6px;
    border-top: none;
    border-bottom: 6px solid var(--border);
}
@media (max-width: 560px) {
    .md-toolbar-floating { max-width: calc(100vw - 16px); }
    .md-toolbar-floating .md-toolbar-btn { min-width: 36px; min-height: 36px; }
}

.mp-desc-textarea {
    width: 100%;
    min-height: 12rem;
    padding: 0.75rem 0.9rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0 0 0.4rem 0.4rem;
    color: var(--text);
    font: inherit;
    line-height: 1.55;
    resize: vertical;
    overflow-wrap: anywhere;
}
.mp-desc-textarea:focus { outline: none; border-color: var(--brand); }

.mp-desc-counter {
    align-self: flex-end;
    font-size: 0.75rem;
    color: var(--text-muted);
}
.mp-desc-counter.is-over { color: #fca5a5; font-weight: 600; }

.mp-desc-preview-wrap {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    background: #0c0c0c;
    padding: 0.5rem 0.75rem 0.75rem;
}
.mp-desc-preview-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    align-self: flex-start;
    padding: 0.3rem 0.5rem;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    cursor: pointer;
    border-radius: 0.3rem;
    min-height: 40px;
}
.mp-desc-preview-toggle:hover { color: var(--text); }
.mp-desc-preview {
    color: var(--text);
    font-size: 0.95rem;
    line-height: 1.55;
    overflow-wrap: anywhere;
}
.mp-desc-preview-empty {
    margin: 0;
    color: var(--text-muted);
    font-style: italic;
    font-size: 0.85rem;
}
.mp-desc-preview-wrap.is-collapsed .mp-desc-preview { display: none; }

/* Markdown preview body styling - mirrors the chat body palette so
   what the seller sees here matches the eventual product page. */
.mp-desc-preview .md-heading { margin: 0.6rem 0 0.35rem; font-weight: 700; line-height: 1.25; }
.mp-desc-preview .md-h1 { font-size: 1.35rem; }
.mp-desc-preview .md-h2 { font-size: 1.15rem; }
.mp-desc-preview .md-h3 { font-size: 1rem; }
.mp-desc-preview .md-quote {
    border-left: 3px solid #3a3a3f;
    padding: 0.1rem 0 0.1rem 0.65rem;
    margin: 0.4rem 0;
    color: #c4c4c8;
}
.mp-desc-preview .md-subtext { color: var(--text-muted); font-size: 0.82rem; }
.mp-desc-preview .md-link { color: #a78bfa; text-decoration: underline; word-break: break-word; }
.mp-desc-preview .md-inline-code {
    background: #1a1a1f;
    padding: 0.05rem 0.35rem;
    border-radius: 0.25rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.9em;
}
.mp-desc-preview .md-codeblock {
    background: #18181b;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0.6rem 0.75rem;
    margin: 0.5rem 0;
    overflow-x: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85rem;
}
.mp-desc-preview .md-list { padding-left: 1.2rem; margin: 0.4rem 0; }
.mp-desc-preview .md-spoiler {
    background: #2a2a30;
    color: transparent;
    border-radius: 0.2rem;
    padding: 0.05rem 0.25rem;
}
.mp-desc-preview p { margin: 0.4rem 0; }

/* ---- Cards (right column) ---- */
.mp-card {
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}
.mp-card-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 700;
    letter-spacing: 0.01em;
}
.mp-card-hint {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.82rem;
    line-height: 1.5;
}

/* ---- Preview image card ---- */
.mp-preview-drop {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1;
    max-width: 240px;
    margin-inline: auto;
    border: 2px dashed #3a3a3f;
    border-radius: 0.6rem;
    background: #0a0a0a;
    color: var(--text-muted);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    cursor: pointer;
    overflow: hidden;
    transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.mp-preview-drop:hover,
.mp-preview-drop:focus { border-color: var(--brand); color: var(--text); outline: none; }
.mp-preview-drop.is-dragover { border-color: var(--brand); background: rgba(124, 58, 237, 0.08); }
.mp-preview-drop.is-filled  { border-style: solid; border-color: var(--border); }
.mp-preview-empty-text { font-size: 0.85rem; text-align: center; padding: 0 0.5rem; }
.mp-preview-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* When the preview slot holds a <video>, expose the controls instead of
   the "Replace" overlay - the seller swaps via the storage button or by
   clicking the remove (x) button. The controls take over the lower
   portion of the tile, which would collide with the hover overlay. */
.mp-preview-drop.is-filled:has(.mp-preview-video) .mp-preview-overlay { display: none; }
.mp-preview-video {
    background: #000;
    object-fit: contain;
}
.mp-preview-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    background: rgba(0, 0, 0, 0.55);
    color: #f4f4f5;
    font-size: 0.85rem;
    opacity: 0;
    transition: opacity 0.15s;
    pointer-events: none;
}
.mp-preview-drop:hover .mp-preview-overlay,
.mp-preview-drop:focus .mp-preview-overlay { opacity: 1; }
.mp-preview-remove {
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    width: 1.85rem;
    height: 1.85rem;
    min-height: 40px;
    min-width:  40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.65);
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 999px;
    color: #fff;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.mp-preview-remove:hover { background: var(--danger); border-color: var(--danger); }
/* The legacy CSS-only ::after spinner is replaced by the
   .mp-upload-progress overlay rendered directly in JS so we can update
   the bar / percentage as upload progress events fire. Keep the rule
   present (empty) so older cached HTML still parses cleanly. */
.mp-preview-drop.is-uploading { cursor: progress; }

/* ---- Toggles + radios ---- */
.mp-radio-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem 1rem;
}
.mp-radio-row-stack {
    flex-direction: column;
    gap: 0.55rem;
}
.mp-radio {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    min-height: 40px;
    padding: 0.2rem 0;
    user-select: none;
}
.mp-radio input { position: absolute; opacity: 0; pointer-events: none; }
.mp-radio-dot {
    width: 18px;
    height: 18px;
    border-radius: 999px;
    border: 1.5px solid #4d4d52;
    flex-shrink: 0;
    position: relative;
    transition: border-color 0.12s, background 0.12s;
}
.mp-radio-dot::after {
    content: "";
    position: absolute;
    inset: 4px;
    border-radius: 999px;
    background: var(--brand);
    transform: scale(0);
    transition: transform 0.12s;
}
.mp-radio input:checked + .mp-radio-dot { border-color: var(--brand); }
.mp-radio input:checked + .mp-radio-dot::after { transform: scale(1); }
.mp-radio-label { font-size: 0.92rem; }

.mp-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    cursor: pointer;
    min-height: 40px;
    user-select: none;
}
.mp-toggle input { position: absolute; opacity: 0; pointer-events: none; }
.mp-toggle-switch {
    position: relative;
    width: 36px;
    height: 20px;
    border-radius: 999px;
    background: #2a2a2f;
    transition: background 0.15s;
    flex-shrink: 0;
}
.mp-toggle-switch::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 999px;
    background: #f4f4f5;
    transition: transform 0.15s;
}
.mp-toggle input:checked + .mp-toggle-switch { background: var(--brand); }
.mp-toggle input:checked + .mp-toggle-switch::after { transform: translateX(16px); }
.mp-toggle-label { font-size: 0.92rem; }

/* ---- Price card layout ---- */
.mp-price-fields {
    display: flex;
    gap: 0.5rem;
    align-items: stretch;
}
.mp-price-amount  { flex: 1 1 auto; min-width: 0; }
.mp-currency-select {
    flex: 0 0 5.5rem;
    appearance: none;
    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%239a9a9a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>');
    background-repeat: no-repeat;
    background-position: right 0.55rem center;
    padding-right: 1.8rem;
}

/* ---- Sale card ---- */
.mp-sale-card { gap: 0.6rem; }
.mp-sale-card.is-disabled .mp-toggle { opacity: 0.5; cursor: not-allowed; }
.mp-sale-disabled-hint { color: var(--text-muted); font-size: 0.82rem; }
.mp-sale-fields {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    padding-top: 0.6rem;
}
.mp-sale-price-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.mp-sale-price-amount { flex: 1 1 auto; min-width: 0; }
.mp-sale-price-currency {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0 0.3rem;
}
.mp-sale-discount {
    margin: 0;
    font-size: 0.85rem;
    font-weight: 600;
    color: #ef4444;
    min-height: 1.1em;
}
.mp-sale-discount:empty { display: none; }
.mp-sale-permanent-note { color: var(--text-muted); font-size: 0.82rem; margin: 0; }
.mp-checkbox-row {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    min-height: 40px;
    cursor: pointer;
    user-select: none;
}
.mp-checkbox-row input[type="checkbox"] {
    width: 18px;
    height: 18px;
    accent-color: var(--brand);
    flex-shrink: 0;
}
.mp-checkbox-row-label { font-size: 0.92rem; }
.mp-sale-ends-input {
    /* iOS Safari renders datetime-local with extra inner padding -
       cap the box so it does not exceed the field column. */
    width: 100%;
    max-width: 100%;
}
@media (max-width: 560px) {
    .mp-sale-price-row { flex-wrap: wrap; }
    .mp-sale-price-currency { padding: 0; }
}

/* ---- Action bar ---- */
.mp-actions-card {
    display: flex;
    flex-direction: row;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.mp-actions-card .btn {
    flex: 1 1 auto;
    min-height: 40px;
}
.mp-save-btn {
    flex: 1 1 100% !important;
}
.mp-delete-btn {
    flex: 1 1 100% !important;
    margin-top: 0.25rem;
}

@media (max-width: 899px) {
    /* Sticky action bar on phones so Save is always reachable. */
    .mp-actions-card {
        position: sticky;
        bottom: 0;
        bottom: env(safe-area-inset-bottom, 0);
        margin-inline: -1rem;
        padding-bottom: calc(0.85rem + env(safe-area-inset-bottom, 0));
        border-radius: 0;
        border-left: 0;
        border-right: 0;
        z-index: 50;
        box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.55);
    }
}

/* ---- Gallery ---- */
.mp-gallery-wrap {
    margin-top: 0.5rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    position: relative;
}
/* Drop-target hover state. Outline (not border) so the layout
   doesn't shift when the dashed ring appears, plus a brand-tinted
   tint and a "Drop to upload" overlay. The ::after element is
   pointer-events:none so it doesn't intercept the drag-leave on
   the host. Mirrors the guides editor's drop visual. */
.mp-gallery-wrap.is-drop-target {
    outline: 2px dashed rgba(168, 85, 247, 0.7);
    outline-offset: 4px;
    background: rgba(168, 85, 247, 0.06);
}
.mp-gallery-wrap.is-drop-target::after {
    content: 'Drop images to upload';
    position: absolute;
    inset: 0;
    display: flex; align-items: center; justify-content: center;
    background: rgba(10, 10, 13, 0.55);
    color: #ddd6fe;
    font-weight: 700;
    font-size: 0.95rem;
    border-radius: 0.55rem;
    pointer-events: none;
    z-index: 5;
}
.mp-gallery-head {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
}
.mp-gallery-count { color: var(--text-muted); font-size: 0.82rem; }
.mp-gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 0.6rem;
}
.mp-gallery-tile {
    position: relative;
    aspect-ratio: 1 / 1;
    background: #0a0a0a;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    overflow: hidden;
    cursor: grab;
    touch-action: pan-y;
    transition: border-color 0.12s, transform 0.12s, box-shadow 0.12s, background 0.12s;
}
.mp-gallery-tile.is-dragging      { opacity: 0.5; cursor: grabbing; }
.mp-gallery-tile.is-touch-dragging { transform: scale(1.04); box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55); border-color: var(--brand); z-index: 10; }
.mp-gallery-tile.is-drop-target   { border-color: var(--brand); box-shadow: inset 0 0 0 2px rgba(124, 58, 237, 0.45); }
.mp-gallery-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}
.mp-gallery-handle {
    position: absolute;
    top: 0.4rem;
    left: 0.4rem;
    width: 1.85rem;
    height: 1.85rem;
    min-height: 40px;
    min-width:  40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.65);
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 0.35rem;
    color: #f4f4f5;
    cursor: grab;
    touch-action: none;
}
.mp-gallery-remove {
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    width: 1.85rem;
    height: 1.85rem;
    min-height: 40px;
    min-width:  40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.65);
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 999px;
    color: #fff;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}
.mp-gallery-remove:hover,
.mp-gallery-remove:active,
.mp-gallery-remove:focus-visible { background: var(--danger); border-color: var(--danger); }
/* Legacy spinner - tiles now render .mp-upload-progress instead so
   users see the live percentage. Class kept (no styling) so any in-
   flight DOM created before a hot-reload doesn't break visually. */
.mp-gallery-spinner { display: none; }

.mp-gallery-add {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    background: #0a0a0a;
    border: 2px dashed #3a3a3f;
    color: var(--text-muted);
    cursor: pointer;
    font: inherit;
    transition: border-color 0.12s, color 0.12s, background 0.12s;
}
.mp-gallery-add:hover { border-color: var(--brand); color: var(--text); }
.mp-gallery-add-icon { color: inherit; }
.mp-gallery-add-text { font-size: 0.82rem; }

@media (max-width: 480px) {
    .mp-gallery-grid {
        grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
        gap: 0.45rem;
    }
}
@media (max-width: 360px) {
    .mp-editor-heading { font-size: 1.35rem; }
    .mp-card { padding: 0.85rem; }
    .mp-gallery-wrap { padding: 0.85rem; }
}

/* ============================================================
   Marketplace browse + detail (Phase 6)
   ------------------------------------------------------------
   Browse: filter bar (search + sort) + horizontal-scrolling
   category strip + responsive product grid (2 cols on phones,
   auto-fill on desktop). SSR template paints first paint;
   marketplace-browse.js takes over for filter/search/pagination.

   Detail: two-column layout above 900 px, single column below.
   Hero image + thumbnail strip + lightbox; sticky sidebar with
   price, seller card, purchase placeholder, share, edit/delete.
   All selectors namespaced .mkt-*.
   ============================================================ */

.mkt-shell {
    display: block;
    color: var(--text);
    max-width: 1600px;
    margin: 0 auto;
    padding: 3rem 1.5rem;
    box-sizing: border-box;
    width: 100%;
}

.mkt-head {
    margin: 0 0 1.25rem;
}
.mkt-head-title {
    margin: 0 0 0.35rem;
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}
.mkt-head-sub {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.95rem;
}
.mkt-head-sub-accent {
    color: #fbbf24;
    font-weight: 600;
}
.mkt-head-sub-link {
    color: #fbbf24;
    text-decoration: underline;
    text-decoration-color: rgba(251, 191, 36, 0.5);
    text-underline-offset: 0.2em;
    cursor: pointer;
    background: transparent;
    border: 0;
    padding: 0;
    font: inherit;
}
.mkt-head-sub-link:hover,
.mkt-head-sub-link:focus-visible {
    color: #fde68a;
    text-decoration-color: #fbbf24;
    outline: none;
}

/* ---- Toolbar (search + sort) ---- */
.mkt-toolbar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0 0 0.85rem;
    flex-wrap: wrap;
}
.mkt-search-wrap {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
}
.mkt-search-icon {
    position: absolute;
    left: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text-muted);
    display: inline-flex;
    pointer-events: none;
}
.mkt-search-input {
    width: 100%;
    min-height: 2.6rem;
    padding: 0.6rem 2.4rem 0.6rem 2.4rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--text);
    font: inherit;
    transition: border-color 0.12s, background 0.12s;
}
.mkt-search-input:focus { outline: none; border-color: var(--brand); background: #131313; }
.mkt-search-input::placeholder { color: #5d5d63; }
/* Hide native search "x" - we ship our own. */
.mkt-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.mkt-search-clear {
    position: absolute;
    right: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    width: 1.85rem;
    height: 1.85rem;
    min-width: 40px;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    border-radius: 999px;
    cursor: pointer;
    transition: color 0.12s, background 0.12s;
}
.mkt-search-clear:hover { color: var(--text); background: #1c1c20; }

/* ---- Seller filter dropdown ---- */
.mkt-seller-dropdown {
    position: relative;
    flex: 0 0 auto;
}
.mkt-seller-btn {
    display: inline-flex; align-items: center; gap: 0.45rem;
    padding: 0 0.85rem;
    height: 2.25rem;
    background: var(--surface, #17171c);
    border: 1px solid var(--border, #27272a);
    border-radius: 0.45rem;
    color: var(--text, #e4e4e7);
    font: inherit; font-size: 0.87rem;
    cursor: pointer;
    white-space: nowrap;
    transition: border-color 0.12s;
}
.mkt-seller-btn:hover { border-color: #52525b; }
.mkt-seller-btn[aria-expanded="true"] { border-color: var(--brand, #8b5cf6); }
.mkt-seller-btn-icon { color: var(--text-muted, #a1a1aa); flex-shrink: 0; }
.mkt-seller-chevron  { color: var(--text-muted, #a1a1aa); flex-shrink: 0; transition: transform 0.15s; }
.mkt-seller-btn[aria-expanded="true"] .mkt-seller-chevron { transform: rotate(180deg); }
.mkt-seller-panel {
    position: absolute;
    top: calc(100% + 0.35rem);
    left: 0;
    min-width: 220px;
    max-width: 280px;
    max-height: 320px;
    overflow-y: auto;
    background: var(--surface-2, #1a1a1f);
    border: 1px solid var(--border, #27272a);
    border-radius: 0.55rem;
    box-shadow: 0 8px 24px rgba(0,0,0,0.45);
    z-index: 400;
    padding: 0.35rem;
}
.mkt-seller-item {
    display: flex; align-items: center; gap: 0.55rem;
    padding: 0.45rem 0.55rem;
    border-radius: 0.35rem;
    cursor: pointer;
    font-size: 0.87rem;
    color: var(--text, #e4e4e7);
    user-select: none;
}
.mkt-seller-item:hover { background: rgba(255,255,255,0.05); }
.mkt-seller-all { border-bottom: 1px solid var(--border, #27272a); margin-bottom: 0.25rem; padding-bottom: 0.55rem; }
.mkt-seller-check { flex-shrink: 0; accent-color: var(--brand, #8b5cf6); width: 1rem; height: 1rem; cursor: pointer; }
.mkt-seller-avatar {
    width: 1.5rem; height: 1.5rem;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
}
.mkt-seller-avatar-fallback {
    display: inline-flex; align-items: center; justify-content: center;
    background: #3f3f46;
    color: #a1a1aa;
    font-size: 0.7rem; font-weight: 700;
}
.mkt-seller-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mkt-seller-count {
    flex-shrink: 0;
    font-size: 0.75rem;
    color: var(--text-muted, #71717a);
    background: rgba(255,255,255,0.06);
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
}
@media (max-width: 768px) {
    .mkt-seller-panel { left: 0; right: auto; max-width: calc(100vw - 1rem); }
}

.mkt-sort {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex: 0 0 auto;
}
.mkt-sort-label {
    font-size: 0.85rem;
    color: var(--text-muted);
}
.mkt-sort-select {
    min-height: 2.6rem;
    padding: 0.55rem 2rem 0.55rem 0.75rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--text);
    font: inherit;
    cursor: pointer;
    appearance: none;
    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%239a9a9a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>');
    background-repeat: no-repeat;
    background-position: right 0.55rem center;
}
.mkt-sort-select:focus { outline: none; border-color: var(--brand); }

/* ---- Category strip ----
 * Mirrors the /guides .gd-cat-pill row: same subtle pill silhouette
 * (transparent bg + transparent border until hover/active), wraps
 * onto multiple lines instead of horizontal-scrolling, separator
 * border-bottom anchors the row to the toolbar above it. The
 * marketplace-specific add is the count badge inside each pill, which
 * uses muted text so it doesn't fight the active accent. Min-height
 * stays at 32px so the pills are still tappable on mobile. */
.mkt-cats {
    margin: 0 0 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border);
}
.mkt-cats-strip {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}
.mkt-cat-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.5rem 0.75rem;
    /* 40px touch target on mobile per the project's mobile-first
       rules. Desktop still reads as a compact pill - the height is
       the same as the chat composer button row. */
    min-height: 40px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.8rem;
    line-height: 1;
    white-space: nowrap;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
/* :focus-visible mirrors :hover so keyboard + touch users get the
   same affordance as mouse users. (iOS doesn't fire :hover until
   tap-and-hold, by which point the user has already committed.) */
.mkt-cat-pill:hover,
.mkt-cat-pill:focus-visible {
    color: var(--text);
    background: rgba(168, 85, 247, 0.08);
}
.mkt-cat-pill.is-active {
    background: rgba(168, 85, 247, 0.18);
    border-color: rgba(168, 85, 247, 0.4);
    color: #ddd6fe;
}
.mkt-cat-pill-icon { color: inherit; flex-shrink: 0; }
.mkt-cat-pill-label { display: inline-block; }
/* Per-category live-product count badge. Sits to the right of the
   label inside the pill. Tabular numerals so the digits don't
   bounce as values change. Translucent so it doesn't fight the
   active-pill purple accent. */
.mkt-cat-pill-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4em;
    padding: 0.02rem 0.4rem;
    background: rgba(255, 255, 255, 0.06);
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
    margin-left: 0.1rem;
}
.mkt-cat-pill.is-active .mkt-cat-pill-count {
    background: rgba(168, 85, 247, 0.28);
    color: #f5f3ff;
}

/* ---- Grid + cards ---- */
.mkt-grid-root {
    position: relative;
    min-height: 200px;
}
.mkt-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1rem;
}
@media (max-width: 560px) {
    .mkt-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 0.75rem;
    }
}

.mkt-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    overflow: hidden;
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.12s, transform 0.12s, box-shadow 0.12s;
    min-width: 0;
    /* Skip layout/paint for off-screen cards. The browser keeps a
       cached layout box of the size hinted by contain-intrinsic-size
       so the scrollbar / page height stay correct, but it never
       paints children outside the viewport. Combined with the lazy
       data-role-name promotion below, off-screen seller chips never
       get FX child injection either - so a 24-card grid pays for
       only the ~6 cards actually in view at any moment. */
    content-visibility: auto;
    contain-intrinsic-size: 1px 360px;
}

/* Marketplace browse-card role-FX cap.
   Seller chips on browse cards keep their COLOR / GRADIENT (the
   visual identity of the seller's role) but lose the moving parts:
   no particle children, no continuous animations. This matters
   because a single browse page can stack 24 chips, and each with
   the full FX stack (sparkles + orbit + snow particles, rainbow
   hue-rotate, shimmer sweep, etc.) adds up to dozens of permanently
   running CSS animations dragging frame rate even on a fast PC.
   The detail page, chat sidebar, member list, and profile header
   all keep the full FX since they only ever show one chip at a
   time. To restore full FX on browse cards, simply delete this
   block - no JS / template changes needed. */
.mkt-card-seller-name .role-sparkle,
.mkt-card-seller-name .role-orbit,
.mkt-card-seller-name .role-snow,
.mkt-card-seller-name .role-laser,
.mkt-card-seller-name .role-crown,
.mkt-card-seller-name .role-lightning,
.mkt-card-seller-name .role-flame,
.mkt-card-seller-name .role-hearts,
.mkt-card-seller-name .role-bubbles,
.mkt-card-seller-name .role-stars,
.mkt-card-seller-name .role-wind,
.mkt-card-seller-name .role-bonfire,
.mkt-card-seller-name .role-firestorm,
.mkt-card-seller-name .role-matrix {
    display: none !important;
}
/* Pause every text-side animation (rainbow hue-rotate, shimmer
   sweep, pulse glow, glitch ghost-translate, crt scanlines, ...).
   The gradient remains at its initial background-position so the
   colours still show; they just don't oscillate. */
.mkt-card-seller-name,
.mkt-card-seller-name *,
.mkt-card-seller-name *::before,
.mkt-card-seller-name *::after {
    animation: none !important;
    transition: none !important;
}
.mkt-card:hover,
.mkt-card:focus,
.mkt-card:focus-within {
    border-color: var(--brand);
    transform: translateY(-2px);
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.45);
    outline: none;
}
/* Buttons inside the action row shouldn't trigger the lift-on-focus
   for the whole card - that pulls the buttons out from under the
   pointer mid-click. Suppress the lift when focus lands on an action. */
.mkt-card:has(.mkt-card-action:focus-visible) {
    transform: none;
}
.mkt-card.is-soldout { opacity: 0.7; }

.mkt-card-media {
    position: relative;
    aspect-ratio: 1 / 1;
    background: #0a0a0a;
    overflow: hidden;
    flex-shrink: 0;
}
.mkt-card-img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    max-width: 100%;
    background: #0a0a0a;
}
/* Video previews on browse cards: same sizing as images, no controls
   (the player lives on the detail page hero). The play badge sits in
   the lower-right corner as a "this is a video" affordance. */
.mkt-card-video { pointer-events: none; }
.mkt-card-video-play {
    position: absolute;
    right: 8px;
    bottom: 8px;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    background: rgba(10, 10, 10, 0.78);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding-left: 2px; /* optical center the play triangle */
    pointer-events: none;
    backdrop-filter: blur(2px);
}
.mkt-card-img-empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #3a3a3f;
}

.mkt-card-cat {
    position: absolute;
    left: 0.5rem;
    top: 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.55rem;
    background: rgba(0, 0, 0, 0.65);
    backdrop-filter: blur(4px);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    font-size: 0.72rem;
    color: #f4f4f5;
    line-height: 1;
    max-width: calc(100% - 1rem);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mkt-card-cat-icon { color: inherit; }

.mkt-card-stock {
    position: absolute;
    right: 0.5rem;
    bottom: 0.5rem;
    padding: 0.25rem 0.55rem;
    background: rgba(16, 185, 129, 0.85);
    color: #fff;
    border-radius: 999px;
    font-size: 0.72rem;
    line-height: 1;
    font-weight: 600;
    letter-spacing: 0.01em;
}
.mkt-card-stock.is-out {
    background: rgba(244, 63, 94, 0.85);
}

.mkt-card-body {
    padding: 0.7rem 0.8rem 0.85rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    min-width: 0;
    text-align: center;
    align-items: center;
}
.mkt-card-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.3;
    /* 2-line clamp. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
    color: var(--text);
}

.mkt-card-seller {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    min-width: 0;
}
.mkt-card-seller-avatar {
    width: 24px;
    height: 24px;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
    background: #1c1c20;
}
.mkt-card-seller-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 0.72rem;
    font-weight: 700;
    background: linear-gradient(135deg, #4c1d95, #7c3aed);
}
.mkt-card-seller-name {
    font-size: 0.82rem;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

.mkt-card-rating {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.78rem;
    color: var(--text-muted);
    line-height: 1;
}
.mkt-card-rating-none { font-style: italic; }
.mkt-card-rating-count { color: var(--text-muted); }

/* Star strip - shared with detail page seller card. */
.mkt-stars { display: inline-flex; gap: 1px; align-items: center; }
.mkt-star { display: inline-flex; align-items: center; }
.mkt-star.is-full  { color: #fbbf24; }
.mkt-star.is-half  { color: #fbbf24; opacity: 0.55; }
.mkt-star.is-empty { color: rgba(255, 255, 255, 0.18); }
/* The lucide sprite's <symbol id="star"> has no fill, and `.lucide` sets
   `fill: none` by default, so without these the gold colour only paints
   the stroke and the star looks like a hollow outline. Mirror the
   .profile-rating-star / .mkt-product-chip-star fill pattern so any
   .mkt-star host renders as a solid gold star when full or half. */
.mkt-star.is-full  svg { fill: currentColor; }
.mkt-star.is-half  svg { fill: currentColor; }
.mkt-star.is-empty svg { fill: none; }

.mkt-card-price {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-top: 0.15rem;
    font-size: 0.92rem;
    font-weight: 600;
    color: var(--text);
}
.mkt-card-price-amount { color: #f4f4f5; }
.mkt-card-price-icon { color: var(--text-muted); }

/* ---- Sale visuals on browse cards ---- */
/* Red SALE pill in the top-right corner of the preview image. The
   media block is `position: relative` already (host of .mkt-card-stock /
   .mkt-card-cat), so the badge anchors to the same coordinate space. */
.mkt-card-sale-badge {
    position: absolute;
    top: 8px;
    right: 8px;
    background: #ef4444;
    color: #fff;
    font-weight: 700;
    font-size: 0.78rem;
    padding: 0.18rem 0.45rem;
    border-radius: 0.4rem;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
    z-index: 2;
    letter-spacing: 0.02em;
}
/* Required-dependencies "!" badge on browse cards. Sits in the
   top-right corner; offsets DOWN when a sale badge is also present
   so they don't overlap (sale badge is the louder of the two). The
   data-tooltip attr is rendered by the global custom-tooltip CSS;
   we use `white-space: pre-line` on the tooltip so the multi-line
   "Required dependencies:\n- A\n- B" string lays out nicely. */
.mkt-card-deps-badge {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 24px;
    height: 24px;
    background: #000;
    color: #fbbf24;
    border: 2px solid #fbbf24;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
    z-index: 2;
    cursor: help;
    line-height: 1;
}
/* Offset DOWN when a sale badge already occupies the top-right slot
   so the two don't overlap. */
.mkt-card-sale-badge ~ .mkt-card-deps-badge { top: 36px; }
.mkt-card-deps-badge-glyph {
    color: #fbbf24;
    font-weight: 900;
    font-size: 0.95rem;
    line-height: 1;
    font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
    /* Center the glyph in its own box so flexbox can center the box
       cleanly. The "!" character has asymmetric whitespace (more
       space below the dot than above the stem) which makes it look
       offset when sat on the baseline; rendering it as a fixed-size
       inline-block strips that asymmetry. */
    display: inline-block;
    width: 1ch;
    height: 1em;
    text-align: center;
}
.mkt-card-deps-badge[data-tooltip]::after {
    /* Override the global tooltip's nowrap so dep lists wrap. */
    white-space: pre-line;
    text-align: left;
    max-width: 280px;
}

/* ---- Highlighted (premium-pinned) browse cards ----
 * Premium sellers can pin up to 2 listings to the top. Highlighted
 * cards get a gold border, soft glow, and a top-left "Highlighted"
 * pill so they stand out above the regular grid without dominating
 * the surrounding layout. The badge sits in the LEFT corner so it
 * doesn't fight the existing sale + deps badges in the right corner.
 */
.mkt-card.is-highlighted {
    border-color: #d4a017;
    box-shadow:
        0 0 0 1px rgba(245, 197, 24, 0.55) inset,
        0 8px 24px rgba(212, 160, 23, 0.18),
        0 2px 6px rgba(0, 0, 0, 0.35);
    background:
        linear-gradient(180deg, rgba(245, 197, 24, 0.06) 0%, rgba(245, 197, 24, 0) 40%),
        var(--card);
    position: relative;
}
.mkt-card.is-highlighted .mkt-card-title {
    color: #fde68a;
}
.mkt-card-highlight-badge {
    position: absolute;
    top: 8px;
    left: 8px;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.18rem 0.5rem;
    border-radius: 0.4rem;
    background: linear-gradient(135deg, #f5c518 0%, #b8860b 100%);
    color: #1a0e00;
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
    line-height: 1;
}
.mkt-card-highlight-icon { color: inherit; flex-shrink: 0; }
.mkt-card-price-original {
    color: var(--text-muted);
    text-decoration: line-through;
    font-size: 0.85rem;
    font-weight: 500;
}
.mkt-card-price-sale {
    color: #ef4444;
    font-weight: 700;
    font-size: 0.98rem;
}
.mkt-card-sale-countdown {
    font-size: 0.75rem;
    color: #ef4444;
    margin-top: 0.2rem;
    font-weight: 600;
}

@media (max-width: 560px) {
    .mkt-card-title { font-size: 0.88rem; }
    .mkt-card-body { padding: 0.55rem 0.65rem 0.7rem; gap: 0.3rem; }
    .mkt-card-seller-name { font-size: 0.76rem; }
    .mkt-card-cat { font-size: 0.68rem; padding: 0.25rem 0.45rem; }
    .mkt-card-stock { font-size: 0.68rem; padding: 0.2rem 0.45rem; }
}

/* ---- Card link wrapper ---------------------------------------------
   The card's media + body live inside an inner anchor so the buttons
   in the action row aren't nested inside an <a>. The hover transform
   was on .mkt-card before; keep it on the outer card so the action
   row lifts with the body. */
.mkt-card-link {
    display: flex;
    flex-direction: column;
    color: var(--text);
    text-decoration: none;
    min-width: 0;
}
.mkt-card-link:focus { outline: none; }

/* ---- Action row (Add to cart + Purchase now) ------------------------ */
.mkt-card-actions {
    display: flex;
    gap: 0.5rem;
    padding: 0 0.8rem 0.8rem;
}
.mkt-card-action {
    flex: 1 1 50%;
    min-width: 0;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    padding: 0.45rem 0.6rem;
    border-radius: 0.5rem;
    border: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.1;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.08s;
}
.mkt-card-action:hover { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.18); }
.mkt-card-action:active { transform: translateY(1px); }
.mkt-card-action:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.mkt-card-action.is-muted,
.mkt-card-action[disabled] {
    opacity: 0.55;
    cursor: not-allowed;
    background: transparent;
}
.mkt-card-action.is-muted:hover,
.mkt-card-action[disabled]:hover { background: transparent; border-color: var(--border); }
.mkt-card-action.is-busy { opacity: 0.7; cursor: progress; }

.mkt-card-action-icon { color: inherit; flex-shrink: 0; }
.mkt-card-action-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* Purchase now uses the marketplace gold accent (matches the nav pill
   active state and rating stars) so it reads as the primary action. */
.mkt-card-action-buy {
    background: rgba(251, 191, 36, 0.12);
    border-color: rgba(251, 191, 36, 0.55);
    color: #fbbf24;
}
.mkt-card-action-buy:hover {
    background: rgba(251, 191, 36, 0.22);
    border-color: #fbbf24;
    color: #fbbf24;
}
.mkt-card-action-buy.is-muted,
.mkt-card-action-buy[disabled] {
    background: transparent;
    border-color: rgba(251, 191, 36, 0.25);
    color: rgba(251, 191, 36, 0.5);
}

/* "In cart" state - the cart button shows muted with a check-style
   palette once the listing is already in the user's cart. Disabled
   so the user can't double-click; tooltip explains why. */
.mkt-card-action-cart.is-in-cart,
.mkt-card-action-cart.is-in-cart[disabled] {
    background: rgba(34, 197, 94, 0.10);
    border-color: rgba(34, 197, 94, 0.45);
    color: #86efac;
    cursor: default;
}
.mkt-card-action-cart.is-in-cart:hover {
    background: rgba(34, 197, 94, 0.10);
    border-color: rgba(34, 197, 94, 0.45);
    color: #86efac;
}

/* Stack vertically on small screens so the labels stay readable rather
   than truncating mid-word. */
@media (max-width: 560px) {
    .mkt-card-actions {
        flex-direction: column;
        gap: 0.4rem;
        padding: 0 0.65rem 0.7rem;
    }
    .mkt-card-action {
        font-size: 0.78rem;
        padding: 0.45rem 0.5rem;
    }
}

/* ---- Empty + status ---- */
.mkt-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: var(--text-muted);
    padding: 3rem 1rem;
    gap: 0.85rem;
}
.mkt-empty-icon { color: #3a3a3f; }
.mkt-empty-title {
    margin: 0;
    font-size: 1rem;
    color: var(--text);
    overflow-wrap: anywhere;
}
.mkt-empty-clear { margin-top: 0.4rem; }

.mkt-page-status {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 1rem 0.5rem 0.5rem;
    color: var(--text-muted);
    font-size: 0.82rem;
}
.mkt-sentinel {
    width: 100%;
    height: 1px;
    pointer-events: none;
}

/* Loading overlay for in-place filter switch. */
.mkt-grid-overlay {
    position: absolute;
    inset: 0;
    background: rgba(10, 10, 10, 0.45);
    z-index: 5;
    display: flex;
    align-items: flex-start;
    justify-content: flex-end;
    padding: 0.75rem;
    pointer-events: none;
}
.mkt-grid-overlay-spinner {
    display: inline-block;
    width: 28px;
    height: 28px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.5) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 40 40"><circle cx="20" cy="20" r="16" fill="none" stroke="%23a78bfa" stroke-width="3" stroke-dasharray="80" stroke-dashoffset="40"><animateTransform attributeName="transform" type="rotate" from="0 20 20" to="360 20 20" dur="1s" repeatCount="indefinite"/></circle></svg>') center / 28px 28px no-repeat;
}
.mkt-page-spinner { width: 18px; height: 18px; background-size: 18px 18px; }
/* Dim the grid while a filter switch is in flight. */
.mkt-grid-root:has(> .mkt-grid-overlay:not([hidden])) > .mkt-grid {
    opacity: 0.5;
    transition: opacity 0.15s;
    pointer-events: none;
}

/* ---- Seller "New listing" entry points ----
   Inline button sits in the toolbar row alongside the search +
   sort controls. Visible at every viewport but the label drops
   on narrow screens (icon-only, the data-tooltip carries the
   action name). Mobile-only floating-action-button is layered
   on top so a thumb-reachable affordance is always present
   while scrolling the grid. */
.mkt-new-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 2.6rem;
    padding: 0.55rem 0.95rem;
    line-height: 1;
    white-space: nowrap;
}
.mkt-new-btn-icon { color: inherit; flex-shrink: 0; }
.mkt-new-btn-label { display: inline-block; }
@media (max-width: 560px) {
    .mkt-new-btn {
        padding: 0.55rem 0.7rem;
        min-width: 2.6rem;
        justify-content: center;
    }
    /* Toolbar gets icon-only - the FAB carries the discoverability
       on mobile, the inline button is just a backup for users who
       look at the toolbar row. */
    .mkt-new-btn-label { display: none; }
}

.mkt-new-fab {
    /* Hidden on desktop. The inline toolbar button is enough there
       and a corner-pinned FAB would just float over the grid for no
       reason at wide widths. */
    display: none;
}
@media (max-width: 768px) {
    .mkt-new-fab {
        position: fixed;
        right: calc(1rem + env(safe-area-inset-right, 0px));
        bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
        width: 56px;
        height: 56px;
        min-width: 56px;
        min-height: 56px;
        border-radius: 999px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        background: var(--brand);
        color: #fff;
        text-decoration: none;
        box-shadow: 0 12px 30px -8px rgba(124, 58, 237, 0.55),
                    0 4px 12px rgba(0, 0, 0, 0.5);
        z-index: 30;
        transition: transform 0.12s, background 0.12s, box-shadow 0.12s;
    }
    .mkt-new-fab:hover,
    .mkt-new-fab:focus {
        background: var(--brand-hover);
        transform: translateY(-1px);
        outline: none;
    }
    .mkt-new-fab:active { transform: translateY(0); }
    .mkt-new-fab .lucide { width: 24px; height: 24px; }

    /* Add bottom breathing room to the grid root so the FAB never
       occludes the last row of cards / pagination sentinel. The FAB
       is 56px tall + ~1rem margin + safe-area, so 6rem covers it
       comfortably with room to spare. */
    .mkt-shell .mkt-grid-root { padding-bottom: 6rem; }
}

/* ============================================================
   Detail page
   ============================================================ */

.mkt-detail-shell { padding-bottom: 2rem; }
.mkt-detail-root { display: block; }

/* SSR fallback - stripped down to a single column; replaced on mount. */
.mkt-detail-fallback {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 0.5rem 0;
}
.mkt-detail-fallback-img {
    width: 100%;
    max-width: 480px;
    aspect-ratio: 16 / 9;
    object-fit: contain;
    background: #0a0a0a;
    border-radius: 0.5rem;
    align-self: flex-start;
}
.mkt-detail-fallback-title {
    margin: 0;
    font-size: 1.4rem;
    font-weight: 700;
    overflow-wrap: anywhere;
}
.mkt-detail-fallback-cat {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    margin: 0;
}
.mkt-detail-fallback-price {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
}
.mkt-detail-fallback-seller { margin: 0; color: var(--text-muted); font-size: 0.9rem; }
.mkt-detail-fallback-seller a { color: var(--brand); }
.mkt-detail-fallback-desc {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0.75rem;
    color: var(--text);
    font-family: inherit;
    font-size: 0.92rem;
    line-height: 1.5;
}
.mkt-detail-fallback-owner {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}
.mkt-detail-fallback-delete-form {
    display: inline-block;
    margin: 0;
}
.mkt-detail-fallback-buy { align-self: flex-start; }

/* The mounted root replaces the fallback content; layout below. */
.mkt-detail-mounted .mkt-detail-fallback { display: none; }

.mkt-detail-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.25rem;
    align-items: start;
}
@media (min-width: 900px) {
    .mkt-detail-layout {
        grid-template-columns: minmax(0, 1.5fr) minmax(280px, 1fr);
        gap: 1.5rem;
    }
}

.mkt-detail-left, .mkt-detail-right {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: 0;
}

@media (min-width: 900px) {
    .mkt-detail-right {
        position: sticky;
        top: 1rem;
        align-self: start;
    }
}

/* On mobile the order is gallery -> title -> price/stock -> seller ->
   purchase -> description -> edit/delete. Right column is rendered
   second in the DOM but we want it stacked between gallery and
   description on mobile, so re-order via CSS grid + order. */
@media (max-width: 899px) {
    .mkt-detail-layout {
        display: grid;
        grid-template-columns: 1fr;
    }
    .mkt-detail-left  { display: contents; }
    .mkt-detail-right { display: contents; }
    .mkt-detail-gallery     { order: 1; }
    .mkt-detail-title-block { order: 2; }
    .mkt-detail-price-block { order: 3; }
    .mkt-detail-stock       { order: 4; }
    .mkt-detail-seller      { order: 5; }
    .mkt-detail-actions     { order: 6; }
    .mkt-detail-desc-card   { order: 7; }
    .mkt-detail-owner-actions { order: 8; }
    .mkt-detail-reviews     { order: 9; }
}

/* ---- Gallery ---- */
.mkt-detail-gallery {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    min-width: 0;
}
.mkt-detail-hero {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: #050505;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #3a3a3f;
}
.mkt-detail-hero.is-clickable { cursor: zoom-in; }
.mkt-detail-hero-img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    max-width: 100%;
    pointer-events: none;
}
/* Video hero override: pointer-events:auto so the controls work, and
   a slightly darker bg so letterboxing isn't a jarring dark band. */
.mkt-detail-hero-video {
    pointer-events: auto;
    background: #000;
}
.mkt-detail-hero-empty { color: #3a3a3f; }

.mkt-detail-thumbs {
    display: flex;
    gap: 0.45rem;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    scrollbar-color: #2a2a2f transparent;
    padding: 0.15rem 0.05rem;
    -webkit-overflow-scrolling: touch;
}
.mkt-detail-thumbs::-webkit-scrollbar       { height: 6px; }
.mkt-detail-thumbs::-webkit-scrollbar-thumb { background: #2a2a2f; border-radius: 999px; }
.mkt-detail-thumb {
    width: 72px;
    height: 72px;
    min-width: 40px;
    min-height: 40px;
    flex: 0 0 auto;
    border: 2px solid var(--border);
    background: #0a0a0a;
    border-radius: 0.45rem;
    overflow: hidden;
    cursor: pointer;
    padding: 0;
    transition: border-color 0.12s, transform 0.12s;
}
.mkt-detail-thumb img,
.mkt-detail-thumb video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}
.mkt-detail-thumb { position: relative; }
.mkt-detail-thumb-play {
    position: absolute;
    inset: auto 4px 4px auto;
    width: 22px;
    height: 22px;
    border-radius: 999px;
    background: rgba(10, 10, 10, 0.78);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding-left: 1px;
    pointer-events: none;
}
.mkt-detail-thumb:hover { border-color: #3a3a3f; }
.mkt-detail-thumb.is-active { border-color: var(--brand); transform: scale(1.02); }

/* ---- Sidebar pieces ---- */
.mkt-detail-title-block {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}
.mkt-detail-title {
    margin: 0;
    font-size: 1.4rem;
    font-weight: 700;
    line-height: 1.2;
    overflow-wrap: anywhere;
    color: var(--text);
}
.mkt-detail-cat-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.25rem 0.6rem;
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: 0.78rem;
    color: var(--text-muted);
    text-decoration: none;
    align-self: flex-start;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    line-height: 1;
    min-height: 28px;
}
.mkt-detail-cat-chip:hover { color: var(--text); border-color: var(--brand); }
.mkt-detail-cat-icon { color: inherit; }

.mkt-detail-price-block {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.65rem 0.85rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--text);
}
.mkt-detail-price-amount {
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: #f4f4f5;
    overflow-wrap: anywhere;
}
.mkt-detail-price-block.is-on-request {
    color: var(--text-muted);
}
.mkt-detail-price-text { font-weight: 600; font-size: 1rem; color: var(--text); }
.mkt-detail-price-icon { color: var(--brand); flex-shrink: 0; }

/* ---- Sale visuals on the detail page ---- */
.mkt-detail-sale-badge {
    display: inline-block;
    background: #ef4444;
    color: #fff;
    font-weight: 800;
    font-size: 0.95rem;
    padding: 0.3rem 0.6rem;
    border-radius: 0.5rem;
    margin-bottom: 0.5rem;
    letter-spacing: 0.03em;
    align-self: flex-start;
}
.mkt-detail-price-block.is-on-sale {
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.55rem;
}
.mkt-detail-price-original {
    color: var(--text-muted);
    text-decoration: line-through;
    font-size: 1rem;
    font-weight: 600;
}
.mkt-detail-price-sale {
    color: #ef4444;
    font-weight: 800;
    font-size: 1.6rem;
    letter-spacing: -0.01em;
}
.mkt-detail-sale-countdown {
    color: #ef4444;
    font-size: 0.9rem;
    margin-top: 0.3rem;
    font-weight: 700;
}
@media (max-width: 768px) {
    /* Countdown wraps below the price block. */
    .mkt-detail-sale-countdown { font-size: 0.85rem; }
    .mkt-detail-price-sale { font-size: 1.35rem; }
}

.mkt-detail-stock {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.65rem;
    border-radius: 999px;
    font-size: 0.82rem;
    background: rgba(16, 185, 129, 0.12);
    color: #6ee7b7;
    border: 1px solid rgba(16, 185, 129, 0.35);
    align-self: flex-start;
    line-height: 1;
}
.mkt-detail-stock.is-out {
    background: rgba(244, 63, 94, 0.12);
    color: #fca5a5;
    border-color: rgba(244, 63, 94, 0.35);
}
.mkt-detail-stock-icon { color: inherit; }

/* Seller card. */
.mkt-detail-seller {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.7rem 0.8rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.12s, background 0.12s;
    min-width: 0;
}
.mkt-detail-seller:hover { border-color: var(--brand); background: #131316; }
.mkt-detail-seller-avatar {
    width: 48px;
    height: 48px;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
    background: #1c1c20;
}
.mkt-detail-seller-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 1.1rem;
    font-weight: 700;
    background: linear-gradient(135deg, #4c1d95, #7c3aed);
}
.mkt-detail-seller-meta {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}
.mkt-detail-seller-name {
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mkt-detail-seller-rating {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text-muted);
    font-size: 0.82rem;
}
.mkt-detail-seller-rating-none { font-style: italic; }
.mkt-detail-seller-rating-count { color: var(--text-muted); }

/* Action buttons. */
.mkt-detail-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.mkt-detail-purchase {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    width: 100%;
    min-height: 44px;
    font-weight: 600;
}
.mkt-detail-purchase:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}
.mkt-detail-purchase.is-muted {
    background: #2a2a2f;
    color: var(--text-muted);
}
.mkt-detail-purchase.is-signin {
    opacity: 1;
    cursor: pointer;
}
.mkt-detail-purchase-icon { color: inherit; }

.mkt-detail-share {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    width: 100%;
    min-height: 40px;
}
.mkt-detail-share-icon { color: inherit; }

.mkt-detail-owner-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.5rem;
    padding-top: 0.85rem;
    border-top: 1px solid var(--border);
}
.mkt-detail-edit, .mkt-detail-delete, .mkt-detail-highlight {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    width: 100%;
    min-height: 40px;
}
/* Highlight toggle. Muted look when the viewer is non-premium so the
   button is visible-but-discouraged (clicking still opens the
   premium info dialog). When ON, render with a gold accent so the
   seller can see at a glance which of their listings is currently
   pinned. */
.mkt-detail-highlight.is-locked {
    opacity: 0.7;
    border-color: #2a2a2f;
    color: var(--text-muted);
}
.mkt-detail-highlight.is-on {
    background: linear-gradient(135deg, #f5c518 0%, #b8860b 100%);
    border-color: #f5c518;
    color: #1a0e00;
    font-weight: 700;
}
.mkt-detail-highlight.is-on:hover { filter: brightness(1.05); }

/* ---- Description ---- */
.mkt-detail-desc-card {
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    padding: 1rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    min-width: 0;
}
.mkt-detail-section-title {
    margin: 0 0 0.35rem;
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: 0.01em;
}
.mkt-detail-desc {
    color: var(--text);
    line-height: 1.55;
    overflow-wrap: anywhere;
    min-width: 0;
}
.mkt-detail-desc img,
.mkt-detail-desc video {
    max-width: 100%;
    height: auto;
}
/* Reuse marketplace-editor's md-* base styles - already defined at the
   top of the editor section. We need them here too (the seller's
   description renders into .mkt-detail-desc not .mp-desc-preview), so
   re-declare the minimal subset rather than coupling with editor CSS. */
.mkt-detail-desc .md-heading { margin: 0.6rem 0 0.35rem; font-weight: 700; line-height: 1.25; }
.mkt-detail-desc .md-h1 { font-size: 1.35rem; }
.mkt-detail-desc .md-h2 { font-size: 1.15rem; }
.mkt-detail-desc .md-h3 { font-size: 1rem; }
.mkt-detail-desc .md-quote {
    border-left: 3px solid #3a3a3f;
    padding: 0.1rem 0 0.1rem 0.65rem;
    margin: 0.4rem 0;
    color: #c4c4c8;
}
.mkt-detail-desc .md-subtext { color: var(--text-muted); font-size: 0.82rem; }
.mkt-detail-desc .md-link { color: #a78bfa; text-decoration: underline; word-break: break-word; }
.mkt-detail-desc .md-inline-code {
    background: #1a1a1f;
    padding: 0.05rem 0.35rem;
    border-radius: 0.25rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.9em;
}
.mkt-detail-desc .md-codeblock {
    background: #18181b;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0.6rem 0.75rem;
    margin: 0.5rem 0;
    overflow-x: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85rem;
}
.mkt-detail-desc .md-list { padding-left: 1.2rem; margin: 0.4rem 0; }
.mkt-detail-desc .md-spoiler {
    background: #2a2a30;
    color: transparent;
    border-radius: 0.2rem;
    padding: 0.05rem 0.25rem;
    cursor: pointer;
}
.mkt-detail-desc .md-spoiler:hover { color: var(--text); background: #1a1a1f; }
.mkt-detail-desc p { margin: 0.4rem 0; }

/* ============================================================
   Lightbox (detail gallery)
   ------------------------------------------------------------
   Full-viewport overlay; image clamps to viewport on both axes.
   Body scroll is locked while open; overscroll-behavior: contain
   on the modal so iOS rubber-band doesn't bleed through to the
   page beneath.
   ============================================================ */
.mkt-lightbox {
    position: fixed;
    inset: 0;
    z-index: 5000;
    display: flex;
    align-items: center;
    justify-content: center;
    overscroll-behavior: contain;
    /* Use dvh so iOS Safari URL bar doesn't push controls off-screen. */
    height: 100dvh;
    width: 100vw;
}
.mkt-lightbox-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.92);
    cursor: zoom-out;
}
.mkt-lightbox-stage {
    position: relative;
    z-index: 1;
    max-width: 100vw;
    max-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: env(safe-area-inset-top, 0) env(safe-area-inset-right, 0) env(safe-area-inset-bottom, 0) env(safe-area-inset-left, 0);
    touch-action: pan-y;
    user-select: none;
}
.mkt-lightbox-img {
    max-width: 100vw;
    max-height: 100dvh;
    object-fit: contain;
    display: block;
    user-select: none;
    -webkit-user-drag: none;
}
/* Lightbox video variant: same sizing constraints, but interactive (the
   controls need to receive clicks) and explicit black bg so letterboxing
   blends with the backdrop. */
.mkt-lightbox-video {
    user-select: auto;
    background: #000;
}
.mkt-lightbox-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    width: 48px;
    height: 48px;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.65);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    color: #fff;
    cursor: pointer;
    transition: background 0.12s, transform 0.12s;
}
.mkt-lightbox-nav:hover { background: rgba(124, 58, 237, 0.85); }
.mkt-lightbox-prev { left: 1rem; }
.mkt-lightbox-next { right: 1rem; }
.mkt-lightbox-close {
    position: absolute;
    top: calc(0.85rem + env(safe-area-inset-top, 0));
    right: calc(0.85rem + env(safe-area-inset-right, 0));
    z-index: 3;
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.7);
    border: 1px solid rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    color: #fff;
    cursor: pointer;
}
.mkt-lightbox-close:hover { background: rgba(244, 63, 94, 0.85); }
.mkt-lightbox-counter {
    position: absolute;
    bottom: calc(0.85rem + env(safe-area-inset-bottom, 0));
    left: 50%;
    transform: translateX(-50%);
    z-index: 2;
    padding: 0.35rem 0.75rem;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    border-radius: 999px;
    font-size: 0.82rem;
    pointer-events: none;
}

@media (max-width: 560px) {
    .mkt-lightbox-prev { left: 0.5rem; }
    .mkt-lightbox-next { right: 0.5rem; }
    .mkt-lightbox-nav  { width: 44px; height: 44px; }
}

/* ---- Reviews section (below description) ---- */
.mkt-detail-reviews {
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    padding: 1rem 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    min-width: 0;
}
.mkt-detail-reviews-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem;
    justify-content: space-between;
}
.mkt-detail-reviews-summary {
    color: var(--text-muted);
    font-size: 0.86rem;
}
.mkt-detail-reviews-total {
    font-weight: 600;
    color: var(--text);
}

.mkt-detail-reviews-list {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    min-width: 0;
}
.mkt-detail-review-card {
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    padding: 0.75rem 0.9rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}
.mkt-detail-review-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
}
.mkt-detail-review-author {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    text-decoration: none;
    color: var(--text);
    min-width: 0;
    flex: 1 1 auto;
    border-radius: 0.4rem;
    padding: 0.15rem;
    margin: -0.15rem;
    min-height: 40px;
}
.mkt-detail-review-author:hover,
.mkt-detail-review-author:focus-visible {
    background: rgba(255, 255, 255, 0.04);
    outline: none;
}
.mkt-detail-review-avatar {
    width: 32px;
    height: 32px;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
    background: #1c1c20;
}
.mkt-detail-review-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 0.85rem;
    font-weight: 700;
    background: linear-gradient(135deg, #4c1d95, #7c3aed);
}
.mkt-detail-review-author-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.2;
}
.mkt-detail-review-name {
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.9rem;
}
.mkt-detail-review-handle {
    font-size: 0.74rem;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mkt-detail-review-time {
    font-size: 0.76rem;
    color: var(--text-muted);
    white-space: nowrap;
    flex-shrink: 0;
}
.mkt-detail-review-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.55rem;
}
.mkt-detail-review-stars { line-height: 0; }
.mkt-detail-review-ctx-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.55rem;
    background: rgba(99, 102, 241, 0.16);
    color: #a5b4fc;
    border: 1px solid rgba(99, 102, 241, 0.3);
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1;
}
.mkt-detail-review-body {
    margin: 0;
    color: var(--text);
    font-size: 0.9rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    word-break: break-word;
    max-width: 100%;
}
.mkt-detail-review-skeleton {
    height: 90px;
    background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02));
    border-radius: 0.55rem;
    border: 1px solid var(--border);
    animation: profileSkeletonPulse 1.4s ease-in-out infinite;
}
.mkt-detail-reviews-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
    padding: 1.75rem 1rem;
    text-align: center;
    border: 1px dashed var(--border);
    border-radius: 0.55rem;
    background: rgba(255, 255, 255, 0.02);
}
.mkt-detail-reviews-empty-icon { color: #3a3a3f; }
.mkt-detail-reviews-empty-title {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.92rem;
    max-width: 28rem;
}
.mkt-detail-reviews-empty-cta {
    margin-top: 0.4rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 40px;
}
.mkt-detail-reviews-more {
    align-self: center;
    min-height: 40px;
    padding: 0.45rem 1rem;
}
.mkt-detail-reviews-more:disabled {
    opacity: 0.6;
    cursor: wait;
}
.mkt-detail-reviews-status {
    text-align: center;
    font-size: 0.82rem;
    color: var(--text-muted);
}

/* ---- Detail page narrow-screen tweaks ---- */
@media (max-width: 560px) {
    .mkt-detail-title { font-size: 1.2rem; }
    .mkt-detail-price-amount { font-size: 1.1rem; }
    .mkt-detail-desc-card { padding: 0.85rem; }
    .mkt-detail-thumb { width: 60px; height: 60px; }
    .mkt-detail-reviews { padding: 0.85rem; }
    .mkt-detail-review-card { padding: 0.65rem 0.75rem; }
    .mkt-detail-review-name { font-size: 0.86rem; }
    .mkt-detail-review-time { font-size: 0.72rem; }
    .mkt-detail-review-body { font-size: 0.86rem; }
}
@media (max-width: 360px) {
    .mkt-head-title { font-size: 1.35rem; }
    .mkt-detail-title { font-size: 1.1rem; }
    .mkt-detail-thumb { width: 56px; height: 56px; }
    .mkt-detail-reviews { padding: 0.7rem; }
    .mkt-detail-review-card { padding: 0.55rem 0.65rem; }
}

/* ============================================================
   Marketplace product chip (Phase 7)
   ------------------------------------------------------------
   In-message rich card for pasted /marketplace/<id> URLs (and
   the DM message produced by the purchase flow). Lives inside
   `.chat-message-content` so it inherits paragraph spacing but
   spans block-width so the layout has room for image, meta and
   the action zone. Two halves:
     `.mkt-product-chip-body`    - the clickable card body
                                   (image + title + seller + price).
     `.mkt-product-chip-actions` - the buttons / status text on
                                   the right (or stacked below
                                   on mobile).
   On <=560px the body stacks (image on top) and action buttons
   become full-width.
   ============================================================ */

.mkt-product-chip {
    display: flex;
    align-items: stretch;
    gap: 0.75rem;
    margin: 0.4rem 0;
    padding: 0.6rem;
    border: 1px solid rgba(167, 139, 250, 0.30);
    border-radius: 0.6rem;
    background: rgba(167, 139, 250, 0.07);
    color: var(--text);
    max-width: 460px;
    overflow-wrap: anywhere;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.mkt-product-chip:hover {
    background: rgba(167, 139, 250, 0.12);
    border-color: rgba(167, 139, 250, 0.45);
}
.mkt-product-chip.is-busy {
    opacity: 0.65;
    pointer-events: none;
}
.mkt-product-chip[data-state="error"] {
    border-color: rgba(244, 63, 94, 0.30);
    background: rgba(244, 63, 94, 0.06);
}
.mkt-product-chip-error {
    display: inline-flex;
    align-items: center;
    padding: 0.3rem 0.5rem;
    color: #fca5a5;
    text-decoration: none;
    font-size: 0.9rem;
}
.mkt-product-chip-error:hover { text-decoration: underline; }

/* Loading shell (mirrors msg-link-chip pulse). */
.mkt-product-chip.is-loading {
    border-color: rgba(255, 255, 255, 0.10);
    background: rgba(255, 255, 255, 0.04);
    padding: 0.55rem 0.7rem;
    color: var(--text-muted);
}
.mkt-product-chip-loading {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    animation: mkt-product-chip-pulse 1.4s ease-in-out infinite;
    font-size: 0.88rem;
}
.mkt-product-chip-spinner {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 2px solid rgba(167, 139, 250, 0.25);
    border-top-color: rgba(167, 139, 250, 0.75);
    animation: mkt-product-chip-spin 0.9s linear infinite;
    flex: 0 0 auto;
}
@keyframes mkt-product-chip-pulse {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 1; }
}
@keyframes mkt-product-chip-spin {
    to { transform: rotate(360deg); }
}

/* Body (clickable). */
.mkt-product-chip-body {
    display: flex;
    flex: 1 1 auto;
    align-items: stretch;
    gap: 0.7rem;
    color: inherit;
    text-decoration: none;
    min-width: 0;
}
.mkt-product-chip-body:hover { text-decoration: none; }
.mkt-product-chip-body:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: 0.4rem;
}

.mkt-product-chip-img {
    flex: 0 0 auto;
    width: 64px;
    height: 64px;
    border-radius: 0.45rem;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.04);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
}
.mkt-product-chip-img img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    max-width: 100%;
}
.mkt-product-chip-img-empty { color: rgba(255, 255, 255, 0.30); }

.mkt-product-chip-meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    justify-content: center;
}
.mkt-product-chip-title {
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.25;
    color: var(--text);
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    word-break: break-word;
}
.mkt-product-chip-cat {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    align-self: flex-start;
    padding: 0.05rem 0.45rem;
    border-radius: 0.35rem;
    background: rgba(255, 255, 255, 0.05);
    color: var(--text-muted);
    font-size: 0.75rem;
    line-height: 1.4;
}
.mkt-product-chip-cat-icon { fill: none; stroke: currentColor; stroke-width: 2; flex: 0 0 auto; }
.mkt-product-chip-cat span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 9rem;
}

.mkt-product-chip-seller {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    color: var(--text-muted);
    font-size: 0.78rem;
    line-height: 1.3;
    flex-wrap: wrap;
}
.mkt-product-chip-stars {
    display: inline-flex;
    align-items: center;
    gap: 1px;
    color: #fbbf24;
}
.mkt-product-chip-star { display: inline-flex; }
.mkt-product-chip-star svg { fill: none; stroke: currentColor; stroke-width: 2; }
.mkt-product-chip-star.is-full svg   { fill: currentColor; }
.mkt-product-chip-star.is-half svg   { fill: url(#never); }
.mkt-product-chip-star.is-half       { color: #fbbf24; opacity: 0.55; }
.mkt-product-chip-star.is-empty      { color: rgba(255, 255, 255, 0.18); }
.mkt-product-chip-star.is-empty svg  { fill: none; }
.mkt-product-chip-rating-num {
    color: var(--text-muted);
    font-size: 0.74rem;
}

.mkt-product-chip-price {
    font-size: 0.92rem;
    font-weight: 600;
    color: #c4b5fd;
}
.mkt-product-chip-price.is-on-request { color: var(--text-muted); font-weight: 500; }

/* ---- Sale visuals on the chip ---- */
.mkt-product-chip-title-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.35rem;
    min-width: 0;
}
.mkt-product-chip-title-row .mkt-product-chip-title {
    flex: 1 1 auto;
    min-width: 0;
}
/* The previous .mkt-product-chip-title rule keeps its weight + ellipsis;
   the row wraps it so the SALE pill can sit beside the title without
   pushing the rest of the meta block into a separate row. */
.mkt-product-chip-sale-pill {
    background: #ef4444;
    color: #fff;
    font-weight: 700;
    font-size: 0.7rem;
    padding: 0.1rem 0.35rem;
    border-radius: 0.3rem;
    letter-spacing: 0.03em;
    flex-shrink: 0;
}
.mkt-product-chip-price.is-on-sale {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.mkt-product-chip-price-original {
    color: var(--text-muted);
    text-decoration: line-through;
    font-size: 0.82rem;
    font-weight: 500;
}
.mkt-product-chip-price-sale {
    color: #ef4444;
    font-weight: 800;
}
/* Multi-item chip lines: stack original (strikethrough) + sale price
   on top of the qty math so the seller still sees what's owed. */
.mkt-product-chip-multi-line-price.is-on-sale {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.3rem;
}
.mkt-product-chip-multi-line-price-original {
    color: var(--text-muted);
    text-decoration: line-through;
    font-size: 0.78rem;
    font-weight: 500;
}
.mkt-product-chip-multi-line-price-sale {
    color: #ef4444;
    font-weight: 700;
}

/* Action zone. */
.mkt-product-chip-actions {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: stretch;
    gap: 0.35rem;
    min-width: 8.5rem;
    padding-left: 0.4rem;
    border-left: 1px solid rgba(255, 255, 255, 0.07);
}
.mkt-product-chip-status {
    color: var(--text-muted);
    font-size: 0.78rem;
    line-height: 1.3;
    text-align: center;
}
.mkt-product-chip-status.is-success { color: #86efac; }
.mkt-product-chip-status.is-muted   { color: rgba(255, 255, 255, 0.40); }
.mkt-product-chip-link {
    text-align: center;
    padding: 0.4rem 0.55rem;
    border-radius: 0.4rem;
    font-size: 0.85rem;
    color: #c4b5fd;
    background: rgba(167, 139, 250, 0.10);
    border: 1px solid rgba(167, 139, 250, 0.30);
    text-decoration: none;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.mkt-product-chip-link:hover {
    background: rgba(167, 139, 250, 0.20);
    border-color: rgba(167, 139, 250, 0.55);
    color: #ddd6fe;
    text-decoration: none;
}
.mkt-product-chip-btn {
    appearance: none;
    border: 1px solid rgba(255, 255, 255, 0.12);
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
    border-radius: 0.4rem;
    padding: 0.45rem 0.6rem;
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    min-height: 40px;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.mkt-product-chip-btn:hover {
    background: rgba(255, 255, 255, 0.08);
    border-color: rgba(255, 255, 255, 0.20);
}
.mkt-product-chip-btn[disabled] { opacity: 0.55; cursor: default; }
.mkt-product-chip-btn-primary {
    background: var(--brand, #6366f1);
    border-color: var(--brand, #6366f1);
    color: #fff;
}
.mkt-product-chip-btn-primary:hover {
    background: #818cf8;
    border-color: #818cf8;
    color: #fff;
}
.mkt-product-chip-btn-ghost-danger {
    color: #fca5a5;
    border-color: rgba(244, 63, 94, 0.35);
    background: transparent;
}
.mkt-product-chip-btn-ghost-danger:hover {
    color: #fecaca;
    border-color: rgba(244, 63, 94, 0.60);
    background: rgba(244, 63, 94, 0.10);
}

/* Mobile: stack body vertically + action zone full-width below. */
@media (max-width: 560px) {
    .mkt-product-chip {
        flex-direction: column;
        max-width: 100%;
    }
    .mkt-product-chip-body {
        flex-direction: row;
        gap: 0.6rem;
    }
    .mkt-product-chip-img {
        width: 56px;
        height: 56px;
    }
    .mkt-product-chip-actions {
        border-left: none;
        border-top: 1px solid rgba(255, 255, 255, 0.07);
        padding-left: 0;
        padding-top: 0.55rem;
        flex-direction: row;
        flex-wrap: wrap;
        justify-content: stretch;
        min-width: 0;
    }
    .mkt-product-chip-actions > * {
        flex: 1 1 0;
        min-width: 0;
    }
    .mkt-product-chip-status {
        flex: 1 0 100%;
        text-align: left;
    }
}
@media (max-width: 360px) {
    .mkt-product-chip { padding: 0.5rem; }
    .mkt-product-chip-img { width: 48px; height: 48px; }
    .mkt-product-chip-title { font-size: 0.9rem; }
}

/* ============================================================
   Marketplace purchase confirm modal (Phase 7)
   ------------------------------------------------------------
   Lazy-built by marketplace-detail.js when the buyer clicks the
   Purchase button. Shows the product header (image / title /
   seller / price) above an optional note textarea + char counter.
   Mirrors the .note-prompt-modal frame but with the product card
   on top.
   ============================================================ */

.mkt-purchase-modal .mkt-purchase-card {
    width: min(100%, 32rem);
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 1rem 1.1rem 0.9rem;
}
.mkt-purchase-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}
.mkt-purchase-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
}
.mkt-purchase-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.5rem;
    border-radius: 0.3rem;
    min-width: 40px;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.mkt-purchase-close:hover {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
}

.mkt-purchase-product-inner {
    display: flex;
    align-items: stretch;
    gap: 0.75rem;
    padding: 0.7rem;
    border-radius: 0.55rem;
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.08);
}
.mkt-purchase-product-img {
    flex: 0 0 auto;
    width: 80px;
    height: 80px;
    border-radius: 0.45rem;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.04);
    object-fit: cover;
    display: block;
}
.mkt-purchase-product-img-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(255, 255, 255, 0.30);
}
.mkt-purchase-product-meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    justify-content: center;
}
.mkt-purchase-product-title {
    font-weight: 600;
    color: var(--text);
    font-size: 0.98rem;
    line-height: 1.25;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    word-break: break-word;
}
.mkt-purchase-product-seller {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    color: var(--text-muted);
    font-size: 0.82rem;
}
.mkt-purchase-product-stars {
    display: inline-flex;
    align-items: center;
    gap: 1px;
    color: #fbbf24;
}
.mkt-purchase-product-rating-num {
    color: var(--text-muted);
    font-size: 0.78rem;
}
.mkt-purchase-product-price {
    font-weight: 700;
    color: #c4b5fd;
    font-size: 0.95rem;
}
.mkt-purchase-product-price.is-on-request { color: var(--text-muted); font-weight: 500; }
.mkt-purchase-product-price.is-on-sale {
    display: inline-flex;
    align-items: baseline;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.mkt-purchase-product-price-original {
    color: var(--text-muted);
    text-decoration: line-through;
    font-weight: 500;
    font-size: 0.85rem;
}
.mkt-purchase-product-price-sale {
    color: #ef4444;
    font-weight: 800;
}

.mkt-purchase-body { display: flex; flex-direction: column; gap: 0.4rem; }
.mkt-purchase-label {
    font-size: 0.85rem;
    color: var(--text-muted);
    font-weight: 500;
}
.mkt-purchase-note {
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
    min-height: 88px;
    max-height: 240px;
    padding: 0.55rem 0.65rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    font-size: 0.92rem;
    line-height: 1.4;
}
.mkt-purchase-note:focus {
    outline: 2px solid var(--brand);
    outline-offset: 1px;
    border-color: transparent;
}
.mkt-purchase-foot-row {
    display: flex;
    justify-content: flex-end;
}
.mkt-purchase-counter {
    color: var(--text-muted);
    font-size: 0.78rem;
}
.mkt-purchase-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 0.5rem;
}
.mkt-purchase-actions .btn { min-height: 40px; }
/* Busy state for the modal's "Send purchase request" button while the
 * /purchase POST is in flight. The disabled attribute already greys the
 * button via the global btn:disabled rules; this just reinforces the
 * affordance with a subtle pulse + wait cursor so the state is obvious
 * at 360px where the button stretches full-width. The cancel buttons
 * are also disabled during this window (see setPurchaseConfirmBusy in
 * marketplace-detail.js); we lean on the default :disabled opacity for
 * them since they don't change label. */
.mkt-purchase-actions .btn.is-busy {
    opacity: 0.75;
    cursor: wait;
    pointer-events: none;
}
.mkt-purchase-actions .btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

@media (max-width: 560px) {
    .mkt-purchase-modal .mkt-purchase-card {
        width: 100%;
        padding: 0.85rem 0.85rem 0.75rem;
    }
    .mkt-purchase-product-inner { padding: 0.55rem; gap: 0.55rem; }
    .mkt-purchase-product-img { width: 64px; height: 64px; }
    .mkt-purchase-actions {
        flex-direction: column-reverse;
        align-items: stretch;
    }
    .mkt-purchase-actions .btn { width: 100%; }
}

/* ============================================================
   Marketplace review modal (Phase 8)
   ------------------------------------------------------------
   Lazy-built by chat-client.js the first time a buyer or seller
   clicks "Leave a review" on a completed order chip. Layout:
     [X close]   Leave a review
                 for {subject_display_name}
     [thumbnail | product title]
     Rating
     [* * * * *]   <- 5 interactive star buttons (>=44px tap)
     "N stars selected"
     Comments (optional)
     [textarea, 2000 chars + counter]
     [Cancel]   [Submit review]
   On <=560px the card is full-width with column-reverse actions
   so Submit visually sits above Cancel - matches the purchase
   modal convention.
   ============================================================ */

.mkt-review-modal .mkt-review-card {
    width: min(100%, 32rem);
    max-height: calc(100dvh - 2rem);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 1rem 1.1rem 0.9rem;
    padding-bottom: calc(0.9rem + env(safe-area-inset-bottom, 0px));
}

.mkt-review-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.5rem;
}
.mkt-review-titles {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
    flex: 1 1 auto;
}
.mkt-review-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
    line-height: 1.25;
}
.mkt-review-subtitle {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.3;
    overflow-wrap: anywhere;
}
.mkt-review-subject {
    color: var(--text);
    font-weight: 600;
}
.mkt-review-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.5rem;
    border-radius: 0.3rem;
    min-width: 40px;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
}
.mkt-review-close:hover {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
}
.mkt-review-close:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Product context block (small image + title). */
.mkt-review-product-inner {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem;
    border-radius: 0.5rem;
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.08);
}
.mkt-review-product-img {
    flex: 0 0 auto;
    width: 48px;
    height: 48px;
    border-radius: 0.4rem;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.05);
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(255, 255, 255, 0.30);
}
.mkt-review-product-img img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    max-width: 100%;
}
.mkt-review-product-img.is-empty svg {
    fill: none;
    stroke: currentColor;
}
.mkt-review-product-meta {
    flex: 1 1 auto;
    min-width: 0;
}
.mkt-review-product-title {
    font-weight: 600;
    color: var(--text);
    font-size: 0.92rem;
    line-height: 1.3;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    word-break: break-word;
}

/* Sections (Rating, Comments). */
.mkt-review-section {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.mkt-review-section-label {
    font-size: 0.85rem;
    color: var(--text-muted);
    font-weight: 500;
}

/* Star widget. */
.mkt-review-stars {
    display: flex;
    flex-wrap: nowrap;
    gap: 0.5rem;
    align-items: center;
}
.mkt-review-star {
    appearance: none;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.4rem;
    padding: 0;
    margin: 0;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: rgba(255, 255, 255, 0.25);
    transition: color 0.12s ease, background 0.12s ease;
    flex: 0 0 auto;
}
.mkt-review-star:hover,
.mkt-review-star:focus-visible {
    background: rgba(255, 255, 255, 0.05);
    outline: none;
}
.mkt-review-star:focus-visible {
    border-color: var(--brand, #6366f1);
}
.mkt-review-star svg {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    width: 28px;
    height: 28px;
    transition: fill 0.12s ease;
}
.mkt-review-star.is-filled {
    color: #fbbf24;
}
.mkt-review-star.is-filled svg {
    fill: currentColor;
}
.mkt-review-rating-text {
    color: var(--text-muted);
    font-size: 0.78rem;
    line-height: 1.3;
    min-height: 1.1em;
}
.mkt-review-rating-text.is-set {
    color: var(--text);
}

/* Comments textarea. */
.mkt-review-body {
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
    min-height: 96px;
    max-height: 280px;
    padding: 0.55rem 0.65rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    font-size: 0.92rem;
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.mkt-review-body:focus {
    outline: 2px solid var(--brand);
    outline-offset: 1px;
    border-color: transparent;
}
.mkt-review-foot-row {
    display: flex;
    justify-content: flex-end;
}
.mkt-review-counter {
    color: var(--text-muted);
    font-size: 0.78rem;
}
/* Inline error - shown when the server rejects the submit (other than
   already_reviewed which auto-resolves). Stays visible until the user
   types or successfully resubmits. */
.mkt-review-error {
    margin: 0.25rem 0 0;
    padding: 0.5rem 0.625rem;
    border-radius: 0.5rem;
    background: rgba(239, 68, 68, 0.12);
    border: 1px solid rgba(239, 68, 68, 0.4);
    color: #fecaca;
    font-size: 0.85rem;
    line-height: 1.35;
    overflow-wrap: anywhere;
}

/* Footer actions. */
.mkt-review-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 0.5rem;
}
.mkt-review-actions .btn { min-height: 40px; }
.mkt-review-actions .btn.is-busy {
    opacity: 0.75;
    cursor: wait;
    pointer-events: none;
}
.mkt-review-actions .btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* Reviewed indicator on the chip itself. */
.mkt-product-chip-reviewed {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.mkt-product-chip-reviewed-stars {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.05rem 0.4rem;
    border-radius: 0.35rem;
    background: rgba(251, 191, 36, 0.12);
    color: #fbbf24;
    font-size: 0.74rem;
    font-weight: 600;
    line-height: 1;
}
.mkt-product-chip-reviewed-star {
    display: inline-flex;
    align-items: center;
}
.mkt-product-chip-reviewed-star svg {
    fill: currentColor;
    stroke: currentColor;
    stroke-width: 1.5;
}

@media (max-width: 560px) {
    .mkt-review-modal .mkt-review-card {
        width: 100%;
        padding: 0.85rem 0.85rem 0.75rem;
        padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
    }
    .mkt-review-actions {
        flex-direction: column-reverse;
        align-items: stretch;
    }
    .mkt-review-actions .btn { width: 100%; }
    .mkt-review-stars { gap: 0.4rem; }
    .mkt-review-star { width: 44px; height: 44px; }
    .mkt-review-star svg { width: 26px; height: 26px; }
}

@media (max-width: 360px) {
    .mkt-review-stars { gap: 0.3rem; }
    .mkt-review-star { width: 42px; height: 42px; }
    .mkt-review-star svg { width: 24px; height: 24px; }
}

/* ================================================================
 * Profile marketplace tabs (Phase 9)
 * ================================================================ */

/* Header star strip - the backend SSRs this; JS may repaint after a
 * Phase 10 madrigal:profile-updated event. */
.profile-stars {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.35rem;
    line-height: 1;
}
.profile-stars-empty {
    font-size: 0.8rem;
    color: var(--text-muted);
    font-style: italic;
}
.profile-stars-count {
    font-size: 0.78rem;
    color: var(--text-muted);
}

/* SSR star strip on the profile header (.profile-rating).
 * Mirrors the working .mkt-product-chip-star pattern: stars default to
 * stroked outline, then is-filled / is-half flip fill to currentColor on
 * a gold parent, and is-empty drops to a faint white. The SSR svgs do
 * NOT carry the .lucide class, so we set stroke + fill explicitly here. */
.profile-rating {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    /* Sits as its own row in the .profile-card flex column between the
       handle and the .profile-presence row. Symmetric vertical margins
       so the gold stars never crowd the handle above or the presence
       text below. */
    margin: 0.25rem 0 0.4rem;
    line-height: 1;
    color: #fbbf24;
}
.profile-rating-stars {
    display: inline-flex;
    align-items: center;
    gap: 1px;
}
.profile-rating-star {
    display: inline-block;
    vertical-align: -0.15em;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex-shrink: 0;
}
.profile-rating-star.is-filled,
.profile-rating-star.is-half { color: #fbbf24; }
.profile-rating-star.is-filled { fill: currentColor; }
.profile-rating-star.is-half   { fill: currentColor; opacity: 0.55; }
.profile-rating-star.is-empty  { color: rgba(255, 255, 255, 0.18); fill: none; }
.profile-rating-count {
    font-size: 0.82rem;
    color: var(--text-muted);
}

/* Tabs row - SSR'd by backend, JS toggles .is-active. Mobile-first. */
.profile-tabs {
    display: flex;
    gap: 0.25rem;
    padding: 0.4rem 0.4rem 0;
    margin: 1rem 0 0;
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.profile-tabs::-webkit-scrollbar { display: none; }

.profile-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.6rem 0.9rem;
    background: transparent;
    color: var(--text-muted);
    border: 0;
    border-bottom: 2px solid transparent;
    font: inherit;
    font-size: 0.92rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    min-height: 40px;
    transition: color 0.12s, border-color 0.12s, background 0.12s;
    border-radius: 0.4rem 0.4rem 0 0;
}
.profile-tab:hover,
.profile-tab:focus-visible {
    color: var(--text);
    background: rgba(255, 255, 255, 0.04);
    outline: none;
}
.profile-tab.is-active {
    color: var(--text);
    border-bottom-color: var(--brand);
    background: rgba(255, 255, 255, 0.03);
}
.profile-tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4rem;
    height: 1.2rem;
    padding: 0 0.35rem;
    background: rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--text-muted);
}
.profile-tab.is-active .profile-tab-count {
    background: rgba(99, 102, 241, 0.2);
    color: var(--text);
}

.profile-pane {
    padding: 1rem 0 0;
}
.profile-pane[hidden] { display: none; }

/* ---- Products pane --------------------------------------------- */

.profile-products-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin: 0 0 0.85rem;
}
.profile-products-new {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 40px;
}
.profile-products-filter {
    display: inline-flex;
    gap: 0.25rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.2rem;
}
.profile-products-tabchip {
    appearance: none;
    background: transparent;
    color: var(--text-muted);
    border: 0;
    border-radius: 999px;
    padding: 0.55rem 0.9rem;
    font: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    /* 40 px touch target per the project's mobile-first rules. */
    min-height: 40px;
}
.profile-products-tabchip:hover,
.profile-products-tabchip:focus-visible { color: var(--text); }
.profile-products-tabchip.is-active {
    background: var(--brand);
    color: #fff;
}

/* Per-category pill row on the profile Products tab. Reuses the
   .mkt-cat-pill class system from the marketplace browse strip so
   the visual language matches; the wrapper just adds bottom margin
   and the same separator line treatment. The row is server-driven
   so it only lists categories the user has products in. */
.profile-products-cats {
    margin: 0 0 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border);
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}
.profile-products-cats .mkt-cat-pill {
    cursor: pointer;
    font: inherit;
}
.profile-products-cats .mkt-cat-pill:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 1px;
}

.profile-products-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 0.85rem;
}
@media (max-width: 560px) {
    .profile-products-grid {
        grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
        gap: 0.6rem;
    }
}

/* Reuses .mkt-card / .mkt-card-* visuals; profile-product-card adds
 * the hidden + edit overlays only. */
.profile-product-card {
    position: relative;
}
.profile-product-card.is-hidden {
    opacity: 0.7;
}

.profile-product-hidden-chip {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.25rem 0.5rem;
    background: rgba(244, 63, 94, 0.85);
    color: #fff;
    border-radius: 999px;
    font-size: 0.68rem;
    font-weight: 600;
    line-height: 1;
}

/* Edit button lives on the media block. Desktop: appears on hover.
 * Mobile / touch: always visible (no hover) so the owner can find it. */
.profile-product-edit-btn {
    position: absolute;
    bottom: 0.5rem;
    left: 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.5rem 0.7rem;
    min-height: 40px;
    background: rgba(15, 15, 15, 0.85);
    backdrop-filter: blur(4px);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 0.4rem;
    color: #fff;
    font-size: 0.78rem;
    font-weight: 600;
    text-decoration: none;
    line-height: 1;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity 0.12s, transform 0.12s, background 0.12s;
    pointer-events: auto;
    z-index: 2;
}
.profile-product-card:hover .profile-product-edit-btn,
.profile-product-card:focus-within .profile-product-edit-btn {
    opacity: 1;
    transform: translateY(0);
}
.profile-product-edit-btn:hover,
.profile-product-edit-btn:focus-visible {
    background: var(--brand);
    border-color: var(--brand);
    outline: none;
}

/* Touch + small viewports: hover affordances don't apply. Always show
 * the Edit pill so the owner can edit on a phone. */
@media (hover: none), (max-width: 768px) {
    .profile-product-edit-btn {
        opacity: 1;
        transform: translateY(0);
    }
}

.profile-product-skeleton {
    aspect-ratio: 1 / 1;
    background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02));
    border-radius: 0.6rem;
    border: 1px solid var(--border);
    animation: profileSkeletonPulse 1.4s ease-in-out infinite;
}
@keyframes profileSkeletonPulse {
    0%, 100% { opacity: 0.55; }
    50%      { opacity: 0.85; }
}

.profile-products-empty,
.profile-reviews-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.65rem;
    padding: 2.5rem 1rem;
    text-align: center;
    border: 1px dashed var(--border);
    border-radius: 0.6rem;
    background: rgba(255, 255, 255, 0.02);
}
.profile-products-empty-icon,
.profile-reviews-empty-icon {
    color: #3a3a3f;
}
.profile-products-empty-title,
.profile-reviews-empty-title {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.92rem;
    max-width: 28rem;
}
.profile-products-empty-cta,
.profile-reviews-empty-cta {
    margin-top: 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 40px;
}

.profile-products-status,
.profile-reviews-status {
    margin-top: 1rem;
    text-align: center;
    font-size: 0.82rem;
    color: var(--text-muted);
    display: inline-flex;
    width: 100%;
    justify-content: center;
    align-items: center;
    gap: 0.5rem;
}
.profile-products-spinner {
    width: 14px;
    height: 14px;
    border: 2px solid rgba(255, 255, 255, 0.18);
    border-top-color: var(--brand);
    border-radius: 50%;
    animation: profileSpin 0.8s linear infinite;
    display: inline-block;
}
@keyframes profileSpin { to { transform: rotate(360deg); } }

.profile-products-sentinel,
.profile-reviews-sentinel {
    height: 1px;
    width: 100%;
    margin-top: 0.5rem;
}

/* ---- Reviews pane ---------------------------------------------- */

.profile-reviews-list {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}

.profile-review-card {
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    padding: 0.85rem 0.95rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}

.profile-review-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
}
.profile-review-author {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    text-decoration: none;
    color: var(--text);
    min-width: 0;
    flex: 1 1 auto;
    border-radius: 0.4rem;
    padding: 0.15rem;
    margin: -0.15rem;
}
.profile-review-author:hover,
.profile-review-author:focus-visible {
    background: rgba(255, 255, 255, 0.04);
    outline: none;
}
.profile-review-avatar {
    width: 32px;
    height: 32px;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
    background: #1c1c20;
}
.profile-review-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 0.85rem;
    font-weight: 700;
    background: linear-gradient(135deg, #4c1d95, #7c3aed);
}
.profile-review-author-meta {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.2;
}
.profile-review-name {
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 0.9rem;
}
.profile-review-handle {
    font-size: 0.74rem;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.profile-review-time {
    font-size: 0.76rem;
    color: var(--text-muted);
    white-space: nowrap;
    flex-shrink: 0;
}

.profile-review-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.55rem;
}
.profile-review-stars { line-height: 0; }
.profile-review-ctx-chip {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.55rem;
    background: rgba(99, 102, 241, 0.16);
    color: #a5b4fc;
    border: 1px solid rgba(99, 102, 241, 0.3);
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1;
}

.profile-review-body {
    margin: 0;
    color: var(--text);
    font-size: 0.9rem;
    line-height: 1.45;
    /* Preserve newlines from raw textContent without HTML interpretation. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    word-break: break-word;
    max-width: 100%;
}

/* Product chip on a review card - links back to the listing the review
   was written for. Same row layout reused on the product detail page
   (no chip there - we're already on the product). */
.profile-review-product {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    text-decoration: none;
    color: var(--text);
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.35rem 0.55rem 0.35rem 0.4rem;
    align-self: flex-start;
    max-width: 100%;
    min-width: 0;
    min-height: 40px;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.profile-review-product:hover,
.profile-review-product:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    border-color: var(--brand);
    outline: none;
}
.profile-review-product-missing {
    cursor: default;
    color: var(--text-muted);
    font-style: italic;
}
.profile-review-product-missing:hover {
    background: rgba(255, 255, 255, 0.03);
    border-color: var(--border);
}
.profile-review-product-thumb {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: 0.4rem;
    overflow: hidden;
    background: #1c1c20;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.profile-review-product-thumb.is-empty { color: #3a3a3f; }
.profile-review-product-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.profile-review-product-meta {
    display: inline-flex;
    align-items: baseline;
    gap: 0.4rem;
    min-width: 0;
    flex: 1 1 auto;
}
.profile-review-product-prefix {
    color: var(--text-muted);
    font-size: 0.78rem;
    flex-shrink: 0;
}
.profile-review-product-title {
    color: var(--text);
    font-weight: 600;
    font-size: 0.86rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

.profile-review-skeleton {
    height: 100px;
    background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02));
    border-radius: 0.6rem;
    border: 1px solid var(--border);
    animation: profileSkeletonPulse 1.4s ease-in-out infinite;
}

@media (max-width: 560px) {
    .profile-review-card { padding: 0.7rem 0.75rem; }
    .profile-review-name { font-size: 0.86rem; }
    .profile-review-time { font-size: 0.72rem; }
    .profile-review-body { font-size: 0.86rem; }
    .profile-products-head { flex-direction: column; align-items: stretch; }
    .profile-products-new  { width: 100%; justify-content: center; }
    .profile-products-filter { align-self: flex-start; }
    .profile-tab { padding: 0.5rem 0.65rem; font-size: 0.86rem; }
}

@media (max-width: 360px) {
    .profile-products-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 0.5rem; }
    .profile-review-card { padding: 0.6rem 0.65rem; }
    .profile-review-product-thumb { width: 36px; height: 36px; }
    .profile-review-product-title { font-size: 0.82rem; }
}

/* ================================================================
 * Marketplace live-sync (Phase 10)
 *  - .mkt-card.is-leaving fades + collapses removed product cards
 *    on the browse grid so live-deletion feels animated.
 *  - .mkt-detail-removed is the "this listing has been removed" shell
 *    that replaces the detail-page mount when the product is deleted
 *    while the user is reading it.
 * ================================================================ */
.mkt-card.is-leaving {
    opacity: 0;
    transform: scale(0.96);
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.mkt-detail-removed {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.85rem;
    padding: 3rem 1rem;
    text-align: center;
    color: var(--text);
}
.mkt-detail-removed-icon { color: var(--text-muted); }
.mkt-detail-removed-title {
    margin: 0;
    font-size: 1.35rem;
    font-weight: 600;
}
.mkt-detail-removed-sub {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.95rem;
}
.mkt-detail-removed-back {
    margin-top: 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 40px;
    padding: 0 1rem;
}

@media (max-width: 560px) {
    .mkt-detail-removed { padding: 2rem 0.75rem; }
    .mkt-detail-removed-title { font-size: 1.15rem; }
}

/* ============================================================
   Marketplace editor: "Choose from storage" affordances.
   - .mp-storage-btn: secondary button under the preview drop-zone.
   - .mp-addmenu: tiny popover anchored to the gallery "+ Add" tile,
     two options (Upload / From storage).
   - .mp-storage-modal / .mp-storage-*: lazy-DOM modal listing the
     user's previously uploaded images. Reuses .modal /
     .modal-backdrop / .modal-card from main.css for backdrop blur,
     animation and stacking; overrides .modal-card width for the
     picker grid.
   ============================================================ */

.mp-storage-btn {
    align-self: stretch;
    margin-top: 0.5rem;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    font-size: 0.85rem;
}
.mp-storage-btn-icon { color: inherit; }

.mp-addmenu {
    position: fixed;
    z-index: 8000;
    min-width: 200px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.55);
    padding: 0.3rem;
    display: flex;
    flex-direction: column;
    gap: 2px;
    animation: modal-pop 0.12s ease-out;
}
.mp-addmenu-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.55rem 0.65rem;
    min-height: 40px;
    background: transparent;
    border: 0;
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.mp-addmenu-item:hover,
.mp-addmenu-item:focus-visible {
    background: rgba(124, 58, 237, 0.16);
    color: var(--text);
    outline: none;
}
.mp-addmenu-icon { color: var(--text-muted); flex-shrink: 0; }
.mp-addmenu-item:hover .mp-addmenu-icon,
.mp-addmenu-item:focus-visible .mp-addmenu-icon { color: var(--brand); }

/* ---- Storage picker modal ---- */

.mp-storage-modal .modal-card {
    width: min(48rem, 100%);
    max-width: calc(100vw - 1rem);
    /* Use dvh so iOS Safari URL-bar collapse doesn't push the
       footer button out of view. */
    max-height: calc(100dvh - 1.5rem);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.mp-storage-head {
    padding: 1rem 1rem 0.5rem;
    border-bottom: 1px solid var(--border);
}
.mp-storage-title {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
}
.mp-storage-subtitle {
    margin: 0.25rem 0 0.75rem;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.mp-storage-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.85rem 1rem 0.5rem;
    -webkit-overflow-scrolling: touch;
}
.mp-storage-state {
    padding: 2.5rem 1rem;
    text-align: center;
    color: var(--text-muted);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
}
.mp-storage-state-text { margin: 0; }
.mp-storage-spinner {
    width: 32px;
    height: 32px;
    border-radius: 999px;
    border: 3px solid rgba(167, 139, 250, 0.25);
    border-top-color: #a78bfa;
    animation: mp-storage-spin 0.9s linear infinite;
}
@keyframes mp-storage-spin {
    to { transform: rotate(360deg); }
}
.mp-storage-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 0.55rem;
}
.mp-storage-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 0;
    background: #0a0a0a;
    border: 2px solid transparent;
    border-radius: 0.55rem;
    overflow: hidden;
    cursor: pointer;
    color: inherit;
    font: inherit;
    text-align: left;
    transition: border-color 0.12s, transform 0.12s, box-shadow 0.12s;
}
.mp-storage-tile:hover,
.mp-storage-tile:focus-visible {
    border-color: var(--brand);
    outline: none;
}
.mp-storage-tile.is-selected {
    border-color: var(--brand);
    box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.35);
}
.mp-storage-tile-img-wrap {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    background: #050505;
    overflow: hidden;
}
.mp-storage-tile-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}
/* Storage picker video tile: same sizing as image tiles, with a play
   badge so the seller can tell videos apart from stills at a glance. */
.mp-storage-tile-img-wrap { position: relative; }
.mp-storage-tile-video-el { background: #000; }
.mp-storage-tile-play {
    position: absolute;
    inset: auto 6px 6px auto;
    width: 28px;
    height: 28px;
    border-radius: 999px;
    background: rgba(10, 10, 10, 0.78);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding-left: 2px;
    pointer-events: none;
    backdrop-filter: blur(2px);
}
.mp-storage-tile-meta {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 0.35rem 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 1px;
    background: linear-gradient(to top, rgba(0,0,0,0.85), rgba(0,0,0,0));
    color: #f4f4f5;
    font-size: 0.72rem;
    line-height: 1.25;
    opacity: 0;
    transition: opacity 0.12s;
    pointer-events: none;
}
.mp-storage-tile:hover .mp-storage-tile-meta,
.mp-storage-tile:focus-visible .mp-storage-tile-meta,
.mp-storage-tile.is-selected .mp-storage-tile-meta {
    opacity: 1;
}
.mp-storage-tile-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mp-storage-tile-dims {
    color: rgba(255, 255, 255, 0.78);
    font-size: 0.68rem;
}
.mp-storage-tile-check {
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    width: 22px;
    height: 22px;
    border-radius: 999px;
    background: var(--brand);
    color: #fff;
    display: none;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}
.mp-storage-tile.is-selected .mp-storage-tile-check { display: inline-flex; }

/* In multi-select mode the meta strip is always visible because
   tapping does not close the modal, so the user wants to see what
   they're picking. */
.mp-storage-modal.is-multi .mp-storage-tile-meta { opacity: 1; }

.mp-storage-foot {
    margin-top: 0.85rem;
    display: flex;
    justify-content: center;
}
.mp-storage-more { min-height: 40px; padding: 0 1rem; }

.mp-storage-actions {
    display: flex;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-top: 1px solid var(--border);
    background: var(--surface);
}
.mp-storage-actions .btn { flex: 1 1 auto; min-height: 40px; }

@media (max-width: 560px) {
    .mp-storage-modal .modal-card {
        width: 100%;
        max-height: 100dvh;
        max-width: 100vw;
        border-radius: 0;
        border-left: 0;
        border-right: 0;
    }
    .mp-storage-grid {
        grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
        gap: 0.45rem;
    }
    /* Always show meta on touch since hover doesn't apply. */
    .mp-storage-tile-meta { opacity: 1; }
}
@media (max-width: 360px) {
    .mp-storage-grid {
        grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    }
    .mp-storage-head { padding: 0.85rem 0.85rem 0.4rem; }
    .mp-storage-body { padding: 0.65rem 0.85rem 0.4rem; }
}

/* ============================================================
   Marketplace upload progress
   ------------------------------------------------------------
   Live progress UI rendered into the preview drop-zone and into
   each in-flight gallery tile. Linear bar at the bottom + the
   percentage text above it; the tile / drop-zone keeps the local
   object-URL preview behind a dark dim so the user sees their
   image loading in. Mobile-first: 360 px viewports get a smaller
   percentage label but the bar stays full-width so progress is
   always visible.
   ============================================================ */
.mp-upload-progress {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    padding: 0.5rem;
    background: rgba(0, 0, 0, 0.62);
    color: #f4f4f5;
    pointer-events: none;
    z-index: 2;
}
.mp-upload-progress-text {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    text-align: center;
    line-height: 1.2;
}
.mp-upload-progress-pct {
    font-size: 1.35rem;
    font-weight: 700;
    color: #fff;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}
.mp-upload-progress-bytes {
    font-size: 0.72rem;
    color: rgba(244, 244, 245, 0.85);
    font-variant-numeric: tabular-nums;
}
.mp-upload-progress-bar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 5px;
    background: rgba(255, 255, 255, 0.12);
    overflow: hidden;
}
.mp-upload-progress-bar-fill {
    height: 100%;
    width: 0%;
    background: linear-gradient(90deg, #a78bfa 0%, #c4b5fd 100%);
    transition: width 0.18s ease-out;
    box-shadow: 0 0 6px rgba(167, 139, 250, 0.55);
}
/* Tile variant - tighter spacing because the tile is smaller. */
.mp-upload-progress-tile {
    gap: 0.25rem;
    padding: 0.35rem;
}
.mp-upload-progress-tile .mp-upload-progress-pct {
    font-size: 1.05rem;
}
.mp-upload-progress-tile .mp-upload-progress-bytes {
    font-size: 0.66rem;
}

/* Cancel button reuses .mp-preview-remove / .mp-gallery-remove
   visuals; the .mp-upload-cancel modifier just nudges the tone. */
.mp-upload-cancel { background: rgba(0, 0, 0, 0.78); z-index: 3; }
.mp-upload-cancel:hover { background: rgba(220, 38, 38, 0.85); border-color: rgba(220, 38, 38, 0.9); }

/* Error overlay - shown when an upload fails with a Retry CTA.
   Sits above the locally-previewed image so the user still sees the
   file they tried to upload while choosing to retry or dismiss. */
.mp-upload-error {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.6rem;
    background: rgba(120, 18, 18, 0.78);
    color: #fef2f2;
    text-align: center;
    z-index: 2;
}
.mp-upload-error-icon { color: #fecaca; flex-shrink: 0; }
.mp-upload-error-text {
    font-size: 0.82rem;
    line-height: 1.25;
    max-width: 100%;
    overflow-wrap: anywhere;
}
.mp-upload-retry {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 32px;
    padding: 0.3rem 0.7rem;
    font-size: 0.82rem;
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: 0.4rem;
}
.mp-upload-retry:hover { background: rgba(255, 255, 255, 0.22); }
.mp-upload-error-tile {
    gap: 0.25rem;
    padding: 0.35rem;
}
.mp-upload-error-tile .mp-upload-error-text { font-size: 0.7rem; }
.mp-upload-retry-tile { font-size: 0.72rem; padding: 0.22rem 0.55rem; min-height: 28px; }

/* While uploading the gallery tile dims slightly so the placeholder
   reads as "in progress" alongside finished siblings. */
.mp-gallery-tile.is-uploading { cursor: progress; }
.mp-gallery-tile.is-uploading .mp-gallery-img { opacity: 0.55; filter: blur(1px); }
.mp-gallery-tile.is-error .mp-gallery-img    { opacity: 0.45; filter: blur(1px); }

/* Same dim + blur on the preview drop-zone preview image so the
   progress overlay reads cleanly on top. */
.mp-preview-drop.is-uploading .mp-preview-img { opacity: 0.55; filter: blur(1px); }
.mp-preview-drop.is-error     .mp-preview-img { opacity: 0.45; filter: blur(1px); }

@media (max-width: 480px) {
    .mp-upload-progress { gap: 0.3rem; padding: 0.35rem; }
    .mp-upload-progress-pct { font-size: 1.15rem; }
    .mp-upload-progress-bytes { font-size: 0.68rem; }
    .mp-upload-progress-tile .mp-upload-progress-pct { font-size: 0.92rem; }
    .mp-upload-progress-tile .mp-upload-progress-bytes { font-size: 0.6rem; }
    .mp-upload-progress-bar { height: 4px; }
}
@media (max-width: 360px) {
    .mp-upload-progress-tile .mp-upload-progress-bytes { display: none; }
}

/* Marketplace media embeds - inline images / videos rendered from URLs
   inside a product description's subset markdown. Mirrored in both the
   editor preview (marketplace-editor.js) and the public detail page
   (marketplace-detail.js). max-width: 100% clamps to the container so
   long descriptions don't overflow on 360 px viewports. */
.md-media {
    max-width: 100%;
    max-height: 480px;
    border-radius: 8px;
    margin: 0.5rem 0;
    background: rgba(0, 0, 0, 0.2);
    display: block;
}
.md-media-video {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: contain;
}
/* YouTube iframe wrapper. The wrapping div carries the .md-media
   constraints (max-width / margin / radius); the inner iframe fills it
   with a 16:9 aspect ratio so it stays visually consistent with the
   <video> element on mobile. */
.md-media-youtube {
    max-width: 720px;
    width: 100%;
    aspect-ratio: 16 / 9;
    margin: 0.5rem 0;
    border-radius: 8px;
    overflow: hidden;
    background: #000;
    max-height: none;
}
.md-media-youtube iframe {
    width: 100%;
    height: 100%;
    border: 0;
    display: block;
}

/* ============================================================
   Marketplace orders header button + dropdown popover (DM only).
   Sits next to the call button in the DM chat header. The button
   itself is hidden until chat-client.js's dm-orders fetch returns
   at least one order; the badge dot turns red when at least one
   order needs viewer action (accepted, or completed-but-not-reviewed).
   ============================================================ */
.chat-header-mkt-btn {
    position: relative;
    flex-shrink: 0;
    width: 2.25rem;
    height: 2.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: #1a1a1a;
    border: 1px solid var(--border);
    color: var(--text-muted);
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
    margin-left: 0.5rem;
}
.chat-header-mkt-btn:hover,
.chat-header-mkt-btn:focus-visible {
    background: #1f2937;
    border-color: #334155;
    color: var(--text);
}
.chat-header-mkt-btn[hidden] { display: none; }
.chat-header-mkt-btn-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ef4444;
    border: 2px solid var(--bg, #0f0f10);
    pointer-events: none;
}
.chat-header-mkt-btn-badge[hidden] { display: none; }

/* Popover: drops below the button, anchored by chat-client.js
   (style.top / style.left / style.width set inline). */
.mkt-orders-popover {
    position: fixed;
    z-index: 120;
    width: min(22rem, calc(100vw - 1rem));
    max-height: min(60dvh, 480px);
    display: flex;
    flex-direction: column;
    background: #0f0f10;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
    overflow: hidden;
}
.mkt-orders-popover[hidden] { display: none; }
.mkt-orders-popover-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.7rem 0.9rem;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.mkt-orders-popover-title {
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.mkt-orders-popover-close {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1.2rem;
    cursor: pointer;
    padding: 0 0.25rem;
    min-width: 32px;
    min-height: 32px;
    line-height: 1;
}
.mkt-orders-popover-close:hover { color: var(--text); }
.mkt-orders-popover-body {
    overflow-y: auto;
    padding: 0.25rem 0;
    flex: 1 1 auto;
    min-height: 0;
}
.mkt-orders-popover-loading,
.mkt-orders-popover-empty {
    padding: 1.5rem 0.75rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.mkt-orders-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.mkt-orders-row {
    display: flex;
    gap: 0.6rem;
    padding: 0.6rem 0.75rem;
    align-items: center;
    border-bottom: 1px solid var(--border);
    min-height: 56px;
    /* Status tint - applied as a left border + subtle bg via the
       .is-status-* modifier classes set in JS (buildOrderRow). The
       border-left is laid out via border-left-color (kept transparent
       by default) so adding a coloured variant doesn't shift the row's
       inner geometry. */
    border-left: 3px solid transparent;
}
.mkt-orders-row:last-child { border-bottom: none; }
/* Bare-selector tints (kept for the DM-only popover variant of the
   orders list, which is NOT scoped under .mkt-orders-pane). The page-
   level pane has higher-specificity overrides below; without those
   the strong dark background on .mkt-orders-pane .mkt-orders-row would
   eat these. */
.mkt-orders-row.is-status-pending {
    border-left-color: #f59e0b;
    background: rgba(245, 158, 11, 0.06);
}
.mkt-orders-row.is-status-completed {
    border-left-color: #10b981;
    background: rgba(16, 185, 129, 0.06);
}
.mkt-orders-row.is-status-cancelled {
    border-left-color: #ef4444;
    background: rgba(239, 68, 68, 0.06);
    opacity: 0.82;
}
.mkt-orders-row-thumb {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: 0.4rem;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.04);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
}
.mkt-orders-row-thumb-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.mkt-orders-row-thumb-fallback .lucide {
    width: 20px;
    height: 20px;
}
.mkt-orders-row-meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.mkt-orders-row-title {
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mkt-orders-row-title-link {
    color: inherit;
    text-decoration: none;
}
.mkt-orders-row-title-link:hover,
.mkt-orders-row-title-link:focus-visible {
    text-decoration: underline;
}
.mkt-orders-row-deleted {
    color: var(--text-muted);
    font-weight: 400;
    font-size: 0.8rem;
}
.mkt-orders-row-status {
    font-size: 0.78rem;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mkt-orders-row-status.is-action-needed {
    color: #fbbf24;
    font-weight: 500;
}
.mkt-orders-row-action {
    flex-shrink: 0;
    display: flex;
    align-items: center;
}
.mkt-orders-row-btn {
    font-size: 0.78rem;
    padding: 0.4rem 0.7rem;
    min-height: 32px;
    border-radius: 0.4rem;
    text-decoration: none;
    white-space: nowrap;
}
.mkt-orders-row-btn.is-disabled {
    color: var(--text-muted);
    cursor: not-allowed;
    border: 1px dashed var(--border);
    padding: 0.35rem 0.55rem;
    border-radius: 0.4rem;
}

/* Mobile: <=560 px the popover takes most of the viewport width and
   the action button drops to its own row so the title isn't squashed. */
@media (max-width: 560px) {
    .mkt-orders-popover {
        width: calc(100vw - 1rem) !important;
        max-height: min(70dvh, 540px);
    }
    .mkt-orders-row {
        flex-wrap: wrap;
        align-items: flex-start;
    }
    .mkt-orders-row-meta {
        flex: 1 1 calc(100% - 52px);
    }
    .mkt-orders-row-action {
        flex: 1 0 100%;
        margin-left: 52px;
        margin-top: 0.35rem;
    }
    .mkt-orders-row-btn {
        min-height: 36px;
    }
}

/* ================================================================
 * Marketplace My Orders tab
 *
 * Tabs row sits above the toolbar in /marketplace; clicking My Orders
 * swaps the visible content from the browse grid to a list of the
 * viewer's orders. Mobile-first.
 *
 * IMPORTANT: the DM-only popover above already uses .mkt-orders-list
 * / .mkt-orders-row / .mkt-orders-empty / .mkt-orders-status etc.
 * To avoid stomping on those rules we scope every selector here under
 * .mkt-orders-pane so the page-level pane's overrides only apply on
 * /marketplace, never inside the DM popover on /chat.
 * ================================================================ */

/* ---- Primary marketplace tabs (Browse / Looking for) -------------
 *
 * Two-tier structure:
 *   .mkt-aux-row      - small auxiliary pills row (How it works /
 *                       Marketplace rules), right-aligned above the
 *                       primary tabs.
 *   .mkt-primary-tabs - two big card buttons.
 *   .mkt-sub-tabs     - smaller pill row inside the Browse pane:
 *                       Listings | My Orders | Cart.
 */
.mkt-aux-row {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0 0 0.75rem;
}
.mkt-primary-tabs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
    margin: 0 0 1.25rem;
}
@media (max-width: 560px) {
    .mkt-primary-tabs {
        grid-template-columns: 1fr;
        gap: 0.5rem;
    }
    .mkt-aux-row {
        justify-content: center;
    }
}
.mkt-primary-tab {
    appearance: none;
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 1rem 1.25rem;
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--border);
    border-radius: 0.75rem;
    cursor: pointer;
    text-align: left;
    transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
    color: var(--text-muted);
    width: 100%;
    min-height: 72px;
    font: inherit;
}
.mkt-primary-tab:hover,
.mkt-primary-tab:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    border-color: rgba(255, 255, 255, 0.15);
    color: var(--text);
    transform: translateY(-1px);
    outline: none;
}
.mkt-primary-tab.is-active {
    background: linear-gradient(135deg, rgba(251, 191, 36, 0.18), rgba(251, 191, 36, 0.06));
    border-color: #fbbf24;
    color: #fbbf24;
    box-shadow: 0 4px 12px rgba(251, 191, 36, 0.15);
}
.mkt-primary-tab.is-active:hover {
    transform: translateY(-1px);
}
.mkt-primary-tab-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 0.6rem;
    background: rgba(255, 255, 255, 0.05);
    flex-shrink: 0;
    color: inherit;
}
.mkt-primary-tab.is-active .mkt-primary-tab-icon {
    background: rgba(251, 191, 36, 0.2);
}
.mkt-primary-tab-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.mkt-primary-tab-title {
    font-size: 1.05rem;
    font-weight: 700;
    line-height: 1.2;
}
.mkt-primary-tab-subtitle {
    font-size: 0.82rem;
    color: var(--text-muted);
    margin-top: 0.15rem;
    line-height: 1.3;
}
.mkt-primary-tab.is-active .mkt-primary-tab-subtitle {
    color: rgba(251, 191, 36, 0.8);
}

/* ---- Browse sub-tabs (Listings / My Orders / Cart) ---------------- */
.mkt-sub-tabs {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    margin: 0 0 1rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--border);
}
.mkt-sub-tabs[hidden] { display: none; }
.mkt-sub-tab {
    appearance: none;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.9rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 0.45rem;
    color: var(--text-muted);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    line-height: 1;
    min-height: 40px;
    position: relative; /* anchor for .mkt-tabs-btn-badge */
    transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.mkt-sub-tab:hover,
.mkt-sub-tab:focus-visible {
    color: var(--text);
    background: rgba(255, 255, 255, 0.04);
    outline: none;
}
.mkt-sub-tab.is-active {
    color: #fbbf24;
    background: rgba(251, 191, 36, 0.1);
    border-color: rgba(251, 191, 36, 0.35);
}
.mkt-sub-tab-icon { flex-shrink: 0; }
.mkt-sub-tab-label { line-height: 1; }
@media (max-width: 560px) {
    .mkt-sub-tab {
        flex: 1 1 auto;
        justify-content: center;
        text-align: center;
    }
}

/* Red 8 px dot in the top-right corner of the My Orders sub-tab when
   the viewer has at least one order needing action. Mirrors
   .primary-nav-badge so the visual language matches the nav pill.
   Class is shared with the legacy tabs button so the count badge
   logic in marketplace-browse.js keeps working. */
.mkt-tabs-btn-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ef4444;
    border: 2px solid var(--bg);
    pointer-events: none;
}
.mkt-tabs-btn-badge[hidden] { display: none; }

/* ---- Orders pane (scoped under .mkt-orders-pane) ---- */

.mkt-orders-pane {
    display: block;
}

.mkt-orders-pane .mkt-orders-filterbar {
    display: flex;
    align-items: center;
    margin: 0 0 0.75rem;
}
.mkt-orders-pane .mkt-orders-filterbar-inner {
    display: inline-flex;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.mkt-orders-pane .mkt-orders-filter-chip {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    font: inherit;
    font-size: 0.82rem;
    padding: 0.45rem 0.8rem;
    min-height: 36px;
    border-radius: 999px;
    cursor: pointer;
    line-height: 1;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.mkt-orders-pane .mkt-orders-filter-chip:hover,
.mkt-orders-pane .mkt-orders-filter-chip:focus-visible {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
    border-color: var(--text-muted);
    outline: none;
}
.mkt-orders-pane .mkt-orders-filter-chip.is-active {
    background: rgba(251, 191, 36, 0.18);
    border-color: rgba(251, 191, 36, 0.55);
    color: #fbbf24;
}

.mkt-orders-pane .mkt-orders-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.mkt-orders-pane .mkt-orders-row {
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    padding: 0.7rem 0.85rem;
    /* Override DM-popover defaults: no border-bottom-stripe shell. */
    border-bottom: 1px solid var(--border);
    min-height: 0;
    display: block;
    transition: border-color 0.12s, background 0.12s;
    /* Status indicator slot: 4px coloured left border for tinted rows.
       Default transparent so untinted rows don't shift; per-status
       overrides below set the colour. */
    border-left: 4px solid var(--border);
}
.mkt-orders-pane .mkt-orders-row:hover {
    border-color: #3a3a40;
    background: #16161a;
}

/* ---- Per-status row tints (scoped, high specificity) -------------
   These need to win against `.mkt-orders-pane .mkt-orders-row { background }`
   above. The :hover state also gets explicit overrides so the tint
   doesn't vanish when the cursor lands on a row. Backgrounds are
   stronger than the bare-selector variants because the pane sits on
   a dark #131316 surface and weaker tints get visually swallowed. */
.mkt-orders-pane .mkt-orders-row.is-status-pending {
    border-left-color: #f59e0b;                /* amber-500 */
    background: #1d1a13;                       /* warm amber wash */
}
.mkt-orders-pane .mkt-orders-row.is-status-pending:hover {
    background: #221e15;
}
.mkt-orders-pane .mkt-orders-row.is-status-completed {
    border-left-color: #10b981;                /* emerald-500 */
    background: #141d18;                       /* cool green wash */
}
.mkt-orders-pane .mkt-orders-row.is-status-completed:hover {
    background: #172319;
}
.mkt-orders-pane .mkt-orders-row.is-status-cancelled {
    border-left-color: #ef4444;                /* red-500 */
    background: #1c1414;                       /* dim red wash */
    opacity: 0.85;
}
.mkt-orders-pane .mkt-orders-row.is-status-cancelled:hover {
    background: #211616;
    opacity: 1;
}
.mkt-orders-pane .mkt-orders-row-body {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    cursor: pointer;
    min-width: 0;
}
.mkt-orders-pane .mkt-orders-row-thumb {
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: 0.4rem;
    overflow: hidden;
    background: #0c0c0e;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #3a3a40;
}
.mkt-orders-pane .mkt-orders-row-thumb-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    user-select: none;
    pointer-events: none;
}
.mkt-orders-pane .mkt-orders-row-meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    overflow: hidden;
}
.mkt-orders-pane .mkt-orders-row-title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.mkt-orders-pane .mkt-orders-row-cp {
    font-size: 0.82rem;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mkt-orders-pane .mkt-orders-row-counterparty {
    color: var(--text);
    text-decoration: none;
    border-bottom: 1px dotted transparent;
}
.mkt-orders-pane .mkt-orders-row-counterparty:hover {
    border-bottom-color: var(--text-muted);
}
.mkt-orders-pane .mkt-orders-row-status {
    font-size: 0.78rem;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mkt-orders-pane .mkt-orders-row-status.is-action-needed {
    color: #fbbf24;
    font-weight: 500;
}
.mkt-orders-pane .mkt-orders-row-status.is-muted {
    color: #6a6a70;
}
.mkt-orders-pane .mkt-orders-row-status-sep {
    color: #3a3a40;
    margin: 0 0.15rem;
}
.mkt-orders-pane .mkt-orders-row-status-time {
    color: inherit;
    opacity: 0.85;
}
.mkt-orders-pane .mkt-orders-row-action {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
}
.mkt-orders-pane .mkt-orders-row-action .mkt-orders-row-btn {
    font-size: 0.82rem;
    padding: 0.45rem 0.85rem;
    min-height: 36px;
    border-radius: 0.45rem;
    text-decoration: none;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    line-height: 1;
}
.mkt-orders-pane .mkt-orders-row-action .mkt-orders-row-btn.is-disabled {
    color: var(--text-muted);
    cursor: not-allowed;
    border: 1px dashed var(--border);
    background: transparent;
}

.mkt-orders-pane .mkt-orders-empty {
    text-align: center;
    padding: 2.5rem 1rem;
    color: var(--text-muted);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
}
.mkt-orders-pane .mkt-orders-empty-icon {
    color: #3a3a40;
}
.mkt-orders-pane .mkt-orders-empty-title {
    margin: 0;
    font-size: 0.95rem;
}
.mkt-orders-pane .mkt-orders-empty-action {
    margin-top: 0.4rem;
    min-height: 40px;
}

.mkt-orders-pane .mkt-orders-status {
    margin-top: 0.5rem;
    font-size: 0.85rem;
    color: var(--text-muted);
    text-align: center;
    min-height: 1.1rem;
}

.mkt-orders-pane .mkt-orders-loadmore-wrap {
    display: flex;
    justify-content: center;
    margin: 1rem 0 0.25rem;
}
.mkt-orders-pane .mkt-orders-loadmore-btn {
    min-width: 9rem;
    min-height: 40px;
}

/* Mobile: <=560 px the row stacks the action button below the meta
   column so a long product title doesn't get squashed by the action. */
@media (max-width: 560px) {
    .mkt-orders-pane .mkt-orders-row {
        padding: 0.65rem 0.7rem;
    }
    .mkt-orders-pane .mkt-orders-row-body {
        flex-wrap: wrap;
        align-items: flex-start;
        row-gap: 0.5rem;
    }
    .mkt-orders-pane .mkt-orders-row-meta {
        flex: 1 1 calc(100% - 52px);
    }
    .mkt-orders-pane .mkt-orders-row-action {
        flex: 1 0 100%;
        margin-left: 52px;
    }
    .mkt-orders-pane .mkt-orders-row-action .mkt-orders-row-btn {
        min-height: 40px;
        width: auto;
    }
}

/* ================================================================
 * Marketplace Cart pane (Phase 11).
 *
 * Sub-tab badge "Cart (N)" lives on the same .mkt-sub-tab as Listings /
 * My Orders. The pane scaffold is .mkt-cart-pane; per-seller groups
 * are .mkt-cart-group; line items are .mkt-cart-item with an inline
 * qty -/+ control + remove button. Footer holds an optional buyer
 * note + Clear / Checkout buttons.
 *
 * All selectors are scoped under .mkt-cart-pane so they don't bleed
 * into the chat client's marketplace chip styles.
 * ================================================================ */
.mkt-tabs-btn-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4rem;
    height: 1.1rem;
    margin-left: 0.15rem;
    padding: 0 0.4rem;
    border-radius: 999px;
    background: rgba(251, 191, 36, 0.18);
    color: #fbbf24;
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1;
}
.mkt-tabs-btn-count[hidden] { display: none; }
.mkt-sub-tab.is-active .mkt-tabs-btn-count {
    background: rgba(251, 191, 36, 0.32);
    color: #fde68a;
}

.mkt-cart-pane {
    margin-top: 1rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.mkt-cart-pane[hidden] { display: none; }

.mkt-cart-pane .mkt-cart-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    padding: 2.5rem 1rem;
    border: 1px dashed rgba(255, 255, 255, 0.08);
    border-radius: 12px;
    color: var(--text-muted);
    text-align: center;
}
.mkt-cart-pane .mkt-cart-empty-icon {
    color: rgba(255, 255, 255, 0.18);
}
.mkt-cart-pane .mkt-cart-empty-title {
    margin: 0;
    font-size: 1rem;
}
.mkt-cart-pane .mkt-cart-empty-action {
    min-height: 40px;
}

.mkt-cart-pane .mkt-cart-groups {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.mkt-cart-pane .mkt-cart-group {
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
    padding: 0.85rem 0.9rem;
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.025);
}
.mkt-cart-pane .mkt-cart-group-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-height: 32px;
}
.mkt-cart-pane .mkt-cart-group-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 32px;
    background: rgba(255, 255, 255, 0.05);
}
.mkt-cart-pane .mkt-cart-group-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    color: var(--text-muted);
}
.mkt-cart-pane .mkt-cart-group-name {
    font-weight: 600;
    color: var(--text);
    text-decoration: none;
}
.mkt-cart-pane a.mkt-cart-group-name:hover,
.mkt-cart-pane a.mkt-cart-group-name:focus-visible {
    text-decoration: underline;
}

.mkt-cart-pane .mkt-cart-items {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-left: 44px; /* avatar 32 + gap 12 = same indent as Discord-style threading */
}

.mkt-cart-pane .mkt-cart-item {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.55rem 0.6rem;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.03);
}
.mkt-cart-pane .mkt-cart-item-thumb {
    width: 40px;
    height: 40px;
    flex: 0 0 40px;
    border-radius: 6px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.04);
    display: flex;
    align-items: center;
    justify-content: center;
}
.mkt-cart-pane .mkt-cart-item-thumb.is-empty {
    color: rgba(255, 255, 255, 0.25);
}
.mkt-cart-pane .mkt-cart-item-thumb-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.mkt-cart-pane .mkt-cart-item-meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.mkt-cart-pane .mkt-cart-item-title {
    color: var(--text);
    font-weight: 500;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}
.mkt-cart-pane a.mkt-cart-item-title:hover,
.mkt-cart-pane a.mkt-cart-item-title:focus-visible {
    text-decoration: underline;
}
.mkt-cart-pane .mkt-cart-item-price {
    font-size: 0.85rem;
    color: var(--text-muted);
}
.mkt-cart-pane .mkt-cart-item-price.is-on-request {
    font-style: italic;
}
/* On-sale cart line: strikethrough original sits inline with bold red
   sale price + a small "-N%" pill, mirroring the browse card / chip
   visual language. flex-wrap so it never blows out 360px viewports. */
.mkt-cart-pane .mkt-cart-item-price.is-on-sale {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.35rem;
    color: var(--text);
}
.mkt-cart-item-price-original {
    color: var(--text-muted);
    text-decoration: line-through;
    font-size: 0.85rem;
    font-weight: 500;
}
.mkt-cart-item-price-sale {
    color: #ef4444;
    font-weight: 700;
}
.mkt-cart-item-sale-badge {
    display: inline-block;
    background: #ef4444;
    color: #fff;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    padding: 0.1rem 0.35rem;
    border-radius: 0.3rem;
    line-height: 1.2;
}

.mkt-cart-pane .mkt-cart-item-controls {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 0 0 auto;
}
.mkt-cart-pane .mkt-cart-item-qty {
    display: inline-flex;
    align-items: center;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    overflow: hidden;
    background: rgba(0, 0, 0, 0.18);
}
.mkt-cart-pane .mkt-cart-qty-btn {
    width: 36px;
    height: 36px;
    min-width: 36px;
    min-height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    color: var(--text);
    border: 0;
    cursor: pointer;
    padding: 0;
}
.mkt-cart-pane .mkt-cart-qty-btn:hover,
.mkt-cart-pane .mkt-cart-qty-btn:focus-visible {
    background: rgba(255, 255, 255, 0.05);
}
.mkt-cart-pane .mkt-cart-qty-num {
    min-width: 2rem;
    text-align: center;
    padding: 0 0.4rem;
    font-weight: 600;
    color: var(--text);
}
.mkt-cart-pane .mkt-cart-item-remove {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    height: 36px;
    min-height: 36px;
    padding: 0 0.7rem;
    border-radius: 8px;
    background: transparent;
    border: 1px solid rgba(239, 68, 68, 0.25);
    color: #fca5a5;
    cursor: pointer;
    font-size: 0.85rem;
}
.mkt-cart-pane .mkt-cart-item-remove:hover,
.mkt-cart-pane .mkt-cart-item-remove:focus-visible {
    background: rgba(239, 68, 68, 0.1);
    color: #fecaca;
}

.mkt-cart-pane .mkt-cart-group-subtotal {
    display: flex;
    justify-content: flex-end;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.4rem 0.4rem 0;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    color: var(--text);
    font-size: 0.9rem;
}
.mkt-cart-pane .mkt-cart-group-subtotal-label {
    color: var(--text-muted);
}
.mkt-cart-pane .mkt-cart-group-subtotal-amount {
    font-weight: 600;
}
.mkt-cart-pane .mkt-cart-group-subtotal.is-mixed {
    color: var(--text-muted);
    font-style: italic;
}

.mkt-cart-pane .mkt-cart-summary {
    margin-top: 0.5rem;
    color: var(--text);
    font-weight: 500;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}
.mkt-cart-pane .mkt-cart-summary-text {
    color: var(--text);
}
/* Grand total row inside the summary slot. Sums every group's
   sale-aware subtotal; falls back to a muted "Mixed pricing" caveat
   with an info icon when groups span currencies / contain on-request
   items. */
.mkt-cart-pane .mkt-cart-grand {
    display: inline-flex;
    align-items: baseline;
    gap: 0.5rem;
    font-size: 1rem;
}
.mkt-cart-pane .mkt-cart-grand-label {
    color: var(--text-muted);
    font-weight: 500;
}
.mkt-cart-pane .mkt-cart-grand-amount {
    color: var(--text);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.mkt-cart-pane .mkt-cart-grand.is-mixed {
    color: var(--text-muted);
    font-style: italic;
    font-weight: 500;
}
.mkt-cart-pane .mkt-cart-grand-mixed {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    color: var(--text-muted);
}
.mkt-cart-pane .mkt-cart-grand-mixed-icon {
    color: var(--text-muted);
    flex: 0 0 auto;
}

.mkt-cart-pane .mkt-cart-footer {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding-top: 0.75rem;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.mkt-cart-pane .mkt-cart-note {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.mkt-cart-pane .mkt-cart-note-label {
    font-size: 0.85rem;
    color: var(--text-muted);
}
.mkt-cart-pane .mkt-cart-note-textarea {
    width: 100%;
    min-height: 70px;
    resize: vertical;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 8px;
    background: rgba(0, 0, 0, 0.18);
    color: var(--text);
    padding: 0.55rem 0.7rem;
    font: inherit;
    line-height: 1.4;
    box-sizing: border-box;
}
.mkt-cart-pane .mkt-cart-note-textarea:focus {
    outline: 2px solid rgba(251, 191, 36, 0.4);
    outline-offset: 1px;
}
.mkt-cart-pane .mkt-cart-note-counter {
    align-self: flex-end;
    font-size: 0.75rem;
    color: var(--text-muted);
}

.mkt-cart-pane .mkt-cart-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.mkt-cart-pane .mkt-cart-clear-btn,
.mkt-cart-pane .mkt-cart-checkout-btn {
    min-height: 40px;
}
.mkt-cart-pane .mkt-cart-checkout-btn {
    background: linear-gradient(135deg, #fbbf24, #f59e0b);
    color: #1f1300;
    border: 0;
}
.mkt-cart-pane .mkt-cart-checkout-btn:hover:not(:disabled),
.mkt-cart-pane .mkt-cart-checkout-btn:focus-visible:not(:disabled) {
    background: linear-gradient(135deg, #fcd34d, #fbbf24);
}
.mkt-cart-pane .mkt-cart-checkout-btn.is-busy,
.mkt-cart-pane .mkt-cart-checkout-btn:disabled {
    opacity: 0.6;
    cursor: wait;
}

/* ================================================================
 * Multi-item chip body (chat marketplace product chip).
 *
 * When order.items.length > 1 we swap the single-product layout for a
 * stacked summary - "{N} items - {Seller}" header, line rows of
 * thumb + title + xQty, capped at 3 + "and N more". Action zone is
 * unchanged.
 * ================================================================ */
.mkt-product-chip.is-multi-item {
    flex-direction: column;
    align-items: stretch;
}
.mkt-product-chip-body-multi {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    padding: 0.55rem 0.75rem;
    color: inherit;
    text-decoration: none;
    border-radius: 8px;
}
.mkt-product-chip-body-multi:hover,
.mkt-product-chip-body-multi:focus-visible {
    background: rgba(255, 255, 255, 0.03);
}
.mkt-product-chip-multi-head {
    font-weight: 600;
    color: var(--text);
}
.mkt-product-chip-multi-count { color: #fbbf24; }
.mkt-product-chip-multi-sep   { color: var(--text-muted); }
.mkt-product-chip-multi-seller { color: var(--text); }

.mkt-product-chip-multi-list {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.mkt-product-chip-multi-line {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    /* Each line is now its own <a> to the line's product page. Defeat
       the default link styling so the row keeps the existing visual
       (no underline, no link color), but surface a subtle hover hint
       so the user can tell it's clickable. */
    color: inherit;
    text-decoration: none;
    border-radius: 0.3rem;
    padding: 0.15rem 0.25rem;
    margin: 0 -0.25rem;
    transition: background-color 0.1s;
}
.mkt-product-chip-multi-line:hover,
.mkt-product-chip-multi-line:focus-visible {
    background: rgba(255, 255, 255, 0.05);
    text-decoration: none;
    outline: none;
}
.mkt-product-chip-multi-line:focus-visible {
    box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.35);
}
.mkt-product-chip-multi-thumb {
    width: 24px;
    height: 24px;
    min-width: 24px;
    border-radius: 4px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.06);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.mkt-product-chip-multi-thumb.is-empty {
    color: rgba(255, 255, 255, 0.25);
}
.mkt-product-chip-multi-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.mkt-product-chip-multi-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
}
.mkt-product-chip-multi-qty {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
}
/* Per-line price marker (qty x unit = line total or "Ask for price").
   Pinned to the right edge with `margin-left: auto` so the title
   ellipsis kicks in before the price wraps. */
.mkt-product-chip-multi-line-price {
    flex: 0 0 auto;
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.85rem;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.mkt-product-chip-multi-line-price.is-on-request {
    font-style: italic;
}
.mkt-product-chip-multi-more {
    font-size: 0.8rem;
    color: var(--text-muted);
}
/* When the "and N more" line is wired as an expand toggle (multi-item
   orders with > 3 lines) it gets is-toggle. Make it look clickable,
   underline on hover, and a focus ring for keyboard users. */
.mkt-product-chip-multi-more.is-toggle {
    cursor: pointer;
    color: var(--brand);
    user-select: none;
    display: inline-block;
    padding: 0.15rem 0;
}
.mkt-product-chip-multi-more.is-toggle:hover,
.mkt-product-chip-multi-more.is-toggle:focus-visible {
    text-decoration: underline;
    outline: none;
}
.mkt-product-chip-multi-more.is-toggle:focus-visible {
    box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.35);
    border-radius: 0.25rem;
}
/* Order total row pinned to the chip footer. Two children: a "Total"
   label on the left, the value on the right. Wraps cleanly at narrow
   widths because the row is `flex-wrap: wrap`; the value gets its
   own line on a 360px viewport when the math is long. */
.mkt-product-chip-multi-total {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.5rem;
    margin-top: 0.4rem;
    padding-top: 0.4rem;
    border-top: 1px solid var(--border);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.mkt-product-chip-multi-total-label {
    color: var(--text-muted);
    font-weight: 500;
}
.mkt-product-chip-multi-total-value {
    color: var(--text);
}
.mkt-product-chip-multi-total-value.is-muted {
    color: var(--text-muted);
    font-weight: 500;
    font-style: italic;
}
/* Tiny caption under the total when at least one line was on sale at
   checkout. Keeps the chip honest about *why* the total looks lower
   than the sticker prices on the live listings. */
.mkt-product-chip-multi-total-note {
    margin-top: 0.15rem;
    color: var(--text-muted);
    font-size: 0.72rem;
    font-style: italic;
    text-align: right;
}
@media (max-width: 420px) {
    .mkt-product-chip-multi-line-price {
        font-size: 0.8rem;
    }
}

/* ================================================================
 * DM orders popover + My Orders tab: multi-item rendering.
 * Keep selectors scoped so they don't bleed (DM popover uses unscoped
 * .mkt-orders-row; the My Orders tab uses .mkt-orders-pane scope).
 * ================================================================ */
.mkt-orders-row.is-multi-item .mkt-orders-row-multi-count {
    color: #fbbf24;
    font-weight: 600;
    margin-right: 0.25rem;
}
.mkt-orders-row.is-multi-item .mkt-orders-row-multi-titles {
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
}

.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-list {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-top: 0.35rem;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-line {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    min-width: 0;
    /* Each line is its own <a> to the line's product page. Defeat the
       default link styling so the row matches the surrounding visual,
       and surface a subtle background tint + focus ring as the
       "clickable" affordance. */
    color: inherit;
    text-decoration: none;
    border-radius: 0.3rem;
    padding: 0.15rem 0.3rem;
    margin: 0 -0.3rem;
    transition: background-color 0.1s;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item a.mkt-orders-row-multi-line:hover,
.mkt-orders-pane .mkt-orders-row.is-multi-item a.mkt-orders-row-multi-line:focus-visible {
    background: rgba(255, 255, 255, 0.06);
    text-decoration: none;
    outline: none;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item a.mkt-orders-row-multi-line:focus-visible {
    box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.35);
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-thumb {
    width: 22px;
    height: 22px;
    min-width: 22px;
    border-radius: 4px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.04);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-thumb.is-empty {
    color: rgba(255, 255, 255, 0.25);
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-thumb-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
    text-decoration: none;
    font-size: 0.9rem;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item a.mkt-orders-row-multi-name:hover,
.mkt-orders-pane .mkt-orders-row.is-multi-item a.mkt-orders-row-multi-name:focus-visible {
    text-decoration: underline;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-qty {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    font-size: 0.85rem;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-more {
    font-size: 0.8rem;
    color: var(--text-muted);
}
/* When the "and N more" line is wired as an expand toggle (multi-item
   orders with > 3 lines) it gets is-toggle. Make it look clickable so
   the user knows it actually opens the rest of the list. */
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-more.is-toggle {
    cursor: pointer;
    color: var(--brand);
    user-select: none;
    display: inline-block;
    padding: 0.15rem 0;
    align-self: flex-start;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-more.is-toggle:hover,
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-more.is-toggle:focus-visible {
    text-decoration: underline;
    outline: none;
}
.mkt-orders-pane .mkt-orders-row.is-multi-item .mkt-orders-row-multi-more.is-toggle:focus-visible {
    box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.35);
    border-radius: 0.25rem;
}

/* ================================================================
 * Detail page: Add to cart button (sits next to Purchase now).
 * Ghost button layout - fits between Share + Purchase visually.
 * ================================================================ */
.mkt-detail-cart {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 40px;
}
.mkt-detail-cart.is-incart {
    background: rgba(251, 191, 36, 0.08);
    border-color: rgba(251, 191, 36, 0.3);
    color: #fbbf24;
}
.mkt-detail-cart.is-incart:hover:not(:disabled),
.mkt-detail-cart.is-incart:focus-visible:not(:disabled) {
    background: rgba(251, 191, 36, 0.16);
}
.mkt-detail-cart.is-busy {
    opacity: 0.7;
    cursor: wait;
}
.mkt-detail-cart.is-muted {
    opacity: 0.6;
    cursor: not-allowed;
}

/* Mobile (<= 560 px): cart pane responsiveness. */
@media (max-width: 560px) {
    .mkt-cart-pane .mkt-cart-items {
        margin-left: 0; /* drop indent so 360 px viewport doesn't squeeze */
    }
    .mkt-cart-pane .mkt-cart-item {
        flex-wrap: wrap;
        align-items: flex-start;
        gap: 0.5rem 0.7rem;
    }
    .mkt-cart-pane .mkt-cart-item-meta {
        flex: 1 1 calc(100% - 52px);
    }
    .mkt-cart-pane .mkt-cart-item-controls {
        flex: 1 0 100%;
        margin-left: 52px;
        justify-content: space-between;
    }
    .mkt-cart-pane .mkt-cart-actions {
        flex-direction: column-reverse;
    }
    .mkt-cart-pane .mkt-cart-actions .btn {
        width: 100%;
    }
    .mkt-detail-cart {
        width: 100%;
        justify-content: center;
    }
}

/* Mobile (<= 360 px): tighten qty controls so the row fits. */
@media (max-width: 360px) {
    .mkt-cart-pane .mkt-cart-qty-btn {
        width: 32px;
        min-width: 32px;
        height: 32px;
        min-height: 32px;
    }
    .mkt-cart-pane .mkt-cart-item-remove .mkt-cart-item-remove-label {
        display: none; /* keep just the trash icon to save horizontal space */
    }
    .mkt-cart-pane .mkt-cart-item-remove {
        padding: 0 0.5rem;
        height: 32px;
        min-height: 32px;
    }
}

/* ================================================================
 * Inapp DM notifications
 *
 * Bottom-right toast stack rendered by the polling IIFE in site.js
 * for new DM/group messages while the user is parked off /chat.
 * Independent of OS Notifications - corner toasts as a courtesy
 * ping that survive blocked / unsupported native notifications.
 * Stack uses column-reverse so the newest card sits at the bottom
 * (closest to the user's mouse / thumb).
 * ================================================================ */
#madrigal-inapp-notif-stack {
    position: fixed;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 1rem);
    right: 1rem;
    z-index: 99999;
    display: flex;
    flex-direction: column-reverse;
    gap: 0.5rem;
    pointer-events: none;
    max-width: min(380px, calc(100vw - 2rem));
}

.madrigal-inapp-notif {
    pointer-events: auto;
    background: #1f1f23;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    padding: 0.75rem 0.85rem;
    display: flex;
    gap: 0.6rem;
    align-items: flex-start;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
    transform: translateX(120%);
    opacity: 0;
    transition: transform 0.25s ease, opacity 0.25s ease;
    cursor: pointer;
    min-width: 0;
}
.madrigal-inapp-notif.is-visible {
    transform: translateX(0);
    opacity: 1;
}
.madrigal-inapp-notif.is-leaving {
    transform: translateX(120%);
    opacity: 0;
    pointer-events: none;
}

.madrigal-inapp-notif-avatar {
    width: 36px;
    height: 36px;
    border-radius: 999px;
    flex-shrink: 0;
    object-fit: cover;
}
.madrigal-inapp-notif-body {
    flex: 1 1 auto;
    min-width: 0;
}
.madrigal-inapp-notif-author {
    font-weight: 600;
    color: var(--text);
    font-size: 0.92rem;
    margin-bottom: 0.15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.madrigal-inapp-notif-text {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.35;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.madrigal-inapp-notif-close {
    flex-shrink: 0;
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 0.2rem;
    border-radius: 0.3rem;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Touch target padding around the small icon. */
    min-width: 28px;
    min-height: 28px;
}
.madrigal-inapp-notif-close:hover { color: var(--text); }
.madrigal-inapp-notif-close:focus-visible {
    outline: 2px solid var(--accent, #7c5cff);
    outline-offset: 2px;
}

@media (max-width: 560px) {
    #madrigal-inapp-notif-stack {
        bottom: calc(env(safe-area-inset-bottom, 0px) + 0.5rem);
        right: 0.5rem;
        left: 0.5rem;
        max-width: none;
    }
}

/* ================================================================
 * Announcements modal (lazy-DOM, mounted by site.js).
 * Centered on desktop, full-screen at <=560px so the body content has
 * room to breathe on phones. The shell reuses .modal / .modal-backdrop
 * for the fade/blur backdrop + Esc + click-out-to-close behaviour.
 * ================================================================ */
#announcements-modal { z-index: 200; }
#announcements-modal .modal-card {
    width: min(40rem, 100%);
    max-height: calc(100dvh - 2rem);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.announcements-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
}
.announcements-title {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}
.announcements-title .lucide {
    color: var(--text-muted);
}
.announcements-list {
    list-style: none;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    flex: 1 1 auto;
    min-height: 6rem;
    -webkit-overflow-scrolling: touch;
}
.announcements-empty,
.announcements-loading,
.announcements-error {
    padding: 2.5rem 1.25rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.9rem;
}
.announcements-error { color: #fda4a4; }
.announcement-item {
    padding: 0.95rem 1.25rem;
    border-bottom: 1px solid var(--border);
    background: transparent;
    transition: background 0.12s;
    cursor: default;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.announcement-item:last-child { border-bottom: 0; }
.announcement-item.is-unread {
    background: rgba(124, 92, 255, 0.06);
    border-left: 3px solid var(--brand, #7c5cff);
    padding-left: calc(1.25rem - 3px);
    cursor: pointer;
}
.announcement-item.is-unread:hover {
    background: rgba(124, 92, 255, 0.1);
}
.announcement-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-bottom: 0.4rem;
}
.announcement-pin {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    color: #fbbf24;
    flex-shrink: 0;
}
.announcement-pin .lucide { width: 14px; height: 14px; }
.announcement-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text);
    flex: 1 1 auto;
    min-width: 0;
}
.announcement-kind {
    display: inline-flex;
    align-items: center;
    padding: 0.1rem 0.55rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    flex-shrink: 0;
}
.announcement-kind.kind-info    { background: rgba(113, 113, 122, 0.25); color: #d4d4d8; }
.announcement-kind.kind-update  { background: rgba(59, 130, 246, 0.22); color: #93c5fd; }
.announcement-kind.kind-event   { background: rgba(168, 85, 247, 0.22); color: #d8b4fe; }
.announcement-kind.kind-warning { background: rgba(245, 158, 11, 0.22); color: #fcd34d; }
.announcement-meta {
    font-size: 0.78rem;
    color: var(--text-muted);
    margin-bottom: 0.5rem;
}
.announcement-meta .announcement-author { font-weight: 600; color: var(--text); }
.announcement-body {
    font-size: 0.92rem;
    color: var(--text);
    line-height: 1.5;
}
.announcement-body img,
.announcement-body video {
    max-width: 100%;
    height: auto;
    border-radius: 0.4rem;
    margin: 0.4rem 0;
}
.announcement-body a { color: var(--brand, #7c5cff); }
.announcement-body pre {
    overflow-x: auto;
    background: rgba(0, 0, 0, 0.4);
    border-radius: 0.4rem;
    padding: 0.6rem 0.8rem;
    margin: 0.4rem 0;
}
.announcement-body code { font-size: 0.85em; }
.announcements-foot {
    padding: 0.85rem 1.25rem;
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    flex-shrink: 0;
    background: var(--surface);
}
.announcements-foot[hidden] { display: none; }

/* Mobile: full-screen the modal so the long-form list breathes. */
@media (max-width: 560px) {
    #announcements-modal { padding: 0; }
    #announcements-modal .modal-card {
        width: 100%;
        max-width: none;
        height: 100dvh;
        max-height: 100dvh;
        border-radius: 0;
        border-left: 0;
        border-right: 0;
    }
    .announcements-head {
        padding: calc(env(safe-area-inset-top, 0px) + 0.85rem) 1rem 0.85rem;
    }
    .announcements-foot {
        padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 0.85rem);
    }
    .announcement-item { padding: 0.85rem 1rem; }
    .announcement-item.is-unread { padding-left: calc(1rem - 3px); }
}

/* ================================================================
 * Staff announcements composer (textarea + live preview).
 * Mirrors the marketplace editor preview-pane look. Used inside the
 * staff panel which doesn't load main.css's chat shell, so the rules
 * here are self-contained.
 * ================================================================ */
.staff-md-composer {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.5rem;
}
.staff-md-composer textarea {
    width: 100%;
    min-height: 14rem;
    background: #0f0f0f;
    border: 1px solid #27272a;
    color: #fff;
    padding: 0.65rem 0.8rem;
    border-radius: 0.4rem;
    font: inherit;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    resize: vertical;
    box-sizing: border-box;
}
.staff-md-preview-label {
    font-size: 0.75rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #71717a;
    margin: 0.5rem 0 0.25rem;
}
.staff-md-preview {
    border: 1px solid #27272a;
    background: #111114;
    border-radius: 0.4rem;
    padding: 0.85rem 1rem;
    color: #e5e7eb;
    line-height: 1.5;
    min-height: 6rem;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.staff-md-preview img,
.staff-md-preview video { max-width: 100%; height: auto; border-radius: 0.3rem; }
.staff-md-preview a { color: #a78bfa; }
.staff-md-preview pre {
    background: #000;
    padding: 0.6rem 0.8rem;
    border-radius: 0.3rem;
    overflow-x: auto;
}
.staff-md-preview .md-heading { margin: 0.2rem 0 0.4rem; }
.staff-md-preview .md-quote {
    border-left: 3px solid #52525b;
    padding-left: 0.6rem;
    color: #a1a1aa;
    margin: 0.3rem 0;
}

/* ----------------------------------------------------------------
 * Seller subscription button.
 *
 * Three surfaces share this style:
 *   - profile.phtml header (full-page profile)
 *   - profile-popup.js modal (next to "Open full profile")
 *   - marketplace-detail.js seller card (right column)
 *
 * Markup is a plain <button class="subscribe-btn">. When the viewer
 * is already subscribed, the button gets `.is-subscribed` which
 * swaps it to a muted ghost look. Lucide bell -> bell-ring on
 * subscribe so there's a visible affordance change. The companion
 * subscribers count lives in `.subscribe-count` (muted text below
 * the button on profile surfaces; inline on the editor).
 * ---------------------------------------------------------------- */
.subscribe-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 1rem;
    min-height: 40px;
    border-radius: 0.375rem;
    border: 1px solid transparent;
    font-weight: 500;
    font-size: 0.9rem;
    cursor: pointer;
    background: var(--brand);
    color: #fff;
    transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s;
    line-height: 1;
}
.subscribe-btn:hover {
    background: var(--brand-hover);
}
.subscribe-btn .lucide {
    flex: 0 0 auto;
}
.subscribe-btn.is-subscribed {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text);
    border-color: var(--border);
}
.subscribe-btn.is-subscribed:hover {
    background: rgba(244, 63, 94, 0.10);
    border-color: rgba(244, 63, 94, 0.45);
    color: #fecaca;
}
.subscribe-btn.is-busy,
.subscribe-btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}
.subscribe-count {
    display: inline-block;
    margin-top: 0.4rem;
    font-size: 0.85rem;
    color: var(--text-muted);
}

/* Profile-page header subscribe wrap: stack the button + count so
   the count sits as a quiet caption underneath. Modal version uses
   the same wrap class. */
.profile-subscribe-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
    margin: 0 0 1.0rem;
}

/* Marketplace detail seller card: subscribe sits below the card so
   it doesn't fight with the card's <a> tap target. */
.mkt-detail-seller-subscribe {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.4rem;
    margin: 0.5rem 0 0;
}
.mkt-detail-seller-subscribe .subscribe-btn {
    width: 100%;
    justify-content: center;
}
.mkt-detail-seller-subscribe .subscribe-count {
    margin-top: 0;
    text-align: center;
}

/* Editor "Notify subscribers" card. Reuses .mp-card visuals but
   the row is a checkbox-and-label, with helper text below in muted. */
.mp-notify-row {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    cursor: pointer;
    user-select: none;
    min-height: 40px;
    padding: 0.2rem 0;
}
.mp-notify-row input[type="checkbox"] {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin-top: 0.15rem;
    cursor: pointer;
    accent-color: var(--brand);
}
.mp-notify-row-label {
    font-size: 0.95rem;
    color: var(--text);
    line-height: 1.35;
}
.mp-notify-hint {
    margin: 0.4rem 0 0 calc(18px + 0.55rem);
    font-size: 0.82rem;
    color: var(--text-muted);
    line-height: 1.4;
}
@media (max-width: 560px) {
    .mp-notify-hint { margin-left: 0; }
}

/* Highlight toggle row in the editor. Reuses .mp-notify-row layout
   but adds gold accents when checked + a muted/locked look for
   non-premium sellers so the perk is visible-but-discouraged. */
.mp-highlight-row.is-locked {
    cursor: not-allowed;
    opacity: 0.7;
}
.mp-highlight-row.is-locked input[type="checkbox"] {
    cursor: not-allowed;
}
.mp-highlight-row input[type="checkbox"]:checked {
    accent-color: #d4a017;
}
.mp-highlight-row input[type="checkbox"]:checked + .mp-notify-row-label {
    color: #fde68a;
}

/* ---- Marketplace editor: Payment methods card ---------------------
   Reuses .mp-card visuals; the row is a clickable label that wraps a
   checkbox + brand icon + method name. Touch targets 40 px tall on
   every viewport (min-height: 2.5rem). */
.mp-payment-list {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}
.mp-pay-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem;
    border-radius: 0.4rem;
    cursor: pointer;
    user-select: none;
    min-height: 2.5rem;
}
.mp-pay-row:hover {
    background: rgba(255, 255, 255, 0.04);
}
.mp-pay-row input[type="checkbox"] {
    flex-shrink: 0;
    margin: 0;
    width: 18px;
    height: 18px;
    cursor: pointer;
    accent-color: var(--brand);
}
.mp-pay-row .payment-icon {
    flex-shrink: 0;
    color: var(--text-muted);
}
.mp-pay-row-label {
    flex: 1;
    cursor: pointer;
    font-size: 0.92rem;
    color: var(--text);
}

/* ---- Marketplace browse card: payment-icon row -------------------- */
.mkt-card-payments {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.4rem;
    color: var(--text-muted);
    flex-wrap: wrap;
}
.mkt-card-payment-icon {
    display: inline-flex;
    align-items: center;
    line-height: 1;
}
.mkt-card-payment-icon .payment-icon {
    color: currentColor;
}
.mkt-card-payments-more {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--text-muted);
    background: rgba(255, 255, 255, 0.06);
    padding: 0.1rem 0.35rem;
    border-radius: 0.3rem;
    margin-left: 0.2rem;
}

/* ---- Marketplace product detail: accepted methods list ------------ */
.mkt-detail-payments {
    margin-top: 0.8rem;
    padding-top: 0.8rem;
    border-top: 1px solid var(--border);
}
.mkt-detail-payments-title {
    font-size: 0.85rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 0.5rem;
}
.mkt-detail-payment-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.3rem 0;
    color: var(--text);
    font-size: 0.92rem;
}
.mkt-detail-payment-row .payment-icon {
    color: var(--text);
    flex-shrink: 0;
}
.mkt-detail-payment-row-label {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}
.mkt-detail-payments-empty {
    margin: 0;
    color: var(--text-muted);
    font-style: italic;
    font-size: 0.85rem;
}

/* ---- Marketplace product detail: required dependencies block ----- */
.mkt-detail-deps {
    margin-top: 0.8rem;
    padding: 0.7rem 0.85rem 0.85rem;
    border: 1px solid rgba(245, 158, 11, 0.32);
    background: rgba(245, 158, 11, 0.08);
    border-radius: 0.55rem;
}
.mkt-detail-deps-title {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.85rem;
    font-weight: 700;
    color: #fbbf24;
    letter-spacing: 0.02em;
    margin-bottom: 0.25rem;
}
.mkt-detail-deps-title-icon { color: #fbbf24; }
.mkt-detail-deps-hint {
    margin: 0 0 0.45rem;
    color: var(--text-muted);
    font-size: 0.8rem;
    line-height: 1.45;
}
.mkt-detail-deps-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.25rem 0;
    color: var(--text);
    font-size: 0.9rem;
}
.mkt-detail-deps-row-icon {
    color: #fbbf24;
    flex-shrink: 0;
    margin-top: 2px;
}
.mkt-detail-deps-row-label,
.mkt-detail-deps-row-link {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}
.mkt-detail-deps-row-link {
    color: #c4b5fd;
    text-decoration: none;
}
.mkt-detail-deps-row-link:hover { text-decoration: underline; }

/* ---- Marketplace editor: required dependencies card -------------- */
.mp-deps-card .mp-deps-list {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-top: 0.4rem;
}
.mp-deps-row .mp-deps-icon {
    color: #fbbf24;
    flex-shrink: 0;
}

/* Marketplace rules page */
.mkt-rules-page {
    max-width: 56rem;
    margin: 0 auto;
    padding: 2rem 1rem 4rem;
    color: var(--text);
}
.mkt-rules-hero {
    text-align: center;
    margin-bottom: 2.5rem;
}
.mkt-rules-hero h1 {
    font-size: clamp(2.2rem, 5vw, 3.4rem);
    font-weight: 800;
    background: linear-gradient(135deg, #fbbf24, #f97316);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    margin: 0 0 0.6rem;
    letter-spacing: -0.02em;
}
.mkt-rules-hero-tagline {
    color: var(--text-muted);
    font-size: 1.05rem;
    max-width: 36rem;
    margin: 0 auto 0.4rem;
}
.mkt-rules-hero-updated {
    color: var(--text-muted);
    font-size: 0.85rem;
}
.mkt-rules-intro {
    color: var(--text-muted);
    font-size: 0.98rem;
    line-height: 1.6;
    max-width: 44rem;
    margin: 0 auto 1.5rem;
    text-align: center;
}
.mkt-rules-intro strong,
.mkt-rules-accent {
    color: #fbbf24;
    font-weight: 600;
}
.mkt-rules-shell {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.5rem;
}
@media (min-width: 900px) {
    .mkt-rules-shell {
        grid-template-columns: 14rem 1fr;
        gap: 2rem;
    }
}
.mkt-rules-toc {
    display: none;
}
@media (min-width: 900px) {
    .mkt-rules-toc {
        display: block;
        position: sticky;
        top: 5rem;
        align-self: start;
        max-height: calc(100dvh - 6rem);
        overflow-y: auto;
        padding-right: 0.5rem;
    }
}
.mkt-rules-toc-title {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    margin-bottom: 0.6rem;
}
.mkt-rules-toc a {
    display: block;
    padding: 0.4rem 0.6rem;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.88rem;
    border-radius: 0.3rem;
    border-left: 3px solid transparent;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.mkt-rules-toc a:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.04);
}
.mkt-rules-toc a.is-active {
    color: #fbbf24;
    border-left-color: #fbbf24;
}
.mkt-rules-section {
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0));
    border: 1px solid var(--border);
    border-radius: 0.7rem;
    padding: 1.4rem 1.6rem;
    margin-bottom: 1rem;
    scroll-margin-top: 5rem;
}
.mkt-rules-section-num {
    display: inline-block;
    font-size: 0.85rem;
    font-weight: 700;
    color: #fbbf24;
    letter-spacing: 0.1em;
    margin-bottom: 0.3rem;
}
.mkt-rules-section h2 {
    margin: 0 0 0.8rem;
    font-size: 1.3rem;
    font-weight: 700;
}
.mkt-rules-section ul {
    margin: 0;
    padding-left: 1.2rem;
    color: var(--text);
}
.mkt-rules-section li {
    margin-bottom: 0.4rem;
    line-height: 1.55;
}
.mkt-rules-section li strong {
    color: #fbbf24;
}
.mkt-rules-cta {
    text-align: center;
    margin-top: 2rem;
    padding: 1.5rem;
    background: rgba(251, 191, 36, 0.06);
    border: 1px solid rgba(251, 191, 36, 0.25);
    border-radius: 0.6rem;
    color: var(--text-muted);
    line-height: 1.55;
}
.mkt-rules-back-top {
    position: fixed;
    bottom: 1.5rem;
    right: 1.5rem;
    width: 44px;
    height: 44px;
    border-radius: 999px;
    background: #fbbf24;
    color: #000;
    border: none;
    display: none;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    cursor: pointer;
    z-index: 100;
}
.mkt-rules-back-top.is-visible {
    display: inline-flex;
}

/* Acceptance gate (Purchase modal + Cart Checkout) */
.mkt-rules-accept-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.75rem;
    background: rgba(251, 191, 36, 0.08);
    border: 1px solid rgba(251, 191, 36, 0.2);
    border-radius: 0.5rem;
    margin-bottom: 0.8rem;
}
.mkt-rules-accept-row input[type="checkbox"] {
    flex-shrink: 0;
    margin-top: 0.15rem;
    width: 16px;
    height: 16px;
    cursor: pointer;
}
.mkt-rules-accept-row label {
    font-size: 0.9rem;
    line-height: 1.45;
    cursor: pointer;
    color: var(--text);
}
.mkt-rules-accept-row a {
    color: #fbbf24;
    text-decoration: underline;
}
.mkt-rules-accept-row.is-required {
    animation: mkt-rules-shake 0.3s;
    border-color: #ef4444;
    background: rgba(239, 68, 68, 0.1);
}
@keyframes mkt-rules-shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-4px); }
    75% { transform: translateX(4px); }
}

/* Footer "Marketplace rules" link in tabs row + cart pane. */
.mkt-rules-footer-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    color: #ef4444;
    font-weight: 600;
    text-decoration: none;
    font-size: 0.85rem;
    padding: 0.3rem 0.55rem;
    border-radius: 0.3rem;
    border: 1px solid rgba(239, 68, 68, 0.4);
    background: rgba(239, 68, 68, 0.08);
    transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.mkt-rules-footer-link:hover,
.mkt-rules-footer-link:focus {
    color: #fff;
    background: #ef4444;
    border-color: #ef4444;
}
/* Seller editor: small "you must follow the rules" hint under the form. */
.mkt-editor-rules-hint {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    justify-content: center;
    margin: 1.5rem auto 0;
    padding: 0.6rem 0.9rem;
    color: var(--text-muted);
    font-size: 0.9rem;
    max-width: 32rem;
    border: 1px dashed rgba(251, 191, 36, 0.3);
    border-radius: 0.5rem;
}
.mkt-editor-rules-hint a {
    color: #fbbf24;
    text-decoration: underline;
}
/* In the aux pills row above the primary tabs the parent already
   uses justify-content: flex-end so no auto-margin is needed. */
.mkt-aux-row .mkt-rules-footer-link,
.mkt-aux-row .mkt-rules-howto-link {
    align-self: center;
}

/* "How the Marketplace works" link in the browse tabs row. Sits next
   to the red Rules link; gold accent so it doesn't compete for the
   "this is the rulebook" affordance.
   Also used by the "Request Seller Role" pill which is rendered as a
   <button> (since clicking it opens a modal, not navigation) - the
   button-specific resets below normalise UA padding/font so the two
   variants render identically. */
.mkt-rules-howto-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    color: #fbbf24;
    font-weight: 600;
    text-decoration: none;
    font-size: 0.85rem;
    padding: 0.3rem 0.55rem;
    border-radius: 0.3rem;
    border: 1px solid rgba(251, 191, 36, 0.4);
    background: rgba(251, 191, 36, 0.08);
    transition: color 0.15s, background 0.15s, border-color 0.15s;
    cursor: pointer;
    font-family: inherit;
    line-height: normal;
}
button.mkt-rules-howto-link {
    appearance: none;
    -webkit-appearance: none;
}
.mkt-rules-howto-link:hover,
.mkt-rules-howto-link:focus {
    color: #000;
    background: #fbbf24;
    border-color: #fbbf24;
}

/* Tabs row inside the /marketplace/rules page (How it works | Rules). */
.mkt-rules-tabs {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid var(--border);
    margin-bottom: 1.5rem;
    flex-wrap: wrap;
}
.mkt-rules-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.6rem 1.2rem;
    background: transparent;
    border: none;
    border-bottom: 3px solid transparent;
    color: var(--text-muted);
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
    /* Visually-balanced touch target on mobile. */
    min-height: 40px;
}
.mkt-rules-tab:hover,
.mkt-rules-tab:focus-visible {
    color: var(--text);
    background: rgba(255, 255, 255, 0.03);
    outline: none;
}
.mkt-rules-tab.is-active {
    color: #fbbf24;
    border-bottom-color: #fbbf24;
}
.mkt-rules-tab .lucide {
    flex-shrink: 0;
}
.mkt-rules-pane[hidden] { display: none; }
.mkt-rules-pane.is-active { display: block; }

/* "How it works" step cards - reuse .mkt-rules-section as the base,
   then override the layout to put a numeral OR an icon next to the
   heading on a single row. The body paragraphs sit beneath. */
.mkt-how-step-head {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    margin-bottom: 0.6rem;
    flex-wrap: wrap;
}
.mkt-how-step-head h2 {
    margin: 0;
    font-size: 1.3rem;
    font-weight: 700;
    flex: 1 1 auto;
    min-width: 0;
}
/* When .mkt-how-step is in play, the numeral lives inside the head
   row not above the heading. Reset the default block-level numeral. */
.mkt-how-step .mkt-how-step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.4rem;
    height: 2.4rem;
    padding: 0 0.5rem;
    background: rgba(251, 191, 36, 0.12);
    border: 1px solid rgba(251, 191, 36, 0.35);
    border-radius: 0.5rem;
    font-size: 0.95rem;
    margin-bottom: 0;
}
.mkt-how-step-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.4rem;
    background: rgba(251, 191, 36, 0.12);
    border: 1px solid rgba(251, 191, 36, 0.35);
    border-radius: 0.5rem;
    color: #fbbf24;
    flex-shrink: 0;
}
.mkt-how-step p {
    margin: 0 0 0.55rem;
    line-height: 1.6;
    color: var(--text);
}
.mkt-how-step p:last-child {
    margin-bottom: 0;
}
.mkt-how-step p strong {
    color: #fbbf24;
}
.mkt-how-step p a {
    color: #fbbf24;
    text-decoration: underline;
}
/* The "Selling on the marketplace?" wrap-up reads as a CTA card -
   slightly stronger gold tint and an icon-led head. */
.mkt-how-step--cta {
    background: linear-gradient(180deg, rgba(251, 191, 36, 0.07), rgba(251, 191, 36, 0.02));
    border-color: rgba(251, 191, 36, 0.3);
}

/* ================================================================
 * Marketplace "Looking for" (LFG) tab + composer + detail modal.
 *
 * Scoped under .lfg-pane / .lfg-* so the rules never leak. Mobile
 * first - the grid collapses to 1 column at <=560px, modals go
 * full-width below the .modal-card breakpoint.
 * ================================================================ */

.lfg-pane { display: block; }

/* ---- Toolbar (action + topic chips + status filter) ----------- */

.lfg-toolbar {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin: 0 0 1rem;
}
.lfg-toolbar-action {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}
.lfg-new-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 40px;
}
.lfg-signin-link {
    color: var(--text-muted);
    text-decoration: underline;
    font-size: 0.9rem;
}
.lfg-signin-link:hover { color: var(--text); }

.lfg-toolbar-chips,
.lfg-toolbar-status {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    align-items: center;
}
.lfg-filter-chip {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    font: inherit;
    font-size: 0.82rem;
    font-weight: 500;
    padding: 0.4rem 0.75rem;
    min-height: 36px;
    border-radius: 999px;
    cursor: pointer;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.lfg-filter-chip:hover,
.lfg-filter-chip:focus-visible {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
    border-color: var(--text-muted);
    outline: none;
}
.lfg-filter-chip.is-active {
    background: rgba(251, 191, 36, 0.18);
    border-color: rgba(251, 191, 36, 0.55);
    color: #fbbf24;
}

/* ---- Topic chip (used on cards + detail head + filter row) ---- */

.lfg-topic-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.6rem;
    border-radius: 999px;
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1;
}
.lfg-topic-chip-icon { flex-shrink: 0; }
.lfg-topic-team_recruiting { background: rgba(34, 197, 94, 0.12);  color: #22c55e; }
.lfg-topic-team_join       { background: rgba(59, 130, 246, 0.12); color: #3b82f6; }
.lfg-topic-product         { background: rgba(168, 85, 247, 0.12); color: #a855f7; }
.lfg-topic-service         { background: rgba(251, 191, 36, 0.12); color: #fbbf24; }
.lfg-topic-other           { background: rgba(255, 255, 255, 0.08); color: var(--text-muted); }

/* Status pill on cards. */
.lfg-status-badge {
    padding: 0.2rem 0.5rem;
    border-radius: 0.3rem;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1;
}
.lfg-status-open   { background: rgba(34, 197, 94, 0.12); color: #22c55e; }
.lfg-status-solved { background: rgba(255, 255, 255, 0.08); color: var(--text-muted); }

/* ---- Card grid ---- */

.lfg-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
}
@media (max-width: 768px) {
    .lfg-grid { grid-template-columns: 1fr; }
}

.lfg-card {
    position: relative;
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 0.7rem;
    padding: 0.85rem 0.95rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    cursor: pointer;
    transition: border-color 0.14s, transform 0.14s, background 0.14s;
    min-width: 0;
}
.lfg-card:hover {
    border-color: #3a3a40;
    background: #16161a;
    transform: translateY(-1px);
}
.lfg-card.is-solved { opacity: 0.7; }

.lfg-card-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.lfg-card-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    line-height: 1.3;
    overflow-wrap: anywhere;
    /* clamp 2 lines */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.lfg-card-section-label {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
}
.lfg-card-excerpt {
    margin: 0;
    color: var(--text);
    font-size: 0.88rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.lfg-card-excerpt-3 { -webkit-line-clamp: 3; }
.lfg-card-excerpt-2 { -webkit-line-clamp: 2; }

.lfg-card-author {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: auto;
    padding-top: 0.5rem;
    border-top: 1px dashed rgba(255, 255, 255, 0.05);
    font-size: 0.78rem;
    color: var(--text-muted);
    min-width: 0;
}
.lfg-card-avatar {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: #0c0c0e;
    user-select: none;
    pointer-events: none;
}
.lfg-card-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-muted);
    font-size: 0.72rem;
    font-weight: 600;
}
.lfg-card-author-name {
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    font-weight: 600;
    text-decoration: none;
    transition: filter 0.15s ease;
}
.lfg-card-author-name:hover,
.lfg-card-author-name:focus-visible {
    filter: brightness(1.15);
    outline: none;
}
.lfg-card-author-sep { color: #3a3a40; }
.lfg-card-time { color: var(--text-muted); flex-shrink: 0; }

.lfg-card-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.25rem;
    flex-wrap: wrap;
}
.lfg-card-footer-left {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.lfg-card-footer-right {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    flex-wrap: wrap;
    margin-left: auto;
}

/* ---- Action buttons inside the footer. The "Mark as solved" button
       is intentionally eye-catching primary green. Edit + Reopen are
       small ghost buttons, and Delete is the most muted of the three
       (icon-only, easy to miss, easy to find when needed). ---- */

.lfg-action-solve {
    appearance: none;
    background: #22c55e;
    color: #0a1f10;
    border: 1px solid #16a34a;
    font: inherit;
    font-size: 0.82rem;
    font-weight: 700;
    padding: 0.5rem 0.85rem;
    min-height: 40px;
    border-radius: 0.4rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    line-height: 1;
    transition: background 0.12s, transform 0.06s;
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}
.lfg-action-solve:hover {
    background: #16a34a;
}
.lfg-action-solve:active { transform: translateY(1px); }
.lfg-action-solve:disabled,
.lfg-action-solve.is-busy {
    opacity: 0.6;
    cursor: not-allowed;
}

.lfg-action-reopen,
.lfg-action-edit {
    appearance: none;
    background: transparent;
    color: var(--text-muted);
    border: 1px solid var(--border);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 500;
    padding: 0.4rem 0.7rem;
    min-height: 36px;
    border-radius: 0.4rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    line-height: 1;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.lfg-action-reopen:hover,
.lfg-action-edit:hover {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
    border-color: var(--text-muted);
}
.lfg-action-reopen:disabled,
.lfg-action-edit:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.lfg-action-delete {
    appearance: none;
    background: transparent;
    color: #6a6a70;
    border: 0;
    padding: 0.45rem;
    min-width: 36px;
    min-height: 36px;
    border-radius: 0.35rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.12s, color 0.12s;
}
.lfg-action-delete:hover {
    background: rgba(239, 68, 68, 0.1);
    color: #ef4444;
}
.lfg-action-delete:disabled { opacity: 0.5; cursor: not-allowed; }

/* ---- Empty / loading / status row inside the pane ---- */

.lfg-empty {
    text-align: center;
    padding: 2.5rem 1rem;
    color: var(--text-muted);
    background: #131316;
    border: 1px dashed var(--border);
    border-radius: 0.7rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6rem;
}
.lfg-empty-icon { color: #3a3a40; }
.lfg-empty-title { margin: 0; font-size: 0.95rem; }
.lfg-empty-action { min-height: 40px; }

.lfg-status {
    margin-top: 0.6rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    text-align: center;
}
.lfg-loadmore-wrap {
    display: flex;
    justify-content: center;
    margin: 0.9rem 0 0.4rem;
}
.lfg-loadmore-btn { min-height: 40px; }

/* ---- Composer modal (create + edit) ---- */

.lfg-composer-card,
.lfg-detail-card {
    width: min(38rem, 100%);
    max-height: min(calc(100dvh - 2rem), calc(100svh - 2rem));
    overflow-y: auto;
    padding: 1.1rem 1.2rem 1.2rem;
}
.lfg-composer-head,
.lfg-detail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin: 0 0 0.85rem;
}
/* Reserve room for the absolutely-positioned .modal-close X button so the
 * status badge ("Open" / "Solved") on the right edge of the head row
 * cannot slide underneath it. Close X sits at right:0.75rem with ~24px
 * width plus padding, so 2.6rem keeps a clean gap on both desktop and
 * narrow phones. */
.lfg-detail-head {
    padding-right: 2.6rem;
}
.lfg-composer-title {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 700;
}

.lfg-composer-body {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.lfg-field {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    position: relative;
}
.lfg-field-label {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-muted);
}
.lfg-field-optional {
    font-weight: 400;
    color: #6a6a70;
}
.lfg-field-input,
.lfg-field-select,
.lfg-field-area {
    appearance: none;
    background: #0c0c0e;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    font: inherit;
    font-size: 0.95rem;
    padding: 0.6rem 0.7rem;
    min-height: 40px;
    width: 100%;
    box-sizing: border-box;
    line-height: 1.4;
    transition: border-color 0.12s, background 0.12s;
}
.lfg-field-area {
    resize: vertical;
    min-height: 110px;
    overflow-wrap: anywhere;
}
.lfg-field-input:focus,
.lfg-field-select:focus,
.lfg-field-area:focus {
    outline: none;
    border-color: rgba(251, 191, 36, 0.55);
    background: #101013;
}
.lfg-field-counter {
    align-self: flex-end;
    font-size: 0.72rem;
    color: var(--text-muted);
}
.lfg-composer-hint {
    margin: 0;
    font-size: 0.78rem;
    color: var(--text-muted);
    line-height: 1.45;
}
.lfg-composer-error {
    margin: 0;
    padding: 0.5rem 0.7rem;
    background: rgba(239, 68, 68, 0.1);
    color: #ef4444;
    border: 1px solid rgba(239, 68, 68, 0.35);
    border-radius: 0.4rem;
    font-size: 0.85rem;
}
.lfg-composer-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1rem;
    flex-wrap: wrap;
}
.lfg-composer-actions .btn { min-height: 40px; }

/* ---- Detail modal ---- */

.lfg-detail-title {
    margin: 0 0 0.6rem;
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.3;
    overflow-wrap: anywhere;
}
.lfg-detail-author {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 1rem;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.lfg-detail-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: #0c0c0e;
    user-select: none;
    pointer-events: none;
}
.lfg-detail-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-muted);
    font-size: 0.78rem;
    font-weight: 600;
}
.lfg-detail-author-meta {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.lfg-detail-author-name {
    color: var(--text);
    text-decoration: none;
    border-bottom: 1px dotted transparent;
}
.lfg-detail-author-name:hover { border-bottom-color: var(--text-muted); }
.lfg-detail-author-time { color: var(--text-muted); }

.lfg-detail-section {
    margin-top: 1rem;
}
.lfg-detail-section-label {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin-bottom: 0.4rem;
}
.lfg-detail-body {
    color: var(--text);
    font-size: 0.95rem;
    line-height: 1.55;
    overflow-wrap: anywhere;
}
.lfg-detail-body img,
.lfg-detail-body video {
    max-width: 100%;
    height: auto;
    border-radius: 0.4rem;
}
.lfg-detail-body a { color: #fbbf24; text-decoration: underline; }
.lfg-detail-body code.md-inline-code {
    background: rgba(255, 255, 255, 0.06);
    padding: 0.1rem 0.3rem;
    border-radius: 0.25rem;
    font-size: 0.85em;
}
.lfg-detail-body pre.md-codeblock {
    background: #0a0a0c;
    border: 1px solid var(--border);
    padding: 0.7rem 0.8rem;
    border-radius: 0.4rem;
    overflow-x: auto;
}

.lfg-detail-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1.2rem;
    padding-top: 0.9rem;
    border-top: 1px solid var(--border);
    flex-wrap: wrap;
}
.lfg-detail-footer-left,
.lfg-detail-footer-right {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}
.lfg-detail-contact {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-height: 40px;
    text-decoration: none;
}

/* Mobile: collapse the LFG card grid + give modal cards more breathing
   room to use the smaller viewport without horizontal scroll. */
@media (max-width: 560px) {
    .lfg-grid { grid-template-columns: 1fr; }
    .lfg-toolbar-action,
    .lfg-toolbar-chips,
    .lfg-toolbar-status { width: 100%; }
    .lfg-composer-card,
    .lfg-detail-card {
        width: 100%;
        padding: 1rem;
    }
    .lfg-composer-actions {
        flex-direction: column-reverse;
        align-items: stretch;
    }
    .lfg-composer-actions .btn { width: 100%; }
    .lfg-card-footer-right { margin-left: 0; }
}

/* =================================================================
 * Discoverable toggle row used in the server-create + server-settings
 * General modals. Mirror of `.mp-notify-row` so it reads consistent
 * with the marketplace editor's notify-subscribers checkbox.
 * ============================================================== */

.server-modal-toggle-row {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    cursor: pointer;
    user-select: none;
    min-height: 40px;
    padding: 0.2rem 0;
}
.server-modal-toggle-row input[type="checkbox"] {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    margin-top: 0.15rem;
    cursor: pointer;
    accent-color: var(--brand);
}
.server-modal-toggle-text {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}
.server-modal-toggle-label {
    font-size: 0.95rem;
    color: var(--text);
    line-height: 1.35;
}
.server-modal-toggle-hint {
    font-size: 0.82rem;
    color: var(--text-muted);
    line-height: 1.4;
}

/* =================================================================
 * Rail: Discover Servers button + Discover Servers modal.
 *
 * The button reuses .chat-rail-item / .chat-rail-pill so it inherits
 * the rail's sizing, hover halo, and tooltip plumbing. Only the inner
 * icon container differs (compass over the dashed-circle "+" of the
 * create button).
 * ============================================================== */

.chat-rail-discover {
    background: transparent;
    border: 0;
    cursor: pointer;
    color: #a1a1aa;
}
.chat-rail-discover .chat-rail-icon-discover {
    width: 2.6rem;
    height: 2.6rem;
    border-radius: 999px;
    background: rgba(56, 189, 248, 0.10);
    border: 1.5px solid rgba(56, 189, 248, 0.45);
    color: #7dd3fc;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.18s, color 0.18s, transform 0.12s, border-color 0.18s;
}
.chat-rail-discover:hover .chat-rail-icon-discover,
.chat-rail-discover:focus-visible .chat-rail-icon-discover {
    background: rgba(56, 189, 248, 0.22);
    color: #fff;
    border-color: rgba(56, 189, 248, 0.85);
    transform: translateY(-1px);
    outline: none;
}
.chat-rail-discover:active .chat-rail-icon-discover {
    transform: translateY(0);
}

.discover-modal .discover-card {
    width: min(40rem, 100%);
    max-height: min(85svh, 85dvh, calc(100svh - 2rem));
    padding: 1.25rem 1.25rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    overflow: hidden; /* the list scrolls instead of the whole card */
}

.discover-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}
.discover-title {
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--text);
}
.discover-title .lucide { color: #7dd3fc; }

.discover-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    overflow-y: auto;
    overflow-x: hidden;
    flex: 1 1 auto;
    min-height: 0;
    /* Scroll-padding lets keyboard focus stay visible above the list edge. */
    scroll-padding-top: 0.4rem;
}

.discover-loading,
.discover-empty,
.discover-error {
    padding: 1.5rem 0.5rem;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.92rem;
}
.discover-error { color: #fca5a5; }

.discover-row {
    display: flex;
    gap: 0.85rem;
    padding: 0.85rem;
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    transition: border-color 0.15s, background 0.15s;
    min-width: 0;
}
.discover-row:hover {
    border-color: rgba(124, 58, 237, 0.45);
    background: rgba(124, 58, 237, 0.05);
}

.discover-icon {
    flex: 0 0 auto;
    width: 3rem;
    height: 3rem;
    border-radius: 0.65rem;
    overflow: hidden;
    background: #1a1a1f;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.discover-icon img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.discover-icon-fallback {
    width: 100%;
    height: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #6366f1, #a855f7);
    color: #fff;
    font-weight: 700;
    font-size: 1.2rem;
    text-transform: uppercase;
}

.discover-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.discover-name-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    min-width: 0;
}
.discover-name {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    overflow-wrap: anywhere;
    line-height: 1.25;
}
.discover-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.12rem 0.5rem;
    background: rgba(34, 197, 94, 0.15);
    color: #86efac;
    border: 1px solid rgba(34, 197, 94, 0.4);
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

.discover-desc {
    margin: 0;
    font-size: 0.88rem;
    color: var(--text-muted);
    line-height: 1.4;
    overflow-wrap: anywhere;
    /* Clamp to two lines so each row stays scannable. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.discover-meta {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    font-size: 0.82rem;
    color: var(--text-muted);
    min-width: 0;
}
.discover-owner {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-width: 0;
}
.discover-owner-label {
    color: var(--text-muted);
}
.discover-owner-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--text);
    text-decoration: none;
    min-width: 0;
    max-width: 100%;
}
.discover-owner-link:hover .discover-owner-name { text-decoration: underline; }
.discover-owner-avatar {
    width: 1.25rem;
    height: 1.25rem;
    border-radius: 999px;
    object-fit: cover;
    flex: 0 0 auto;
    background: #1a1a1f;
}
.discover-owner-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #6366f1, #a855f7);
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
}
.discover-owner-name {
    overflow-wrap: anywhere;
    min-width: 0;
}
.discover-member-count {
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.discover-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-top: 0.25rem;
}
.discover-action-hint {
    font-size: 0.82rem;
    color: var(--text-muted);
    font-style: italic;
}
.discover-visit {
    min-height: 36px;
}

.discover-foot {
    display: flex;
    justify-content: center;
    padding-top: 0.4rem;
    border-top: 1px solid var(--border);
    flex: 0 0 auto;
}
.discover-foot[hidden] { display: none; }
.discover-load-more {
    min-height: 40px;
    min-width: 8rem;
}

@media (max-width: 560px) {
    .discover-modal .discover-card {
        width: 100%;
        max-height: 100svh;
        max-height: 100dvh;
        height: 100svh;
        height: 100dvh;
        border-radius: 0;
        padding: 1rem 0.85rem 0.85rem;
        padding-top: max(1rem, env(safe-area-inset-top));
        padding-bottom: max(0.85rem, env(safe-area-inset-bottom));
    }
    .discover-row {
        padding: 0.75rem;
        gap: 0.65rem;
    }
    .discover-icon {
        width: 2.6rem;
        height: 2.6rem;
    }
    .discover-name { font-size: 0.95rem; }
    .discover-desc { font-size: 0.85rem; }
    .discover-meta { font-size: 0.78rem; }
    .discover-actions { width: 100%; }
    .discover-visit { width: 100%; text-align: center; }
}

/* ================================================================
 * Giveaway page
 *
 * Browse grid + detail page + create/edit modal. Mobile-first.
 * Namespace .gw-*. Active "Open" countdown uses purple #a855f7;
 * "Drawn" uses green #22c55e; premium-only badge uses gold #fbbf24.
 * ================================================================ */

/* ---- Top-nav pill (active accent: purple) ---- */
.primary-nav-item[data-giveaway-pill].is-active {
    background: rgba(168, 85, 247, 0.18);
    border-color: rgba(168, 85, 247, 0.5);
    color: #c4b5fd;
}
.primary-nav-item[data-giveaway-pill].is-active:hover {
    background: rgba(168, 85, 247, 0.26);
    border-color: #a855f7;
}

.gw-shell {
    display: block;
    color: var(--text);
}

.gw-head {
    margin: 0 0 1.25rem;
}
.gw-head-title {
    margin: 0 0 0.35rem;
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}
.gw-head-sub {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.95rem;
}
/* "Sellers" highlight matches the seller role's gold (#fbbf24, set by
   the seeded `seller` row in 080_seller_application.sql) so the
   subtitle reads visually consistent with how seller names paint
   elsewhere on the site. */
.gw-head-sub-seller {
    color: #fbbf24;
    font-weight: 600;
}

/* ---- Toolbar (filter pills + new button) ---- */
.gw-toolbar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0 0 1rem;
    flex-wrap: wrap;
}
.gw-filter-pills {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex: 1 1 auto;
    min-width: 0;
    flex-wrap: wrap;
}
.gw-filter-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.85rem;
    min-height: 40px;
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.88rem;
    line-height: 1;
    white-space: nowrap;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.gw-filter-pill:hover,
.gw-filter-pill:focus-visible {
    color: var(--text);
    border-color: #3a3a3f;
    background: #18181c;
    outline: none;
}
.gw-filter-pill.is-active {
    background: rgba(168, 85, 247, 0.18);
    border-color: rgba(168, 85, 247, 0.6);
    color: #ddd6fe;
}
.gw-filter-pill-icon { color: inherit; flex-shrink: 0; }

.gw-notify-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    flex: 0 0 auto;
    padding: 0.45rem 0.75rem;
    min-height: 40px;
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-muted);
    font-size: 0.85rem;
    cursor: pointer;
    user-select: none;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.gw-notify-toggle:hover {
    color: var(--text);
    border-color: #3a3a3f;
    background: #18181c;
}
.gw-notify-toggle:has(.gw-notify-checkbox:checked) {
    color: #ddd6fe;
    background: rgba(168, 85, 247, 0.18);
    border-color: rgba(168, 85, 247, 0.6);
}
.gw-notify-checkbox {
    width: 16px;
    height: 16px;
    accent-color: #a855f7;
    cursor: pointer;
    flex-shrink: 0;
}
.gw-notify-icon { color: inherit; flex-shrink: 0; }
.gw-notify-label { white-space: nowrap; }
@media (max-width: 560px) {
    .gw-notify-label { display: none; }
}

.gw-new-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex: 0 0 auto;
    background: #a855f7;
    border-color: #9333ea;
    color: #fff;
}
.gw-new-btn:hover { background: #9333ea; border-color: #7e22ce; }
.gw-new-btn-icon { color: inherit; }
.gw-new-btn-plus { color: inherit; opacity: 0.85; }
.gw-new-btn-label { font-weight: 600; }

@media (max-width: 560px) {
    .gw-new-btn-label { display: none; }
    .gw-new-btn { padding: 0.55rem 0.7rem; }
}

/* ---- Browse grid ---- */
.gw-grid-root {
    position: relative;
    min-height: 200px;
}
.gw-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1rem;
}
@media (max-width: 560px) {
    .gw-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 0.75rem;
    }
}

.gw-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 3rem 1rem;
    color: var(--text-muted);
    border: 1px dashed var(--border);
    border-radius: 0.75rem;
    background: #0c0c0e;
}
.gw-empty-icon { color: #71717a; }
.gw-empty-title { margin: 0.75rem 0 0; font-size: 0.95rem; }

/* ---- Card ---- */
.gw-card {
    position: relative;
    display: flex;
    flex-direction: column;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    overflow: hidden;
    color: var(--text);
    text-decoration: none;
    transition: border-color 0.12s, transform 0.12s, box-shadow 0.12s;
    min-width: 0;
}
.gw-card:hover,
.gw-card:focus-within {
    border-color: #a855f7;
    transform: translateY(-2px);
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.45);
    outline: none;
}

.gw-card-media {
    position: relative;
    aspect-ratio: 16 / 10;
    background: #0a0a0a;
    overflow: hidden;
}
.gw-card-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    max-width: 100%;
}
.gw-card-img-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: #4a4a52;
}

.gw-card-premium-badge {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.25rem 0.55rem;
    background: rgba(251, 191, 36, 0.95);
    color: #1a1300;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.gw-card-premium-icon { color: inherit; }

.gw-card-body {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    padding: 0.7rem 0.8rem 0.85rem;
    min-width: 0;
}
.gw-card-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}

.gw-card-creator {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
}
.gw-card-creator-avatar {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: #1f1f24;
}
.gw-card-creator-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    font-size: 0.72rem;
    font-weight: 600;
    background: #1f1f24;
}
.gw-card-creator-name {
    font-size: 0.82rem;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.gw-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-top: 0.15rem;
}
.gw-card-countdown {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.78rem;
    font-weight: 600;
    color: #c4b5fd;
}
.gw-card-countdown--open  { color: #c4b5fd; }
.gw-card-countdown--drawn { color: #86efac; }
.gw-card-countdown--ended { color: #71717a; }
.gw-card-countdown-icon { color: inherit; }

.gw-card-stats {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    color: var(--text-muted);
    font-size: 0.78rem;
}
.gw-card-stat {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
}
.gw-card-stat-icon { color: inherit; }

.gw-load-more-wrap {
    display: flex;
    justify-content: center;
    margin: 1.25rem 0 0;
}
.gw-load-more { min-width: 200px; }

/* ---- Detail page ---- */
.gw-detail-shell { padding-bottom: 2rem; }

.gw-detail-fallback {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 1rem 0;
}
.gw-detail-fallback-img {
    width: 100%;
    max-width: 100%;
    max-height: 320px;
    object-fit: cover;
    border-radius: 0.75rem;
}
.gw-detail-fallback-title {
    margin: 0;
    font-size: 1.4rem;
    font-weight: 700;
}
.gw-detail-fallback-creator { margin: 0; color: var(--text-muted); }
.gw-detail-fallback-desc {
    margin: 0;
    padding: 0.85rem 1rem;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--text);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font: inherit;
    font-size: 0.92rem;
    line-height: 1.5;
}

.gw-detail-layout {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.25rem;
    align-items: start;
}
@media (min-width: 900px) {
    .gw-detail-layout {
        grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
        gap: 1.75rem;
    }
}
.gw-detail-left,
.gw-detail-right {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: 0;
}

.gw-detail-hero {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: 0.75rem;
    background: #0a0a0a;
    border: 1px solid var(--border);
}
.gw-detail-hero-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    max-width: 100%;
}
.gw-detail-hero-empty {
    display: flex;
    align-items: center;
    justify-content: center;
}
.gw-detail-hero-empty-icon { color: #4a4a52; }

.gw-detail-premium-badge {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.4rem 0.7rem;
    background: rgba(251, 191, 36, 0.95);
    color: #1a1300;
    border-radius: 999px;
    font-size: 0.78rem;
    font-weight: 700;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.gw-detail-premium-badge-icon { color: inherit; }

.gw-detail-title-block {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.gw-detail-title {
    margin: 0;
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1.2;
    overflow-wrap: anywhere;
}
.gw-detail-creator {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    text-decoration: none;
    color: inherit;
    align-self: flex-start;
    padding: 0.4rem 0.6rem 0.4rem 0.4rem;
    border-radius: 0.5rem;
    transition: background 0.12s;
}
.gw-detail-creator:hover { background: #131316; }
.gw-detail-creator-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: #1f1f24;
}
.gw-detail-creator-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 600;
}
.gw-detail-creator-meta { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.gw-detail-creator-byline { font-size: 0.72rem; color: var(--text-muted); }
.gw-detail-creator-name { font-size: 0.95rem; font-weight: 600; }

.gw-detail-section-title {
    margin: 0 0 0.6rem;
    font-size: 1.05rem;
    font-weight: 700;
}

.gw-detail-desc-card,
.gw-detail-winners-card {
    padding: 1rem 1.1rem;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
}
.gw-detail-desc {
    color: var(--text);
    font-size: 0.92rem;
    line-height: 1.6;
    overflow-wrap: anywhere;
}
.gw-detail-desc img,
.gw-detail-desc video { max-width: 100%; height: auto; }

.gw-detail-winners-head {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.55rem;
}
.gw-detail-winners-head .gw-detail-section-title { margin: 0; }
.gw-detail-winners-icon { color: #fbbf24; }
.gw-detail-winners-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}
.gw-detail-winner-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.6rem;
    background: #131316;
    border-radius: 0.45rem;
    border: 1px solid #1f1f24;
}
.gw-detail-winner-pos {
    font-weight: 700;
    color: #fbbf24;
    font-size: 0.85rem;
    min-width: 2.4rem;
}
.gw-detail-winner-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: #1f1f24;
}
.gw-detail-winner-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    font-size: 0.78rem;
    font-weight: 600;
}
.gw-detail-winner-name {
    color: var(--text);
    text-decoration: none;
    font-weight: 600;
    overflow-wrap: anywhere;
    min-width: 0;
}
.gw-detail-winner-name:hover { text-decoration: underline; }

/* ---- Right column cards ---- */
.gw-detail-status-card {
    padding: 1rem 1.1rem;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    text-align: center;
}
.gw-detail-status-card--open  { border-color: rgba(168, 85, 247, 0.45); }
.gw-detail-status-card--drawn { border-color: rgba(34, 197, 94, 0.45); }
.gw-detail-status-card--ending { border-color: rgba(113, 113, 122, 0.5); }
.gw-detail-status-head {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-bottom: 0.4rem;
}
.gw-detail-status-icon { color: inherit; }
.gw-detail-status-label { letter-spacing: 0.01em; }
.gw-detail-status-countdown {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--text);
    overflow-wrap: anywhere;
}
.gw-detail-status-card--open .gw-detail-status-countdown  { color: #c4b5fd; }
.gw-detail-status-card--drawn .gw-detail-status-countdown { color: #86efac; font-size: 1.05rem; }
.gw-detail-status-hint { margin: 0.35rem 0 0; color: var(--text-muted); font-size: 0.85rem; }

.gw-detail-stats-card {
    padding: 0.85rem 1rem;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.gw-detail-stat-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: 0.92rem;
}
.gw-detail-stat-icon { color: var(--text-muted); flex-shrink: 0; }
.gw-detail-stat-value { font-weight: 700; color: var(--text); }
.gw-detail-stat-label { color: var(--text-muted); }

/* ---- Inline entries panel (giveaway detail) -------------------- */
.gw-detail-entries-card {
    padding: 1rem;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
}
.gw-detail-entries-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.gw-detail-entries-icon { color: var(--text-muted); flex-shrink: 0; }
.gw-detail-entries-count {
    margin-left: auto;
    padding: 0.15rem 0.5rem;
    background: rgba(168, 85, 247, 0.15);
    color: #d8b4fe;
    border-radius: 999px;
    font-weight: 700;
    font-size: 0.85rem;
}
.gw-detail-entries-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    max-height: 340px;
    overflow-y: auto;
}
.gw-detail-entry-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.55rem 0.3rem 0.3rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid var(--border);
    border-radius: 999px;
    text-decoration: none;
    color: var(--text);
    font-size: 0.88rem;
    max-width: 100%;
    transition: background 120ms, border-color 120ms;
}
.gw-detail-entry-chip:hover {
    background: rgba(168, 85, 247, 0.1);
    border-color: rgba(168, 85, 247, 0.45);
}
.gw-detail-entry-avatar {
    width: 22px; height: 22px;
    border-radius: 50%;
    object-fit: cover;
    background: #1a1a1f;
    flex-shrink: 0;
}
.gw-detail-entry-avatar-fallback {
    display: inline-flex;
    align-items: center; justify-content: center;
    font-weight: 700;
    font-size: 0.7rem;
    color: var(--text-muted);
    background: #1a1a1f;
}
.gw-detail-entry-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 12rem;
}
.gw-detail-entries-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.gw-detail-entries-empty,
.gw-detail-entries-loading {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.9rem;
}
.gw-detail-entries-more { min-height: 36px; }
@media (max-width: 480px) {
    .gw-detail-entry-name { max-width: 8rem; }
}

/* ---- 24h handover agreement (giveaway editor) ---------------- */
.gw-editor-agree-card {
    padding: 0.7rem 0.85rem;
    background: rgba(251, 191, 36, 0.08);
    border: 1px solid rgba(251, 191, 36, 0.3);
    border-radius: 0.5rem;
}
.gw-editor-agree-label {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
    cursor: pointer;
    color: var(--text);
    font-size: 0.92rem;
    line-height: 1.35;
}
.gw-editor-agree-checkbox {
    margin-top: 0.15rem;
    flex-shrink: 0;
    width: 18px; height: 18px;
    cursor: pointer;
}
.gw-editor-agree-text { flex: 1 1 auto; }

/* ---- Entries-list modal --------------------------------------- */
.gw-entries-overlay {
    position: fixed; inset: 0;
    display: flex; align-items: center; justify-content: center;
    z-index: 100;
}
.gw-entries-overlay[hidden] { display: none; }
body.gw-entries-open { overflow: hidden; }
.gw-entries-card {
    position: relative;
    width: min(560px, 92vw);
    max-height: min(78dvh, 720px);
    display: flex;
    flex-direction: column;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.7rem;
    box-shadow: 0 18px 60px rgba(0,0,0,0.55);
    z-index: 1;
}
.gw-entries-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 0.8rem 1rem;
    border-bottom: 1px solid var(--border);
}
.gw-entries-heading {
    margin: 0;
    font-size: 1.05rem;
    color: var(--text);
}
.gw-entries-close { padding: 0.3rem 0.45rem; min-height: 32px; }
.gw-entries-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.5rem 0.6rem 0.7rem;
}
.gw-entries-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.gw-entries-row { margin: 0; }
.gw-entries-link {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.55rem;
    border-radius: 0.45rem;
    text-decoration: none;
    color: var(--text);
    transition: background 120ms;
    min-height: 40px;
}
.gw-entries-link:hover { background: rgba(168, 85, 247, 0.08); }
.gw-entries-avatar {
    width: 32px; height: 32px;
    border-radius: 50%;
    object-fit: cover;
    background: #1a1a1f;
    flex-shrink: 0;
}
.gw-entries-avatar-fallback {
    display: inline-flex;
    align-items: center; justify-content: center;
    font-weight: 700;
    color: var(--text-muted);
    background: #1a1a1f;
}
.gw-entries-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 auto;
}
.gw-entries-status,
.gw-entries-empty {
    margin: 1rem 0;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.92rem;
}
.gw-entries-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.6rem 1rem;
    border-top: 1px solid var(--border);
    color: var(--text-muted);
    font-size: 0.85rem;
}
.gw-entries-more { min-height: 36px; }
@media (max-width: 480px) {
    .gw-entries-card { width: 100vw; max-height: 100dvh; border-radius: 0; }
}

.gw-detail-action-panel {
    padding: 1rem;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}
.gw-detail-action-muted {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.9rem;
    text-align: center;
}
.gw-detail-action-row {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.gw-detail-action-row .btn { flex: 1 1 auto; min-height: 40px; }

.gw-detail-enter-btn {
    width: 100%;
    min-height: 48px;
    background: linear-gradient(135deg, #a855f7, #7c3aed);
    border-color: #7c3aed;
    color: #fff;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    text-decoration: none;
}
.gw-detail-enter-btn:hover {
    background: linear-gradient(135deg, #9333ea, #6d28d9);
    border-color: #6d28d9;
}
.gw-detail-enter-btn:disabled { opacity: 0.6; cursor: not-allowed; }

.gw-detail-leave-btn {
    width: 100%;
    min-height: 40px;
    color: var(--text-muted);
}
.gw-detail-entered-banner {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    padding: 0.7rem;
    background: rgba(34, 197, 94, 0.12);
    border: 1px solid rgba(34, 197, 94, 0.4);
    border-radius: 0.5rem;
    color: #86efac;
    font-weight: 700;
}
.gw-detail-entered-icon { color: inherit; }

/* ---- Editor modal ---- */
.gw-editor-card {
    width: min(640px, 96vw);
    max-height: 92svh;
    overflow: auto;
    overscroll-behavior: contain;
}
.gw-editor-body {
    padding: 1.25rem 1.4rem 1.4rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.gw-editor-heading {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 700;
}
.gw-editor-error {
    margin: 0;
    padding: 0.5rem 0.75rem;
    color: #fca5a5;
    background: rgba(244, 63, 94, 0.1);
    border: 1px solid rgba(244, 63, 94, 0.4);
    border-radius: 0.4rem;
    font-size: 0.85rem;
}
.gw-editor-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.gw-editor-field-label {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text);
}
.gw-editor-input {
    width: 100%;
    padding: 0.6rem 0.8rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    line-height: 1.4;
    min-height: 2.5rem;
    transition: border-color 0.12s, background 0.12s;
}
.gw-editor-input:focus {
    outline: none;
    border-color: #a855f7;
    background: #131313;
}
.gw-editor-textarea {
    min-height: 10rem;
    resize: vertical;
}
.gw-editor-hint {
    margin: 0.15rem 0 0;
    color: var(--text-muted);
    font-size: 0.78rem;
}
.gw-editor-checkbox-row {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    user-select: none;
    color: var(--text);
    font-size: 0.92rem;
    min-height: 40px;
}
.gw-editor-checkbox-row input[type="checkbox"] {
    width: 18px;
    height: 18px;
    cursor: pointer;
    accent-color: #a855f7;
}

.gw-editor-preview {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 1px dashed var(--border);
    border-radius: 0.5rem;
    background: #0a0a0a;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}
.gw-editor-preview-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.gw-editor-preview-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.gw-editor-preview-empty-icon { color: #52525b; }
.gw-editor-preview-actions {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
    margin-top: 0.5rem;
}
.gw-editor-preview-btn { min-height: 40px; }

.gw-editor-duration-row {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.5rem;
}
.gw-editor-premium-card .gw-editor-duration-row {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 480px) {
    .gw-editor-duration-row,
    .gw-editor-premium-card .gw-editor-duration-row {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
.gw-editor-num-input {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}
.gw-editor-num-input-label {
    font-size: 0.75rem;
    color: var(--text-muted);
}

.gw-editor-premium-card {
    padding: 0.85rem 1rem;
    background: rgba(168, 85, 247, 0.06);
    border: 1px solid rgba(168, 85, 247, 0.25);
    border-radius: 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.gw-editor-section-heading {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 700;
    color: #c4b5fd;
}

.gw-editor-actions {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
    flex-wrap: wrap;
    margin-top: 0.4rem;
}
.gw-editor-actions .btn { min-height: 42px; }
.gw-editor-submit { background: #a855f7; border-color: #9333ea; }
.gw-editor-submit:hover { background: #9333ea; border-color: #7e22ce; }

/* ---- Storage picker (one-off overlay inside the editor modal) ---- */
.gw-storage-picker-overlay {
    position: fixed;
    inset: 0;
    z-index: 100000; /* sits above the editor modal */
    display: flex;
    align-items: center;
    justify-content: center;
}
.gw-storage-picker-card {
    width: min(560px, 94vw);
    max-height: 80svh;
    overflow: auto;
}
.gw-storage-picker {
    padding: 1rem 1.25rem 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.gw-storage-picker-heading {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
}
.gw-storage-picker-status {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.88rem;
}
.gw-storage-picker-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
    gap: 0.5rem;
}
.gw-storage-picker-tile {
    aspect-ratio: 1 / 1;
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    background: #0a0a0a;
    padding: 0;
    cursor: pointer;
    transition: border-color 0.12s, transform 0.12s;
}
.gw-storage-picker-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.gw-storage-picker-tile:hover { border-color: #a855f7; transform: translateY(-1px); }
.gw-storage-picker-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
}

@media (max-width: 560px) {
    .gw-detail-title { font-size: 1.3rem; }
    .gw-detail-status-countdown { font-size: 1.25rem; }
    .gw-editor-body { padding: 1rem; gap: 0.85rem; }
}

/* ================================================================
   Guides & Releases - browse + detail + editor.
   Visual language matches Marketplace + Giveaways: dark cards on
   surface, brand purple accent, premium badge in gold.
   ================================================================ */
.gd-shell { max-width: 1400px; margin: 0 auto; padding: 3rem 1.5rem 4rem; width: 100%; box-sizing: border-box; }
.gd-head { margin: 0 0 1.25rem; }
.gd-head-title { margin: 0 0 0.35rem; font-size: 1.6rem; font-weight: 700; letter-spacing: -0.01em; }
.gd-head-sub { margin: 0; color: var(--text-muted); font-size: 0.95rem; }

.gd-toolbar { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 0 0 1rem; }
.gd-kind-pills, .gd-sort-pills { display: inline-flex; align-items: center; gap: 0.4rem; }
.gd-kind-pill, .gd-sort-pill {
    display: inline-flex; align-items: center; gap: 0.4rem;
    padding: 0.5rem 0.85rem; min-height: 40px;
    background: #131316; border: 1px solid var(--border); border-radius: 999px;
    color: var(--text-muted); text-decoration: none;
    font-size: 0.88rem; line-height: 1; white-space: nowrap;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.gd-sort-pill { padding: 0.4rem 0.7rem; min-height: 32px; font-size: 0.8rem; }
.gd-kind-pill:hover, .gd-sort-pill:hover { color: var(--text); border-color: #3a3a3f; background: #18181c; }
.gd-kind-pill.is-active, .gd-sort-pill.is-active {
    background: rgba(168, 85, 247, 0.18);
    border-color: rgba(168, 85, 247, 0.6);
    color: #ddd6fe;
}
.gd-kind-pill-icon, .gd-sort-pill-icon { color: inherit; flex-shrink: 0; }

/* "Notify me about new posts" pill - same shape as the kind /
   sort pills so the toolbar reads cohesively. Goes purple-tinted
   when checked. */
.gd-notify-toggle {
    display: inline-flex; align-items: center; gap: 0.45rem;
    flex: 0 0 auto;
    padding: 0.45rem 0.75rem; min-height: 40px;
    background: #131316; border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-muted);
    font-size: 0.85rem;
    cursor: pointer; user-select: none;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
    margin-left: auto;
}
.gd-notify-toggle:hover { color: var(--text); border-color: #3a3a3f; background: #18181c; }
.gd-notify-toggle:has(.gd-notify-checkbox:checked) {
    color: #ddd6fe;
    background: rgba(168, 85, 247, 0.18);
    border-color: rgba(168, 85, 247, 0.6);
}
.gd-notify-checkbox {
    width: 16px; height: 16px;
    accent-color: #a855f7;
    cursor: pointer; flex-shrink: 0;
}
.gd-notify-icon { color: inherit; flex-shrink: 0; }
.gd-notify-label { white-space: nowrap; }
@media (max-width: 560px) {
    .gd-notify-label { display: none; }
}

.gd-new-btn {
    display: inline-flex; align-items: center; gap: 0.4rem;
    flex: 0 0 auto;
    background: #a855f7; border-color: #9333ea; color: #fff;
}
.gd-new-btn:hover { background: #9333ea; border-color: #7e22ce; }
@media (max-width: 560px) {
    .gd-new-btn-label { display: none; }
    .gd-new-btn { padding: 0.55rem 0.7rem; }
}

.gd-grid-root { position: relative; min-height: 200px; }
.gd-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1rem;
}
@media (max-width: 560px) {
    .gd-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 0.7rem; }
}
.gd-card {
    display: flex; flex-direction: column;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.7rem;
    overflow: hidden;
    text-decoration: none;
    color: inherit;
    transition: border-color 0.12s, transform 0.12s;
}
.gd-card:hover { border-color: rgba(168, 85, 247, 0.5); transform: translateY(-1px); }
.gd-card--premium { border-color: rgba(251, 191, 36, 0.45); }
.gd-card--premium:hover { border-color: rgba(251, 191, 36, 0.7); }
.gd-card-media {
    position: relative;
    aspect-ratio: 16/10;
    /* Subtle radial backdrop so a `contain`-fitted preview reads as
       intentional rather than letterboxed by accident. The image
       sits on top with the full-frame visible (no zoom-crop). */
    background: radial-gradient(ellipse at center, #15151a, #0a0a0d 75%);
    overflow: hidden;
}
.gd-card-img { width: 100%; height: 100%; object-fit: contain; display: block; }
.gd-card-img-empty {
    display: flex; align-items: center; justify-content: center;
    width: 100%; height: 100%;
    color: rgba(168, 85, 247, 0.4);
}
.gd-card-premium-badge {
    position: absolute; top: 0.5rem; right: 0.5rem;
    display: inline-flex; align-items: center; gap: 0.25rem;
    padding: 0.2rem 0.5rem;
    background: rgba(0,0,0,0.7); color: #fbbf24;
    border-radius: 999px;
    font-size: 0.7rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.05em;
}
.gd-card-premium-icon { color: inherit; }

/* Kind badge bottom-left of the card media. Distinct colours per
   kind so the unified feed stays scannable at a glance. */
.gd-card-kind-badge {
    position: absolute; bottom: 0.5rem; left: 0.5rem;
    display: inline-flex; align-items: center; gap: 0.25rem;
    padding: 0.2rem 0.5rem;
    background: rgba(0,0,0,0.7);
    border-radius: 999px;
    font-size: 0.68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.06em;
}
.gd-card-kind-badge--guide   { color: #93c5fd; }
.gd-card-kind-badge--release { color: #86efac; }
.gd-card-body {
    padding: 0.75rem 0.85rem 0.85rem;
    display: flex; flex-direction: column; gap: 0.5rem; flex: 1;
}
.gd-card-title {
    margin: 0; font-size: 1rem; font-weight: 600;
    color: var(--text); line-height: 1.3;
    overflow: hidden; text-overflow: ellipsis;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.gd-card-author { display: flex; align-items: center; gap: 0.4rem; color: var(--text-muted); font-size: 0.82rem; }
.gd-card-author-avatar { width: 18px; height: 18px; border-radius: 50%; object-fit: cover; background: #1a1a1f; }
.gd-card-author-avatar-fallback {
    display: inline-flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: 0.6rem;
    color: var(--text-muted); background: #1a1a1f;
}
.gd-card-author-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; color: var(--text); }
.gd-card-foot { display: flex; align-items: center; gap: 0.6rem; margin-top: auto; color: var(--text-muted); font-size: 0.78rem; }
.gd-card-thanks, .gd-card-views, .gd-card-comments { display: inline-flex; align-items: center; gap: 0.25rem; }
.gd-card-thanks.is-thanked { color: #f472b6; }
.gd-card-comments.is-off { opacity: 0.5; }
.gd-card-thanks-icon, .gd-card-views-icon, .gd-card-comments-icon { color: inherit; flex-shrink: 0; }

/* Sticky pin badge on browse cards. Anchored top-right corner of the
   .gd-card so it overlays the preview image without shifting layout.
   Subtle accent + tinted border, matching the existing kind-badge
   style but with a warm "staff pin" hue. */
.gd-card { position: relative; }
.gd-card-sticky-badge {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.15rem 0.5rem 0.15rem 0.4rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    color: #fff7ed;
    background: rgba(234, 88, 12, 0.88);
    border: 1px solid rgba(255, 237, 213, 0.35);
    backdrop-filter: blur(4px);
    z-index: 2;
    pointer-events: none;
}
.gd-card-sticky-badge-icon { color: inherit; flex-shrink: 0; }
/* Faint tinted outline on the whole card so a stuck post reads as
   "pinned" even when the preview hides the badge corner. */
.gd-card--sticky {
    border-color: rgba(234, 88, 12, 0.5);
    box-shadow: 0 0 0 1px rgba(234, 88, 12, 0.18) inset;
}

/* Detail-page sticky button. Lives in the right column above the
   author edit/delete row. Highlighted state when the post is
   currently stuck so the toggle direction is obvious. */
.gd-detail-sticky-row {
    margin-top: 0.5rem;
    display: flex;
}
.gd-detail-sticky-btn {
    width: 100%;
    justify-content: center;
    gap: 0.4rem;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text);
}
.gd-detail-sticky-btn:hover {
    background: rgba(234, 88, 12, 0.08);
    border-color: rgba(234, 88, 12, 0.4);
}
.gd-detail-sticky-btn.is-sticky {
    color: #fed7aa;
    border-color: rgba(234, 88, 12, 0.5);
    background: rgba(234, 88, 12, 0.12);
}
.gd-detail-sticky-btn.is-sticky:hover {
    background: rgba(234, 88, 12, 0.18);
}

/* Category filter pill row (browse). Same shape as kind / sort
   pills but slightly tighter so a long taxonomy doesn't overflow. */
.gd-cat-pills {
    display: flex; flex-wrap: wrap; gap: 0.35rem;
    margin: 0 0 1rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--border);
}
.gd-cat-pill {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.3rem 0.65rem; min-height: 28px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.78rem;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.gd-cat-pill:hover { color: var(--text); background: rgba(168, 85, 247, 0.08); }
.gd-cat-pill.is-active {
    background: rgba(168, 85, 247, 0.18);
    border-color: rgba(168, 85, 247, 0.4);
    color: #ddd6fe;
}
.gd-cat-pill-icon { color: inherit; flex-shrink: 0; }

/* Card category badge */
.gd-card-cat-badge {
    display: inline-flex; align-items: center; gap: 0.25rem;
    align-self: flex-start;
    padding: 0.12rem 0.45rem;
    background: rgba(168, 85, 247, 0.15);
    color: #c4b5fd;
    border-radius: 999px;
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.gd-card-cat-icon { color: inherit; }

/* Detail page tag row + category chip */
.gd-detail-tag-row { display: flex; align-items: center; gap: 0.45rem; flex-wrap: wrap; }
.gd-detail-cat-tag {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.18rem 0.5rem;
    background: rgba(168, 85, 247, 0.15);
    color: #c4b5fd;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    text-decoration: none;
    transition: background 0.12s;
}
.gd-detail-cat-tag:hover { background: rgba(168, 85, 247, 0.28); }

.gd-empty {
    grid-column: 1 / -1;
    display: flex; flex-direction: column; align-items: center; gap: 0.7rem;
    padding: 3rem 1rem; text-align: center;
}
.gd-empty-icon { color: rgba(168, 85, 247, 0.35); }
.gd-empty-title { margin: 0; color: var(--text-muted); }

/* ---- Detail page ---- */
.gd-detail-shell { max-width: 1280px; margin: 0 auto; padding: 1.25rem 1.25rem 4rem; }
.gd-detail-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 1.25rem; }
@media (max-width: 900px) { .gd-detail-layout { grid-template-columns: 1fr; } }
.gd-detail-left { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
.gd-detail-right { display: flex; flex-direction: column; gap: 0.7rem; }

.gd-detail-hero {
    position: relative;
    /* Block container - takes the image's natural height so the
       hero's left + right edges always line up with the title and
       description below. The radial backdrop shows through the
       letterboxed area for portrait / oddly-sized sources. */
    background: radial-gradient(ellipse at center, #15151a, #0a0a0d 75%);
    border-radius: 0.8rem;
    overflow: hidden;
    border: 1px solid var(--border);
}
.gd-detail-hero-img {
    /* Full-width so the IMG box hugs the column edges; that means
       the visible image is always horizontally aligned with the
       title beneath. object-fit: contain keeps the source's natural
       proportions visible without cropping; portrait / undersized
       sources get pillarboxed left/right with the radial backdrop
       showing through the IMG's transparent letterbox pixels.
       max-height caps tall portraits so they don't shove the
       description below the fold. */
    display: block;
    width: 100%;
    height: auto;
    max-height: 480px;
    object-fit: contain;
}
.gd-detail-hero-empty {
    display: flex; align-items: center; justify-content: center;
    aspect-ratio: 16/9;
    width: 100%;
}
.gd-detail-hero-empty-icon { color: rgba(168, 85, 247, 0.4); }
/* Clickable hero (has a preview image). The whole .gd-detail-hero box
   becomes a button that opens the image-lightbox; cursor + subtle
   hover treatment hint at the interaction without disturbing layout. */
.gd-detail-hero-clickable { cursor: zoom-in; }
.gd-detail-hero-clickable .gd-detail-hero-img {
    transition: transform 0.18s ease, filter 0.18s ease;
}
.gd-detail-hero-clickable:hover .gd-detail-hero-img,
.gd-detail-hero-clickable:focus-visible .gd-detail-hero-img {
    transform: scale(1.012);
    filter: brightness(1.04);
}
.gd-detail-hero-clickable:focus-visible {
    outline: 2px solid rgba(168, 85, 247, 0.55);
    outline-offset: 3px;
}
.gd-detail-premium-badge {
    position: absolute; top: 0.7rem; right: 0.7rem;
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.3rem 0.6rem;
    background: rgba(0,0,0,0.7); color: #fbbf24;
    border-radius: 999px; font-size: 0.78rem; font-weight: 700;
}
.gd-detail-title-block { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.5rem 0; }
.gd-detail-kind-tag {
    display: inline-flex; align-self: flex-start;
    padding: 0.18rem 0.5rem;
    background: rgba(168, 85, 247, 0.18); color: #ddd6fe;
    border-radius: 999px;
    font-size: 0.72rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.07em;
}
.gd-detail-title { margin: 0; font-size: 1.7rem; font-weight: 700; line-height: 1.2; color: var(--text); }
.gd-detail-author { display: inline-flex; align-items: center; gap: 0.6rem; text-decoration: none; color: inherit; align-self: flex-start; }
.gd-detail-author-avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; background: #1a1a1f; }
.gd-detail-author-avatar-fallback { display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.9rem; color: var(--text-muted); background: #1a1a1f; }
.gd-detail-author-meta { display: flex; flex-direction: column; }
.gd-detail-author-byline { color: var(--text-muted); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.07em; }
.gd-detail-author-name { font-weight: 600; color: var(--text); }

.gd-detail-section-title { margin: 0; font-size: 1.05rem; font-weight: 600; color: var(--text); }
.gd-detail-desc-card,
.gd-detail-att-card,
.gd-detail-stats-card,
.gd-detail-lock-card {
    padding: 1rem; background: #0d0d10;
    border: 1px solid var(--border); border-radius: 0.7rem;
}
.gd-detail-desc { margin-top: 0.6rem; color: var(--text); line-height: 1.55; min-width: 0; }
/* Inline + fenced code styling for the description body. The
   container card (`.gd-detail-desc-card`) doesn't constrain its
   <pre> children by default, so a long unwrappable line of code
   would push the card wider than the surrounding column and break
   the layout (the "About this release" / "About this guide" box
   visibly bleeds out of its frame). overflow-x: auto on the <pre>
   gives the codeblock its own horizontal scrollbar instead, while
   max-width: 100% pins it to the card width. white-space: pre keeps
   the original line breaks. */
.gd-detail-desc .md-inline-code {
    background: #1a1a1f;
    padding: 0.05rem 0.35rem;
    border-radius: 0.25rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.9em;
    word-break: break-word;
}
.gd-detail-desc .md-codeblock {
    display: block;
    background: #18181b;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    padding: 0.6rem 0.75rem;
    margin: 0.6rem 0;
    max-width: 100%;
    overflow-x: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85rem;
    line-height: 1.45;
    white-space: pre;
}
.gd-detail-desc .md-codeblock code {
    display: block;
    background: transparent;
    padding: 0;
    font: inherit;
    color: inherit;
    white-space: pre;
}
/* Inline media embeds inside the description body. URLs on their
   own line get auto-rendered as <img> / <video> / <audio> just like
   in chat messages. The wrapper anchors images so click-through to
   the source still works. */
.gd-detail-desc .md-media-link {
    display: block;
    margin: 0.6rem 0;
    max-width: 100%;
    line-height: 0;
}
.gd-detail-desc .md-media-img {
    max-width: 100%;
    max-height: 480px;
    border-radius: 0.4rem;
    object-fit: contain;
    background: radial-gradient(ellipse at center, #15151a, #0a0a0d 75%);
    border: 1px solid var(--border);
}
.gd-detail-desc .md-media-video,
.gd-detail-desc .md-media-audio {
    display: block;
    margin: 0.6rem 0;
    max-width: 100%;
    border-radius: 0.4rem;
}
.gd-detail-desc .md-media-video {
    max-height: 480px;
    background: #000;
}
.gd-detail-desc .md-media-audio { width: 100%; }

/* Expandable / spoiler blocks. Authored as [SPOILER]...[/SPOILER]
   in the markdown source; rendered as a native <details> with a
   styled summary so the open/close affordance is keyboard-accessible
   and works without JS. Subtle border + tinted background so the
   panel reads as "set apart" without being shouty. */
.gd-detail-desc .md-spoiler-block {
    margin: 0.6rem 0;
    border: 1px solid var(--border);
    background: rgba(124, 58, 237, 0.06);
    border-radius: 0.45rem;
    overflow: hidden;
}
.gd-detail-desc .md-spoiler-summary {
    cursor: pointer;
    user-select: none;
    list-style: none;
    padding: 0.5rem 0.85rem;
    font-weight: 600;
    color: var(--text);
    background: rgba(124, 58, 237, 0.10);
    display: flex;
    align-items: center;
    gap: 0.5rem;
    line-height: 1.3;
}
.gd-detail-desc .md-spoiler-summary::-webkit-details-marker { display: none; }
.gd-detail-desc .md-spoiler-summary::before {
    content: '';
    width: 0;
    height: 0;
    border-left: 5px solid currentColor;
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform 0.15s ease;
    flex-shrink: 0;
}
.gd-detail-desc .md-spoiler-block[open] > .md-spoiler-summary::before {
    transform: rotate(90deg);
}
.gd-detail-desc .md-spoiler-summary:hover {
    background: rgba(124, 58, 237, 0.18);
}
.gd-detail-desc .md-spoiler-body {
    padding: 0.7rem 0.85rem;
    color: var(--text);
}
/* Tighten the first/last child's margin so the body padding above
   doesn't double up with paragraph margins. */
.gd-detail-desc .md-spoiler-body > *:first-child { margin-top: 0; }
.gd-detail-desc .md-spoiler-body > *:last-child  { margin-bottom: 0; }

/* OpenGraph link cards underneath the description. Discord-style:
   left-bordered colour accent, site name + favicon at the top,
   title in white, description in muted text, optional media on
   the right (small thumb) or beneath (big image / playable video). */
.gd-detail-embeds {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.85rem;
}
.gd-detail-embed-card {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.7rem 0.85rem;
    background: rgba(168, 85, 247, 0.05);
    border: 1px solid var(--border);
    border-left: 3px solid #a855f7;
    border-radius: 0 0.5rem 0.5rem 0;
    text-decoration: none;
    color: inherit;
    max-width: 540px;
    transition: background 0.12s, border-color 0.12s;
}
.gd-detail-embed-card:hover {
    background: rgba(168, 85, 247, 0.1);
    border-color: rgba(168, 85, 247, 0.4);
    border-left-color: #a855f7;
}
.gd-detail-embed-head { display: flex; align-items: center; gap: 0.4rem; }
.gd-detail-embed-favicon { width: 14px; height: 14px; border-radius: 2px; flex-shrink: 0; }
.gd-detail-embed-site { color: var(--text-muted); font-size: 0.78rem; }
.gd-detail-embed-title {
    color: #c4b5fd;
    font-weight: 600;
    line-height: 1.3;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.gd-detail-embed-desc {
    color: var(--text-muted);
    font-size: 0.88rem;
    line-height: 1.4;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
}
.gd-detail-embed-video,
.gd-detail-embed-image {
    width: 100%;
    max-height: 360px;
    border-radius: 0.4rem;
    object-fit: contain;
    background: #0a0a0d;
    margin-top: 0.4rem;
}
.gd-detail-embed-thumb {
    width: 96px;
    height: 96px;
    object-fit: cover;
    border-radius: 0.3rem;
    align-self: flex-end;
    background: #1a1a1f;
}

.gd-detail-att-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.7rem; }
.gd-detail-att-icon { color: var(--text-muted); }
.gd-detail-att-count { margin-left: auto; color: var(--text-muted); font-size: 0.85rem; }
.gd-detail-att-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.gd-detail-att-row {
    display: flex; align-items: center; gap: 0.7rem;
    padding: 0.5rem 0.6rem;
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
}
.gd-detail-att-thumb { width: 44px; height: 44px; border-radius: 0.4rem; object-fit: cover; background: #1a1a1f; flex-shrink: 0; }
.gd-detail-att-thumb-file { display: inline-flex; align-items: center; justify-content: center; color: rgba(168, 85, 247, 0.6); }
.gd-detail-att-meta { flex: 1 1 auto; min-width: 0; }
.gd-detail-att-name { font-weight: 500; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gd-detail-att-sub { color: var(--text-muted); font-size: 0.78rem; }
.gd-detail-att-dl { flex: 0 0 auto; }

.gd-detail-stat-row { display: flex; align-items: center; gap: 0.55rem; font-size: 0.92rem; }
.gd-detail-stat-icon { color: var(--text-muted); flex-shrink: 0; }
.gd-detail-stat-value { font-weight: 700; color: var(--text); }
.gd-detail-stat-label { color: var(--text-muted); }
.gd-detail-stat-date { font-weight: 500; font-size: 0.85rem; }

.gd-detail-thanks-btn {
    width: 100%; min-height: 44px;
    background: rgba(244, 114, 182, 0.15);
    border-color: rgba(244, 114, 182, 0.4);
    color: #f9a8d4;
    display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
    font-weight: 600;
}
.gd-detail-thanks-btn:hover { background: rgba(244, 114, 182, 0.25); border-color: rgba(244, 114, 182, 0.6); }
.gd-detail-thanks-btn.is-thanked {
    background: rgba(244, 114, 182, 0.35);
    border-color: rgba(244, 114, 182, 0.7);
    color: #fff;
}
.gd-detail-action-row { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.gd-detail-action-row .btn { flex: 1 1 auto; min-height: 40px; }

.gd-detail-lock-card {
    text-align: center; padding: 2rem 1.25rem;
    background: linear-gradient(135deg, rgba(168,85,247,0.08), rgba(251,191,36,0.06));
    border-color: rgba(251, 191, 36, 0.4);
}
.gd-detail-lock-icon { color: #fbbf24; margin-bottom: 0.5rem; }
.gd-detail-lock-title { margin: 0 0 0.4rem; color: var(--text); }
.gd-detail-lock-text { margin: 0 0 1rem; color: var(--text-muted); }
.gd-detail-lock-cta { background: linear-gradient(135deg, #fbbf24, #f59e0b); border-color: #f59e0b; color: #1a1a1f; font-weight: 700; }

/* ---- Editor modal ---- */
.gd-editor-overlay { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; }
.gd-editor-overlay[hidden] { display: none; }
body.gd-editor-open { overflow: hidden; }
.gd-editor-card {
    position: relative; width: min(720px, 94vw); max-height: min(92dvh, 880px);
    display: flex; flex-direction: column;
    background: #0d0d10; border: 1px solid var(--border); border-radius: 0.8rem;
    box-shadow: 0 20px 60px rgba(0,0,0,0.55);
    z-index: 1;
}
.gd-editor-head { display: flex; align-items: center; justify-content: space-between; padding: 0.85rem 1.1rem; border-bottom: 1px solid var(--border); }
.gd-editor-heading { margin: 0; font-size: 1.1rem; }
.gd-editor-close { padding: 0.3rem 0.45rem; min-height: 32px; }
.gd-editor-body { overflow-y: auto; padding: 1.25rem; display: flex; flex-direction: column; gap: 1rem; }
.gd-editor-error {
    padding: 0.6rem 0.8rem;
    background: rgba(220,38,38,0.12);
    border: 1px solid rgba(220,38,38,0.4);
    border-radius: 0.4rem;
    color: #fecaca;
    margin: 0;
}
.gd-editor-kind-row { display: flex; gap: 0.5rem; }
.gd-editor-kind-btn {
    flex: 1 1 0;
    display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
    padding: 0.7rem;
    background: #131316; border: 1px solid var(--border); border-radius: 0.5rem;
    color: var(--text-muted); cursor: pointer; font-weight: 500;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.gd-editor-kind-btn:hover { color: var(--text); }
.gd-editor-kind-btn.is-active {
    background: rgba(168, 85, 247, 0.18);
    border-color: rgba(168, 85, 247, 0.6);
    color: #ddd6fe;
}
.gd-editor-field { display: flex; flex-direction: column; gap: 0.35rem; position: relative; }
.gd-editor-field-label { color: var(--text); font-size: 0.85rem; font-weight: 600; }
/* Drop-target hover state. The fields that accept drag-and-drop
   (preview image + attachments) get this class while a file drag is
   over them. Outline (not border) so the layout doesn't shift, plus
   a brand-tinted background and a subtle "Drop to upload" overlay. */
.gd-editor-field.is-drop-target {
    outline: 2px dashed rgba(168, 85, 247, 0.7);
    outline-offset: 4px;
    border-radius: 0.5rem;
    background: rgba(168, 85, 247, 0.06);
}
.gd-editor-field.is-drop-target::after {
    content: 'Drop to upload';
    position: absolute;
    inset: 0;
    display: flex; align-items: center; justify-content: center;
    background: rgba(10, 10, 13, 0.55);
    color: #ddd6fe;
    font-weight: 700;
    font-size: 0.95rem;
    border-radius: 0.5rem;
    pointer-events: none;
    z-index: 2;
}
.gd-editor-input {
    background: #0a0a0d; border: 1px solid var(--border); border-radius: 0.4rem;
    color: var(--text); padding: 0.55rem 0.7rem; font: inherit;
    width: 100%; box-sizing: border-box;
}
.gd-editor-input:focus { outline: none; border-color: var(--brand); }
.gd-editor-textarea { resize: vertical; min-height: 8rem; line-height: 1.5; }
.gd-editor-hint { margin: 0; color: var(--text-muted); font-size: 0.78rem; }
.gd-editor-preview-wrap {
    background: #0a0a0d; border: 1px dashed var(--border); border-radius: 0.5rem;
    aspect-ratio: 16/8;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden; margin-bottom: 0.5rem;
}
.gd-editor-preview-img { max-width: 100%; max-height: 100%; object-fit: contain; }
.gd-editor-preview-empty { color: var(--text-muted); font-size: 0.85rem; }
.gd-editor-preview-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.4rem; }
.gd-editor-preview-btn { flex: 0 0 auto; min-height: 36px; }
.gd-editor-att-list {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-direction: column; gap: 0.4rem;
    max-height: 14rem; overflow-y: auto;
}
.gd-editor-att-row {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.4rem 0.55rem;
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--border);
    border-radius: 0.45rem;
}
.gd-editor-att-thumb { width: 40px; height: 40px; border-radius: 0.35rem; object-fit: cover; background: #1a1a1f; flex-shrink: 0; }
.gd-editor-att-thumb-file { display: inline-flex; align-items: center; justify-content: center; color: rgba(168, 85, 247, 0.55); }
.gd-editor-att-meta { flex: 1 1 auto; min-width: 0; }
.gd-editor-att-name { font-weight: 500; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gd-editor-att-sub { color: var(--text-muted); font-size: 0.75rem; }
.gd-editor-att-remove { padding: 0.3rem; min-height: 32px; flex: 0 0 auto; }
.gd-editor-checkbox-row {
    display: flex; align-items: center; gap: 0.55rem;
    padding: 0.55rem 0.7rem;
    background: rgba(251, 191, 36, 0.08);
    border: 1px solid rgba(251, 191, 36, 0.3);
    border-radius: 0.4rem;
    color: var(--text); font-size: 0.9rem; cursor: pointer;
}
.gd-editor-checkbox-row input { width: 16px; height: 16px; accent-color: #fbbf24; flex-shrink: 0; }
.gd-editor-actions {
    display: flex; justify-content: flex-end; gap: 0.5rem;
    padding-top: 0.5rem; border-top: 1px solid var(--border);
}
.gd-editor-submit { min-width: 120px; }

/* ---- Storage picker ---- */
.gd-storage-picker-overlay { position: fixed; inset: 0; z-index: 110; display: flex; align-items: center; justify-content: center; }
.gd-storage-picker-card {
    position: relative; width: min(520px, 92vw); max-height: min(80dvh, 720px);
    display: flex; flex-direction: column;
    background: #0d0d10; border: 1px solid var(--border); border-radius: 0.7rem;
    z-index: 1;
}
.gd-storage-picker { padding: 1rem; display: flex; flex-direction: column; gap: 0.7rem; min-height: 0; }
.gd-storage-picker-heading { margin: 0; }
.gd-storage-picker-hint { margin: 0; color: var(--text-muted); font-size: 0.82rem; }
.gd-storage-picker-status { margin: 0; color: var(--text-muted); }
/* Default grid: even square tiles for the image-only preview picker. */
.gd-storage-picker-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 0.5rem;
    overflow-y: auto;
    flex: 1 1 auto;
}
/* Multi (attachments) mode: vertical list because non-image rows
   need horizontal room for filename + size + mime. Images in this
   mode keep a thumbnail on the left and the filename next to it. */
.gd-storage-picker-grid--rows {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.gd-storage-picker-grid--rows .gd-storage-picker-tile {
    aspect-ratio: auto;
    flex-direction: row;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.55rem;
    min-height: 56px;
}
.gd-storage-picker-grid--rows .gd-storage-picker-tile img {
    width: 48px; height: 48px;
    flex: 0 0 auto;
    object-fit: cover;
    border-radius: 0.3rem;
}
.gd-storage-picker-grid--rows .gd-storage-picker-tile .gd-storage-picker-fname {
    background: transparent;
    padding: 0;
    flex: 1 1 auto;
    color: var(--text);
    font-size: 0.85rem;
}

.gd-storage-picker-tile {
    aspect-ratio: 1/1;
    background: #0a0a0d;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    overflow: hidden;
    cursor: pointer;
    padding: 0;
    display: flex; flex-direction: column;
    align-items: stretch;
    text-align: left;
    color: var(--text);
}
.gd-storage-picker-tile img { width: 100%; height: 100%; object-fit: cover; }
.gd-storage-picker-tile .gd-storage-picker-file {
    flex: 1; display: flex; align-items: center; justify-content: center;
    color: rgba(168, 85, 247, 0.6);
}
.gd-storage-picker-tile .gd-storage-picker-fname {
    padding: 0.25rem 0.4rem;
    background: rgba(0,0,0,0.4);
    color: var(--text); font-size: 0.7rem;
    text-overflow: ellipsis; white-space: nowrap; overflow: hidden;
}
/* Non-image rows (file-icon variant) - filename + size stack to
   the right of the icon. Long names truncate; full name stays in
   the title attribute so a hover reveals it. */
.gd-storage-picker-tile--file {
    aspect-ratio: auto;
    flex-direction: row;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0.6rem;
    min-height: 56px;
}
.gd-storage-picker-tile--file .gd-storage-picker-file {
    flex: 0 0 auto;
    width: 40px; height: 40px;
    background: rgba(168, 85, 247, 0.1);
    border-radius: 0.3rem;
}
.gd-storage-picker-meta { flex: 1 1 auto; min-width: 0; }
.gd-storage-picker-fname-strong {
    font-weight: 600;
    color: var(--text);
    font-size: 0.88rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gd-storage-picker-fmeta {
    color: var(--text-muted);
    font-size: 0.74rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.gd-storage-picker-tile:hover { border-color: rgba(168, 85, 247, 0.6); background: #131316; }
.gd-storage-picker-actions { display: flex; justify-content: flex-end; }

/* Profile -> Guides tab "Load more" CTA. Centred under the grid
   so it doesn't fight the cards visually. */
.profile-guides-more {
    display: flex;
    justify-content: center;
    margin-top: 1rem;
}

/* ================================================================
   Desktop polish: tasteful animations gated to non-mobile + non-
   reduced-motion users. Mobile keeps the existing fast / instant
   feedback so taps don't feel laggy. Anything in here should be
   purely visual sugar - never load-bearing for layout or
   accessibility.
   ================================================================ */
@media (hover: hover) and (min-width: 769px) and (prefers-reduced-motion: no-preference) {

    /* ---- Generic buttons ----
       Subtle lift + shadow on hover, settle on press. Plus a soft
       diagonal sweep ("shimmer") that sails across the button on
       hover so primary CTAs feel a touch more alive. The `::before`
       is invisible until hover so non-hovered buttons stay flat. */
    .btn {
        position: relative;
        overflow: hidden;
        transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),
                    box-shadow 0.18s cubic-bezier(0.4, 0, 0.2, 1),
                    background-color 0.15s, border-color 0.15s, color 0.15s;
    }
    .btn::before {
        content: '';
        position: absolute;
        inset: 0;
        background: linear-gradient(115deg,
            transparent 30%,
            rgba(255, 255, 255, 0.08) 50%,
            transparent 70%);
        transform: translateX(-110%);
        transition: transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
        pointer-events: none;
    }
    .btn:hover { transform: translateY(-1px); }
    .btn:hover::before { transform: translateX(110%); }
    .btn:active { transform: translateY(0); transition-duration: 60ms; }

    /* Buttons that already use a strong purple background gain a
       coloured glow on hover so the primary CTA feels punchy. */
    .btn-primary:hover,
    .gw-detail-enter-btn:hover,
    .gd-new-btn:hover,
    .gw-new-btn:hover {
        box-shadow: 0 6px 20px rgba(168, 85, 247, 0.35);
    }

    /* ---- Marketplace / giveaway / guide cards ----
       Lift + soft purple shadow + tiny scale. Replaces the prior
       1px lift with something that reads as "tactile" without
       pushing the grid around. */
    .mkt-product-card,
    .gw-card,
    .gd-card {
        transition: transform 0.2s cubic-bezier(0.34, 1.4, 0.64, 1),
                    box-shadow 0.2s cubic-bezier(0.34, 1.4, 0.64, 1),
                    border-color 0.15s;
    }
    .mkt-product-card:hover,
    .gw-card:hover,
    .gd-card:hover {
        transform: translateY(-3px) scale(1.012);
        box-shadow: 0 10px 28px rgba(168, 85, 247, 0.22);
    }
    .gd-card--premium:hover,
    .gw-card-premium-badge ~ * .gw-card:hover {
        box-shadow: 0 10px 28px rgba(251, 191, 36, 0.25);
    }

    /* Card preview image: tiny zoom on card hover so the artwork
       feels active without breaking the contain-fit framing. */
    .mkt-product-card:hover .mkt-product-card-img,
    .gw-card:hover .gw-card-img,
    .gd-card:hover .gd-card-img {
        transform: scale(1.04);
    }
    .mkt-product-card-img,
    .gw-card-img,
    .gd-card-img {
        transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
    }

    /* ---- Avatars ----
       Subtle pop on hover for any clickable avatar. Excludes the
       in-message avatar which is already its own affordance, and
       the chat-rail server icons which have their own hover. */
    .gw-detail-creator-avatar,
    .gw-card-creator-avatar,
    .gd-card-author-avatar,
    .gd-detail-author-avatar,
    .gd-entries-avatar,
    .mkt-product-card-seller-avatar {
        transition: transform 0.18s cubic-bezier(0.34, 1.4, 0.64, 1);
    }
    .gw-detail-creator:hover .gw-detail-creator-avatar,
    .gw-card:hover .gw-card-creator-avatar,
    .gd-card:hover .gd-card-author-avatar,
    .gd-detail-author:hover .gd-detail-author-avatar,
    .mkt-product-card:hover .mkt-product-card-seller-avatar {
        transform: scale(1.08);
    }

    /* ---- Top-nav primary items ----
       Underline-grow under each nav pill on hover, full-width when
       active. Cheaper than a full background pill animation and
       feels more "modern". */
    .primary-nav-item { position: relative; }
    .primary-nav-item::after {
        content: '';
        position: absolute;
        left: 50%;
        bottom: -4px;
        width: 0;
        height: 2px;
        background: linear-gradient(90deg, #a855f7, #c4b5fd);
        border-radius: 2px;
        transform: translateX(-50%);
        transition: width 0.22s cubic-bezier(0.4, 0, 0.2, 1);
    }
    .primary-nav-item:hover::after,
    .primary-nav-item.is-active::after { width: 70%; }

    /* ---- Modal entrance ----
       Fade + scale-from-just-below for any modal that paints with
       the standard `.modal-card` shell. Keeps the existing CSS
       transition logic but adds a one-shot animation when the
       parent's `[hidden]` flips to false. */
    .modal:not([hidden]) > .modal-card,
    .modal:not([hidden]) .modal-card,
    .gd-editor-overlay:not([hidden]) .modal-card,
    .gw-entries-overlay:not([hidden]) .modal-card,
    .gd-storage-picker-overlay .modal-card {
        animation: madrigalModalEnter 0.18s cubic-bezier(0.34, 1.4, 0.64, 1);
    }
    @keyframes madrigalModalEnter {
        from { opacity: 0; transform: translateY(10px) scale(0.97); }
        to   { opacity: 1; transform: translateY(0)    scale(1); }
    }
    .modal:not([hidden]) .modal-backdrop,
    .gd-editor-overlay:not([hidden]) .modal-backdrop {
        animation: madrigalBackdropEnter 0.18s ease-out;
    }
    @keyframes madrigalBackdropEnter {
        from { opacity: 0; }
        to   { opacity: 1; }
    }

    /* ---- Toast / chat-toast slide-in ----
       Bouncy entry from off-screen for any site / chat toast. The
       animation only fires once on insertion - subsequent class
       toggles don't replay. */
    .site-toast,
    .chat-toast,
    .madrigal-inapp-notif {
        animation: madrigalToastIn 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
    }
    @keyframes madrigalToastIn {
        from { opacity: 0; transform: translateX(110%); }
        to   { opacity: 1; transform: translateX(0); }
    }

    /* ---- Thanks button heart pop ----
       Pop the heart when the user clicks Thanks. CSS animation runs
       only when the .is-thanked class lands on the button (set by
       JS after the toggle response succeeds). */
    .gd-detail-thanks-btn.is-thanked .lucide {
        animation: madrigalHeartPop 0.45s cubic-bezier(0.34, 1.6, 0.5, 1);
    }
    @keyframes madrigalHeartPop {
        0%   { transform: scale(1)   rotate(0); }
        30%  { transform: scale(1.4) rotate(-8deg); }
        60%  { transform: scale(0.92) rotate(4deg); }
        100% { transform: scale(1)   rotate(0); }
    }

    /* ---- Premium badge gentle pulse ----
       Soft brightness sway on the Premium pill so it reads as
       "special" without looking like a flashing ad. Slow + subtle. */
    .gd-card-premium-badge,
    .gw-detail-premium-badge,
    .gd-detail-premium-badge,
    .mkt-product-card-premium-badge {
        animation: madrigalPremiumPulse 3.4s ease-in-out infinite;
    }
    @keyframes madrigalPremiumPulse {
        0%, 100% { filter: brightness(1)   drop-shadow(0 0 0 rgba(251, 191, 36, 0)); }
        50%      { filter: brightness(1.1) drop-shadow(0 0 6px rgba(251, 191, 36, 0.45)); }
    }

    /* ---- Sparkles wand-sparkles wobble ----
       Tiny rotation tick on the magic-themed icons in the giveaway
       hero so they feel less static. Capped at 2deg so the page
       never looks twitchy. */
    .gw-detail-hero-empty-icon,
    .gd-detail-hero-empty-icon {
        animation: madrigalIconFloat 4s ease-in-out infinite;
    }
    @keyframes madrigalIconFloat {
        0%, 100% { transform: translateY(0)   rotate(0); }
        50%      { transform: translateY(-3px) rotate(-2deg); }
    }

    /* ---- Server rail icons ----
       Quick scale on hover plus a tiny bounce on the active
       indicator. Replaces the previous flat hover state. */
    .chat-rail-item .chat-rail-icon-server,
    .chat-rail-item .chat-rail-icon-create {
        transition: transform 0.18s cubic-bezier(0.34, 1.4, 0.64, 1),
                    box-shadow 0.18s cubic-bezier(0.34, 1.4, 0.64, 1);
    }
    .chat-rail-item:hover .chat-rail-icon-server,
    .chat-rail-item:hover .chat-rail-icon-create {
        transform: translateY(-2px) scale(1.04);
    }

    /* ---- Filter / kind / sort pills ----
       Pretty hover (subtle grow + brightness) on every pill the
       site uses. Matches the brand purple ramp-up. */
    .gd-kind-pill,
    .gd-sort-pill,
    .gd-cat-pill,
    .gw-filter-pill,
    .mkt-filter-pill {
        transition: transform 0.15s, background-color 0.15s, border-color 0.15s, color 0.15s;
    }
    .gd-kind-pill:hover,
    .gd-sort-pill:hover,
    .gd-cat-pill:hover,
    .gw-filter-pill:hover,
    .mkt-filter-pill:hover {
        transform: translateY(-1px);
    }

    /* ---- Embed cards on guides detail ----
       Lift on hover with a coloured ring matching the left-border
       accent. */
    .gd-detail-embed-card {
        transition: transform 0.18s cubic-bezier(0.34, 1.4, 0.64, 1),
                    box-shadow 0.18s, background-color 0.15s, border-color 0.15s;
    }
    .gd-detail-embed-card:hover {
        transform: translateY(-2px);
        box-shadow: 0 8px 22px rgba(168, 85, 247, 0.18);
    }

    /* ---- Page enter ----
       One-shot fade-in on first load so a navigation feels less
       abrupt. Brief and not noticeable on cached repeat visits. */
    body {
        animation: madrigalPageEnter 0.22s ease-out;
    }
    @keyframes madrigalPageEnter {
        from { opacity: 0.85; }
        to   { opacity: 1; }
    }

    /* ---- Inputs / textareas focus glow ----
       Soft expanding ring when an input gains focus. Keeps the
       solid border but layers a brand-tinted box-shadow so the
       focus state is visible against any surface colour. */
    input[type="text"]:focus-visible,
    input[type="number"]:focus-visible,
    input[type="email"]:focus-visible,
    input[type="password"]:focus-visible,
    input[type="search"]:focus-visible,
    input[type="url"]:focus-visible,
    textarea:focus-visible {
        animation: madrigalFocusGlow 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.22);
    }
    @keyframes madrigalFocusGlow {
        from { box-shadow: 0 0 0 6px rgba(168, 85, 247, 0); }
        to   { box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.22); }
    }
}


/* =================================================================
   Robot Bang chat UI - lives at /robot-bang.
   Layout: fixed-height column. Header on top, scrolling thread in the
   middle, composer pinned to the bottom. Mobile-first; bubbles cap
   at min(72ch, 100%) so long lines stay readable.
   ================================================================= */
/* Two-column shell: sidebar (saved chats) on the left, chat on the right.
   Mobile: sidebar collapses behind the chat by default; toggle reveals it. */
.rb-shell {
    display: flex;
    height: 100%;
    min-height: 0;
    width: 100%;
    box-sizing: border-box;
    padding-left: 72px; /* room for the bottom-left FAB column */
}
.rb-sidebar {
    flex: 0 0 18rem;
    max-width: 18rem;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-right: 1px solid var(--border);
    overflow: hidden;
    transition: flex-basis 0.18s ease, max-width 0.18s ease;
}
.rb-sidebar[hidden] {
    flex: 0 0 0;
    max-width: 0;
    border-right: 0;
}
.rb-sidebar-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.7rem 0.6rem;
    border-bottom: 1px solid var(--border);
}
.rb-new-chat-btn {
    flex: 1 1 auto;
    appearance: none;
    background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
    color: #fff;
    border: 0;
    padding: 0.5rem 0.75rem;
    border-radius: 0.5rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    font: inherit;
    font-weight: 600;
    font-size: 0.85rem;
    box-shadow: 0 4px 12px -4px rgba(139, 92, 246, 0.45);
    transition: transform 0.12s ease;
}
.rb-new-chat-btn:hover  { transform: translateY(-1px); }
.rb-new-chat-btn:active { transform: translateY(1px); }
.rb-sidebar-toggle,
.rb-sidebar-reopen {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    appearance: none;
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-muted);
    border-radius: 0.4rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.rb-sidebar-toggle:hover,
.rb-sidebar-reopen:hover {
    color: var(--text);
    border-color: rgba(139, 92, 246, 0.45);
}
.rb-sidebar-reopen {
    position: fixed;
    left: 80px;
    top: 80px;
    z-index: 30;
    background: var(--surface);
}
.rb-sidebar-tabs {
    display: flex;
    gap: 0.25rem;
    padding: 0.4rem 0.5rem 0;
    border-bottom: 1px solid var(--border);
}
.rb-sidebar-tab {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    padding: 0.45rem 0.65rem;
    border-radius: 0.4rem 0.4rem 0 0;
    cursor: pointer;
    font: inherit;
    font-size: 0.82rem;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color 0.15s, border-color 0.15s;
}
.rb-sidebar-tab:hover { color: var(--text); }
.rb-sidebar-tab.is-active {
    color: var(--text);
    border-bottom-color: rgba(139, 92, 246, 0.7);
}

.rb-conv-list {
    flex: 1 1 auto;
    list-style: none;
    margin: 0;
    padding: 0.4rem 0.4rem;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

/* Privacy lock icon next to private convos in My tab. */
.rb-conv-item-lock {
    flex-shrink: 0;
    color: #fbbf24;
    width: 14px;
    height: 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.rb-conv-item-lock[data-is-private="0"] { display: none; }

/* Read-only banner above the chat when viewing someone else's. */
.rb-readonly-banner {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.85rem;
    margin: 0.5rem 0 0.4rem;
    background: rgba(56, 189, 248, 0.10);
    border: 1px solid rgba(56, 189, 248, 0.28);
    border-radius: 0.45rem;
    color: #7dd3fc;
    font-size: 0.82rem;
}
.rb-readonly-banner svg { color: #38bdf8; flex-shrink: 0; }
.rb-readonly-banner[hidden] { display: none; }
/* Site-wide disable banner (staff /site-settings toggle). Amber so
   it reads as "temporary maintenance" rather than "you're locked
   out" - the chat still renders for read-only review of past
   conversations. */
.rb-disabled-banner {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.85rem;
    margin: 0.5rem 0 0.4rem;
    background: rgba(245, 158, 11, 0.10);
    border: 1px solid rgba(245, 158, 11, 0.32);
    border-radius: 0.45rem;
    color: #fbbf24;
    font-size: 0.82rem;
}
.rb-disabled-banner svg { color: #fbbf24; flex-shrink: 0; }
/* When read-only, hide the composer + Reset button entirely. */
.rb.is-readonly .rb-composer,
.rb.is-readonly .rb-reset-btn {
    display: none;
}
.rb-conv-item {
    position: relative;
    padding: 0.55rem 0.7rem;
    border-radius: 0.45rem;
    color: var(--text);
    cursor: pointer;
    font-size: 0.86rem;
    line-height: 1.35;
    border: 1px solid transparent;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    overflow: hidden;
}
.rb-conv-item:hover {
    background: rgba(139, 92, 246, 0.08);
}
.rb-conv-item.is-active {
    background: rgba(139, 92, 246, 0.18);
    border-color: rgba(139, 92, 246, 0.45);
}
.rb-conv-item-title {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.rb-conv-item-actions {
    display: none;
    align-items: center;
    gap: 0.15rem;
    flex-shrink: 0;
}
.rb-conv-item:hover .rb-conv-item-actions,
.rb-conv-item:focus-within .rb-conv-item-actions {
    display: inline-flex;
}
.rb-conv-item-action {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    width: 22px;
    height: 22px;
    border-radius: 4px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}
.rb-conv-item-action:hover {
    color: var(--text);
    background: rgba(255, 255, 255, 0.08);
}
.rb-conv-item-action.is-danger:hover {
    color: #fff;
    background: #dc2626;
}
.rb-conv-empty {
    color: var(--text-muted);
    font-size: 0.82rem;
    text-align: center;
    padding: 1rem 0.6rem;
}

.rb {
    /* Fills its parent flex slot in .rb-shell. Lock at the top of
       this file (body:has(.rb) { overflow:hidden; height:100dvh })
       guarantees no page-level scroll. Only .rb-stage scrolls. */
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    margin: 0 auto;
    width: 100%;
    /* Wider than before so the chat uses the available real estate on
       big screens. Bubbles still cap at 72ch internally so reading
       lines stay comfortable - this just lets the column breathe and
       gives the activity log + bot avatar room. */
    max-width: 1500px;
    padding: 0 1.25rem;
    box-sizing: border-box;
}

@media (max-width: 768px) {
    .rb-shell { padding-left: 0; }
    .rb-sidebar { position: absolute; top: 0; bottom: 0; left: 0; z-index: 25; flex: 0 0 80vw; max-width: 80vw; }
    .rb-sidebar[hidden] { transform: translateX(-100%); display: flex; max-width: 80vw; flex: 0 0 80vw; }
    /* Center the reopen handle vertically along the left edge so it
       doesn't overlap the site-header hamburger at the top-left.
       transform: translateY(-50%) anchors the 28 px icon's mid-line
       to the viewport mid-line, well clear of both the header above
       and the composer below. */
    .rb-sidebar-reopen {
        left: 0.5rem;
        top: 50%;
        transform: translateY(-50%);
    }
}
.rb-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 1rem 0 0.85rem;
    border-bottom: 1px solid var(--border);
}
.rb-head-left {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    min-width: 0;
}
.rb-head-titleblock { min-width: 0; }
.rb-head-title {
    margin: 0 0 0.15rem;
    font-size: 1.15rem;
    line-height: 1.1;
    font-weight: 700;
}
.rb-head-sub {
    margin: 0;
    font-size: 0.82rem;
    color: var(--text-muted);
    line-height: 1.35;
    max-width: 56ch;
    overflow-wrap: anywhere;
}
.rb-head-right {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    flex-shrink: 0;
}
.rb-reset-btn,
.rb-rules-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
    text-decoration: none;
}
.rb-reset-btn svg,
.rb-rules-btn svg { display: block; }
/* Privacy toggle in the chat header. iOS-style switch + "Private"
   label. Premium-only; JS hides it for non-premium and read-only views. */
.rb-privacy-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    user-select: none;
    flex-shrink: 0;
    font-size: 0.85rem;
    color: var(--text);
    padding: 0.35rem 0.55rem;
    border-radius: 0.45rem;
    transition: background 0.15s;
}
.rb-privacy-toggle:hover { background: rgba(255, 255, 255, 0.04); }
.rb-privacy-toggle[hidden] { display: none; }
.rb-privacy-toggle-input {
    position: absolute;
    opacity: 0;
    width: 0; height: 0;
    pointer-events: none;
}
.rb-privacy-toggle-track {
    position: relative;
    display: inline-block;
    width: 36px;
    height: 20px;
    background: rgba(255, 255, 255, 0.14);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 999px;
    transition: background 0.18s ease, border-color 0.18s;
    flex-shrink: 0;
}
.rb-privacy-toggle-knob {
    position: absolute;
    top: 1px;
    left: 1px;
    width: 16px;
    height: 16px;
    background: #fff;
    border-radius: 50%;
    transition: transform 0.18s ease;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.rb-privacy-toggle-input:checked + .rb-privacy-toggle-track {
    background: linear-gradient(135deg, #fbbf24, #f59e0b);
    border-color: rgba(251, 191, 36, 0.5);
}
.rb-privacy-toggle-input:checked + .rb-privacy-toggle-track .rb-privacy-toggle-knob {
    transform: translateX(16px);
}
.rb-privacy-toggle-input:focus-visible + .rb-privacy-toggle-track {
    box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.35);
}
.rb-privacy-toggle-label {
    font-weight: 600;
    letter-spacing: 0.02em;
}
.rb-privacy-toggle:has(.rb-privacy-toggle-input:checked) .rb-privacy-toggle-label {
    color: #fde68a;
}

/* ----- Token meter -----
   Compact horizontal pill that shows daily token usage. The bar fills
   left-to-right; it shifts colour at 80% (warn) and 100% (full). The
   tooltip shows the raw remaining count + countdown to UTC reset. */
.rb-token-meter {
    display: inline-flex;
    flex-direction: column;
    gap: 0.18rem;
    min-width: 120px;
    padding: 0.32rem 0.55rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    cursor: help;
}
.rb-token-meter[hidden] { display: none; }
.rb-token-meter-bar {
    position: relative;
    height: 6px;
    border-radius: 999px;
    background: rgba(255,255,255,0.08);
    overflow: hidden;
}
.rb-token-meter-fill {
    height: 100%;
    width: 0%;
    border-radius: 999px;
    background: linear-gradient(90deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%);
    transition: width 0.4s ease, background 0.3s ease;
}
.rb-token-meter-fill.is-warn { background: linear-gradient(90deg, #f59e0b, #ef4444); }
.rb-token-meter-fill.is-full { background: linear-gradient(90deg, #ef4444, #dc2626); }
.rb-token-meter-label {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.72rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
.rb-token-meter-label svg {
    color: #a78bfa;
    flex-shrink: 0;
}
@media (max-width: 560px) {
    .rb-token-meter { min-width: 96px; padding: 0.28rem 0.45rem; }
    .rb-token-meter-label { font-size: 0.68rem; }
    .rb-reset-btn span { display: none; } /* keep just the trash icon on tiny widths */
}

.rb-stage {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 1rem 0.25rem;
    scroll-behavior: smooth;
}

/* ----- empty / first-load state ----- */
.rb-empty {
    margin: 2rem auto 1.5rem;
    text-align: center;
    max-width: 56ch;
    color: var(--text-muted);
}
.rb-empty.is-hidden { display: none; }
.rb-empty-orb {
    width: 128px;
    height: 128px;
    margin: 0 auto 1rem;
    border-radius: 999px;
    object-fit: cover;
    display: block;
    background: var(--surface-2);
    box-shadow:
        0 0 0 4px rgba(139, 92, 246, 0.18),
        0 8px 24px -6px rgba(139, 92, 246, 0.55);
    animation: rb-orb-bob 3s ease-in-out infinite;
    image-rendering: auto;
    image-rendering: smooth;
    image-rendering: high-quality;
}
@keyframes rb-orb-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-3px); }
}
.rb-empty-title {
    margin: 0 0 0.55rem;
    font-size: 1.25rem;
    color: var(--text);
}
.rb-empty-msg {
    margin: 0 0 1.4rem;
    line-height: 1.55;
    font-size: 0.95rem;
}
.rb-empty-suggestions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-content: center;
}
.rb-suggestion {
    appearance: none;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    padding: 0.55rem 0.9rem;
    border-radius: 999px;
    font-size: 0.85rem;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, transform 0.1s;
}
.rb-suggestion:hover {
    background: rgba(139, 92, 246, 0.12);
    border-color: rgba(139, 92, 246, 0.5);
}
.rb-suggestion:active { transform: translateY(1px); }

/* ----- thread / messages ----- */
.rb-thread {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.rb-row {
    display: flex;
    gap: 0.65rem;
    align-items: flex-end;
}
.rb-row-bot { justify-content: flex-start; }
.rb-row-user { justify-content: flex-end; }
.rb-avatar {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    overflow: hidden;
    object-fit: cover;
    /* Force the smooth high-quality downscaler. Without this Chromium
       can pick a low-quality scaler when the source PNG is much larger
       than the displayed box (our profile pic is 687KB native, this
       slot is 36px) and the avatar reads as pixelated. */
    image-rendering: auto;
    image-rendering: smooth;
    image-rendering: high-quality;
}
.rb-avatar-bot {
    /* The avatar is now an <img> showing the Robot Pang profile pic.
       Box-shadow stays; gradient drops because the image covers the
       full circle. object-fit:cover handles non-square sources. */
    object-fit: cover;
    box-shadow: 0 2px 8px -2px rgba(139, 92, 246, 0.5);
    background: var(--surface-2);
}
.rb-avatar-user {
    background: var(--surface-2);
    border: 1px solid var(--border);
    color: var(--text);
    font-weight: 600;
}
img.rb-avatar { object-fit: cover; }
.rb-avatar-fallback { font-size: 0.95rem; }

.rb-bubble {
    max-width: min(72ch, calc(100% - 56px));
    padding: 0.7rem 0.95rem;
    border-radius: 18px;
    line-height: 1.55;
    font-size: 0.95rem;
    overflow-wrap: anywhere;
    word-break: break-word;
}
.rb-bubble p { margin: 0 0 0.6rem; }
.rb-bubble p:last-child { margin: 0; }
.rb-bubble-user {
    background: linear-gradient(135deg, #6366f1, #8b5cf6);
    color: #fff;
    border-bottom-right-radius: 6px;
}
.rb-bubble-user a { color: #fff; text-decoration: underline; }
.rb-bubble-bot {
    background: var(--surface);
    border: 1px solid var(--border);
    border-bottom-left-radius: 6px;
}
.rb-bubble-bot .rb-h2,
.rb-bubble-bot .rb-h3 {
    margin: 0.6rem 0 0.4rem;
    font-weight: 700;
}
.rb-bubble-bot .rb-h2 { font-size: 1.05rem; }
.rb-bubble-bot .rb-h3 { font-size: 0.98rem; }
.rb-bubble-bot .rb-list {
    margin: 0.4rem 0;
    padding-left: 1.2rem;
}
.rb-bubble-bot .rb-list li {
    margin: 0.15rem 0;
    line-height: 1.5;
}
.rb-bubble-bot .rb-icode {
    background: rgba(99, 102, 241, 0.12);
    padding: 0.1rem 0.35rem;
    border-radius: 4px;
    font-size: 0.88em;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.rb-bubble-bot .rb-code {
    margin: 0.55rem 0;
    padding: 0.7rem 0.85rem;
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 10px;
    overflow-x: auto;
    font-size: 0.85rem;
    line-height: 1.45;
}
.rb-bubble-bot .rb-code code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: #f1f5f9;
    white-space: pre;
}

/* ----- thinking indicator + live activity log ----- */
.rb-thinking {
    display: flex !important;
    flex-direction: column;
    gap: 0.5rem;
    max-width: min(72ch, calc(100% - 56px));
    /* Anchor for the live token chip in the top-right corner. */
    position: relative;
}
/* Live token chip on the thinking bubble. Pinned top-right; updates
   in place every ~5s as the sidecar emits usage_partial events.
   Hidden until the first event arrives so brief runs that complete
   before the first 5s window don't flash an empty chip. */
.rb-thinking-tokens {
    position: absolute;
    top: 0.45rem;
    right: 0.6rem;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.12rem 0.45rem;
    background: rgba(124, 58, 237, 0.16);
    border: 1px solid rgba(124, 58, 237, 0.35);
    color: #c4b5fd;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    cursor: help;
    z-index: 1;
}
.rb-thinking-tokens .lucide { color: #c4b5fd; }
.rb-thinking-tokens[hidden]  { display: none; }
.rb-thinking-head {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.rb-thinking-dot {
    width: 7px;
    height: 7px;
    border-radius: 999px;
    background: var(--text-muted);
    animation: rb-think 1.4s ease-in-out infinite;
}
.rb-thinking-dot:nth-child(2) { animation-delay: 0.18s; }
.rb-thinking-dot:nth-child(3) { animation-delay: 0.36s; }
.rb-thinking-label {
    margin-left: 0.35rem;
    color: var(--text-muted);
    font-size: 0.88rem;
}
@keyframes rb-think {
    0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
    40%           { transform: scale(1);   opacity: 1;   }
}
/* Streamed activity rows. Newer rows append at the bottom; JS trims to
   MAX_LOG entries to keep the bubble bounded. */
.rb-thinking-log {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    font-size: 0.82rem;
    line-height: 1.45;
    color: var(--text-muted);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    border-left: 2px solid rgba(139, 92, 246, 0.35);
    padding-left: 0.65rem;
    margin-left: 0.2rem;
}
.rb-thinking-log:empty { display: none; }
.rb-thinking-line {
    animation: rb-thinking-line-in 0.18s ease-out;
    overflow-wrap: anywhere;
}
.rb-thinking-line-tool { color: var(--text); opacity: 0.95; }
.rb-thinking-line-text {
    /* Intermediate text deltas: regular font, italic, slightly muted -
       reads like a live preview of what claude is writing. */
    font-family: inherit;
    font-size: 0.88rem;
    color: var(--text-muted);
    font-style: italic;
}
@keyframes rb-thinking-line-in {
    from { opacity: 0; transform: translateY(2px); }
    to   { opacity: 1; transform: translateY(0);   }
}

.rb-meta {
    margin-top: 0.55rem;
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.45rem;
    font-size: 0.72rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
}
.rb-meta-tokens {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.18rem 0.5rem;
    border-radius: 999px;
    background: rgba(139, 92, 246, 0.12);
    border: 1px solid rgba(139, 92, 246, 0.28);
    color: #c4b5fd;
    font-variant-numeric: tabular-nums;
    text-transform: none;
    cursor: help;
}
.rb-meta-tokens svg { color: #a78bfa; flex-shrink: 0; }
/* Duration chip - same shape as the token chip but in a cooler hue
   so the eye can quickly read "X tokens, Ys" without re-scanning. */
.rb-meta-time {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.18rem 0.5rem;
    border-radius: 999px;
    background: rgba(56, 189, 248, 0.10);
    border: 1px solid rgba(56, 189, 248, 0.24);
    color: #7dd3fc;
    font-variant-numeric: tabular-nums;
    text-transform: none;
    cursor: help;
}
.rb-meta-time svg { color: #38bdf8; flex-shrink: 0; }
.rb-meta-modes { text-transform: uppercase; letter-spacing: 0.06em; }
.rb-error {
    color: #fca5a5;
    margin: 0;
}

/* ----- composer ----- */
.rb-composer {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.75rem 0 calc(0.75rem + env(safe-area-inset-bottom));
    border-top: 1px solid var(--border);
    background: var(--bg);
    position: sticky;
    bottom: 0;
}
.rb-composer.is-dropping {
    /* Visual hint while a file drag is hovering - subtle outline ring
       around the whole composer matches what chat does on drop. */
    outline: 2px dashed rgba(139, 92, 246, 0.55);
    outline-offset: 4px;
    border-radius: 12px;
}
.rb-composer-row {
    display: flex;
    gap: 0.55rem;
    align-items: flex-end;
}
.rb-hidden-file {
    /* The native file input is invisible; we open it via the attach
       button. `display: none` is fine here because the input value is
       still readable from JS when files are picked. */
    display: none !important;
}
/* ----- attach button + strip + add menu ----- */
.rb-attach-btn {
    flex-shrink: 0;
    width: 40px;
    height: 44px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text-muted);
    border-radius: 12px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.rb-attach-btn:hover:not(:disabled) {
    color: var(--text);
    border-color: rgba(139, 92, 246, 0.55);
    background: var(--bg);
}
.rb-attach-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}
.rb-attach-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0;
}
.rb-attach-chip {
    position: relative;
    width: 64px;
    height: 64px;
    border-radius: 10px;
    overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--border);
    flex-shrink: 0;
}
.rb-attach-chip.is-uploading::after {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    pointer-events: none;
}
.rb-attach-thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.rb-attach-uploading {
    position: absolute;
    inset: 50% auto auto 50%;
    transform: translate(-50%, -50%);
    width: 18px;
    height: 18px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
    border-radius: 999px;
    animation: rb-attach-spin 0.8s linear infinite;
    z-index: 1;
}
@keyframes rb-attach-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }
.rb-attach-remove {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 20px;
    height: 20px;
    border: 0;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
    padding: 0;
}
.rb-attach-remove:hover { background: rgba(220, 38, 38, 0.92); }
.rb-addmenu {
    position: fixed;
    z-index: 9300;
    min-width: 220px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 12px 32px -8px rgba(0, 0, 0, 0.45);
    padding: 0.35rem;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.rb-addmenu-item {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.55rem 0.7rem;
    border: 0;
    background: transparent;
    color: var(--text);
    cursor: pointer;
    border-radius: 7px;
    text-align: left;
    font: inherit;
    width: 100%;
}
.rb-addmenu-item:hover { background: var(--bg); }
/* ----- attached image rendering inside user bubbles ----- */
.rb-attach-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0 0 0.45rem 0;
}
.rb-attach-img-link {
    display: block;
    line-height: 0;
}
.rb-attach-img {
    max-width: 240px;
    max-height: 240px;
    width: auto;
    height: auto;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.05);
    display: block;
}
@media (max-width: 560px) {
    .rb-attach-img { max-width: 200px; max-height: 200px; }
}
/* ----- storage picker modal (rb-picker-*) ----- */
.rb-picker-card {
    width: min(720px, 92vw);
    max-height: 80vh;
    display: flex;
    flex-direction: column;
}
.rb-picker-head {
    padding: 1.1rem 1.4rem 0.6rem;
}
.rb-picker-title {
    margin: 0 0 0.25rem;
    font-size: 1.1rem;
    color: var(--text);
}
.rb-picker-sub {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
}
.rb-picker-body {
    padding: 0.6rem 1.4rem 0.4rem;
    overflow-y: auto;
    flex: 1;
}
.rb-picker-state { padding: 1.5rem 0; text-align: center; color: var(--text-muted); }
.rb-picker-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 0.55rem;
}
@media (max-width: 560px) {
    .rb-picker-grid { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
}
.rb-picker-tile {
    position: relative;
    aspect-ratio: 1 / 1;
    width: 100%;
    border-radius: 10px;
    overflow: hidden;
    border: 2px solid transparent;
    background: var(--surface);
    padding: 0;
    cursor: pointer;
    outline: none;
}
.rb-picker-tile:hover     { border-color: rgba(139, 92, 246, 0.45); }
.rb-picker-tile:focus     { border-color: rgba(139, 92, 246, 0.75); }
.rb-picker-tile.is-selected { border-color: #8b5cf6; }
.rb-picker-tile-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
}
.rb-picker-tile-check {
    position: absolute;
    top: 5px;
    right: 5px;
    width: 22px;
    height: 22px;
    border-radius: 999px;
    background: rgba(15, 16, 20, 0.7);
    color: transparent;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s ease, color 0.15s ease;
}
.rb-picker-tile.is-selected .rb-picker-tile-check {
    background: #8b5cf6;
    color: #fff;
}
.rb-picker-foot {
    padding: 0.6rem 0;
    text-align: center;
}
.rb-picker-actions {
    padding: 0.7rem 1.4rem calc(0.9rem + env(safe-area-inset-bottom));
    display: flex;
    justify-content: flex-end;
    gap: 0.6rem;
    border-top: 1px solid var(--border);
}
.rb-input {
    flex: 1;
    min-height: 44px;
    max-height: 240px;
    padding: 0.7rem 0.9rem;
    border-radius: 14px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    line-height: 1.5;
    resize: none;
    overflow-y: auto;
    box-sizing: border-box;
}
.rb-input:focus {
    outline: none;
    border-color: rgba(139, 92, 246, 0.55);
}
.rb-send {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 999px;
    background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #ec4899 100%);
    color: #fff;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 14px -4px rgba(139, 92, 246, 0.55);
    transition: transform 0.12s ease, box-shadow 0.18s ease;
}
.rb-send:hover  { transform: translateY(-1px) scale(1.04); }
.rb-send:active { transform: translateY(1px); }
.rb-send:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    transform: none;
}
.rb-send[hidden] { display: none; }
/* Stop button - mirrors the Send slot while a request is in flight.
   Red gradient so the user reads it as "interrupt", not "submit". */
.rb-stop {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 999px;
    background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
    color: #fff;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 4px 14px -4px rgba(220, 38, 38, 0.55);
    transition: transform 0.12s ease, box-shadow 0.18s ease;
    animation: rb-stop-pulse 1.4s ease-in-out infinite;
}
.rb-stop:hover  { transform: translateY(-1px) scale(1.04); }
.rb-stop:active { transform: translateY(1px); }
.rb-stop[hidden] { display: none; }
@keyframes rb-stop-pulse {
    0%, 100% { box-shadow: 0 4px 14px -4px rgba(220,38,38,0.55); }
    50%      { box-shadow: 0 6px 22px -4px rgba(220,38,38,0.8); }
}
/* Elapsed-time chip in the thinking head. Tabular numerals so the
   ticking digits don't bounce. */
.rb-thinking-elapsed {
    margin-left: 0.4rem;
    color: var(--text-muted);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    opacity: 0.85;
}

@media (max-width: 768px) {
    /* On a 360-414 px viewport the rb-head puts the title-block
       (left) AND a 4-button row (token meter / privacy toggle / Rules
       / Reset) on the same row. The right side is `flex-shrink: 0` so
       it claims its full width and squeezes the left to ~50 px,
       which (combined with `overflow-wrap: anywhere` on .rb-head-sub)
       broke the subtitle into one character per line.
       Stack the rows on mobile so the title-block keeps the full
       column width and the buttons wrap below it. */
    .rb-head {
        flex-direction: column;
        align-items: stretch;
        gap: 0.5rem;
    }
    .rb-head-right {
        flex-wrap: wrap;
        justify-content: flex-end;
    }
    .rb-head-sub { max-width: none; }
}

@media (max-width: 560px) {
    .rb { padding: 0 0.65rem; }
    .rb-head-sub { font-size: 0.78rem; }
    .rb-bubble { font-size: 0.92rem; }
}

/* ================================================================
 * Planner (Tools / Planner). Namespace: .pl-*
 *
 * List page (planner-list.phtml) and detail board page
 * (planner-detail.phtml) share the .pl-shell wrapper. Mobile-first:
 * board columns stack vertically below 720px; horizontal scroll only
 * kicks in at 720-980px when there's not enough room for three
 * columns side-by-side.
 * ================================================================ */

/* ---- Top-nav Tools pill active accent (purple, brand) ---- */
.primary-nav-item[data-tools-pill].is-active {
    background: rgba(124, 58, 237, 0.18);
    border-color: rgba(124, 58, 237, 0.5);
    color: var(--text);
}
.primary-nav-item[data-tools-pill].is-active:hover {
    background: rgba(124, 58, 237, 0.26);
    border-color: var(--brand);
}

.pl-shell {
    display: block;
    color: var(--text);
    animation: pl-shell-in 0.18s ease-out;
}
@keyframes pl-shell-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0);   }
}

/* ---- List page header ---- */
.pl-head { margin: 0 0 1.25rem; }
.pl-head-titlewrap {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 0.35rem;
}
.pl-head-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 0.6rem;
    background: rgba(124, 58, 237, 0.14);
    color: #c4b5fd;
}
.pl-head-icon-svg { color: inherit; }
.pl-head-title {
    margin: 0;
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}
.pl-head-sub {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.95rem;
    max-width: 60ch;
}

/* ---- Toolbar (cap stat + new-button) ---- */
.pl-toolbar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: 0 0 1rem;
    flex-wrap: wrap;
}
.pl-toolbar-stat {
    display: inline-flex;
    align-items: baseline;
    gap: 0.25rem;
    padding: 0.5rem 0.85rem;
    min-height: 40px;
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-muted);
    font-size: 0.88rem;
    line-height: 1;
}
.pl-toolbar-stat-num {
    color: var(--text);
    font-weight: 700;
    font-size: 1rem;
    font-variant-numeric: tabular-nums;
}
.pl-toolbar-stat-sep { opacity: 0.5; }
.pl-toolbar-stat-cap { font-variant-numeric: tabular-nums; }
.pl-toolbar-stat-label { margin-left: 0.35rem; opacity: 0.85; }

.pl-new-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex: 0 0 auto;
    background: var(--brand);
    border: 1px solid color-mix(in srgb, var(--brand) 60%, black);
    color: #fff;
    min-height: 40px;
    padding: 0.55rem 1rem;
    border-radius: 999px;
    font-weight: 600;
    cursor: pointer;
}
.pl-new-btn:hover { background: color-mix(in srgb, var(--brand) 88%, black); }
.pl-new-btn.is-disabled,
.pl-new-btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    filter: saturate(0.6);
}
.pl-new-btn-icon { color: inherit; }

/* ---- List rows ---- */
.pl-list-root { position: relative; min-height: 200px; }
.pl-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 0.6rem;
}
.pl-list-row {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.85rem 1rem;
    background: #0f0f0f;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    transition: border-color 0.12s, transform 0.12s, box-shadow 0.18s, background 0.12s;
    overflow: hidden;
}
/* Radial-glow card hover: subtle purple bleed at the top-left so the
   row reads "interactive". */
.pl-list-row::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(420px 200px at 0% 0%, rgba(124, 58, 237, 0.10), transparent 65%);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s;
}
.pl-list-row:hover { border-color: #2a2a30; transform: translateY(-1px); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35); }
.pl-list-row:hover::before { opacity: 1; }

.pl-list-row-main {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text);
    text-decoration: none;
}
.pl-list-row-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 0.55rem;
    background: rgba(124, 58, 237, 0.14);
    color: #c4b5fd;
    flex-shrink: 0;
}
.pl-list-row-body {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
    flex: 1 1 auto;
}
.pl-list-row-name {
    font-weight: 600;
    font-size: 1rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pl-list-row-desc {
    color: var(--text-muted);
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow-wrap: anywhere;
}
.pl-list-row-meta {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    flex-wrap: wrap;
    margin-top: 0.25rem;
    color: var(--text-muted);
    font-size: 0.78rem;
}
.pl-list-row-stat {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    line-height: 1;
}
.pl-list-row-stat-role {
    padding: 0.2rem 0.5rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.03);
}
.pl-role-owner  { color: #fbbf24; }
.pl-role-admin  { color: #67e8f9; }
.pl-role-viewer { color: #a1a1aa; }

.pl-list-row-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
}
.pl-list-row-open {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.55rem 0.85rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    color: var(--text);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
    background: #131316;
    min-height: 40px;
    transition: background 0.12s, border-color 0.12s;
}
.pl-list-row-open:hover {
    background: rgba(124, 58, 237, 0.18);
    border-color: rgba(124, 58, 237, 0.55);
}
.pl-list-row-delete {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    border-radius: 999px;
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
.pl-list-row-delete:hover {
    color: #fca5a5;
    border-color: #ef4444;
    background: rgba(239, 68, 68, 0.12);
}

@media (max-width: 560px) {
    .pl-list-row { padding: 0.7rem 0.75rem; }
    .pl-list-row-name { font-size: 0.95rem; }
    .pl-list-row-open span { display: none; }
    .pl-list-row-open {
        width: 40px;
        padding: 0;
        justify-content: center;
    }
}

/* ---- Empty + not-found states ---- */
.pl-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 3rem 1rem;
    color: var(--text-muted);
    border: 1px dashed var(--border);
    border-radius: 0.75rem;
    background: #0c0c0e;
}
.pl-empty-icon { color: #71717a; }
.pl-empty-title { margin: 0.75rem 0 0; font-size: 0.95rem; }

.pl-shell-not-found { padding-top: 1rem; }
.pl-not-found-card {
    text-align: center;
    padding: 3rem 1rem;
    border: 1px dashed var(--border);
    border-radius: 0.75rem;
    background: #0c0c0e;
    max-width: 30rem;
    margin: 0 auto;
}
.pl-not-found-icon { color: #71717a; display: inline-block; margin-bottom: 0.75rem; }
.pl-not-found-title { font-size: 1.4rem; margin: 0 0 0.4rem; }
.pl-not-found-sub { margin: 0 0 1.25rem; color: var(--text-muted); }
.pl-not-found-back { display: inline-flex; align-items: center; gap: 0.45rem; }

/* ---- Detail page header ---- */
.pl-detail-shell { padding-bottom: 2rem; }
.pl-detail-root { display: block; }
.pl-detail-mounted { animation: pl-shell-in 0.18s ease-out; }
.pl-detail-fallback {
    padding: 1.5rem 0;
    color: var(--text-muted);
}
.pl-detail-fallback-back,
.pl-detail-back {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.85rem;
    margin-bottom: 0.5rem;
}
.pl-detail-back:hover,
.pl-detail-fallback-back:hover { color: var(--text); }

.pl-detail-head {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0.75rem 1rem;
    align-items: start;
    margin-bottom: 1.25rem;
}
.pl-detail-title-wrap { min-width: 0; }
.pl-detail-title-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    min-width: 0;
}
.pl-detail-title {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    overflow-wrap: anywhere;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    border-radius: 0.4rem;
    padding: 0.1rem 0.3rem;
    margin-left: -0.3rem;
}
.pl-detail-title-editable { cursor: text; }
.pl-detail-title-editable:hover { background: rgba(255, 255, 255, 0.03); }
.pl-detail-title-editicon { opacity: 0; transition: opacity 0.12s; color: var(--text-muted); }
.pl-detail-title-editable:hover .pl-detail-title-editicon { opacity: 0.85; }
.pl-detail-title-input {
    width: 100%;
    background: #0e0e10;
    border: 1px solid rgba(124, 58, 237, 0.55);
    color: var(--text);
    border-radius: 0.4rem;
    padding: 0.35rem 0.55rem;
    font-size: 1.4rem;
    font-weight: 700;
    outline: none;
}

.pl-detail-role {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.78rem;
    padding: 0.25rem 0.55rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.03);
}

.pl-detail-desc {
    margin: 0.45rem 0 0;
    color: var(--text-muted);
    font-size: 0.92rem;
    border-radius: 0.4rem;
    padding: 0.25rem 0.4rem;
    margin-left: -0.4rem;
    overflow-wrap: anywhere;
}
.pl-detail-desc-empty { font-style: italic; opacity: 0.7; }
.pl-detail-desc-editable { cursor: text; }
.pl-detail-desc-editable:hover { background: rgba(255, 255, 255, 0.03); color: var(--text); }
.pl-detail-desc-input {
    width: 100%;
    background: #0e0e10;
    border: 1px solid rgba(124, 58, 237, 0.55);
    color: var(--text);
    border-radius: 0.4rem;
    padding: 0.5rem 0.6rem;
    font-size: 0.92rem;
    outline: none;
    resize: vertical;
    min-height: 4rem;
}

.pl-detail-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    flex-wrap: wrap;
    align-self: start;
}
.pl-detail-action {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.85rem;
    min-height: 40px;
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.pl-detail-action:hover {
    color: var(--text);
    background: rgba(124, 58, 237, 0.16);
    border-color: rgba(124, 58, 237, 0.5);
}
.pl-detail-action-count {
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    background: rgba(124, 58, 237, 0.22);
    color: #ddd6fe;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
}

@media (max-width: 720px) {
    .pl-detail-head { grid-template-columns: 1fr; }
    .pl-detail-actions { width: 100%; }
}

/* ---- Board ---- */
.pl-board {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.85rem;
    align-items: start;
    overflow-x: auto;
    padding-bottom: 0.5rem;
    scroll-snap-type: x proximity;
}
.pl-col {
    display: flex;
    flex-direction: column;
    background: #0c0c0e;
    border: 1px solid var(--border);
    border-radius: 0.7rem;
    padding: 0.7rem;
    min-height: 300px;
    min-width: 0;
    scroll-snap-align: start;
}
.pl-col-head {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding-bottom: 0.55rem;
    margin-bottom: 0.55rem;
    border-bottom: 1px solid var(--border);
}
.pl-col-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex-shrink: 0;
}
.pl-col-dot-pending { background: #a1a1aa; }
.pl-col-dot-in_work { background: #38bdf8; }
.pl-col-dot-done    { background: #34d399; }
.pl-col-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
    flex: 1 1 auto;
    min-width: 0;
}
.pl-col-count {
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.05);
    color: var(--text-muted);
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
}
.pl-col-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-height: 1.5rem;
}
.pl-col-add {
    appearance: none;
    background: transparent;
    border: 1px dashed var(--border);
    color: var(--text-muted);
    border-radius: 0.5rem;
    padding: 0.55rem 0.7rem;
    margin-top: 0.55rem;
    font-size: 0.85rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 40px;
    justify-content: center;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.pl-col-add:hover {
    color: var(--text);
    border-color: rgba(124, 58, 237, 0.55);
    background: rgba(124, 58, 237, 0.08);
}

@media (max-width: 720px) {
    .pl-board {
        grid-template-columns: 1fr;
        overflow-x: visible;
    }
}

/* While a card is being dragged, lock board pointer behaviour so the
   browser doesn't fight the drag with native scroll. */
body.pl-board-dragging { user-select: none; -webkit-user-select: none; }

/* ---- Task card ---- */
.pl-task {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    background: #131316;
    border: 1px solid var(--border);
    border-radius: 0.55rem;
    padding: 0.65rem 0.75rem;
    color: var(--text);
    cursor: pointer;
    transition: border-color 0.12s, transform 0.12s, box-shadow 0.18s, background 0.12s;
    /* `none` - NOT `manipulation`. With `manipulation` the browser
       still claims pan/scroll gestures, which on mobile meant the
       user could long-press a card to start a drag but the very
       first finger-move was hijacked as a page scroll - the card
       stayed put. Switching to `none` reserves every touch on a
       card for our pointer-event drag handler. Trade-off: users
       can't scroll the page by touching a card (must touch empty
       space between cards / a column header / outside the board).
       Acceptable for a drag-friendly Trello board. */
    touch-action: none;
    animation: pl-task-in 0.16s ease-out;
}
@keyframes pl-task-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0);   }
}
.pl-task::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: radial-gradient(360px 180px at 0% 0%, rgba(124, 58, 237, 0.10), transparent 65%);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s;
}
.pl-task:hover {
    border-color: #2a2a30;
    background: #15151a;
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.pl-task:hover::before { opacity: 1; }
.pl-task:focus-visible {
    outline: 2px solid rgba(124, 58, 237, 0.6);
    outline-offset: 2px;
}
.pl-task.is-dragging {
    cursor: grabbing;
    border-color: rgba(124, 58, 237, 0.7);
    background: #1a1a22;
}

.pl-task-top {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    justify-content: space-between;
}
.pl-task-cat {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.55rem;
    border: 1px solid;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1;
}
.pl-task-cat-dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
}
.pl-task-handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    opacity: 0.55;
    cursor: grab;
    width: 28px;
    height: 28px;
    border-radius: 0.35rem;
    transition: opacity 0.12s, background 0.12s;
    touch-action: none; /* let pointermove handle drag */
}
.pl-task-handle:hover { opacity: 1; background: rgba(255, 255, 255, 0.05); }
.pl-task-handle:active { cursor: grabbing; }

.pl-task-title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.3;
    overflow-wrap: anywhere;
    color: var(--text);
}
.pl-task-foot {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.65rem;
    flex-wrap: wrap;
    color: var(--text-muted);
    font-size: 0.74rem;
    margin-top: 0.1rem;
}
.pl-task-ts {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    line-height: 1;
    cursor: help;
}
.pl-task-ts-edited {
    color: color-mix(in srgb, var(--brand, #a78bfa) 70%, var(--text-muted));
}

/* "View" filter popover - lets the viewer narrow the board to one
   or more categories. Empty filter = show everything. Anchored under
   the View button via JS positioning, scoped under .pl-view-menu so
   restyling here can't bleed into other dropdowns. */
.pl-view-menu {
    position: fixed;
    z-index: 950;
    width: min(280px, calc(100vw - 1rem));
    background: #16161a;
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 0.55rem;
    box-shadow: 0 18px 36px -10px rgba(0, 0, 0, 0.6);
    padding: 0.65rem;
    color: var(--text);
    font-size: 0.88rem;
}
.pl-view-menu-title {
    font-weight: 700;
    color: var(--text);
    margin-bottom: 0.15rem;
}
.pl-view-menu-sub {
    color: var(--text-muted);
    font-size: 0.78rem;
    margin: 0 0 0.55rem;
    line-height: 1.4;
}
.pl-view-menu-list {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    max-height: 16rem;
    overflow-y: auto;
}
.pl-view-menu-row {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.35rem 0.4rem;
    border-radius: 0.35rem;
    cursor: pointer;
    user-select: none;
}
.pl-view-menu-row:hover { background: rgba(167, 139, 250, 0.10); }
.pl-view-menu-row input[type="checkbox"] {
    accent-color: var(--brand, #a78bfa);
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}
.pl-view-menu-dot {
    width: 0.65rem;
    height: 0.65rem;
    border-radius: 50%;
    flex-shrink: 0;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
}
.pl-view-menu-label { flex: 1; min-width: 0; }
.pl-view-menu-foot {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.5rem;
    padding-top: 0.45rem;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.pl-view-menu-clear { font-size: 0.82rem; }

/* Active-filter badge on the View button (count of checked rows). */
.pl-view-badge[hidden] { display: none; }

/* Assignees now live on their own row below the timestamps so the
   avatars never crowd the Created/Edited chips. Layout: small label
   ("Assigned to:") + horizontal avatar stack. */
.pl-task-assignees-row {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin-top: 0.4rem;
    color: var(--text-muted);
    font-size: 0.74rem;
}
.pl-task-assignees-label {
    color: var(--text-muted);
    line-height: 1;
}
.pl-task-assignees {
    display: inline-flex;
    align-items: center;
}
.pl-task .pl-task-assignee,
.pl-task-assignees > .pl-task-assignee {
    width: 1.4rem !important;
    height: 1.4rem !important;
    min-width: 1.4rem !important;
    min-height: 1.4rem !important;
    max-width: 1.4rem !important;
    max-height: 1.4rem !important;
    border-radius: 50% !important;
    overflow: hidden !important;
    background: #1f1f23;
    border: 2px solid var(--bg, #0a0a0a);
    margin-left: -0.4rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--brand, #a78bfa);
    font-size: 0.65rem;
    font-weight: 700;
    flex-shrink: 0 !important;
    box-sizing: border-box !important;
    padding: 0 !important;
    line-height: 1;
    vertical-align: middle;
}
.pl-task .pl-task-assignee:first-child { margin-left: 0; }
/* `!important` here is a defensive nuke: some other img rule was
   winning on this surface (a high-res profile picture rendered at
   intrinsic size, blowing up the card). Fixed-pin the image to the
   wrapper's box so nothing can escape it. */
.pl-task .pl-task-assignee > img,
.pl-task-assignees .pl-task-assignee > img {
    width: 100% !important;
    height: 100% !important;
    min-width: 0 !important;
    min-height: 0 !important;
    max-width: 100% !important;
    max-height: 100% !important;
    object-fit: cover !important;
    display: block !important;
    border-radius: 50% !important;
    margin: 0 !important;
    padding: 0 !important;
    vertical-align: middle;
}
.pl-task-assignee-fb { line-height: 1; }
.pl-task-assignee-more {
    background: rgba(124, 58, 237, 0.18);
    color: #c4b5fd;
}
.pl-task-assignee.is-mine {
    border-color: var(--brand, #a78bfa);
    box-shadow: 0 0 0 1px var(--brand, #a78bfa);
}

/* Highlight a task that's assigned to the viewer. Subtle gradient
   accent + brand-coloured left rail so a glance at the board picks
   out "what's on me" without needing to read every card. */
.pl-task.is-mine {
    border-left: 3px solid var(--brand, #a78bfa);
    background:
        linear-gradient(135deg, rgba(167, 139, 250, 0.07), transparent 65%),
        var(--surface, #16161a);
}
.pl-task.is-mine:hover {
    background:
        linear-gradient(135deg, rgba(167, 139, 250, 0.12), transparent 65%),
        var(--surface-hover, #1c1c22);
}

/* Assignee management section inside the task detail modal. */
.pl-modal-task-assignees {
    margin: 1rem 0 0.5rem;
    padding: 0.85rem 0.95rem;
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.06);
    border-radius: 0.5rem;
}
.pl-modal-task-section {
    margin: 0 0 0.55rem;
    font-size: 0.85rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
}
.pl-modal-task-assignee-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.65rem;
}
.pl-modal-task-no-assignees {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-style: italic;
}
.pl-modal-task-assignee-row {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.5rem 0.3rem 0.35rem;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 999px;
    font-size: 0.85rem;
}
.pl-modal-task-assignee-avatar {
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    overflow: hidden;
    background: #1f1f23;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--brand, #a78bfa);
    font-size: 0.7rem;
    font-weight: 700;
    flex-shrink: 0;
}
.pl-modal-task-assignee-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pl-modal-task-assignee-name { color: var(--text); }
.pl-modal-task-assignee-remove {
    margin-left: 0.15rem;
    width: 1.2rem;
    height: 1.2rem;
    border-radius: 50%;
    border: 0;
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-muted);
    cursor: pointer;
    line-height: 1;
    font-size: 0.85rem;
}
.pl-modal-task-assignee-remove:hover {
    background: rgba(248, 113, 113, 0.18);
    color: #fca5a5;
}
.pl-modal-task-assignee-add {
    display: flex;
    gap: 0.4rem;
    align-items: center;
    position: relative;
}
.pl-modal-task-assignee-add-btn { flex-shrink: 0; }

/* Custom teammate picker (replaces the native <select>). Styled to
   match the rest of the planner UI; menu is absolutely positioned
   under the trigger and closes on outside click / Escape / pick. */
.pl-picker-trigger {
    flex: 1;
    min-width: 0;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.4rem 0.6rem;
    background: rgba(15, 15, 18, 0.85);
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 0.4rem;
    color: var(--text);
    font: inherit;
    cursor: pointer;
    text-align: left;
}
.pl-picker-trigger:hover  { border-color: rgba(167, 139, 250, 0.55); }
.pl-picker-trigger:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}
.pl-picker-trigger-label {
    flex: 1;
    min-width: 0;
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pl-picker-trigger-label.is-placeholder {
    color: var(--text-muted);
}
.pl-picker-trigger-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pl-picker-caret {
    color: var(--text-muted);
    flex-shrink: 0;
    font-size: 0.85rem;
    line-height: 1;
}
.pl-picker-trigger[aria-expanded="true"] .pl-picker-caret { transform: rotate(180deg); }

.pl-picker-menu {
    position: absolute;
    top: calc(100% + 0.3rem);
    left: 0;
    right: calc(2.4rem + 0.4rem); /* leave room for the Assign button column */
    z-index: 10;
    background: #16161a;
    border: 1px solid rgba(255, 255, 255, 0.10);
    border-radius: 0.5rem;
    box-shadow: 0 16px 36px -12px rgba(0, 0, 0, 0.7);
    padding: 0.3rem;
    max-height: 16rem;
    overflow-y: auto;
}
.pl-picker-menu[hidden] { display: none; }
.pl-picker-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    width: 100%;
    padding: 0.4rem 0.5rem;
    background: transparent;
    border: 0;
    border-radius: 0.35rem;
    color: var(--text);
    text-align: left;
    cursor: pointer;
    font: inherit;
}
.pl-picker-item:hover,
.pl-picker-item:focus-visible {
    background: rgba(167, 139, 250, 0.14);
    outline: none;
}
.pl-picker-item-name {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.pl-picker-item-name-text {
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.92rem;
}
.pl-picker-item-meta {
    color: var(--text-muted);
    font-size: 0.74rem;
}
.pl-picker-empty {
    padding: 0.55rem 0.6rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-style: italic;
}
.pl-picker-avatar {
    width: 1.6rem;
    height: 1.6rem;
    min-width: 1.6rem;
    min-height: 1.6rem;
    max-width: 1.6rem;
    max-height: 1.6rem;
    border-radius: 50%;
    overflow: hidden;
    background: #1f1f23;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--brand, #a78bfa);
    font-weight: 700;
    font-size: 0.7rem;
    flex-shrink: 0;
    box-sizing: border-box;
}
.pl-picker-avatar img {
    width: 100%;
    height: 100%;
    max-width: 100%;
    max-height: 100%;
    object-fit: cover;
    display: block;
    border-radius: 50%;
}

/* Placeholder slot rendered while a card is dragged out of its column. */
.pl-task-placeholder {
    border: 1.5px dashed rgba(124, 58, 237, 0.5);
    background: rgba(124, 58, 237, 0.08);
    border-radius: 0.55rem;
    transition: background 0.12s;
}

/* ---- Modals (planner-specific) ---- */
.pl-modal { z-index: 200; }
.pl-modal-card {
    width: min(36rem, 100%);
    padding: 1.5rem 1.25rem 1.25rem;
}
.pl-modal-title {
    margin: 0 0 0.35rem;
    font-size: 1.15rem;
    font-weight: 700;
}
.pl-modal-sub {
    margin: 0 0 1rem;
    color: var(--text-muted);
    font-size: 0.88rem;
}
.pl-modal-error {
    margin: 0 0 0.75rem;
    padding: 0.55rem 0.75rem;
    border-radius: 0.45rem;
    background: rgba(239, 68, 68, 0.12);
    border: 1px solid rgba(239, 68, 68, 0.5);
    color: #fca5a5;
    font-size: 0.85rem;
}
.pl-modal-field {
    display: block;
    margin-bottom: 0.85rem;
}
.pl-modal-field-label {
    display: block;
    margin-bottom: 0.35rem;
    font-size: 0.82rem;
    color: var(--text-muted);
    font-weight: 600;
    letter-spacing: 0.01em;
}
.pl-modal-input {
    width: 100%;
    background: #0e0e10;
    border: 1px solid var(--border);
    color: var(--text);
    border-radius: 0.45rem;
    padding: 0.55rem 0.7rem;
    font-size: 0.92rem;
    line-height: 1.4;
    outline: none;
    transition: border-color 0.12s, background 0.12s;
}
.pl-modal-input:focus {
    border-color: rgba(124, 58, 237, 0.6);
    background: #131316;
}
.pl-modal-textarea {
    resize: vertical;
    min-height: 4rem;
    font-family: inherit;
}

.pl-modal-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1.25rem;
}

/* Category options inside the new-task modal */
.pl-modal-cat-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.pl-modal-cat-opt {
    appearance: none;
    background: transparent;
    border: 1px solid;
    border-radius: 999px;
    padding: 0.35rem 0.65rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.82rem;
    font-weight: 600;
    transition: background 0.12s, transform 0.12s;
    min-height: 36px;
}
.pl-modal-cat-opt:hover { transform: translateY(-1px); }
.pl-modal-cat-opt.is-active {
    background: rgba(124, 58, 237, 0.12);
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.05) inset;
}
.pl-modal-cat-opt-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}

/* Task detail modal */
.pl-modal-task { width: min(40rem, 100%); }
.pl-modal-task-top {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin-bottom: 0.45rem;
    flex-wrap: wrap;
}
.pl-modal-task-status {
    color: var(--text-muted);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.pl-modal-task-title {
    margin: 0 0 0.85rem;
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.3;
    overflow-wrap: anywhere;
    border-radius: 0.4rem;
    padding: 0.1rem 0.3rem;
    margin-left: -0.3rem;
}
.pl-modal-task-title-editable { cursor: text; }
.pl-modal-task-title-editable:hover { background: rgba(255, 255, 255, 0.04); }

.pl-modal-task-body {
    margin: 0 0 1rem;
    color: var(--text);
    overflow-wrap: anywhere;
    line-height: 1.55;
    border-radius: 0.4rem;
    padding: 0.5rem 0.6rem;
    margin-left: -0.6rem;
    background: rgba(255, 255, 255, 0.02);
    min-height: 3rem;
}
/* Plain-text fallback before markdown rendering kicks in. md-body
   subtree (img / video / audio / lists / headings) gets sensible
   defaults via the global .md-* rules; we only need a couple of
   container-scoped tweaks. */
.pl-modal-task-body:not(.md-body) { white-space: pre-wrap; }
.pl-modal-task-body.md-body img.md-media-img,
.pl-modal-task-body.md-body video.md-media-video {
    max-width: 100%;
    max-height: 18rem;
    border-radius: 0.4rem;
    display: block;
    margin: 0.4rem 0;
}
.pl-modal-task-body-empty { color: var(--text-muted); font-style: italic; }
.pl-modal-task-body-editable { cursor: text; }
.pl-modal-task-body-editable:hover { background: rgba(124, 58, 237, 0.06); }
.pl-modal-task-body-edit { margin: 0 0 1rem; }
.pl-modal-task-body-edit .md-edit-textarea { width: 100%; box-sizing: border-box; }
.pl-modal-task-body-edit .md-edit-footer {
    display: flex;
    gap: 0.5rem;
    justify-content: flex-end;
    align-items: center;
    margin-top: 0.55rem;
}
.pl-modal-task-body-edit .md-edit-codeblock {
    margin-right: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.pl-modal-codeblock-row {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.4rem;
}
.pl-modal-codeblock-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

/* Code block rendered inside a task body. Mirrors the chat composer's
   pre.md-codeblock styling so the look is consistent across surfaces. */
.pl-modal-task-body.md-body pre.md-codeblock {
    background: #0a0a0d;
    border: 1px solid #27272a;
    border-radius: 0.4rem;
    padding: 0.65rem 0.85rem;
    margin: 0.5rem 0;
    overflow-x: auto;
    font-size: 0.86rem;
    line-height: 1.45;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.pl-modal-task-body.md-body pre.md-codeblock code.hljs {
    background: transparent;
    padding: 0;
}
.pl-modal-task-body.md-body pre.md-codeblock[data-lang]::before {
    content: attr(data-lang);
    display: block;
    font-size: 0.7rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #71717a;
    margin-bottom: 0.4rem;
}
.pl-modal-task-body.md-body code.md-inline-code {
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0.25rem;
    padding: 0.05rem 0.35rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85em;
}

/* Storage picker (image-only) - opened from the markdown toolbar's
   "Insert image" -> "From your storage" button. Lazy modal, mirrors
   the chat storage picker visually but is self-contained inside
   planner.js. */
.pl-modal-storage .pl-modal-card { width: min(880px, 100%); }
.pl-storage-status {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin: 0 0 0.75rem;
    text-align: center;
}
.pl-storage-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 0.55rem;
    max-height: 60vh;
    overflow-y: auto;
}
.pl-storage-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    background: #0f0f12;
    border: 1px solid #27272a;
    border-radius: 0.5rem;
    padding: 0;
    overflow: hidden;
    cursor: pointer;
    text-align: left;
    color: inherit;
    transition: border-color 0.12s, transform 0.12s;
}
.pl-storage-tile:hover {
    border-color: var(--brand, #a78bfa);
    transform: translateY(-1px);
}
.pl-storage-tile img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
}
.pl-storage-meta {
    display: flex;
    flex-direction: column;
    padding: 0.35rem 0.5rem;
    gap: 0.1rem;
}
.pl-storage-name {
    font-size: 0.78rem;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pl-storage-dim {
    font-size: 0.7rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.pl-storage-summary {
    color: var(--text-muted);
    font-size: 0.78rem;
    flex: 1;
    text-align: center;
    min-width: 8rem;
}
@media (max-width: 720px) {
    .pl-storage-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
}

/* OpenGraph link previews under the task body. Compact card variant
   of the same layout chat + guides use; same flavour, scoped to the
   planner so future restyles can't bleed in. */
.pl-modal-task-embeds {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 0 0 1rem;
}
.pl-embed-card {
    display: block;
    text-decoration: none;
    color: inherit;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-left: 3px solid var(--brand, #a78bfa);
    border-radius: 0.45rem;
    padding: 0.6rem 0.75rem;
    transition: background 0.12s ease, border-color 0.12s ease;
}
.pl-embed-card:hover {
    background: rgba(255, 255, 255, 0.07);
    border-color: rgba(124, 58, 237, 0.5);
    border-left-color: var(--brand, #a78bfa);
}
.pl-embed-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--text-muted);
    font-size: 0.78rem;
    margin-bottom: 0.25rem;
}
.pl-embed-favicon { width: 14px; height: 14px; border-radius: 3px; }
.pl-embed-title {
    font-weight: 600;
    color: var(--text);
    font-size: 0.95rem;
    margin: 0.05rem 0 0.2rem;
}
.pl-embed-desc {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
    margin: 0 0 0.35rem;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.pl-embed-image,
.pl-embed-thumb {
    max-width: 100%;
    max-height: 14rem;
    border-radius: 0.35rem;
    margin-top: 0.4rem;
    display: block;
}

.pl-modal-task-meta {
    color: var(--text-muted);
    font-size: 0.78rem;
    display: flex;
    gap: 0.35rem;
    flex-wrap: wrap;
}

.pl-modal-task-actions {
    margin-top: 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.pl-modal-task-statuses {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.pl-modal-task-status-btn {
    appearance: none;
    background: #0e0e10;
    border: 1px solid var(--border);
    color: var(--text-muted);
    border-radius: 999px;
    padding: 0.4rem 0.75rem;
    font-size: 0.82rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 36px;
}
.pl-modal-task-status-btn:hover { color: var(--text); border-color: #2a2a30; }
.pl-modal-task-status-btn.is-active {
    background: rgba(124, 58, 237, 0.16);
    border-color: rgba(124, 58, 237, 0.55);
    color: #ddd6fe;
}
.pl-modal-task-cats {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}
.pl-modal-task-cats-label {
    color: var(--text-muted);
    font-size: 0.82rem;
    margin-right: 0.25rem;
}
.pl-modal-task-cat-opt {
    appearance: none;
    background: transparent;
    border: 1px solid;
    border-radius: 999px;
    padding: 0.3rem 0.6rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.78rem;
    font-weight: 600;
    min-height: 32px;
}
.pl-modal-task-cat-opt.is-active {
    background: rgba(124, 58, 237, 0.12);
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.06) inset;
}
.pl-modal-task-delete {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

/* Members modal */
.pl-modal-members { width: min(38rem, 100%); }
.pl-modal-members-list {
    list-style: none;
    margin: 0 0 1.5rem;
    padding: 0;
    display: grid;
    gap: 0.5rem;
}
.pl-modal-member-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem 0.75rem;
    background: #0e0e10;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
}
.pl-modal-member-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    flex-shrink: 0;
    object-fit: cover;
    background: #1a1a22;
}
.pl-modal-member-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text);
    font-weight: 700;
    font-size: 0.95rem;
}
.pl-modal-member-ident {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
    flex: 1 1 auto;
}
.pl-modal-member-name {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pl-modal-member-handle {
    font-size: 0.78rem;
    color: var(--text-muted);
}
.pl-modal-member-role {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.5rem;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: rgba(255, 255, 255, 0.03);
    font-size: 0.75rem;
    font-weight: 600;
    flex-shrink: 0;
}
.pl-modal-member-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex-shrink: 0;
}
.pl-modal-member-toggle,
.pl-modal-member-remove {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    border-radius: 999px;
    width: 36px;
    height: 36px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.pl-modal-member-toggle:hover { color: var(--text); border-color: #2a2a30; }
.pl-modal-member-remove:hover {
    color: #fca5a5;
    border-color: #ef4444;
    background: rgba(239, 68, 68, 0.12);
}

.pl-modal-add-section {
    border-top: 1px solid var(--border);
    padding-top: 1.1rem;
}
.pl-modal-add-title {
    margin: 0 0 0.6rem;
    font-size: 0.95rem;
    font-weight: 600;
}
.pl-modal-friend-list {
    list-style: none;
    margin: 0.6rem 0 0;
    padding: 0;
    display: grid;
    gap: 0.4rem;
    max-height: 16rem;
    overflow-y: auto;
}
.pl-modal-friend-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.5rem 0.7rem;
    background: #0e0e10;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    flex-wrap: wrap;
}
.pl-modal-friend-avatar {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    background: #1a1a22;
}
.pl-modal-friend-avatar-fallback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text);
    font-weight: 700;
}
.pl-modal-friend-ident {
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    min-width: 0;
    flex: 1 1 8rem;
}
.pl-modal-friend-name {
    font-weight: 600;
    font-size: 0.92rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.pl-modal-friend-handle {
    font-size: 0.76rem;
    color: var(--text-muted);
}
.pl-modal-friend-roles {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    flex-wrap: wrap;
}
.pl-modal-friend-role {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    cursor: pointer;
    color: var(--text-muted);
    font-size: 0.82rem;
    user-select: none;
}
.pl-modal-friend-role input[type="radio"] { accent-color: var(--brand); }
.pl-modal-friend-add {
    margin-left: auto;
    padding: 0.5rem 0.85rem;
    min-height: 36px;
    border-radius: 999px;
}
.pl-modal-friend-loading,
.pl-modal-friend-empty,
.pl-modal-friend-error {
    padding: 0.85rem 0.75rem;
    color: var(--text-muted);
    font-size: 0.85rem;
    text-align: center;
    background: #0e0e10;
    border: 1px dashed var(--border);
    border-radius: 0.45rem;
}
.pl-modal-friend-error { color: #fca5a5; border-color: rgba(239, 68, 68, 0.4); }

/* Categories modal */
.pl-modal-cats { width: min(40rem, 100%); }
.pl-modal-cats-head {
    margin: 1rem 0 0.45rem;
    font-size: 0.82rem;
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.pl-modal-cats-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
}
.pl-modal-cats-empty {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-style: italic;
}
.pl-modal-cat-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.3rem 0.6rem 0.3rem 0.55rem;
    border: 1px solid;
    border-radius: 999px;
    font-size: 0.82rem;
    font-weight: 600;
}
.pl-modal-cat-chip-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}
.pl-modal-cat-chip-remove {
    appearance: none;
    background: transparent;
    border: 0;
    color: inherit;
    opacity: 0.7;
    cursor: pointer;
    width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    margin-left: 0.15rem;
}
.pl-modal-cat-chip-remove:hover { opacity: 1; background: rgba(0, 0, 0, 0.35); }

.pl-modal-cat-form {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-top: 0.5rem;
}
.pl-modal-cat-form .pl-modal-input {
    flex: 1 1 12rem;
    min-width: 8rem;
}
.pl-modal-swatch-row {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex-wrap: wrap;
}
.pl-modal-swatch {
    appearance: none;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
    transition: transform 0.12s, border-color 0.12s;
}
.pl-modal-swatch:hover { transform: scale(1.1); }
.pl-modal-swatch.is-active {
    border-color: #fff;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.15);
}
.pl-modal-cat-add {
    flex: 0 0 auto;
}

@media (max-width: 560px) {
    .pl-modal-card { padding: 1.1rem 0.85rem 1rem; }
    .pl-modal-task-title { font-size: 1.1rem; }
    .pl-modal-friend-row { padding: 0.55rem; }
    .pl-modal-friend-add { width: 100%; margin-left: 0; }
}

/* ============================================================
   Guides & Releases - comment section
   ============================================================ */
.gd-detail-comments-card {
    padding: 1rem;
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.7rem;
    margin-top: 1rem;
}
.gd-comments-head {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin-bottom: 0.55rem;
}
.gd-comments-head-icon { color: var(--text-muted); flex: 0 0 auto; }
.gd-comments-count { color: var(--text-muted); font-size: 0.85rem; }
.gd-comments-status { margin-bottom: 0.5rem; }
.gd-comments-empty {
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: 0.55rem 0;
}
.gd-comments-locked {
    color: var(--text-muted);
    background: rgba(255, 255, 255, 0.02);
    border: 1px dashed var(--border);
    border-radius: 0.5rem;
    padding: 0.55rem 0.75rem;
    font-size: 0.9rem;
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.gd-comments-locked-icon { color: var(--text-muted); flex: 0 0 auto; }
/* Author-only notice shown above the composer when comments are off
   but the viewer is the post author (they're exempt from the gate). */
.gd-comments-author-notice {
    color: var(--text-muted);
    background: rgba(124, 58, 237, 0.06);
    border: 1px dashed rgba(124, 58, 237, 0.35);
    border-radius: 0.5rem;
    padding: 0.45rem 0.7rem;
    font-size: 0.82rem;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.5rem;
}

/* Composer */
.gd-comments-composer { margin-bottom: 0.85rem; }
.gd-comments-composer-row {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.gd-comments-textarea {
    width: 100%;
    box-sizing: border-box;
    min-height: 4.5rem;
    resize: vertical;
    padding: 0.55rem 0.7rem;
    border-radius: 0.5rem;
    border: 1px solid var(--border);
    background: #111114;
    color: var(--text);
    font: inherit;
    line-height: 1.45;
}
.gd-comments-textarea:focus {
    outline: none;
    border-color: var(--accent, #7c3aed);
    box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.18);
}
.gd-comments-composer-actions {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    flex-wrap: wrap;
}
.gd-comments-counter {
    color: var(--text-muted);
    font-size: 0.78rem;
    margin-right: auto;
}
.gd-comments-counter.is-near { color: #f59e0b; }
.gd-comments-submit { min-height: 2.25rem; }

/* List */
.gd-comments-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.gd-comment {
    display: flex;
    gap: 0.7rem;
    padding: 0.6rem 0;
    border-top: 1px solid var(--border);
}
.gd-comment:first-child { border-top: none; padding-top: 0; }
.gd-comment.is-deleted .gd-comment-body { opacity: 0.7; }
.gd-comment.is-deleted .gd-comment-body .md-body,
.gd-comment.is-deleted .gd-comment-body > * {
    text-decoration: line-through;
    text-decoration-color: rgba(239, 68, 68, 0.55);
}
.gd-comment.is-deleted .gd-comment-tombstone {
    text-decoration: none;
    color: var(--text-muted);
    font-style: italic;
}
.gd-comment-avatar-link { flex: 0 0 auto; text-decoration: none; }
.gd-comment-avatar {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    border: 1px solid var(--border);
}
.gd-comment-avatar-fallback {
    width: 36px; height: 36px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: #1f1f24; color: var(--text); font-weight: 600;
    font-size: 0.95rem;
    border: 1px solid var(--border);
}
.gd-comment-main {
    flex: 1 1 auto;
    min-width: 0;
}
.gd-comment-header {
    display: flex;
    align-items: baseline;
    gap: 0.45rem;
    flex-wrap: wrap;
    margin-bottom: 0.2rem;
}
.gd-comment-author {
    font-weight: 600;
    color: var(--text);
    text-decoration: none;
}
.gd-comment-author:hover { text-decoration: underline; }
.gd-comment-time, .gd-comment-edited {
    color: var(--text-muted);
    font-size: 0.78rem;
}
.gd-comment-edited { font-style: italic; }
.gd-comment-deleted-tag {
    color: #f87171;
    font-size: 0.75rem;
    background: rgba(239, 68, 68, 0.08);
    border: 1px solid rgba(239, 68, 68, 0.25);
    border-radius: 999px;
    padding: 0.05rem 0.5rem;
}
.gd-comment-body {
    color: var(--text);
    line-height: 1.5;
    word-break: break-word;
    overflow-wrap: anywhere;
}
.gd-comment-body .md-codeblock {
    max-width: 100%;
    overflow-x: auto;
}
.gd-comment-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.4rem;
}
.gd-comment-action {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    padding: 0.25rem 0.55rem;
    border-radius: 0.4rem;
    font-size: 0.78rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    min-height: 1.85rem;
    line-height: 1;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.gd-comment-action:hover {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text);
}
.gd-comment-action-danger:hover {
    color: #fca5a5;
    border-color: rgba(239, 68, 68, 0.4);
    background: rgba(239, 68, 68, 0.07);
}
/* Nuke (hard-delete) is staff-only and irreversible. Distinct orange
   styling stops mods from confusing it with the regular Delete. */
.gd-comment-action-nuke {
    color: #fdba74;
    border-color: rgba(249, 115, 22, 0.35);
}
.gd-comment-action-nuke:hover {
    color: #fed7aa;
    border-color: rgba(249, 115, 22, 0.6);
    background: rgba(249, 115, 22, 0.08);
}
/* Sticky toggle (post-author only). Subtle when inactive, lit up
   purple when the comment is already sticked so the toggle direction
   reads at a glance. */
.gd-comment-action-sticky.is-sticky {
    color: #ddd6fe;
    border-color: rgba(124, 58, 237, 0.55);
    background: rgba(124, 58, 237, 0.12);
}
.gd-comment-action-sticky.is-sticky:hover {
    background: rgba(124, 58, 237, 0.18);
}

/* Sticky-comment row highlight. Lives on .gd-comment.is-sticky so the
   whole row reads as pinned without depending on the inline badge. */
.gd-comment.is-sticky {
    background: rgba(124, 58, 237, 0.05);
    border-left: 2px solid rgba(124, 58, 237, 0.45);
    border-top-color: rgba(124, 58, 237, 0.25);
    padding-left: 0.5rem;
    margin-left: -0.5rem;
    border-radius: 0 0.35rem 0.35rem 0;
}
.gd-comment.is-sticky + .gd-comment.is-sticky { border-top-color: rgba(124, 58, 237, 0.25); }

/* Inline "Sticked" badge in the comment header. Matches the post-
   level pin styling but smaller. */
.gd-comment-sticky-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.05rem 0.45rem 0.05rem 0.35rem;
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 600;
    color: #ddd6fe;
    background: rgba(124, 58, 237, 0.15);
    border: 1px solid rgba(124, 58, 237, 0.4);
    line-height: 1.3;
}

.gd-comments-more { display: flex; justify-content: center; margin-top: 0.6rem; }
.gd-comments-more-btn { min-height: 2rem; }

/* History modal */
.gd-comments-modal-overlay {
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9000;
    padding: 1rem;
    padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0));
}
.gd-comments-modal {
    background: #0d0d10;
    border: 1px solid var(--border);
    border-radius: 0.7rem;
    width: 100%;
    max-width: 640px;
    max-height: 85svh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
.gd-comments-modal-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 0.95rem;
    border-bottom: 1px solid var(--border);
}
.gd-comments-modal-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    flex: 1 1 auto;
}
.gd-comments-modal-close {
    appearance: none;
    background: transparent;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 0.25rem;
    border-radius: 0.3rem;
}
.gd-comments-modal-close:hover {
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
}
.gd-comments-modal-body {
    padding: 0.85rem 0.95rem;
    overflow-y: auto;
    flex: 1 1 auto;
}
.gd-comments-modal-loading,
.gd-comments-modal-empty,
.gd-comments-modal-error { color: var(--text-muted); font-size: 0.9rem; padding: 0.5rem 0; }
.gd-comments-modal-error { color: #fca5a5; }
.gd-comments-history-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}
.gd-comments-history-row {
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.6rem 0.75rem;
    background: #111114;
}
.gd-comments-history-meta {
    font-size: 0.78rem;
    color: var(--text-muted);
    margin-bottom: 0.4rem;
}
.gd-comments-history-label {
    color: var(--text);
    font-weight: 600;
}
.gd-comments-history-body {
    color: var(--text);
    line-height: 1.5;
    word-break: break-word;
    overflow-wrap: anywhere;
}

@media (max-width: 560px) {
    .gd-comment { gap: 0.5rem; }
    .gd-comment-avatar,
    .gd-comment-avatar-fallback { width: 32px; height: 32px; font-size: 0.85rem; }
    .gd-comments-textarea { min-height: 5rem; }
    .gd-comments-modal { max-height: 90svh; }
}

