/* ============================================================================
   mobile.css — phones and portrait tablets.
   Loaded LAST (after styles.css, the inline <style> blocks in index.html, and
   dark.css) so it can override without an !important arms race. Only rules
   that fight an existing !important carry one.

   The problem it solves: below ~980px the page simply stacked — logo, donate
   bar, nav, banners — and the message log, which is `flex: 1 1 auto` inside a
   column that had no height to give, collapsed to 40px. Both side panels were
   hidden outright, taking the rooms list, XP Shop, Drunken Penguin, the
   leaderboard and the online list with them.

   The fix is to stop treating the page as a document that scrolls and start
   treating it as an app: a full-height flex column where only the message log
   scrolls, with the two side panels reachable as drawers from a bottom bar.
   ========================================================================= */

@media (max-width: 980px) {
  html, body { height: 100%; }
  /* The PAGE never scrolls; the message log does. Without this the whole
     column scrolls away and the composer disappears above the keyboard. */
  body { overflow: hidden; }

  .window {
    /* dvh = the DYNAMIC viewport height: always exactly the visible area,
       toolbars shown or hidden. The old svh-last ordering froze the shell
       at the toolbars-SHOWING size, so when iOS Safari collapsed its chrome
       after a scroll the app kept its small height — a dead black band
       under the bottom bar and the status clock stamped over the logo
       (device screenshots 2026-08-16, "fixed by refresh" because refresh
       re-expands the chrome). svh/vh remain as fallbacks only. */
    height: 100vh;
    height: 100svh;
    height: 100dvh;
    display: flex;
    flex-direction: column;
    gap: 6px;
    /* NO safe-area padding here. Putting the bottom inset on the window
       shortened the whole app, leaving a dead black band between the bottom
       bar and Safari's toolbar on an iPhone. The insets belong on the two
       elements that actually touch the screen edges — the topbar and the
       bottom bar — so their backgrounds bleed into the corners while their
       CONTENT stays clear of the notch and home indicator. */
    padding: 0;
    max-width: none;
    border-radius: 0;
    overflow: hidden;
  }
  /* Full-bleed: no page gutters, no floating-card look on a phone. */
  body { margin: 0; padding: 0; background: var(--surface, #0f0f10); }
  .topbar { padding-top: calc(env(safe-area-inset-top, 0px) + 5px) !important; }

  /* ── Header: one compact row ─────────────────────────────────────────── */
  /* A narrow-screen rule turns the topbar into a COLUMN, which is how three
     small controls became 223px of a 800px phone. Force it back to a single
     row that scrolls sideways if it must. */
  .topbar {
    flex: 0 0 auto;
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    align-items: center !important;
    padding: 5px 8px !important;
    gap: 8px !important;
    border-radius: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  .topbar::-webkit-scrollbar { display: none; }
  .topbar > * { flex: 0 0 auto; }
  /* `body.is-ultra-compact-width .topbar` (a JS-applied class, so specificity
     0,2,0) stacks the topbar into a column — that alone was 222px of an
     800px phone. Matching its weight is the only way to win. */
  body.is-ultra-compact-width .topbar,
  body.is-compact-width .topbar,
  .topbar {
    flex-direction: row !important;
    align-items: center !important;
    gap: 8px !important;
  }
  body.is-ultra-compact-width .brand-mark-shell {
    width: 112px !important;
    max-width: 112px !important;
  }
  .topbar .brand-lockup,
  .topbar .topbar-actions,
  .topbar #userActions { flex-direction: row !important; align-items: center !important; }
  /* The same class hides the rooms panel outright; the drawer needs it back. */
  body.is-ultra-compact-width .chat-shell > .sidebar.channels-panel { display: block !important; }
  /* The avatar + Log out stack is 76px tall on its own — lay it out sideways
     and shrink the chip. */
  .topbar-user-stack {
    flex-direction: row !important;
    align-items: center !important;
    gap: 6px !important;
  }
  #topbarUserChip {
    width: 34px !important;
    height: 34px !important;
    min-width: 34px !important;
    min-height: 34px !important;
  }
  #topbarUserChip img { width: 34px !important; height: 34px !important; }
  /* The logo image keeps its desktop size inside the shrunken shell and gets
     cropped — scale it to fit instead. */
  .brand-mark-shell .brand-logo {
    width: 100% !important;
    height: 100% !important;
    object-fit: contain !important;
    max-height: 100% !important;
  }
  .topbar-actions,
  #userActions { flex-wrap: nowrap !important; }
  .brand-lockup { flex-direction: row !important; align-items: center !important; gap: 8px; }
  .brand-mark-shell {
    height: 40px !important;
    width: 112px !important;
    max-width: 112px !important;
    margin: 0 !important;
    border-radius: 10px !important;
  }
  /* The donate bar, recent-donations strip, Spotify dock and Track of the
     Week each cost 40–60px of height on a screen that has none to spare.
     Donating stays one tap away on the bottom bar's ❤️ tab instead. */
  .topbar-spotify-dock,
  .support-bar-dock,
  .track-of-the-week-banner,
  .support-stack .monthly-support-trigger,
  .support-stack .monthly-support-progress,
  .support-stack .topbar-recent-donations,
  .support-stack #topbarDonorBoard { display: none !important; }
  /* The stack itself must stay displayable: #monthlySupportPopup lives
     inside it, and a display:none ANCESTOR would keep the ❤️ tab's sheet
     hidden no matter what the popup's own rules say. Take it out of the
     topbar's flex flow (so it costs no gap) and collapse it to nothing. */
  .support-stack {
    position: absolute !important;
    width: 0 !important;
    height: 0 !important;
    min-width: 0 !important;
    margin: 0 !important;
    overflow: visible !important;
  }
  .support-stack .monthly-support-bar {
    position: static !important;
    min-width: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    background: none !important;
    box-shadow: none !important;
  }
  /* ❤️ Support sheet: the popup is authored as a 360px dropdown hanging
     under the desktop donate bar; on phones it pins above the bottom tab
     bar as a full-width sheet (same conversion as the six nav sheets).
     client.js reparents it to <body> while open — the topbar's
     backdrop-filter makes the HEADER the containing block for fixed
     children, so left inside it the sheet would anchor to the topbar box
     and land ~800px above the viewport. At body level this z-index puts
     it over the drawers/scrim (2147483000) and under the tab bar
     (2147483002), which it never overlaps anyway. */
  .monthly-support-popup {
    position: fixed !important;
    left: 10px !important;
    right: 10px !important;
    top: auto !important;
    bottom: calc(64px + env(safe-area-inset-bottom, 0px)) !important;
    width: auto !important;
    max-width: none !important;
    max-height: calc(var(--wpz-vvh, 100svh) - 84px - env(safe-area-inset-bottom, 0px)) !important;
    overflow-y: auto !important;
    overscroll-behavior: contain !important;
    touch-action: pan-y;
    z-index: 2147483001 !important;
  }
  .topbar-actions { gap: 6px !important; flex-wrap: nowrap; }
  .topbar-user-stack { transform: scale(0.86); transform-origin: right center; }

  /* Nav chips: one horizontally-scrollable row rather than a wrapped block,
     so VIP / Book WPZ / Tour dates stay reachable without eating the screen. */
  .session-strip {
    flex: 0 0 auto;
    padding: 0 8px !important;
    margin: 0 !important;
  }
  .session-pill {
    /* !important: the desktop wrap-rules added Aug 2026 in styles.css set
       overflow back to visible and let chips SHRINK (flex 0 1, min-width 0)
       — on a phone that mashed every pill into overlapping 22px circles.
       The strip scrolls; the chips never shrink. */
    overflow-x: auto !important;
    overflow-y: hidden !important;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    padding: 4px 6px !important;
    border-radius: 12px !important;
  }
  .session-pill::-webkit-scrollbar { display: none; }
  /* Scroll-edge fade (2026-08-15): the pill hard-clipped chips mid-letter at
     its edge ("SERV|" right under the spin dice on phones, "Book WP|" on
     tablets) with nothing telling the user the row scrolls. client.js
     toggles these classes from scrollLeft, so a fade only shows on an edge
     that actually has more content behind it. */
  .session-pill.wpz-fade-r {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 30px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 30px), transparent);
  }
  .session-pill.wpz-fade-l {
    -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 30px), transparent);
    mask-image: linear-gradient(to left, #000 calc(100% - 30px), transparent);
  }
  .session-pill.wpz-fade-l.wpz-fade-r {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 30px, #000 calc(100% - 30px), transparent);
    mask-image: linear-gradient(to right, transparent, #000 30px, #000 calc(100% - 30px), transparent);
  }
  /* "‹ Swipe menu ›" hint chip (2026-08-15): floated over the pill by
     client.js the first few visits, nudges sideways, dismissed for good the
     moment the user actually swipes the strip. NOT .wpz-swipe-hint — that
     class is the drawer-gesture pill from engagement.js, whose rules further
     down this file (opacity 0 until .is-in) would swallow this chip. */
  .wpz-strip-hint {
    position: fixed;
    z-index: 2147483005;
    display: inline-flex;
    align-items: center;
    padding: 6px 13px;
    border-radius: 999px;
    background: rgba(16, 42, 72, 0.88);
    color: #fff;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.05em;
    white-space: nowrap;
    box-shadow: 0 4px 16px rgba(4, 14, 26, 0.4);
    animation: wpzSwipeHintIn 0.3s ease, wpzSwipeNudge 1.5s ease-in-out 0.5s 3;
    transition: opacity 0.4s ease;
  }
  .wpz-strip-hint.is-leaving { opacity: 0; }
  body.auth-modal-open .wpz-strip-hint,
  body.wpz-kb-open .wpz-strip-hint { display: none !important; }
  @keyframes wpzSwipeHintIn {
    from { opacity: 0; margin-top: 5px; }
  }
  @keyframes wpzSwipeNudge {
    0%, 100% { transform: translateX(0); }
    30% { transform: translateX(-7px); }
    70% { transform: translateX(5px); }
  }
  .session-strip .session-links,
  .session-strip .main-menu-left { flex: 0 0 auto !important; min-width: auto !important; }
  .session-strip .session-link { flex: 0 0 auto !important; min-width: auto !important; }
  /* index.html's inline blocks force these into COLUMNS with !important —
     `.title-stack` at 107px and `.main-menu-row` at ≤900px are what made the
     nav strip 176px tall. Everything here needs the same weight to win, and
     the row scrolls sideways instead of stacking. */
  .session-strip .title-stack,
  .session-strip .main-menu-row,
  .session-strip .main-menu-left,
  .session-strip .session-links,
  .session-strip .main-menu-right {
    flex-direction: row !important;
    align-items: center !important;
    flex-wrap: nowrap !important;
    white-space: nowrap;
    width: auto !important;
  }
  .session-strip .title-stack { gap: 6px !important; }
  .session-strip .title-detail { display: none !important; }  /* subtitle line */
  .session-strip .session-links { display: flex !important; }
  /* Nav chips and the spin/bell/profile tools ride in ONE scrolling strip
     instead of two stacked rows — worth ~40px of chat on a phone. */
  #wpzTopTools {
    flex-wrap: nowrap !important;
    gap: 6px !important;
    padding: 0 !important;
    margin: 0 !important;
  }
  .session-strip .session-pill { padding: 3px 6px !important; }
  .session-strip .main-menu-trigger,
  .session-strip .session-link,
  .session-strip .book-wpz-link,
  .session-strip .vip-link,
  #wpzTopTools > * {
    font-size: 11px !important;
    padding: 5px 10px !important;
  }
  .guest-notice { font-size: 11px !important; }

  /* ── The chat fills everything that's left ───────────────────────────── */
  .chat-shell {
    flex: 1 1 auto;
    display: block !important;          /* out of the grid entirely */
    grid-template-columns: none !important;
    height: auto !important;
    max-height: none !important;
    min-height: 0 !important;
    padding: 0 8px;
    gap: 0;
    overflow: hidden;
  }
  .main-column {
    height: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
  .message-log {
    flex: 1 1 auto !important;
    min-height: 0 !important;           /* the 240px floor is what pinned it */
    padding: 10px 12px !important;
  }
  .message-form { flex: 0 0 auto; }
  .room-header { flex: 0 0 auto; padding: 6px 10px !important; }
  #roomPinnedMessage {
    flex: 0 0 auto;
    padding: 5px 10px !important;
    font-size: 11px !important;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ── Side panels become drawers ──────────────────────────────────────── */
  .chat-shell > .sidebar {
    position: fixed;
    top: 0;
    /* NOT `bottom: 0`: that sizes the drawer to the LAYOUT viewport, while
       the shell + bottom bar are pinned to the VISUAL viewport height
       (--wpz-vvh, set by wpzViewportLock). With Safari's chrome expanded
       the two differ by ~80px and the drawer visibly spilled past the bar
       into dead space below the app. */
    bottom: auto;
    z-index: 2147483000;
    width: min(300px, 86vw);
    max-width: 86vw;
    height: var(--wpz-vvh, 100svh);
    margin: 0;
    padding: 10px 8px calc(70px + env(safe-area-inset-bottom));
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    border-radius: 0;
    box-shadow: 0 0 40px rgba(4, 14, 26, 0.45);
    transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .chat-shell > .sidebar.channels-panel {
    left: 0;
    /* An existing narrow-screen rule sets display:none on this panel; without
       overriding it the drawer slid in something invisible. */
    display: block !important;
    transform: translateX(-102%);
  }
  /* Beats `body.is-compact-width .users-panel{display:none!important}`, which
     is what removed the online list from every iPad in portrait. */
  .chat-shell > .sidebar.users-panel {
    right: 0;
    display: block !important;
    width: min(300px, 86vw) !important;
    min-width: 0 !important;
    transform: translateX(102%);
  }
  body.wpz-drawer-menu .chat-shell > .sidebar.channels-panel { transform: none; }

  /* Drawer is display:block, so the sidebar flex row-gap (10px on desktop) is
     gone and the stacked feature panels would sit flush. Restore an even rhythm
     here; on desktop they carry no vertical margin (the gap owns it). */
  .chat-shell > .sidebar.channels-panel > .wpz-shop-btn,
  .chat-shell > .sidebar.channels-panel > .wpz-game-btn,
  .chat-shell > .sidebar.channels-panel > .wpz-clip-btn,
  .chat-shell > .sidebar.channels-panel > .wpz-quests,
  .chat-shell > .sidebar.channels-panel > .wpz-lb { margin-top: 10px; }
  body.wpz-drawer-people .chat-shell > .sidebar.users-panel { transform: none; }

  .wpz-drawer-scrim {
    position: fixed;
    inset: 0;
    z-index: 2147482999;
    /* Blur diet 2026-08-16: a full-screen blur during the drawer gesture
       is jank at the worst moment — the dim alone reads the same. */
    background: rgba(4, 14, 26, 0.55);
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease;
  }
  body.wpz-drawer-menu .wpz-drawer-scrim,
  body.wpz-drawer-people .wpz-drawer-scrim { opacity: 1; pointer-events: auto; }

  /* ── Bottom bar ──────────────────────────────────────────────────────── */
  .wpz-mobile-bar {
    display: flex;
    flex: 0 0 auto;
    /* Above the drawers AND the scrim: with a drawer open you must still be
       able to tap straight across to another tab, the way a real app works.
       Below this the scrim swallowed the taps. */
    position: relative;
    z-index: 2147483002;
    align-items: stretch;
    gap: 6px;
    /* The home-indicator reserve is capped (2026-08-19): the raw inset is
       34pt on a notched iPhone and we were adding another 6px on top, which
       left a visibly empty slab under the tab labels. 20pt still clears the
       indicator — it is 5pt tall and sits ~8pt off the bottom — and hands
       14pt back to the chat. The min/max pair also gives non-notched devices
       (env = 0) a sane 12pt instead of nothing. */
    padding: 6px 8px min(max(env(safe-area-inset-bottom, 0px), 12px), 20px);
    background: rgba(246, 251, 255, 0.97);
    border-top: 1px solid rgba(90, 167, 255, 0.18);
    /* Blur diet 2026-08-16: 0.97-opaque bar doesn't need a backdrop blur */
  }
  .wpz-mobile-bar button {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: 46px;                  /* comfortable tap target */
    padding: 4px 6px;
    border: 0;
    border-radius: 12px;
    background: transparent;
    color: #5b7794;
    font-size: 10px;
    font-weight: 900;
    cursor: pointer;
  }
  .wpz-mobile-bar button span.ico { font-size: 17px; line-height: 1; }
  .wpz-mobile-bar button.is-on { background: rgba(90, 167, 255, 0.16); color: #2f6fae; }
  .wpz-mobile-bar .wpz-mobile-badge {
    position: absolute;
    transform: translate(14px, -10px);
    min-width: 15px;
    padding: 0 4px;
    border-radius: 999px;
    background: #ff5f5f;
    color: #fff;
    font-size: 9px;
    line-height: 15px;
  }

  /* ── Things that must not overflow a small screen ────────────────────── */
  .wpz-game-win,
  .wpz-inbox,
  .wpz-fwin { max-width: calc(100vw - 16px); }
  .admin-panel { max-width: 100%; overflow-x: auto; }
  .wpz-pedestal { margin: 0 0 6px; font-size: 11.5px; padding: 7px 12px; }
  .wpz-pedestal-days { display: none; }   /* the name is the point */
}

/* Phones proper: trim harder still. */
@media (max-width: 560px) {
  .session-strip {
    display: flex !important;
    flex-direction: row !important;
    align-items: center !important;
    gap: 6px !important;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .session-strip::-webkit-scrollbar { display: none; }
  .session-strip > * { flex: 0 0 auto; }
  .session-strip .session-pill { border: 0 !important; background: transparent !important; }
  .brand-mark-shell { height: 32px !important; width: 88px !important; }
  /* On a 360px screen the header contents genuinely don't fit on one line.
     A sideways-scrolling bar would hide the avatar and theme toggle off the
     right edge where nobody would find them, so it wraps to two short rows
     instead — everything visible, ~30px. */
  .topbar {
    padding: 4px 8px !important;
    flex-wrap: wrap !important;
    justify-content: center !important;
    row-gap: 4px !important;
    column-gap: 6px !important;
    overflow-x: hidden !important;
  }
  .topbar #userActions button,
  .topbar .topbar-actions button { font-size: 10.5px !important; padding: 4px 8px !important; }
  .chat-shell { padding: 0 6px; }
  .message-log { padding: 8px 10px !important; }
  .room-header .room-topic { display: none; }
  .wpz-mobile-bar button { font-size: 9.5px; }
}

/* Landscape phones are ~380px tall — every fixed strip has to go or there is
   no chat left at all. */
@media (max-width: 980px) and (max-height: 460px) {
  .session-strip { display: none !important; }
  .topbar { padding: 3px 8px !important; }
  .brand-mark-shell { height: 28px !important; width: 80px !important; }
  .wpz-mobile-bar button { min-height: 38px; font-size: 9px; }
}

@media (max-width: 980px) {
  html[data-theme="dark"] .wpz-mobile-bar {
    background: rgba(27, 27, 28, 0.97);
    border-top-color: rgba(255, 255, 255, 0.08);
  }
  html[data-theme="dark"] .wpz-mobile-bar button { color: #93a7bb; }
  html[data-theme="dark"] .wpz-mobile-bar button.is-on { background: rgba(90, 167, 255, 0.2); color: #7cc0ff; }
}

/* Desktop keeps both out of the document. Written as a MIN-width query on
   purpose: as a plain rule it sat after the mobile block, matched everywhere,
   and (same specificity, later in the file) hid the bar on phones too. */
@media (min-width: 981px) {
  .wpz-mobile-bar,
  .wpz-drawer-scrim { display: none !important; }
}

/* ══ 📱 PHONE PASS 2 — reclaim the screen for the chat ═════════════════════
   Measured on a 375×667 iPhone SE: 326px of chrome vs 222px of actual
   conversation. The topbar alone was 122px because it wrapped to two rows.
   Everything below shrinks the furniture so it fits one line, rather than
   hiding controls where nobody can find them. */
@media (max-width: 560px) {
  /* ── Topbar: one row, ~44px instead of 122px ── */
  body.is-ultra-compact-width .topbar,
  body.is-compact-width .topbar,
  .topbar {
    flex-wrap: nowrap !important;
    align-items: center !important;
    /* Left-align: on the nowrap scroll strip a surviving justify-content:center
       (from the ≤560px block above) pushed the logo to a negative scroll offset
       that overflow-x:auto can never reach — that was the "logo cut off / icons
       overlapping the logo" bug. flex-start keeps the logo the first visible item. */
    justify-content: flex-start !important;
    padding: 3px 6px !important;
    column-gap: 5px !important;
    row-gap: 0 !important;
    min-height: 0 !important;
    overflow-x: auto !important;
    overflow-y: hidden !important;
  }
  .brand-mark-shell { height: 26px !important; width: 68px !important; flex: 0 0 auto !important; }
  /* ONLY the moon toggle is a square icon. `.theme-button` is the shared class
     for Login/Sign up too — sizing that to 30px squashed the pills while their
     labels stayed full size and spilled out over each other. */
  .topbar .wpz-theme-toggle,
  .topbar #themeToggle {
    width: 30px !important; height: 30px !important;
    min-width: 30px !important; min-height: 30px !important;
    padding: 0 !important; font-size: 15px !important;
    display: inline-flex !important; align-items: center !important; justify-content: center !important;
    flex: 0 0 auto !important;
  }
  .topbar .theme-button,
  .topbar button,
  .topbar #userActions button,
  .topbar .topbar-actions button {
    font-size: 11.5px !important;
    line-height: 1 !important;
    padding: 7px 11px !important;
    min-height: 30px !important;
    height: auto !important;
    width: auto !important;
    white-space: nowrap !important;
    flex: 0 0 auto !important;
  }

  /* ── Brand/session strip: 73px → ~34px ── */
  .session-strip { padding: 2px 6px !important; min-height: 0 !important; }
  .session-strip .session-pill { padding: 2px 4px !important; }
  .title-stack .brand-title { font-size: 12px !important; line-height: 1.1 !important; }
  .title-stack .brand-sub,
  .session-strip .guest-notice { display: none !important; }

  /* ── Nav chips: one compact sideways strip ── */
  .main-menu-row {
    padding: 2px 6px !important;
    gap: 4px !important;
    min-height: 0 !important;
  }
  .main-menu-row a,
  .main-menu-row button,
  .main-menu-row .menu-chip {
    font-size: 10.5px !important;
    padding: 5px 9px !important;
    min-height: 28px !important;
    letter-spacing: 0.02em !important;
  }

  /* ── Room header + pinned: 71px → ~34px, pinned on one truncated line ── */
  .room-header {
    padding: 4px 8px !important;
    min-height: 0 !important;
    gap: 6px !important;
  }
  .room-header .room-name,
  .room-header h2 { font-size: 13.5px !important; }
  .room-pinned {
    padding: 2px 8px !important;
    font-size: 11px !important;
    line-height: 1.25 !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    max-height: 20px !important;
  }

  /* ── Composer: bigger tap targets, no iOS zoom-on-focus ──
     Any font-size under 16px makes Safari zoom the whole page when the field
     is focused, which then needs a manual pinch to undo. */
  .message-form { padding: 5px 6px !important; gap: 5px !important; }
  .message-form input[type="text"],
  .message-form textarea,
  #messageInput {
    font-size: 16px !important;
    min-height: 38px !important;
    padding: 7px 10px !important;
  }
  .message-form button {
    min-width: 38px !important; min-height: 38px !important;
    padding: 6px 10px !important;
  }

  /* Messages themselves get the reclaimed room. */
  .message-log { padding: 6px 8px !important; }
}

/* The auth pills are styled by ID with !important (`#loginButton`,
   `#signupButton` in styles.css) — 14–16px text and 26–34px of side padding,
   which is ~220px of a 375px screen for two buttons. A class-based override
   loses on specificity even when it is also !important, so match the ID. */
@media (max-width: 560px) {
  .topbar #loginButton,
  .topbar #signupButton {
    font-size: 12px !important;
    font-weight: 800 !important;
    padding: 8px 14px !important;
    min-height: 32px !important;
    letter-spacing: 0 !important;
  }
  /* The signup pill also pulses via a scale animation; on a phone that makes
     it jitter against its neighbours in a nowrap row. */
  .topbar #signupButton { animation: none !important; }
}

/* Nested padding around the nav strip and room header — the chips themselves
   are a fine 42px touch target, but three layers of wrapper padding add ~30px
   of nothing. Scoped under .window so these beat the inline !important blocks. */
@media (max-width: 560px) {
  .window .session-strip { padding: 1px 6px !important; }
  .window .session-pill { padding: 1px 4px !important; }
  .window .room-header { padding: 5px 10px !important; }
}

/* ══ 🐧 Drunken Penguin on a phone ═════════════════════════════════════════
   The window is a floating panel on desktop; on a phone it takes the whole
   screen, and the playfield is sized off the AVAILABLE HEIGHT rather than the
   width. Sizing by width on a tall phone makes a 3:4 canvas taller than the
   screen, so the bottom of the playfield — where the penguin usually is —
   ends up below the fold.

   The second condition catches a phone turned sideways: a landscape iPhone is
   ~930 CSS px wide, so a plain max-width test misses it and the game fell back
   to the desktop floating window — which is capped at the viewport height and
   was cropping the bottom of the playfield clean off. Short AND narrow, rather
   than a pointer test, because a browser window under 1024×500 wants the
   same treatment whatever is pointing at it. */
@media (max-width: 820px), (max-height: 500px) and (max-width: 1024px) {
  .wpz-game-win {
    position: fixed !important;
    inset: 0 !important;
    width: 100vw !important;
    max-width: 100vw !important;
    height: 100svh !important;
    height: 100dvh !important;
    max-height: 100svh !important;
    max-height: 100dvh !important;
    margin: 0 !important;
    border-radius: 0 !important;
    padding: 0 !important;
    padding-top: env(safe-area-inset-top) !important;
    padding-bottom: env(safe-area-inset-bottom) !important;
    display: flex !important;
    flex-direction: column !important;
    transform: none !important;
  }
  .wpz-game-head {
    flex: 0 0 auto;
    padding: 8px 12px !important;
  }
  .wpz-game-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex !important;
    flex-direction: column !important;
    align-items: center !important;
    gap: 8px !important;
    padding: 6px 8px !important;
    overflow-y: auto;
  }
  /* 3:4 playfield, capped by height so it always fits. */
  .wpz-game-stage {
    flex: 0 0 auto;
    width: auto !important;
    height: min(62svh, calc((100vw - 16px) * 4 / 3)) !important;
    aspect-ratio: 3 / 4;
    max-width: calc(100vw - 16px) !important;
    border-radius: 14px !important;
    /* No scrolling, no pull-to-refresh, no double-tap zoom while flying. */
    touch-action: none !important;
    -webkit-user-select: none !important;
    user-select: none !important;
    -webkit-touch-callout: none !important;
  }
  .wpz-game-canvas {
    width: 100% !important;
    height: 100% !important;
    display: block !important;
  }
  .wpz-game-side {
    flex: 1 1 auto;
    width: 100% !important;
    max-height: none !important;
    min-height: 0;
    overflow-y: auto;
  }
  /* Buttons big enough to hit with a thumb mid-game. */
  .wpz-game-card button,
  .wpz-game-start,
  .wpz-game-giveup {
    min-height: 44px !important;
    font-size: 15px !important;
    padding: 10px 18px !important;
  }
  .wpz-game-tap {
    display: inline-block;
    padding: 6px 14px;
    border-radius: 999px;
    background: rgba(255,255,255,0.14);
    font-size: 14px; font-weight: 800; letter-spacing: 0.02em;
  }
}

/* ── Minimised leaderboard = near-full-screen game ────────────────────────
   "Full screen ⤢" in the window header hides the board and hands its height
   to the playfield. The canvas keeps drawing a 480×640 field — game.js grows
   glacier/deep-water bands around it to fill the taller box — so nothing
   stretches and nobody gets an easier run for having a taller phone.
   (The board itself is hidden by the base rule in styles.css; what's here is
   the phone-only business of letting the stage stretch to fill the column.) */
@media (max-width: 820px), (max-height: 500px) and (max-width: 1024px) {
  .wpz-game-win.is-board-min {
    width: 100vw !important;
    max-width: 100vw !important;
  }
  .wpz-game-win.is-board-min .wpz-game-body {
    overflow: hidden !important;
    gap: 6px !important;
    padding-bottom: 4px !important;
  }
  .wpz-game-win.is-board-min .wpz-game-stage {
    flex: 1 1 auto !important;
    align-self: stretch !important;
    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;
    min-height: 0 !important;
    aspect-ratio: auto !important;
  }
  /* Thumb-sized header control. */
  .wpz-game-board-toggle { min-height: 32px; }
}

/* Landscape phone: the playfield gets the height, the board moves beside it. */
@media (max-width: 980px) and (max-height: 460px) {
  .wpz-game-body { flex-direction: row !important; align-items: stretch !important; }
  .wpz-game-stage { height: calc(100svh - 60px) !important; }
  .wpz-game-side { max-height: calc(100svh - 60px) !important; }
  /* Minimised in landscape: the box would end up WIDER than the playfield,
     and extra sky can't fix that — only cropping the sides could, which would
     change how much warning you get before an obstacle. So here the stage
     keeps its 3:4 shape and simply centres. */
  .wpz-game-win.is-board-min .wpz-game-stage {
    flex: 0 0 auto !important;
    width: auto !important;
    aspect-ratio: 3 / 4 !important;
    height: calc(100svh - 60px) !important;
    margin: 0 auto !important;
  }
}

/* 💊 sober-pill button — touch only. Bottom-left of the playfield, away from
   where a right thumb rests to fly, and above the sea where the penguin dies. */
.wpz-game-pill-btn {
  position: absolute;
  left: 10px; bottom: 10px;
  z-index: 5;
  width: 54px; height: 54px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 26px; line-height: 1;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.5);
  background: rgba(12, 32, 56, 0.72);
  backdrop-filter: blur(3px);
  box-shadow: 0 6px 16px rgba(0,0,0,0.4);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.wpz-game-pill-btn[hidden] { display: none !important; }
.wpz-game-pill-btn:active { transform: scale(0.92); }
.wpz-game-pill-btn span {
  position: absolute; top: -4px; right: -4px;
  min-width: 20px; height: 20px; padding: 0 4px;
  border-radius: 999px;
  background: #ffb52e; color: #2a1600;
  font-size: 12px; font-weight: 900; line-height: 20px;
  text-align: center;
}
.wpz-game-pill-btn span:empty { display: none; }

/* ══ 📱 Full-bleed on phones ═══════════════════════════════════════════════
   From a real iPhone 17 Pro Max: the app sat in a box with gutters down both
   sides and a dead black band under the bottom bar. The band was the
   safe-area inset being applied TWICE — once on .window and again inside
   .wpz-mobile-bar — so the app shrank by the home-indicator height and then
   the bar padded itself by the same amount again. */
@media (max-width: 560px) {
  .window .chat-shell { padding: 0 !important; }
  .window { gap: 4px !important; }
  /* The message log was a floating rounded card inside a gutter; on a phone
     it should be the screen. */
  .window .message-log {
    border-radius: 0 !important;
    border-left: 0 !important;
    border-right: 0 !important;
    margin: 0 !important;
  }
  .window .room-header,
  .window .room-pinned { border-radius: 0 !important; margin-left: 0 !important; margin-right: 0 !important; }
  .window .message-form {
    border-radius: 0 !important;
    margin: 0 !important;
    padding-left: 8px !important;
    padding-right: 8px !important;
  }
  /* Bar background reaches the very bottom; only its CONTENT clears the
     home indicator. */
  .wpz-mobile-bar { margin: 0 !important; border-radius: 0 !important; }
}

/* The last 6px: .window's flex `gap` also applies below the final child (the
   bottom bar), and .main-column keeps a small horizontal padding. Both read
   as a thin frame around an app that should be edge-to-edge. */
@media (max-width: 560px) {
  .window { gap: 0 !important; }
  .window .main-column { padding-left: 0 !important; padding-right: 0 !important; }
  .window .message-log { margin-left: 0 !important; margin-right: 0 !important; }
}

/* `body.is-compact-width .window` (JS-applied class, specificity 0,2,0) keeps
   6px of padding on the shell, which is the thin frame still visible around
   an otherwise edge-to-edge app. Same trap as is-ultra-compact-width earlier. */
@media (max-width: 560px) {
  body.is-compact-width .window,
  body.is-ultra-compact-width .window { padding: 0 !important; }
}

/* ══ Stop iOS auto-zoom on focus ═══════════════════════════════════════════
   Safari zooms the whole page in whenever a focused form control has text
   under 16px — and it never zooms back out, so you end up pinching to escape
   after every tap into a field. The composer was fixed earlier; this covers
   every other input on the site (signup, login, search, admin forms, the
   donate box…), which is what kept re-triggering it.
   16px is the threshold, not a design choice — anything less re-enables it. */
@media (max-width: 980px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),
  textarea,
  select {
    font-size: 16px !important;
  }
}

/* These two carry their own !important font-size and slipped through. */
@media (max-width: 980px) {
  #socialSearch,
  .bk-textarea,
  textarea.bk-textarea { font-size: 16px !important; }
}
@media (max-width: 980px) {
  /* Styled as `#socialWindow #socialSearch` — two IDs, so a single-ID
     override loses even with !important. */
  #socialWindow #socialSearch { font-size: 16px !important; }
}

/* ══ 📱 Overlays vs the bottom bar ═════════════════════════════════════════
   The mobile bar sits at z-index 2147483002 so a drawer can't swallow taps
   on it. Anything that should appear OVER the bar has to clear that number —
   and several panels don't:
     .merch-checkout-overlay  100004      (shop + WPZ Limited checkout)
     .admin-panel.op-window   2147483000  (Operator Panel)
   Both were therefore drawn underneath the bar. On a phone they also want to
   be full-screen rather than centred cards. */
@media (max-width: 560px) {
  /* ── Shop / WPZ Limited: full-screen ── */
  .merch-checkout-overlay {
    z-index: 2147483010 !important;
    padding: 0 !important;
  }
  .merch-checkout-card {
    width: 100vw !important;
    max-width: 100vw !important;
    height: 100svh !important;
    height: 100dvh !important;
    max-height: 100svh !important;
    max-height: 100dvh !important;
    border-radius: 0 !important;
    border-left: 0 !important;
    border-right: 0 !important;
  }
  .merch-checkout-head {
    padding-top: calc(env(safe-area-inset-top, 0px) + 12px) !important;
  }
  /* The last product/field must clear the home indicator. */
  .merch-catalog-grid { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 16px) !important; }

  /* ── Operator Panel: full-screen sheet, above the bar ── */
  .admin-panel.op-window {
    z-index: 2147483010 !important;
    left: 0 !important;
    top: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    width: 100vw !important;
    max-width: 100vw !important;
    height: 100svh !important;
    height: 100dvh !important;
    max-height: 100svh !important;
    max-height: 100dvh !important;
    border-radius: 0 !important;
    transform: none !important;
  }
  .op-window-head { padding-top: calc(env(safe-area-inset-top, 0px) + 10px) !important; }
  /* Minimised state must not inherit the full-screen height. */
  .admin-panel.op-window.is-minimized {
    height: auto !important;
    bottom: auto !important;
  }

  /* ── Operator Panel: the section nav becomes a ☰-toggled overlay ──
     The fixed 148px desktop nav ate 40% of a 390px phone. Here the content
     pane gets the full window width; the ☰ in the window head slides the
     nav over it, and picking a section (or tapping the scrim) closes it
     again — operator-panel.js toggles .op-nav-open on #adminPanel. */
  .op-window-head .op-nav-toggle { display: grid !important; }
  .op-window-body { position: relative !important; }
  .admin-panel.op-window .op-nav {
    position: absolute !important;
    left: 0 !important;
    top: 0 !important;
    bottom: 0 !important;
    z-index: 6;
    width: min(230px, 72vw) !important;
    flex: none !important;
    /* The desktop tint is 55% translucent — the overlay needs an opaque
       ground or the pane's text shows through the nav rows. */
    background: #eef6ff !important;
    box-shadow: 0 14px 44px rgba(8, 22, 39, 0.35);
    transform: translateX(-105%);
    transition: transform 200ms ease;
  }
  html[data-theme="dark"] .admin-panel.op-window .op-nav {
    background: var(--d-flat-2, #232326) !important;
  }
  .admin-panel.op-window.op-nav-open .op-nav { transform: none; }
  .admin-panel.op-window.op-nav-open .op-nav-scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 5;
    background: rgba(8, 22, 39, 0.35);
  }

  /* ── VIP popup: centre it ──
     It is an absolutely-positioned dropdown anchored to the VIP chip with
     `right: 0`. That chip lives in a horizontally-SCROLLING strip, so on a
     phone the popup landed wherever the chip had scrolled to — usually off
     to the left. Centre it on the screen instead of hanging it off the chip. */
  .vip-menu-popup {
    position: fixed !important;
    top: 50% !important;
    left: 50% !important;
    right: auto !important;
    width: min(360px, calc(100vw - 24px)) !important;
    max-height: 80svh !important;
    overflow-y: auto !important;
    z-index: 2147483011 !important;
    transform: translate(-50%, calc(-50% + 6px)) !important;
  }
  .vip-menu-popup.is-open { transform: translate(-50%, -50%) !important; }
}

/* ══ Logo legibility on phones ═════════════════════════════════════════════
   The brand shell keeps `padding: 10px 14px` from the desktop design. Once
   the shell was shrunk to 26px tall for the compact header, that padding left
   a SIX pixel content box — the wordmark was rendering at 84x6 from a
   1413x696 source, which is why it looked like a smudge. Give it a real
   height and trim the padding; the topbar only uses ~282px of 393, so there
   is room. */
@media (max-width: 560px) {
  .topbar .brand-mark-shell {
    height: 34px !important;
    width: 124px !important;
    max-width: 124px !important;
    padding: 3px 7px !important;
    display: flex !important;
    align-items: center !important;
    justify-content: center !important;
  }
  .topbar .brand-mark-shell .brand-logo {
    width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    object-fit: contain !important;
  }
}

/* On a phone the minimised call bar would land on top of the ☰/Chat/People
   bar as well as the composer. Park it directly above the bottom bar and
   give the composer the same clearance. */
@media (max-width: 980px) {
  .vip-call-panel.is-mini {
    left: 8px !important;
    right: 8px !important;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 58px) !important;
    padding: 6px 14px !important;
  }
  body:has(.vip-call-panel.is-mini:not(.hidden)) .message-form {
    margin-bottom: 52px !important;
  }
}

/* ══ 📱 Full VIP call window: sheet by default, draggable for real ════
   History: the window used to spawn at the desktop top:96/right:26 anchor,
   so it was pinned as a centred sheet with !important. Device report
   2026-08-23: the !important pin also silently defeated makeDraggable's
   inline styles (drag did nothing), sat ON TOP of the nav-chip strip, and
   outranked .is-maximized, so Maximize did nothing on phones either. Now:
   • the sheet pin stands down once the window carries [data-wpz-dragged]
     (set by makeDraggable on the first real move) or .is-maximized;
   • it spawns BELOW the nav-chip strip (+108px clears topbar + chips in
     both browser and app — app topbar is taller but env() covers it);
   • Maximize gets its own phone geometry (near-fullscreen, island-safe).
   Centred via left/right margins, NOT transform, so the vipCallSpawn
   drop-in animation still plays. Breakpoint = the ≤700 single-pane switch. */
@media (max-width: 700px) {
  .vip-call-panel:not(.is-mini):not(.hidden) {
    bottom: auto !important;
    max-width: none !important;
    min-width: 0 !important;
    max-height: min(74vh, calc(var(--wpz-vvh, 100svh) - 150px)) !important;
    box-shadow: 0 22px 55px rgba(0, 0, 0, 0.5) !important;
    z-index: 2147483040 !important;
  }
  .vip-call-panel:not(.is-mini):not(.hidden):not(.is-maximized):not([data-wpz-dragged]) {
    top: calc(env(safe-area-inset-top, 0px) + 108px) !important;
    left: 3vw !important;
    right: 3vw !important;
    width: auto !important;
  }
  /* Dragged: inline left/top from makeDraggable take over; keep a sane
     width so the panel doesn't snap back to the desktop 252px tier. */
  .vip-call-panel:not(.is-mini):not(.hidden)[data-wpz-dragged]:not(.is-maximized) {
    right: auto !important;
    width: min(560px, 94vw) !important;
  }
  /* Maximize, phone edition: near-fullscreen but clear of the island and
     the bottom tab bar. Outspecifies both the sheet pin (excluded above)
     and the desktop .is-maximized geometry. */
  .vip-call-panel.is-maximized:not(.is-mini):not(.hidden) {
    top: calc(max(env(safe-area-inset-top, 0px), 8px) + 4px) !important;
    left: 2vw !important;
    right: 2vw !important;
    width: auto !important;
    transform: none !important;
    max-height: calc(var(--wpz-vvh, 100svh) - max(env(safe-area-inset-top, 0px), 8px) - 90px) !important;
  }
  /* Head menu: 10 controls (incl. Leave) never fit one row on a phone —
     Hide and Leave were clipped clean off the panel. Let the head wrap so
     the controls take their own full-width row(s). */
  .vip-call-panel:not(.is-mini) .vip-call-head {
    flex-wrap: wrap;
  }
  .vip-call-panel:not(.is-mini) .vip-call-controls {
    flex-wrap: wrap;
    row-gap: 4px;
    justify-content: flex-end;
    max-width: 100%;
    flex: 1 1 auto;
  }
}


/* ══ Logged-in topbar must fit ═════════════════════════════════════════════
   Signed in, the bar holds logo + Inbox + Friend list + Blocked users +
   Log out + theme = 469px of content in a 393px viewport. It scrolled, and
   what scrolled off the left was the logo — hence the cut-off wordmark.
   The three secondary buttons become icons: 248px of labels -> ~102px. */
@media (max-width: 560px) {
  /* :not(.hidden) is load-bearing: this ID selector (1,1,0) beats
     `.hidden{display:none !important}` (0,1,0) in the !important tie, and
     the unconditional `display:inline-flex !important` here is what kept
     Inbox/Friends/Blocked visible for GUESTS on phones while every ≥561px
     check said they were hidden. */
  .topbar #inboxButton:not(.hidden),
  .topbar #friendsButton:not(.hidden),
  .topbar #blockedButton:not(.hidden) {
    font-size: 15px !important;         /* the label is already an icon */
    width: 34px !important;
    min-width: 34px !important;
    height: 34px !important;
    min-height: 34px !important;
    padding: 0 !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    overflow: hidden !important;
  }
  /* The icon itself is set in client.js (CSS kept losing the specificity
     fight against several competing !important rules). A ::before here too
     would render the icon TWICE. */
  /* Log out is a stack with an avatar; trim it to the avatar plus a tiny label. */
  .topbar .topbar-user-stack { transform: scale(0.8) !important; transform-origin: right center !important; }
}

/* Last few pixels: the logo can be slightly narrower on a phone, and the
   user stack no longer needs room for a "Log out" word. */
@media (max-width: 560px) {
  .topbar .brand-mark-shell { width: 104px !important; max-width: 104px !important; }
  .topbar .topbar-user-stack { gap: 3px !important; }
}

/* `body.is-ultra-compact-width .brand-mark-shell` pins the width at 112px
   (specificity 0,2,0), so a plain `.topbar .brand-mark-shell` override loses.
   Same JS-applied-class trap as the topbar and window padding. */
@media (max-width: 560px) {
  body.is-compact-width .brand-mark-shell,
  body.is-ultra-compact-width .brand-mark-shell,
  body .topbar .brand-mark-shell {
    width: 98px !important;
    max-width: 98px !important;
  }
}

/* ══ Support window ════════════════════════════════════════════════════════
   `#supportWindow` sits at z-index 70. On a phone the bottom bar (2147483002)
   and the People drawer both render above it, so tapping 💬 Support opened a
   window you could never see — indistinguishable from a dead button.
   Full-screen sheet above the furniture, like the other panels. */
@media (max-width: 560px) {
  #supportWindow {
    z-index: 2147483012 !important;
    left: 0 !important;
    right: 0 !important;
    top: 0 !important;
    bottom: 0 !important;
    width: 100vw !important;
    max-width: 100vw !important;
    height: 100svh !important;
    height: 100dvh !important;
    max-height: 100svh !important;
    max-height: 100dvh !important;
    border-radius: 0 !important;
    padding-top: env(safe-area-inset-top, 0px) !important;
    padding-bottom: env(safe-area-inset-bottom, 0px) !important;
  }
}

/* ══ 📸 Clip wall on a phone ════════════════════════════════════════════════
   Full-screen like every other window here, two columns of cards, and the
   composer stays reachable above the grid rather than scrolling away. */
@media (max-width: 820px), (max-height: 500px) and (max-width: 1024px) {
  .wpz-clip-win {
    position: fixed !important;
    inset: 0 !important;
    width: 100vw !important;
    max-width: 100vw !important;
    height: 100svh !important;
    height: 100dvh !important;
    max-height: 100svh !important;
    max-height: 100dvh !important;
    margin: 0 !important;
    border-radius: 0 !important;
    padding: 0 8px !important;
    padding-top: env(safe-area-inset-top) !important;
    padding-bottom: env(safe-area-inset-bottom) !important;
    transform: none !important;
  }
  .wpz-clip-head { padding: 8px 4px !important; }
  .wpz-clip-tabs { order: 3; width: 100%; }
  .wpz-clip-grid {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)) !important;
    gap: 8px !important;
  }
  /* 16px or iOS zooms the whole page the moment the caption box is focused. */
  #wpzClipCaption { font-size: 16px !important; }
  .wpz-clip-quota { width: 100%; margin-left: 0 !important; }
}

/* ══ 📱 Nav "hover" popups (Book WPZ, Merch, Newsletter, Download, Tour
   Dates, Services) ══════════════════════════════════════════════════════
   These cards are desktop artifacts: client.js positions them absolute
   under the hovered nav chip with a hard-coded width (400px for bookings)
   and max-height:480px. On a phone that means (a) the card lands wherever
   the nav strip happens to be scrolled, (b) 12px + 400px overflows a
   390px screen so the page can be panned sideways, and (c) a touch-scroll
   that hits the 480px cap chains through to the app behind the card —
   the "random scrolling". Pin them as fixed sheets instead.
   The JS-set inline left/top are NOT !important, so stylesheet !important
   here wins; the .is-visible twin selectors are needed to outweigh the
   (0,2,0) transform rule in index.html's inline block. */
@media (max-width: 980px) {
  .bookings-hover-popup,
  .merch-hover-popup,
  .subscribe-hover-popup,
  .download-hover-popup,
  .tour-dates-hover-popup,
  .services-hover-popup,
  .bookings-hover-popup.is-visible,
  .merch-hover-popup.is-visible,
  .subscribe-hover-popup.is-visible,
  .download-hover-popup.is-visible,
  .tour-dates-hover-popup.is-visible,
  .services-hover-popup.is-visible {
    position: fixed !important;
    left: 10px !important;
    right: 10px !important;
    top: calc(10px + env(safe-area-inset-top, 0px)) !important;
    bottom: auto !important;
    width: auto !important;
    /* Cap above the bottom bar (46px buttons + 12px padding) + indicator. */
    max-height: calc(100svh - 80px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)) !important;
    transform: none !important;
    overflow-y: auto !important;
    overscroll-behavior: contain !important;
    -webkit-overflow-scrolling: touch;
  }
  /* The sheet already IS the full window; the pin button just moves the
     form into a second, differently-broken container on a phone. */
  .bk-open-full-btn { display: none !important; }
}

/* The pinned full-window booking modal — on touch this is now the ONLY
   booking surface: client.js routes the trigger tap straight here at ≤980
   (the hover sheet has no close control on a phone). Its desktop 88vh card
   and 24px overlay padding waste a phone screen and it sat under the
   bottom bar (z 100003 < 2147483002). Same treatment as the checkout
   overlay. */
@media (max-width: 980px) {
  .bookings-full-modal {
    z-index: 2147483010 !important;
    padding: 0 !important;
  }
  .bookings-full-card {
    width: 100vw !important;
    max-width: 100vw !important;
    height: 100svh !important;
    height: 100dvh !important;
    max-height: 100svh !important;
    max-height: 100dvh !important;
    border-radius: 0 !important;
    border-left: 0 !important;
    border-right: 0 !important;
  }
  /* Keep the title/✕ row out from under the notch, and give the ✕ a real
     thumb target (desktop ships it as a 22px glyph with 4px padding). */
  .bookings-full-head {
    padding-top: calc(14px + env(safe-area-inset-top, 0px)) !important;
  }
  .bookings-full-close {
    font-size: 26px !important;
    padding: 8px 12px !important;
  }
  /* A touch-scroll that hits the form's end must not chain into the app
     behind the overlay — the original "random scrolling" complaint. */
  .bookings-full-body {
    overscroll-behavior: contain !important;
    -webkit-overflow-scrolling: touch;
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px)) !important;
    /* iOS: input[type=date] ignores width:100% and keeps an intrinsic
       min-content width, pushing the form wider than the screen; the
       resulting horizontal scroll makes the whole sheet draggable (device
       screenshots show only the date row running off-screen — Chromium
       sizes it normally, so emulation can't reproduce this). Forbid
       sideways scroll outright and let only vertical pans through. */
    overflow-x: hidden !important;
    touch-action: pan-y;
  }
  .bookings-full-body .bk-form,
  .bookings-full-body .bk-form-group,
  .bookings-full-body .bk-grid-2,
  .bookings-full-body .bk-grid-2 > * {
    max-width: 100% !important;
    min-width: 0 !important;
  }
  .bk-input,
  .bk-textarea {
    max-width: 100% !important;
    min-width: 0 !important;
  }
  /* The iOS date-width fix proper: strip the native widget sizing. flex +
     align-items is the standard WebKit trick to keep the value vertically
     centered once appearance is gone. */
  .bk-date {
    -webkit-appearance: none !important;
    appearance: none !important;
    display: flex !important;
    align-items: center !important;
    min-width: 0 !important;
    width: 100% !important;
  }
  /* Same intrinsic-width hazard exists in the six hover sheets (Tour
     Dates, Services… still shown as sheets on tablets 561-980). */
  .bookings-hover-popup,
  .merch-hover-popup,
  .subscribe-hover-popup,
  .download-hover-popup,
  .tour-dates-hover-popup,
  .services-hover-popup {
    overflow-x: hidden !important;
    touch-action: pan-y;
  }
}

/* ══ 📱 Email-verify banner ════════════════════════════════════════════════
   A flex row where the email address — one unbreakable token — overflowed
   its column INTO the Enter code/Resend buttons on phones. Let the row wrap
   (text full-width, buttons drop to their own line) and let the address
   break anywhere. */
@media (max-width: 560px) {
  .wpz-emailverify-bar { flex-wrap: wrap; }
  .wpz-emailverify-text { flex: 1 1 100%; overflow-wrap: anywhere; }
}

/* ══ 📱 VIP popup ══════════════════════════════════════════════════════════
   Same desktop-hover artifact as the six nav popups (Pass 4), missed then
   because it lives under #vipMenuWrap, not the shared popup classes. On a
   phone: JS parked it below wherever the ⭐VIP chip was, taller than the
   screen with no scroll (the Cancel-anytime note sat under the bottom bar),
   and `.vip-menu-popup:hover` — which iOS makes STICKY after a tap — plus a
   mouseleave-only close meant it never went away. Sheet geometry here;
   the tap-outside close lives in client.js. */
@media (max-width: 980px) {
  .vip-menu-popup.is-floating,
  .vip-menu-popup.is-floating.is-open {
    position: fixed !important;
    left: 10px !important;
    right: 10px !important;
    top: calc(10px + env(safe-area-inset-top, 0px)) !important;
    bottom: auto !important;
    width: auto !important;
    max-height: calc(100svh - 80px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)) !important;
    overflow-y: auto !important;
    overscroll-behavior: contain !important;
    -webkit-overflow-scrolling: touch;
    /* An older centering attempt translates the popup by -50%,-50%; with
       the sheet's left/right pinning that shoved it half off-screen. */
    transform: none !important;
  }
  /* Only `.is-open` may show it on touch — the :hover rules in styles.css
     otherwise hold it visible forever once a tap has "hovered" it. */
  .vip-menu-popup:not(.is-open) {
    opacity: 0 !important;
    visibility: hidden !important;
    pointer-events: none !important;
  }
}

/* ══ 📱 System modals (VIP-room upsell, access restricted, warn) ═══════════
   These are JS-built with inline styles: a fixed inset-0 overlay centring a
   panel with NO height cap. On phones the bottom bar renders ABOVE the
   overlay (z 2147483002 vs 100000), so the panel's tail — the Get VIP
   note — vanished under it. The overlay keeps the bar's height free at the
   bottom, so the panel centres in the space that is actually visible and
   sizes to it dynamically; tighter padding so the tall VIP perks list fits
   without scrolling on most phones. Stylesheet !important outranks the
   inline (non-important) padding. */
@media (max-width: 980px) {
  #wpzSystemModal,
  #wpzWarnModal {
    padding: calc(10px + env(safe-area-inset-top, 0px)) 10px calc(70px + env(safe-area-inset-bottom, 0px)) !important;
  }
  #wpzSystemModal > div,
  #wpzWarnModal > div {
    max-height: 100% !important;
    overflow-y: auto !important;
    overscroll-behavior: contain !important;
    -webkit-overflow-scrolling: touch;
    padding: 20px 16px !important;
    gap: 12px !important;
  }
}

/* ══ 📱 Online-now list in the People drawer ═══════════════════════════════
   #userList keeps its desktop rule inside the drawer — a 220px max-height
   scroll box, i.e. a cramped little window in a full-height panel. Make the
   drawer a flex column and give the list all remaining height, so it sizes
   itself to ANY phone screen and scrolls internally; the heading and the
   member-only cards below keep their natural height. */
@media (max-width: 980px) {
  .chat-shell > .sidebar.users-panel {
    display: flex !important;
    flex-direction: column !important;
  }
  .chat-shell > .sidebar.users-panel .user-list,
  .chat-shell > .sidebar.users-panel #userList {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    max-height: none !important;
    overflow-y: auto !important;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
}

/* Compact type for the VIP upsell on phones — with the desktop sizes the
   card always needed an internal scroll; a notch smaller and the whole
   pitch fits a normal phone screen in one glance. */
@media (max-width: 980px) {
  #wpzSystemModal .wpz-vip-star { font-size: 34px !important; }
  #wpzSystemModal .wpz-sysmodal-title { font-size: 19px !important; }
  #wpzSystemModal .wpz-sysmodal-body { font-size: 13px !important; line-height: 1.45 !important; }
  #wpzSystemModal .wpz-vip-perks { font-size: 12.5px !important; line-height: 1.5 !important; padding: 10px 14px !important; }
  #wpzSystemModal .wpz-vip-reassure { font-size: 10.5px !important; }
}

/* The VIP aura ring (::before, inset:-3px in styles.css) pokes past the
   panel's padding box; inside the card's overflow-y:auto that reads as 3px
   of scrollable content, and iOS renders ANY scrollable box draggable with
   a rubber-band — the card felt loose/"pushable" and the ring's edge
   scrolled into view as a stray gold line. Flush on phones: no overflow,
   no scroll, no drag (the card still scrolls if content truly exceeds a
   small screen). */
@media (max-width: 980px) {
  #wpzSystemModal .wpz-vip-aura::before { inset: 0 !important; }
}

/* ══ 📱 Keyboard + signup modal cleanliness ════════════════════════════════
   Two things the Discord comparison made obvious:
   1. While the keyboard is up, the bottom tab bar is dead weight — and with
      the shell pinned to the shrunken visual viewport it floated mid-screen.
      `wpz-kb-open` is set by wpzViewportLock (engagement.js) whenever the
      visual viewport is keyboard-short; the bar steps aside and its rows go
      to the chat log, like every native chat app.
   2. The signup/login card capped itself at 88vh OF THE LAYOUT VIEWPORT —
      meaningless once the keyboard takes half the screen — and the bottom
      bar (z 2147483002) sliced straight through the card (z 5000).
      --wpz-vvh is the LIVE visible height, also published by the lock. */
@media (max-width: 980px) {
  body.wpz-kb-open .wpz-mobile-bar,
  body.auth-modal-open .wpz-mobile-bar,
  /* The first-run swipe hint is a fixed pill above the bottom bar; it was
     floating over the signup form and the on-screen keyboard. Hide it whenever
     the auth modal or the keyboard is up. */
  body.wpz-kb-open .wpz-swipe-hint,
  body.auth-modal-open .wpz-swipe-hint { display: none !important; }
  .auth-modal { padding: 10px !important; }
  .auth-panel {
    max-height: calc(var(--wpz-vvh, 100svh) - 20px) !important;
    overflow-y: auto !important;
    overscroll-behavior: contain !important;
  }

  /* ── is-email-focused neutralised on mobile ─────────────────────────────
     The email-focus effect blurs #appWindow (filter) and the modal
     (backdrop-filter) and disables the other fields (pointer-events:none).
     On iOS those heavy filters on a position:fixed layer, toggled across the
     keyboard showing/hiding, FREEZE all touch on the modal — and because
     dismissing the keyboard with "Done" keeps the field focused, the class
     never clears, so the freeze sticks ("focus email, hide keyboard, tap
     email again, nothing works"). It's a desktop-only flourish; kill it on
     phones and force every field interactive so the form can't get stuck. */
  body.is-email-focused #appWindow { filter: none !important; }
  body.is-email-focused .auth-modal {
    background: rgba(6, 10, 16, 0.55) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
  body.is-email-focused .topbar { filter: none !important; }
  body.is-email-focused .auth-panel-header,
  body.is-email-focused .tabs,
  body.is-email-focused .auth-group-username,
  body.is-email-focused .auth-group-password {
    filter: none !important;
    opacity: 1 !important;
    pointer-events: auto !important;
  }
}

/* Swipe-drag state: while a finger is driving a drawer, the transition must
   be OFF or the panel lags the touch by 220ms and feels like syrup. */
@media (max-width: 980px) {
  .chat-shell > .sidebar.wpz-drawer-drag,
  .wpz-drawer-scrim.wpz-drawer-drag { transition: none !important; }
}

/* ══ 📱 First-run swipe hint ═══════════════════════════════════════════════
   The rooms drawer peeks out twice while a pill names the gesture — shown
   once per device (localStorage wpzSwipeHintSeen, engagement.js). The
   keyframes must restate the CLOSED transform: while an animation plays it
   owns `transform` entirely, so omitting the base would snap the drawer to
   translateX(0) between keyframes. */
@keyframes wpzDrawerPeek {
  0%, 100% { transform: translateX(-102%); }
  45%      { transform: translateX(calc(-102% + 46px)); }
}
@media (max-width: 980px) {
  .chat-shell > .sidebar.wpz-drawer-peek {
    animation: wpzDrawerPeek 1000ms cubic-bezier(0.33, 1, 0.68, 1) 2;
  }
  .wpz-swipe-hint {
    position: fixed;
    left: 50%;
    bottom: calc(86px + env(safe-area-inset-bottom, 0px));
    transform: translate(-50%, 10px);
    z-index: 2147483003;              /* one above the bottom bar */
    padding: 9px 16px;
    border-radius: 999px;
    background: rgba(20, 24, 30, 0.92);
    border: 1px solid rgba(120, 168, 224, 0.35);
    color: #eaf2fb;
    font-size: 13px;
    font-weight: 700;
    white-space: nowrap;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
    opacity: 0;
    pointer-events: none;             /* never blocks a first real swipe */
    transition: opacity 300ms ease, transform 300ms ease;
  }
  .wpz-swipe-hint.is-in { opacity: 1; transform: translate(-50%, 0); }
}

/* ══ 📱 Inbox: full-screen sheet on phones ═════════════════════════════════
   The inbox is a draggable floating window whose left/top/width/height are
   set INLINE by openInbox() from window.innerHeight at open time. On iOS,
   Safari's dynamic chrome and the keyboard change innerHeight constantly,
   so the window rendered offset from where touches actually land — taps
   aimed at the header ✕ hit the Block pill ~55px below it (the "pressing
   the x blocks the person" report), and the window could not be closed at
   all. Same cure as the bookings sheets and checkout overlay: pin it
   edge-to-edge in the VISIBLE viewport. These are !important stylesheet
   rules, so they beat the inline geometry without touching the JS; the
   drag handlers keep writing inline left/top, which simply stop mattering
   at this width. Breakpoint matches styles.css's ≤700 single-pane switch. */
@media (max-width: 700px) {
  .wpz-inbox {
    left: 0 !important;
    top: 0 !important;
    right: auto !important;
    bottom: auto !important;
    width: 100vw !important;
    max-width: 100vw !important;
    min-width: 0 !important;
    /* Visual-viewport height, not vh/svh: shrinks with the keyboard so the
       composer stays reachable (see the --wpz-vvh notes further up). */
    height: var(--wpz-vvh, 100svh) !important;
    max-height: none !important;
    border-radius: 0 !important;
    resize: none !important;
    grid-template-rows: auto 1fr !important;
  }
  /* The head is the screen's top edge now — clear the notch, drop the
     drag affordance. */
  .wpz-inbox-top {
    padding-top: calc(env(safe-area-inset-top, 0px) + 10px) !important;
    cursor: default !important;
  }
  /* Finger-sized ↻ / ✕ / ← (32px desktop buttons are mouse targets). */
  .wpz-inbox-icon-btn {
    width: 40px !important;
    height: 40px !important;
  }
  /* The composer is the screen's bottom edge — clear the home indicator. */
  .wpz-inbox-compose {
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 8px) !important;
  }
}

/* ══ 📱 Inbox thread header: full name + scrollable action pills ═══════════
   Device report: the username rendered as "mi..." and the Block pill was
   sliced off the right edge. Desktop crams ← + name + up to 5 pills (Nudge /
   Call / Video / Summon / Block) onto ONE non-wrapping row — the name is
   flex:1 with an ellipsis, so on a phone it collapses to three letters while
   the pills still overflow. On mobile, let the head WRAP: row 1 = back button
   + the FULL username, row 2 = the pills as a horizontally-scrollable strip
   so every one stays reachable and nothing is clipped. */
@media (max-width: 700px) {
  .wpz-inbox-conv-head {
    flex-wrap: wrap !important;
    row-gap: 8px !important;
    padding: 10px 12px !important;
  }
  .wpz-inbox-conv-name {
    flex: 1 1 auto !important;
    font-size: 16px !important;
  }
  .wpz-inbox-conv-actions {
    order: 3 !important;
    flex: 1 0 100% !important;
    width: 100% !important;
    margin-left: 0 !important;
    overflow-x: auto !important;
    -webkit-overflow-scrolling: touch !important;
    scrollbar-width: none !important;
    padding-bottom: 2px !important;
  }
  .wpz-inbox-conv-actions::-webkit-scrollbar { display: none !important; }
  .wpz-inbox-call-pill {
    flex: 0 0 auto !important;
    padding: 8px 14px !important;
    animation: none !important;
  }
}

/* ══ 📱 Kill double-tap "smart zoom" on app chrome ═════════════════════════
   Device report: focusing/typing in the inbox composer "completely zooms
   in". The composer input is 16px and the viewport meta carries
   maximum-scale=1, so this is NOT the classic focus auto-zoom — it is
   Safari's double-tap smart zoom, which iOS treats as USER zoom and
   allows regardless of the meta (accessibility decision, iOS 10+). Rapid
   taps on the emoji/GIF/mic/Send cluster register as a double-tap and
   Safari zooms the tapped block to fill the screen — exactly the
   screenshot. `touch-action: manipulation` disables only that gesture;
   scrolling, JS touch handlers (drawer swipes, game) and pinch keep
   working. Not `*`-wide: the six nav sheets + support popup already carry
   a stricter `pan-y` that a later blanket would accidentally loosen. */
@media (max-width: 980px) {
  .window, .window *,
  .wpz-inbox, .wpz-inbox *,
  .wpz-mobile-bar, .wpz-mobile-bar * {
    touch-action: manipulation;
  }
}

/* ══ 📱 Roster rows: name back on the left ═════════════════════════════════
   Two desktop-era !important layers collide on phones: a ≤920px rule in
   styles.css stacks .user-copy into a COLUMN (name above the role pill,
   both flex-start), but the later "strong username visibility" blocks force
   `align-items: center !important` and give .nick-role `margin-left: auto
   !important` — both written for the desktop ROW layout. Applied to the
   column they center the username and shove the OWNER/VIP pill to the
   right edge (the "usernames positioned weird" report). Re-assert the
   stacked-left intent with matching weight; the dot keeps its own grid
   column. */
@media (max-width: 980px) {
  .user-item .user-copy {
    flex-direction: row !important;
    flex-wrap: wrap !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 2px 8px !important;
  }
  .user-item .nick-role {
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
}

/* ══ 📱 Friend list (#socialWindow): full-screen sheet ═════════════════════
   Same floating-window disease as the inbox: desktop-anchored fixed window
   (right/bottom 20px, resizable, geometry persisted inline) with a hard
   min-width:380px and inner scrollboxes — on a phone the window ran under
   the bottom tab bar, the friends list clipped rows mid-avatar and the
   detail card's Remove-friend button was half off-screen. Pin it
   edge-to-edge and let the CONTENT be one scrolling column: header, tabs,
   search and add-row stay put; everything below scrolls together (no more
   scrollbox-inside-scrollbox on a 6-inch screen). */
@media (max-width: 700px) {
  /* .blocked-mode listed explicitly: the desktop compact-blocked block in
     styles.css targets .social-window.blocked-mode at (0,2,0), which would
     out-rank a bare .social-window here and shrink the phone sheet back
     into a corner box. Same specificity + mobile.css loads last = sheet
     wins on phones, desktop compact look untouched. */
  .social-window,
  .social-window.blocked-mode {
    left: 0 !important;
    top: 0 !important;
    right: auto !important;
    bottom: auto !important;
    width: 100vw !important;
    max-width: 100vw !important;
    min-width: 0 !important;
    height: var(--wpz-vvh, 100svh) !important;
    max-height: none !important;
    min-height: 0 !important;
    border-radius: 0 !important;
    resize: none !important;
    display: flex !important;
    flex-direction: column !important;
    gap: 8px !important;
    padding: calc(env(safe-area-inset-top, 0px) + 12px) 12px
             calc(env(safe-area-inset-bottom, 0px) + 12px) !important;
  }
  .social-window > * { flex: 0 0 auto; }
  .social-window .social-shell {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    display: flex !important;
    flex-direction: column !important;
    gap: 10px !important;
    overflow-y: auto !important;
    overscroll-behavior: contain;
    touch-action: pan-y;
  }
  /* The shell is the ONLY scroller — the list gives up its own. */
  .social-window .social-list {
    max-height: none !important;
    min-height: 0 !important;
    overflow: visible !important;
  }
  .social-window .social-list-panel,
  .social-window .social-detail {
    min-height: auto !important;
    flex: 0 0 auto !important;
  }
  /* Finger-sized ✕. Two IDs: styles.css sizes it as
     `#socialWindow #closeSocialButton { width:30px !important }` — the same
     double-ID trap as #socialSearch — so a class selector can never win. */
  #socialWindow #closeSocialButton {
    width: 40px !important;
    height: 40px !important;
  }

  /* ✕ FIX (2026-08-12): toggleSocialWindow(false) adds `.hidden`, but the
     `display:flex !important` on `.social-window` above has the SAME specificity
     as `.hidden{display:none!important}` (styles.css) and mobile.css loads
     later, so flex won and the window never closed. An ID+class selector
     (1,1,0) beats both, so the ✕ actually hides the window now. */
  #socialWindow.hidden { display: none !important; }

  /* "Blocked users" does NOT need the full-screen sheet — it's a short list +
     add-row. Override the sheet pinning above (this selector is (0,2,0), the
     bare `.social-window` sheet rule is (0,1,0), and it also ties + beats the
     `.social-window.blocked-mode` sheet selector by source order) into a
     compact, top-anchored popup. Detail panel is already hidden in blocked
     mode (styles.css). — Pontuz: "doesn't need to spawn in a full window". */
  .social-window.blocked-mode {
    left: 50% !important;
    top: calc(env(safe-area-inset-top, 0px) + 54px) !important;
    right: auto !important;
    bottom: auto !important;
    transform: translateX(-50%) !important;
    width: min(92vw, 400px) !important;
    max-width: 92vw !important;
    height: auto !important;
    max-height: min(72vh, calc(var(--wpz-vvh, 100svh) - 96px)) !important;
    min-height: 0 !important;
    border-radius: 16px !important;
    padding: 14px !important; /* the sheet's safe-area padding is wrong for a floating popup */
  }
}

/* ══ 📱 Profile modal: dynamic height + tamed VIP gold ═════════════════════
   Two devices-only faults (report: "has to be dynamic, the gold bar is out
   of control"):
   1. #wpzProfileModalCard is positioned by INLINE JS styles with a
      `max-height: calc(100vh - 32px)` — layout-viewport math, so on iOS the
      card runs under Safari's chrome and its bottom is unreachable. The
      inline left/top/width/max-height carry no !important, so these rules
      win; pin it to the VISIBLE viewport instead.
   2. The VIP aura ring (::before) and diagonal shine (::after) are
      absolutely-positioned pseudos inside the card — and the CARD IS THE
      SCROLLER, so they size to one visible box at scroll offset 0.
      Scrolling drags the ring's bottom edge through the middle of the
      content (the runaway gold bar) and the shine smears across the first
      screenful only. Kill the pseudos on phones; the gold outline + the
      breathing glow (a box-shadow on the border box, immune to scroll)
      keep the VIP look. Outline, not border: the inline border carries
      !important, which no stylesheet rule can beat. */
@media (max-width: 980px) {
  .wpz-profile-modal .wpz-modal-card {
    left: 10px !important;
    right: 10px !important;
    top: calc(env(safe-area-inset-top, 0px) + 10px) !important;
    bottom: auto !important;
    width: auto !important;
    max-height: calc(var(--wpz-vvh, 100svh)
                     - env(safe-area-inset-top, 0px) - 20px) !important;
    overscroll-behavior: contain;
    touch-action: pan-y;
  }
  .wpz-profile-modal .wpz-modal-card.wpz-vip-aura::before,
  .wpz-profile-modal .wpz-modal-card.wpz-vip-aura::after {
    display: none !important;
  }
  .wpz-profile-modal .wpz-modal-card.wpz-vip-aura {
    outline: 2px solid rgba(255, 205, 90, 0.6);
    outline-offset: -2px;
  }
}

/* ══ 📱 VIP room header: icon-only call buttons ════════════════════════════
   #vip's header carries three labeled pills (Create channel / Voice chat /
   Video chat, client.js injects #vipHeaderCallBtns) — ~380px of buttons on
   a 390px screen, so Video chat ran off the right edge. On phones the
   labels go and the icons stand alone (count badge on Channels stays);
   the header may wrap to a second row rather than ever clipping — NOT an
   overflow-x scrollbox, which would clip the absolutely-positioned
   #vipChannelsMenu dropdown. */
@media (max-width: 700px) {
  .room-header { flex-wrap: wrap !important; }
  .vip-header-call-btns {
    display: flex !important;
    gap: 6px !important;
    flex: 0 0 auto !important;
  }
  .vip-header-call-btns .vip-call-join-btn > span:not(.vip-channels-count) {
    display: none !important;
  }
  .vip-header-call-btns .vip-call-join-btn {
    padding: 8px !important;
    min-width: 34px !important;
    min-height: 34px !important;
    justify-content: center !important;
    border-radius: 999px !important;
  }
  .vip-header-call-btns .vip-call-join-btn svg {
    width: 16px !important;
    height: 16px !important;
  }
}

/* ══ 📱 Friend list, blocked tab: collapse the phantom heights ═════════════
   Desktop rules pin `.social-list-panel/.social-detail/.social-list` to
   `height:100% !important` — correct in the desktop grid, but inside the
   phone sheet's content-sized column "100%" resolves against the full
   shell, so the empty Blocked panel ballooned to ~680px with "No blocked
   users." and the block form flung to the bottom (plus an invisible
   680px detail pane below the fold). Content-sized heights on phones;
   the form's inline `margin-top:auto` (bottom-pinning, same cause) is
   plain inline style, so !important here wins. */
@media (max-width: 700px) {
  .social-window .social-list-panel,
  .social-window .social-detail,
  .social-window .social-list {
    height: auto !important;
  }
  .social-window #wpzBlockAddForm {
    margin-top: 8px !important;
  }
}

/* ══ 📱 Roster: friend star must never slide under the role pill ═══════════
   .nick-name-wrap (name + ✓ + ★ + POP) carries `min-width:0` from its base
   rule and `overflow:visible !important` from a later one — as a flex item
   in the badge-inline row it could SHRINK below its content, and the
   overflowing ★ rendered underneath the SOP/OWNER pill sitting next to it.
   No shrinking: the wrap keeps its full content, and the pill wraps to the
   next line when the row is tight (flex-wrap is already on). */
@media (max-width: 980px) {
  .user-item .nick-name-wrap {
    flex: 0 0 auto !important;
    max-width: 100% !important;
  }
}

/* ══ 📱 Deleting messages & files on touch ═════════════════════════════════
   Two gaps (report: "make it possible to remove files and messages on
   mobile"):
   1. Inbox PM messages — incl. image/file attachments — carry a ✕ delete
      button that is `opacity:0` until `.social-pm-msg:hover`. Touch has no
      hover, so the button effectively did not exist on phones. Always
      visible there, finger-sized, tucked at the bubble corner. The builder
      only attaches it when authorized (own messages / staff), so showing
      it unconditionally leaks nothing.
   2. The chat log's Edit/Delete chips render at 9px/18px-tall — visible
      but nearly untappable. Finger-sized on phones. */
@media (max-width: 700px) {
  /* Tap-to-reveal (device feedback: a permanent red ✕ on every bubble
     read as clutter). Size/position are constant so revealing never
     shifts layout; client.js toggles .wpz-actions-open on bubble tap,
     auto-hiding after 3s or on the next tap elsewhere. */
  .social-pm-msg .social-pm-delete-btn {
    width: 26px !important;
    height: 26px !important;
    top: -10px !important;
    right: -6px !important;
    font-size: 15px !important;
  }
  /* iOS leaves :hover STUCK on every tapped bubble, so the desktop
     hover-reveal rule kept ✕s visible on anything the user had touched —
     this was the actual "shows the x all the time" mechanism. On phones
     hover means nothing: zero it, and let ONLY the tap-class reveal
     (same specificity, this rule later → wins while open). */
  .social-pm-msg:hover .social-pm-delete-btn {
    opacity: 0 !important;
    transform: scale(0.85) !important;
  }
  .social-pm-msg.wpz-actions-open .social-pm-delete-btn {
    opacity: 1 !important;
    transform: none !important;
  }
  /* ↩ Reply rides the exact same tap-reveal as the ✕ (left corner). */
  .social-pm-msg .social-pm-reply-btn {
    width: 26px !important;
    height: 26px !important;
    top: -10px !important;
    left: -6px !important;
    font-size: 14px !important;
  }
  .social-pm-msg:hover .social-pm-reply-btn {
    opacity: 0 !important;
    transform: scale(0.85) !important;
  }
  .social-pm-msg.wpz-actions-open .social-pm-reply-btn {
    opacity: 1 !important;
    transform: none !important;
  }
  /* Belt for the composer zoom report: ID-weight 16px, immune to any
     future specificity fight (the global blanket already covers this,
     but it has been out-specified before — see #socialSearch). */
  #wpzInbox input,
  #wpzInbox textarea {
    font-size: 16px !important;
  }
  .message-edit-btn,
  .message-delete-btn,
  .pm-edit-btn,
  .pm-delete-btn {
    font-size: 11px !important;
    padding: 4px 10px !important;
    min-height: 24px !important;
    border-radius: 999px !important;
  }
}

/* ══ 📱 Friend list: search stays reachable while the list scrolls ═════════
   The search field moved INSIDE .social-list-panel (desktop request);
   in the phone sheet .social-shell is the single scroller, so the field
   would scroll away with the list. Sticky pins it to the top of the
   scroller while the panel is in view — with an opaque ground so rows
   slide UNDER it, not through it. */
@media (max-width: 700px) {
  /* Sticky containment: the panel's own `overflow:hidden` would make IT
     the sticky scrollport (which never scrolls) — the field just scrolled
     away. The shell is the scroller; the panel must not clip. */
  .social-window .social-list-panel {
    overflow: visible !important;
  }
  .social-window .social-list-panel .social-search-wrap {
    position: sticky !important;
    top: 0;
    z-index: 3;
    margin: 0 -4px !important;
    padding: 4px 4px 8px !important;
    background: rgba(240, 247, 255, 0.97);
  }
  html[data-theme="dark"] .social-window .social-list-panel .social-search-wrap {
    background: var(--d-flat-2, #232326);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE POLISH PASS — 2026-08-12 (Discord-feel + touch bug sweep)
   Appended after a 7-dimension audit. Every rule is media-gated → desktop is
   untouched. Grouped by the issue it fixes. mobile.css loads AFTER styles.css
   and dark.css, so equal-specificity rules here win by source order.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Kill the grey iOS tap-highlight box app-wide; :active states already give
   press feedback, so nothing is lost and taps feel native/Discord-like. */
html { -webkit-tap-highlight-color: transparent; }

/* REVERTED 2026-08-12 (Pontuz: "emojis and reply way too big"): the block that
   forced the message action bar visible + enlarged on touch is removed. The
   react/reply bar returns to its original desktop-hover behavior. Reply on
   mobile stays reachable via long-press menu + tap-on-background. */

@media (max-width: 560px) {
  /* Topbar logo no longer clipped off the left edge; left-align + never let the
     logo container collapse under its own logo and spill over the action icons. */
  body.is-ultra-compact-width .topbar,
  body.is-compact-width .topbar,
  .topbar { justify-content: flex-start !important; }
  .topbar .brand-lockup { flex: 0 0 auto !important; }

  /* Toasts floated ABOVE the bottom nav bar (they used to cover the tabs for
     ~2.2s) and span the width instead of running off the left edge on 320px. */
  .wpz-toast-stack {
    left: 10px !important;
    right: 10px !important;
    bottom: calc(64px + env(safe-area-inset-bottom, 0px)) !important;
  }
  .wpz-toast { max-width: none !important; width: auto !important; min-width: 0 !important; }

  /* New-PM notification card was z-index 100000 — BEHIND the mobile bottom bar
     (2147483002) and the composer, so it only peeked out as a sliver. Lift it
     ABOVE the bar + composer and stack it over everything so an incoming DM is
     actually visible/tappable. — 2026-08-12 */
  .wpz-pm-notify-stack {
    z-index: 2147483040 !important;
    left: 10px !important;
    right: 10px !important;
    bottom: calc(118px + env(safe-area-inset-bottom, 0px)) !important;
  }
  .wpz-pm-notify { max-width: none !important; width: auto !important; }
}

@media (max-width: 980px) {
  /* Emoji / GIF / reaction pickers were drawing UNDER the bottom bar (z-index 90
     vs 2.1B) with their search box + bottom rows untappable. Lift them clear of
     the bar (2147483002) and drawers. */
  .emoji-popup, .gif-popup { z-index: 2147483011 !important; }
  .emoji-popup-backdrop, .gif-popup-backdrop { z-index: 2147483005 !important; }

  /* Side drawers: contain overscroll so dragging past the end doesn't
     rubber-band / scroll-chain the page behind (feels like a real sheet). */
  .chat-shell > .sidebar { overscroll-behavior: contain; touch-action: pan-y; }

  /* Dark mode: the drawer scrim + panel shadow were swept to opaque charcoal by
     dark.css [class*="drawer"]/[class*="panel"], erasing the dim-through and the
     seam. Restore a translucent dim + even elevation glow, and un-dim the
     first-run swipe hint (caught by the [class*="hint"] muted-text sweep). */
  html[data-theme="dark"] .wpz-drawer-scrim {
    background: rgba(0, 0, 0, 0.55) !important;
    box-shadow: none !important;
  }
  html[data-theme="dark"] .chat-shell > .sidebar.channels-panel,
  html[data-theme="dark"] .chat-shell > .sidebar.users-panel {
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.6) !important;
  }
  html[data-theme="dark"] .wpz-swipe-hint { color: #eaf2fb !important; }
}

@media (max-width: 980px) and (max-height: 460px) {
  /* Landscape on notched iPhones: keep edge content out from under the notch
     (only top/bottom insets were handled before; L/R appeared 0 times). */
  .topbar {
    padding-left: calc(8px + env(safe-area-inset-left)) !important;
    padding-right: calc(8px + env(safe-area-inset-right)) !important;
  }
  .message-log {
    padding-left: calc(6px + env(safe-area-inset-left)) !important;
    padding-right: calc(6px + env(safe-area-inset-right)) !important;
  }
  .wpz-mobile-bar {
    padding-left: env(safe-area-inset-left) !important;
    padding-right: env(safe-area-inset-right) !important;
  }
}

@media (max-width: 980px) {
  /* Discord-style message grouping (MOBILE ONLY — desktop render untouched).
     A run of consecutive messages from the same author within 5 min collapses
     to a tight stack: the repeated avatar+name+time header and the duplicate
     "written by …" byline text are hidden, but the reaction pills (which live
     inside the byline) and the Edit/Delete buttons (which live inside the meta)
     stay reachable — only the identity children are hidden, never the whole
     container. — mobile polish pass 2026-08-12 */
  .message-row.is-grouped { margin-top: 2px !important; }
  .message-row.is-grouped .message-meta > .message-avatar,
  .message-row.is-grouped .message-meta > .message-user,
  .message-row.is-grouped .message-meta > .message-flair,
  .message-row.is-grouped .message-meta > .message-vip-star,
  .message-row.is-grouped .message-meta > .message-time { display: none !important; }
  .message-row.is-grouped .message-byline-meta { display: none !important; }
  .message-row.is-grouped .message-meta { min-height: 0 !important; margin: 0 !important; gap: 6px !important; }
  /* When only reaction pills remain in the byline, drop its top gap so the
     grouped line hugs the previous one. */
  .message-row.is-grouped .message-byline:not(:has(.message-reaction-pill)) { display: none !important; }
  .message-row.is-grouped .message-byline { margin-top: 0 !important; min-height: 0 !important; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE BUG-SWEEP FIXES — 2026-08-12 (all media-gated → desktop untouched)
   From a 7-surface code sweep. Class-B first-tap fixes are in client.js
   (wpzGlobalTapFix SEL) + the gated global button:hover in styles.css; this
   block covers Class A (behind-bar z-index), C (keyboard), D (hover-only),
   E (tap targets). The mobile bottom bar is z-index 2147483002.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 980px) {
  /* Class A — reward/shop/profile overlays rendered BEHIND the bottom bar.
     Lift them above the bar (2147483002) and drawers (2147483000). */
  .wpz-shop-overlay,
  .wpz-spin-overlay,
  .wpz-scratch-overlay { z-index: 2147483040 !important; }
  .wpz-profile-popover { z-index: 2147483041 !important; }

  /* Class C — the XP coin-shop card has gift/title/beer inputs; pin the overlay
     to the visible viewport so the card centres ABOVE the keyboard, not behind
     it. (inset:0 sets bottom:0, so we clear it and set an explicit height.) */
  .wpz-shop-overlay { height: var(--wpz-vvh, 100svh) !important; bottom: auto !important; }
  .wpz-shop-card { max-height: calc(var(--wpz-vvh, 100svh) - 20px) !important; overflow-y: auto !important; }

  /* Class D — VIP voice-lounge staff moderation controls were hover-only
     (display:none until :hover) → invisible/unusable on touch. Show them. */
  .vip-call-mod-actions,
  .vip-call-stage-actions { display: inline-flex !important; }

  /* Class E — sub-44px tap targets. */
  .wpz-game-close,
  .wpz-clip-close { width: 40px !important; height: 40px !important; }
  .wpz-clip-viewer-close { width: 44px !important; height: 44px !important; }
  .wpz-clip-vote,
  .wpz-clip-report,
  .wpz-clip-remove { min-height: 40px !important; }
  .op-window-btn { width: 40px !important; height: 40px !important; font-size: 18px !important; }
}

@media (max-width: 560px) {
  /* Class A+C — the quick-donate window is the PRIMARY phone donate path; it
     rendered behind the bar and its Continue-to-checkout button hid under the
     keyboard. Lift it above everything and make it a bottom sheet pinned to the
     visible height so the CTA clears both the bar and the keyboard. */
  .wpz-quickdonate-overlay { z-index: 2147483042 !important; align-items: flex-end !important; padding: 0 !important; }
  .wpz-quickdonate-card {
    width: 100vw !important; max-width: 100vw !important;
    border-radius: 16px 16px 0 0 !important;
    max-height: var(--wpz-vvh, 100svh) !important; overflow-y: auto !important;
  }

  /* Class C — custom-amount donate form: client.js now takes the centered path
     on phones (no innerHeight anchoring); top-align it and cap the height so the
     auto-focused amount field + Support button stay above the keyboard. */
  #wpzDonateModal.is-open { align-items: flex-start !important; }
  #wpzDonateModal .wpz-confirm-card {
    margin-top: calc(env(safe-area-inset-top, 0px) + 12px) !important;
    max-height: calc(var(--wpz-vvh, 100svh) - 24px) !important;
    overflow-y: auto !important;
  }

  /* Class C — bookings-admin drawer form (many inputs): pin its card to the
     visible height so the Save/Delete action bar stays above the keyboard. */
  .bk-drawer-card { height: var(--wpz-vvh, 100svh) !important; }

  /* Class E — friend-request Accept/Decline + staff donate-message remove ✕. */
  .friend-accept-btn,
  .friend-decline-btn { min-height: 40px !important; padding: 8px 14px !important; }
  .monthly-support-msg-remove { min-width: 32px !important; min-height: 32px !important; }
}

@media (max-width: 980px) {
  /* The animated gold conic-ring border on the checkout card (wpzVipAuraSpin +
     wpzVipAuraGlow, styles.css:15118) rotates a conic-gradient border-box +
     pulses a glow every frame. The card WRAPS the Stripe iframe, so the whole
     checkout repaints continuously → the "way too laggy" jank Pontuz reported.
     Kill the animation on phones and use a plain static gold border instead.
     — 2026-08-12 */
  .vip-checkout-card {
    animation: none !important;
    background: linear-gradient(180deg, #f5f8ff, #e7f0fd) !important;
    border: 1px solid rgba(150, 172, 205, 0.5) !important;
  }
}

@media (max-width: 700px) {
  /* WhatsApp-style FULL-SCREEN call UI (was a small centred box with the
     hang-up hidden behind the bottom bar): remote video fills the screen, your
     own camera is a floating PiP, name at the top, round controls floating at
     the bottom with a big red hang-up. — 2026-08-12 */
  #wpzCallOverlay {
    position: fixed !important;
    inset: 0 !important; top: 0 !important; left: 0 !important; right: 0 !important; bottom: 0 !important;
    transform: none !important;
    width: 100vw !important; height: var(--wpz-vvh, 100dvh) !important;
    max-width: none !important; max-height: none !important; min-width: 0 !important; min-height: 0 !important;
    border: none !important; border-radius: 0 !important; padding: 0 !important;
    background: #0b0f16 !important;
    z-index: 2147483046 !important;
    display: block !important; resize: none !important;
    backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
  }
  /* Name + "calling…" eyebrow + status → overlaid at the top, over the video. */
  #wpzCallOverlay .wpz-call-eyebrow,
  #wpzCallOverlay .wpz-call-name,
  #wpzCallOverlay #wpzCallStatus {
    position: absolute !important; left: 0 !important; right: 0 !important; margin: 0 !important;
    z-index: 6 !important; text-shadow: 0 1px 6px rgba(0,0,0,0.75) !important; pointer-events: none !important;
  }
  #wpzCallOverlay .wpz-call-eyebrow { top: calc(env(safe-area-inset-top,0px) + 16px) !important; }
  #wpzCallOverlay .wpz-call-name    { top: calc(env(safe-area-inset-top,0px) + 34px) !important; font-size: 22px !important; }
  #wpzCallOverlay #wpzCallStatus    { top: calc(env(safe-area-inset-top,0px) + 64px) !important; }
  /* Big avatar centred while calling; the video layer covers it once connected. */
  #wpzCallOverlay #wpzCallAvatar {
    position: absolute !important; top: 32% !important; left: 50% !important;
    transform: translate(-50%, -50%) !important; margin: 0 !important;
    width: 112px !important; height: 112px !important; font-size: 44px !important; z-index: 2 !important;
  }
  /* Video layer fills the screen (display stays JS-controlled: none → flex). */
  #wpzCallOverlay #wpzVideoContainer {
    position: absolute !important; inset: 0 !important; margin: 0 !important; gap: 0 !important; z-index: 3 !important;
  }
  #wpzCallOverlay #wpzRemoteWrap { position: absolute !important; inset: 0 !important; flex: none !important; min-height: 0 !important; }
  #wpzCallOverlay #wpzRemoteVideo { border-radius: 0 !important; }
  /* Your own camera → small floating PiP, top-right. */
  #wpzCallOverlay #wpzLocalWrap {
    position: absolute !important; flex: none !important; min-height: 0 !important;
    top: calc(env(safe-area-inset-top,0px) + 92px) !important; right: 12px !important;
    width: 104px !important; height: 150px !important; z-index: 4 !important;
    border-radius: 14px !important; overflow: hidden !important; box-shadow: 0 6px 18px rgba(0,0,0,0.55) !important;
  }
  #wpzCallOverlay #wpzLocalWrap #wpzLocalVideo { border-radius: 14px !important; }
  /* The tiny ⤢ fullscreen buttons are pointless once the remote IS fullscreen. */
  #wpzCallOverlay .wpz-call-fs-btn { display: none !important; }
  /* Controls → floating round buttons at the bottom, over the video. */
  #wpzCallOverlay #wpzCallButtonBar {
    position: absolute !important; left: 0 !important; right: 0 !important; margin: 0 !important;
    bottom: calc(env(safe-area-inset-bottom,0px) + 26px) !important; z-index: 6 !important; gap: 20px !important;
  }
  /* Icon controls (mic/camera/screen-share/hang-up) → bigger solid circles. */
  #wpzCallOverlay #wpzCallButtonBar button:has(svg) {
    width: 56px !important; height: 56px !important; border-radius: 50% !important; padding: 0 !important;
    box-shadow: 0 6px 18px rgba(0,0,0,0.4) !important;
  }
  #wpzCallOverlay #wpzCallButtonBar button:has(svg) svg { width: 22px !important; height: 22px !important; }
  /* The red hang-up is the biggest, WhatsApp-style. */
  #wpzCallOverlay #wpzCallButtonBar #wpzCallDecline:has(svg) { width: 64px !important; height: 64px !important; }
}

/* ── 📱 Checkout goes FULL-SCREEN on phones (2026-08-16) ──────────────────
   The floating centered card left a backdrop band at the bottom that CLOSED
   the checkout when a finger landed there mid-scroll (overlay click-to-
   close), and iOS's keyboard pan around a small fixed card could leave
   touches landing one row off inside the Stripe iframe (device video:
   tapping "Card" selected Alipay). Full-screen = no backdrop to mis-tap,
   the whole height for the card accordion to expand into, ONE scroll
   context, and the card pinned at the layout origin where touch and layout
   coordinates agree. Height is --wpz-vvh so the card shrinks with the
   keyboard instead of hiding fields under it. */
@media (max-width: 700px) {
  .vip-checkout-overlay {
    padding: 0 !important;
    align-items: stretch !important;
    justify-content: stretch !important;
    background: none !important;
  }
  .vip-checkout-card {
    width: 100% !important;
    max-width: none !important;
    height: var(--wpz-vvh, 100svh) !important;
    max-height: none !important;
    border-radius: 0 !important;
    border: 0 !important;
    box-shadow: none !important;
    position: relative !important;
  }
  .vip-checkout-mount {
    min-height: 0 !important;
    -webkit-overflow-scrolling: touch;
  }
  /* THE Card-tap fix (2026-08-16, reproduced headlessly): Stripe's
     touch→click synthesis assumes its iframe sits at the VIEWPORT ORIGIN.
     With our 59px header above it in flow, every tap inside the form
     "clicked" 59px higher — tapping Card hit the heading, tapping lower
     rows selected the wrong method (device video). On phones the header
     leaves the flow so the iframe starts at (0,0); only the ✕ floats over
     the form's top-right corner. Stripe's own content carries the product
     name + price, so the title costs nothing to drop. */
  .vip-checkout-head {
    position: absolute !important;
    top: 0 !important;
    right: 0 !important;
    left: auto !important;
    z-index: 5 !important;
    padding: 8px !important;
    background: none !important;
    border-bottom: none !important;
  }
  .vip-checkout-head .vip-checkout-title { display: none !important; }
  .vip-checkout-close {
    width: 34px !important;
    height: 34px !important;
    background: rgba(20, 26, 34, 0.55) !important;
    color: #ffffff !important;
    border: none !important;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35) !important;
  }
}

@media (max-width: 700px) {
  /* Minimized call bubble — must beat the fullscreen call rules above
     (same specificity; file order decides, so this block stays at the
     END of mobile.css). Sits above the bottom tab bar + composer. */
  #wpzCallOverlay.is-minimized{
    inset: auto !important; top: auto !important; left: auto !important;
    right: 12px !important;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 132px) !important;
    width: 118px !important; height: 158px !important;
    border-radius: 16px !important;
    display: block !important;
  }
}

/* ── native app shells (2026-08-17): the wrappers run FULLSCREEN, so pad
   for the notch/home-bar even when WKWebView reports env()=0 ── */
html.wpz-app-ios .topbar{
  padding-top: calc(max(env(safe-area-inset-top, 0px), 54px) + 5px) !important;
}
/* .wpz-mobile-bar is the class engagement.js actually builds; the other two
   never existed in the DOM, so this rule had never once applied. Kept as
   aliases in case a future shell renames it. Same capped reserve as above. */
html.wpz-app-ios .wpz-mobile-bar,
html.wpz-app-ios .mobile-bottom-bar,
html.wpz-app-ios .wpz-bottom-bar,
html.wpz-app-android .wpz-mobile-bar{
  padding-bottom: min(max(env(safe-area-inset-bottom, 0px), 12px), 20px) !important;
}
html.wpz-app-android .topbar{
  padding-top: calc(max(env(safe-area-inset-top, 0px), 28px) + 5px) !important;
}

/* app shells: the fullscreen drawers/sheets start at y=0 — pad them below
   the status bar like the topbar (2026-08-17) */
@media (max-width: 980px) {
  html.wpz-app-ios .chat-shell > .sidebar,
  html.wpz-app-ios .chat-shell > .sidebar.users-panel,
  html.wpz-app-ios #supportWindow,
  html.wpz-app-ios .wpz-inbox{
    padding-top: max(env(safe-area-inset-top, 0px), 54px) !important;
  }
  html.wpz-app-android .chat-shell > .sidebar,
  html.wpz-app-android .chat-shell > .sidebar.users-panel,
  html.wpz-app-android #supportWindow,
  html.wpz-app-android .wpz-inbox{
    padding-top: max(env(safe-area-inset-top, 0px), 28px) !important;
  }
}

/* ══════════ 📱 MOBILE DESIGNS (2026-08-17) ══════════
   Complete phone-shell designs, selected via html[data-m-skin] (picker in
   the Menu drawer, persisted per device). Scoped to ≤980px so desktop web
   never changes. Each design forces its own dark-first palette over either
   site theme + its own structure for the bar/panels/bubbles. */
@media (max-width: 980px){
  /* shared engine: any skin re-paints the shell from its variables */
  html[data-m-skin] body{ background:var(--m-bg) !important; }
  html[data-m-skin] .topbar,
  html[data-m-skin] .session-strip{
    background:var(--m-panel) !important;
    border-color:var(--m-edge) !important;
  }
  html[data-m-skin] .session-link{
    background:var(--m-chip) !important; color:var(--m-ink) !important;
    border:1px solid var(--m-edge) !important; border-radius:var(--m-radius) !important;
  }
  html[data-m-skin] .wpz-mobile-bar{
    background:var(--m-bar) !important; border-top:1px solid var(--m-edge) !important;
  }
  html[data-m-skin] .wpz-mobile-bar button{ color:var(--m-dim) !important; border-radius:var(--m-radius) !important; }
  html[data-m-skin] .wpz-mobile-bar button.is-on{ color:var(--m-accent) !important; background:var(--m-accent-soft) !important; }
  html[data-m-skin] .message-log{ background:var(--m-log) !important; border-color:var(--m-edge) !important; }
  html[data-m-skin] body .message-bubble{
    background:var(--m-bubble) !important; color:var(--m-ink) !important;
    border:1px solid var(--m-edge) !important; border-radius:var(--m-radius) !important;
  }
  html[data-m-skin] .message-form{ background:var(--m-panel) !important; border-color:var(--m-edge) !important; }
  html[data-m-skin] body .message-form input[type="text"]{
    background:var(--m-input) !important; color:var(--m-ink) !important;
    border:1px solid var(--m-edge) !important; border-radius:var(--m-radius) !important;
  }
  html[data-m-skin] body .message-form button[type="submit"]{
    background:var(--m-accent) !important; color:var(--m-on-accent, #fff) !important;
    border-color:var(--m-accent) !important; border-radius:var(--m-radius) !important;
  }
  html[data-m-skin] .chat-shell > .sidebar,
  html[data-m-skin] .chat-shell > .sidebar.users-panel{
    background:var(--m-panel) !important; border-color:var(--m-edge) !important;
  }
  html[data-m-skin] .room-header{ background:var(--m-panel) !important; border-color:var(--m-edge) !important; border-radius:var(--m-radius) !important; }
  html[data-m-skin] .channel-item,
  html[data-m-skin] .user-item{ border-radius:var(--m-radius) !important; }
  html[data-m-skin] .channel-item.active{ background:var(--m-accent-soft) !important; border-color:var(--m-accent) !important; }
  html[data-m-skin] .panel-heading{ color:var(--m-dim) !important; }
  html[data-m-skin] .message-time,
  html[data-m-skin] .message-byline-meta{ color:var(--m-dim) !important; }
  html[data-m-skin] .nick-name{ }

  /* ── palettes + structure ── */
  html[data-m-skin="glacier"]{ --m-bg:#071822; --m-panel:#0b2531; --m-bar:rgba(11,37,49,.92); --m-log:#08202b; --m-chip:#0e2f3e; --m-input:#0a2733; --m-bubble:#103647; --m-ink:#e8f6fb; --m-dim:#8fc0d2; --m-accent:#4fd8eb; --m-accent-soft:rgba(79,216,235,.15); --m-edge:rgba(140,220,255,.16); --m-radius:18px; }
  html[data-m-skin="glacier"] .wpz-mobile-bar{ left:10px !important; right:10px !important; bottom:8px !important; border:1px solid var(--m-edge) !important; border-radius:26px !important; backdrop-filter:blur(24px) !important; -webkit-backdrop-filter:blur(24px) !important; }

  html[data-m-skin="deepsea"]{ --m-bg:#03101c; --m-panel:#07203a; --m-bar:#062038; --m-log:#051a2e; --m-chip:#0a2c4a; --m-input:#082440; --m-bubble:#0d3050; --m-ink:#e6f2ff; --m-dim:#7fa8c9; --m-accent:#2ee6c8; --m-accent-soft:rgba(46,230,200,.15); --m-edge:rgba(100,230,255,.14); --m-radius:16px; }
  html[data-m-skin="deepsea"] .message-log,{}
  html[data-m-skin="deepsea"] .room-header{ box-shadow:0 12px 30px rgba(0,10,30,.5) !important; }

  html[data-m-skin="emerald"]{ --m-bg:#04120a; --m-panel:#071c11; --m-bar:#071c11; --m-log:#051609; --m-chip:#0a2617; --m-input:#08200f; --m-bubble:#0c2a18; --m-ink:#d8ffe9; --m-dim:#6dbb8f; --m-accent:#3ef58f; --m-accent-soft:rgba(62,245,143,.13); --m-edge:rgba(120,255,190,.16); --m-radius:3px; --m-on-accent:#04120a; }
  html[data-m-skin="emerald"] body, html[data-m-skin="emerald"] input, html[data-m-skin="emerald"] button{ font-family:ui-monospace,"SF Mono",Menlo,monospace !important; }

  html[data-m-skin="royalgold"]{ --m-bg:#0e0d0a; --m-panel:#181510; --m-bar:#14110c; --m-log:#121008; --m-chip:#201c14; --m-input:#1a1710; --m-bubble:#221e15; --m-ink:#f4ecd8; --m-dim:#a89a78; --m-accent:#f2c14e; --m-accent-soft:rgba(242,193,78,.13); --m-edge:rgba(255,220,150,.16); --m-radius:10px; --m-on-accent:#14110c; }
  html[data-m-skin="royalgold"] .room-title, html[data-m-skin="royalgold"] .panel-heading{ font-family:Didot,Georgia,serif !important; letter-spacing:.04em !important; }

  html[data-m-skin="molten"]{ --m-bg:#150a05; --m-panel:#21100a; --m-bar:#1c0d07; --m-log:#190c06; --m-chip:#2b160d; --m-input:#241209; --m-bubble:#2e1810; --m-ink:#ffe9dd; --m-dim:#c08a6c; --m-accent:#ff6b35; --m-accent-soft:rgba(255,107,53,.14); --m-edge:rgba(255,170,120,.18); --m-radius:0px; }
  html[data-m-skin="molten"] .wpz-mobile-bar{ border-top:3px solid var(--m-accent) !important; }
  html[data-m-skin="molten"] .wpz-mobile-bar button{ text-transform:uppercase !important; font-weight:800 !important; font-size:10px !important; }

  html[data-m-skin="sunset"]{ --m-bg:#170a14; --m-panel:#221020; --m-bar:rgba(34,16,32,.94); --m-log:#1c0d1a; --m-chip:#2e1629; --m-input:#271224; --m-bubble:#321a2e; --m-ink:#ffe9f2; --m-dim:#c188a5; --m-accent:#ff7a45; --m-accent-soft:rgba(255,122,69,.15); --m-edge:rgba(255,170,180,.16); --m-radius:22px; }
  html[data-m-skin="sunset"] .wpz-mobile-bar{ left:12px !important; right:12px !important; bottom:10px !important; border:1px solid var(--m-edge) !important; border-radius:999px !important; }

  html[data-m-skin="bloodrave"]{ --m-bg:#0d0406; --m-panel:#170a0e; --m-bar:#12070a; --m-log:#100608; --m-chip:#221016; --m-input:#1c0c11; --m-bubble:#261218; --m-ink:#ffe3e6; --m-dim:#b57d86; --m-accent:#ff3b4e; --m-accent-soft:rgba(255,59,78,.15); --m-edge:rgba(255,140,150,.17); --m-radius:5px; }
  html[data-m-skin="bloodrave"] .wpz-mobile-bar button.is-on{ box-shadow:0 0 14px rgba(255,59,78,.4) !important; }

  html[data-m-skin="synthwave"]{ --m-bg:#100823; --m-panel:#191036; --m-bar:#150d2e; --m-log:#130b29; --m-chip:#221543; --m-input:#1c123a; --m-bubble:#261a4a; --m-ink:#f2e9ff; --m-dim:#9d8cc9; --m-accent:#ff5fd2; --m-accent-soft:rgba(255,95,210,.15); --m-edge:rgba(255,150,240,.17); --m-radius:9px; }
  html[data-m-skin="synthwave"] body::after{ content:""; position:fixed; inset:0; z-index:2147482000; pointer-events:none; background:repeating-linear-gradient(0deg, rgba(0,0,0,.12) 0 1px, transparent 1px 3px); }
  html[data-m-skin="synthwave"] .room-title{ text-shadow:0 0 10px rgba(255,95,210,.5) !important; }

  html[data-m-skin="ultraviolet"]{ --m-bg:#0d0618; --m-panel:rgba(26,11,48,.5); --m-bar:rgba(13,6,24,.9); --m-log:rgba(26,11,48,.35); --m-chip:transparent; --m-input:rgba(26,11,48,.5); --m-bubble:transparent; --m-ink:#efe6ff; --m-dim:#a48cd4; --m-accent:#b26bff; --m-accent-soft:rgba(178,107,255,.12); --m-edge:rgba(178,107,255,.4); --m-radius:13px; }
  html[data-m-skin="ultraviolet"] body .message-bubble{ border:1px solid var(--m-edge) !important; }

  html[data-m-skin="blackout"]{ --m-bg:#050506; --m-panel:#0c0c0e; --m-bar:#0a0a0c; --m-log:#08080a; --m-chip:#141417; --m-input:#101013; --m-bubble:#151518; --m-ink:#e8e9ec; --m-dim:#7f838c; --m-accent:#d7dae0; --m-accent-soft:rgba(215,218,224,.1); --m-edge:rgba(255,255,255,.12); --m-radius:2px; --m-on-accent:#0a0a0c; }
  html[data-m-skin="blackout"] .wpz-mobile-bar button{ font-size:0 !important; gap:0 !important; }
  html[data-m-skin="blackout"] .wpz-mobile-bar button .ico{ font-size:20px !important; }
  html[data-m-skin="blackout"] body, html[data-m-skin="blackout"] input{ font-family:ui-monospace,"SF Mono",Menlo,monospace !important; }

  /* ── the picker card in the Menu drawer ── */
  .wpz-mskin-card{ margin:10px 12px; padding:10px 12px; border-radius:14px; background:rgba(127,127,160,.08); border:1px solid rgba(127,127,160,.18); }
  .wpz-mskin-head{ font-size:10px; font-weight:800; letter-spacing:.14em; text-transform:uppercase; opacity:.7; margin-bottom:8px; }
  .wpz-mskin-row{ display:flex; flex-wrap:wrap; gap:8px; }
  .wpz-mskin-row button{
    width:34px; height:34px; border-radius:50%; cursor:pointer; flex:0 0 auto;
    border:2px solid rgba(255,255,255,.25); padding:0;
  }
  .wpz-mskin-row button.is-active{ border-color:#fff; box-shadow:0 0 0 2px rgba(127,160,255,.6); }
}

/* ══════════ 📱 RADICAL LAYOUTS (2026-08-17 01:30) ══════════
   Ten designs that restructure the phone UI itself — nav position,
   message presentation, chrome, composer — all CSS over the same DOM. */
@media (max-width: 980px){

  /* 1 ─ TOPLINE: navigation lives at the TOP, chat owns the bottom */
  html[data-m-skin="topline"]{ --m-bg:#0c111d; --m-panel:#131a2b; --m-bar:#101726; --m-log:#0e1422; --m-chip:#1a2338; --m-input:#141c30; --m-bubble:#1b2540; --m-ink:#e9eefb; --m-dim:#8b96b4; --m-accent:#5b8cff; --m-accent-soft:rgba(91,140,255,.16); --m-edge:rgba(140,170,255,.15); --m-radius:12px; }
  html[data-m-skin="topline"] .wpz-mobile-bar{
    top:0 !important; bottom:auto !important;
    padding-top:max(env(safe-area-inset-top,0px), 54px) !important;
    padding-bottom:6px !important;
    border-top:none !important; border-bottom:1px solid var(--m-edge) !important;
  }
  html[data-m-skin="topline"] .topbar{ display:none !important; }
  html[data-m-skin="topline"] .session-strip{ margin-top:96px !important; }
  html[data-m-skin="topline"].wpz-app-ios .session-strip{ margin-top:104px !important; }
  html[data-m-skin="topline"] .message-form{ padding-bottom:max(env(safe-area-inset-bottom,0px),14px) !important; }

  /* 2 ─ SIDEBAR OS: vertical icon rail on the left, desktop-in-your-pocket */
  html[data-m-skin="sidebaros"]{ --m-bg:#101014; --m-panel:#17171d; --m-bar:#121218; --m-log:#141419; --m-chip:#1e1e26; --m-input:#191920; --m-bubble:#20202a; --m-ink:#ececf2; --m-dim:#8e8e9c; --m-accent:#8b7cf7; --m-accent-soft:rgba(139,124,247,.16); --m-edge:rgba(170,160,255,.14); --m-radius:10px; }
  html[data-m-skin="sidebaros"] .wpz-mobile-bar{
    top:0 !important; bottom:0 !important; left:0 !important; right:auto !important;
    width:58px !important; flex-direction:column !important;
    justify-content:center !important; gap:18px !important;
    border-top:none !important; border-right:1px solid var(--m-edge) !important;
    padding:max(env(safe-area-inset-top,0px),54px) 0 18px !important;
  }
  html[data-m-skin="sidebaros"] .wpz-mobile-bar button{ font-size:0 !important; }
  html[data-m-skin="sidebaros"] .wpz-mobile-bar button .ico{ font-size:21px !important; }
  html[data-m-skin="sidebaros"] .window,
  html[data-m-skin="sidebaros"] .chat-shell{ margin-left:58px !important; }
  html[data-m-skin="sidebaros"] .topbar{ padding-left:66px !important; }

  /* 3 ─ CARDS: every message is an elevated card in a padded feed */
  html[data-m-skin="cards"]{ --m-bg:#0f1115; --m-panel:#151821; --m-bar:#12141c; --m-log:#0f1115; --m-chip:#1c2029; --m-input:#171a23; --m-bubble:#1c2029; --m-ink:#edf0f7; --m-dim:#8d94a6; --m-accent:#3ddc97; --m-accent-soft:rgba(61,220,151,.14); --m-edge:rgba(160,180,210,.12); --m-radius:16px; }
  html[data-m-skin="cards"] .message-log{ padding:14px 10px !important; }
  html[data-m-skin="cards"] body .message-row{
    background:var(--m-bubble) !important; border:1px solid var(--m-edge) !important;
    border-radius:16px !important; padding:10px 12px !important; margin:10px 4px !important;
    box-shadow:0 8px 22px rgba(0,0,0,.35) !important;
  }
  html[data-m-skin="cards"] body .message-bubble{ background:transparent !important; border:none !important; padding:0 !important; }

  /* 4 ─ ZEN: chrome melts away — slim strip, floating corner controls */
  html[data-m-skin="zen"]{ --m-bg:#101312; --m-panel:#161a18; --m-bar:transparent; --m-log:#101312; --m-chip:#1c211e; --m-input:#171c19; --m-bubble:transparent; --m-ink:#e7ece9; --m-dim:#87938c; --m-accent:#9bd4b0; --m-accent-soft:rgba(155,212,176,.14); --m-edge:rgba(170,200,180,.12); --m-radius:14px; }
  html[data-m-skin="zen"] .topbar{ transform:scale(.82); transform-origin:top left; opacity:.75; }
  html[data-m-skin="zen"] .session-strip{ display:none !important; }
  html[data-m-skin="zen"] .room-header{ background:transparent !important; border:none !important; }
  html[data-m-skin="zen"] body .message-bubble{ border:none !important; padding:2px 0 !important; }
  html[data-m-skin="zen"] .wpz-mobile-bar{
    left:auto !important; right:10px !important; bottom:80px !important; top:auto !important;
    width:52px !important; flex-direction:column !important; gap:10px !important;
    background:transparent !important; border:none !important; padding:0 !important;
  }
  html[data-m-skin="zen"] .wpz-mobile-bar button{
    font-size:0 !important; width:48px; height:48px;
    background:var(--m-panel) !important; border:1px solid var(--m-edge) !important;
    border-radius:50% !important; box-shadow:0 8px 20px rgba(0,0,0,.4);
  }
  html[data-m-skin="zen"] .wpz-mobile-bar button .ico{ font-size:19px !important; }
  html[data-m-skin="zen"] .message-form{ border:none !important; background:transparent !important; }

  /* 5 ─ FEED: centered social-feed column, transparent chrome */
  html[data-m-skin="feed"]{ --m-bg:#12100e; --m-panel:#1a1714; --m-bar:rgba(26,23,20,.95); --m-log:#12100e; --m-chip:#221e1a; --m-input:#1c1815; --m-bubble:#201c18; --m-ink:#f2ede6; --m-dim:#a3988a; --m-accent:#e8863a; --m-accent-soft:rgba(232,134,58,.15); --m-edge:rgba(220,190,150,.13); --m-radius:14px; }
  html[data-m-skin="feed"] .message-log{ padding:10px clamp(8px, 6vw, 40px) !important; }
  html[data-m-skin="feed"] body .message-row{
    border-bottom:1px solid var(--m-edge) !important; border-radius:0 !important;
    padding:12px 2px !important; margin:0 !important;
  }
  html[data-m-skin="feed"] body .message-bubble{ background:transparent !important; border:none !important; font-size:15px !important; line-height:1.5 !important; }
  html[data-m-skin="feed"] .message-avatar{ border-radius:12px !important; }

  /* 6 ─ BUBBLE POP: messenger-fat asymmetric bubbles + floating pill dock */
  html[data-m-skin="bubblepop"]{ --m-bg:#0e1220; --m-panel:#151a2e; --m-bar:rgba(21,26,46,.95); --m-log:#101527; --m-chip:#1d2340; --m-input:#171d36; --m-bubble:#232a4d; --m-ink:#eef1ff; --m-dim:#8e97bd; --m-accent:#4f7bff; --m-accent-soft:rgba(79,123,255,.17); --m-edge:rgba(140,165,255,.14); --m-radius:20px; }
  html[data-m-skin="bubblepop"] body .message-bubble{ border-radius:20px 20px 20px 6px !important; padding:9px 13px !important; border:none !important; }
  html[data-m-skin="bubblepop"] body .message-row.is-own .message-bubble{
    background:linear-gradient(135deg,#4f7bff,#7a5cff) !important; color:#fff !important;
    border-radius:20px 20px 6px 20px !important;
  }
  html[data-m-skin="bubblepop"] .wpz-mobile-bar{
    left:24px !important; right:24px !important; bottom:12px !important;
    border:1px solid var(--m-edge) !important; border-radius:999px !important;
    box-shadow:0 14px 34px rgba(0,0,0,.5) !important;
  }
  html[data-m-skin="bubblepop"] .wpz-mobile-bar button{ font-size:0 !important; }
  html[data-m-skin="bubblepop"] .wpz-mobile-bar button .ico{ font-size:21px !important; }

  /* 7 ─ CRT: full phosphor terminal, prompt prefixes, no chrome shine */
  html[data-m-skin="crt"]{ --m-bg:#020703; --m-panel:#04120a; --m-bar:#04120a; --m-log:#020703; --m-chip:#07200f; --m-input:#051609; --m-bubble:transparent; --m-ink:#39ff88; --m-dim:#1f9c53; --m-accent:#39ff88; --m-accent-soft:rgba(57,255,136,.1); --m-edge:rgba(57,255,136,.25); --m-radius:0px; --m-on-accent:#020703; }
  html[data-m-skin="crt"] body, html[data-m-skin="crt"] input, html[data-m-skin="crt"] button{ font-family:ui-monospace,"SF Mono",Menlo,monospace !important; }
  html[data-m-skin="crt"] body *{ color:var(--m-ink) !important; text-shadow:0 0 6px rgba(57,255,136,.35); }
  html[data-m-skin="crt"] body::after{ content:""; position:fixed; inset:0; z-index:2147482000; pointer-events:none; background:repeating-linear-gradient(0deg, rgba(0,0,0,.22) 0 1px, transparent 1px 3px); }
  html[data-m-skin="crt"] body .message-bubble{ border:none !important; padding:0 !important; }
  html[data-m-skin="crt"] body .message-row{ margin:2px 0 !important; padding:1px 6px !important; }
  html[data-m-skin="crt"] body .message-row::before{ content:">"; margin-right:6px; opacity:.7; }
  html[data-m-skin="crt"] .message-avatar{ display:none !important; }
  html[data-m-skin="crt"] body .message-row .message-stack{ padding-left:0 !important; }
  html[data-m-skin="crt"] .room-title::after{ content:"_"; animation:wpzCrtBlink 1s steps(1) infinite; }

  /* 8 ─ POSTER: editorial typography, huge headers, underline tabs */
  html[data-m-skin="poster"]{ --m-bg:#141414; --m-panel:#181818; --m-bar:#141414; --m-log:#141414; --m-chip:#1f1f1f; --m-input:#1b1b1b; --m-bubble:transparent; --m-ink:#f4f2ee; --m-dim:#8f8d88; --m-accent:#ff4747; --m-accent-soft:rgba(255,71,71,.12); --m-edge:rgba(255,255,255,.14); --m-radius:0px; }
  html[data-m-skin="poster"] .room-title{ font-size:26px !important; font-weight:900 !important; letter-spacing:-.02em !important; }
  html[data-m-skin="poster"] .room-header{ background:transparent !important; border:none !important; padding:14px 12px 4px !important; }
  html[data-m-skin="poster"] body .message-bubble{ border:none !important; font-size:16px !important; line-height:1.55 !important; }
  html[data-m-skin="poster"] body .message-row{ margin-top:16px !important; }
  html[data-m-skin="poster"] .wpz-mobile-bar{ background:var(--m-bg) !important; border-top:1px solid var(--m-edge) !important; }
  html[data-m-skin="poster"] .wpz-mobile-bar button{ border-radius:0 !important; background:transparent !important; }
  html[data-m-skin="poster"] .wpz-mobile-bar button.is-on{ background:transparent !important; box-shadow:inset 0 -3px 0 var(--m-accent) !important; color:var(--m-ink) !important; }
  html[data-m-skin="poster"] .wpz-mobile-bar button .ico{ display:none !important; }
  html[data-m-skin="poster"] .wpz-mobile-bar button{ font-size:13px !important; font-weight:800 !important; text-transform:uppercase !important; letter-spacing:.08em !important; }

  /* 9 ─ GLASSHOUSE: aurora ground + every surface floating frosted glass */
  html[data-m-skin="glasshouse"]{ --m-bg:#0b0f1e; --m-panel:rgba(30,40,70,.5); --m-bar:rgba(20,26,50,.6); --m-log:rgba(18,24,44,.45); --m-chip:rgba(40,52,90,.5); --m-input:rgba(16,22,40,.6); --m-bubble:rgba(45,58,100,.5); --m-ink:#edf2ff; --m-dim:#9aa8d0; --m-accent:#7a86f7; --m-accent-soft:rgba(122,134,247,.2); --m-edge:rgba(165,195,255,.2); --m-radius:18px; }
  html[data-m-skin="glasshouse"] body{ background:linear-gradient(160deg,#0b0f1e 10%, #182352 45%, #3b1c5e 80%) fixed !important; }
  html[data-m-skin="glasshouse"] .topbar, html[data-m-skin="glasshouse"] .session-strip,
  html[data-m-skin="glasshouse"] .wpz-mobile-bar, html[data-m-skin="glasshouse"] .message-form,
  html[data-m-skin="glasshouse"] .message-log, html[data-m-skin="glasshouse"] .room-header{
    backdrop-filter:blur(22px) saturate(170%) !important; -webkit-backdrop-filter:blur(22px) saturate(170%) !important;
  }
  html[data-m-skin="glasshouse"] .wpz-mobile-bar{
    left:10px !important; right:10px !important; bottom:8px !important;
    border:1px solid var(--m-edge) !important; border-radius:24px !important;
  }
  html[data-m-skin="glasshouse"] .message-log{ margin:8px !important; border-radius:18px !important; border:1px solid var(--m-edge) !important; }

  /* 10 ─ PIXEL: 8-bit arcade — hard offsets, game-button tabs */
  html[data-m-skin="pixel"]{ --m-bg:#101021; --m-panel:#181834; --m-bar:#181834; --m-log:#12122a; --m-chip:#212145; --m-input:#1a1a3a; --m-bubble:#212145; --m-ink:#f5f5ff; --m-dim:#9797c9; --m-accent:#ffd23e; --m-accent-soft:rgba(255,210,62,.14); --m-edge:#3a3a72; --m-radius:0px; --m-on-accent:#101021; }
  html[data-m-skin="pixel"] body, html[data-m-skin="pixel"] input, html[data-m-skin="pixel"] button{ font-family:ui-monospace,"SF Mono",Menlo,monospace !important; letter-spacing:.02em; }
  html[data-m-skin="pixel"] body .message-bubble{ border:2px solid var(--m-edge) !important; box-shadow:3px 3px 0 #000 !important; border-radius:0 !important; }
  html[data-m-skin="pixel"] .wpz-mobile-bar button{ border:2px solid var(--m-edge) !important; box-shadow:2px 2px 0 #000 !important; margin:0 3px !important; background:var(--m-chip) !important; }
  html[data-m-skin="pixel"] .wpz-mobile-bar button.is-on{ background:var(--m-accent) !important; color:var(--m-on-accent) !important; box-shadow:1px 1px 0 #000 !important; transform:translate(1px,1px); }
  html[data-m-skin="pixel"] .session-link{ border:2px solid var(--m-edge) !important; box-shadow:2px 2px 0 #000 !important; }
  html[data-m-skin="pixel"] body .message-form button[type="submit"]{ border:2px solid var(--m-edge) !important; box-shadow:2px 2px 0 #000 !important; font-weight:800 !important; }

  @keyframes wpzCrtBlink{ 50%{ opacity:0; } }
}


/* ── Mobile app keyboard (2026-08-19) ────────────────────────────────────
   Only for the Capacitor app. The site's own body.wpz-kb-open rules stay in
   charge in a browser, where visualViewport really does shrink; in a
   WKWebView it does not, so client.js's wpzAppKeyboard publishes the exact
   keyboard height from the Capacitor bridge and this shortens the shell by
   that much. .window sets its height three times (vh/svh/dvh fallbacks) at
   (0,1,0), so this needs !important — but NOT an inline style, which would
   collide with wpzViewportLock if it ever does start firing here. */
html.wpz-app-ios body.wpz-app-kb-open .window,
html.wpz-app-android body.wpz-app-kb-open .window {
  height: calc(100dvh - var(--wpz-app-kb, 0px)) !important;
  max-height: calc(100dvh - var(--wpz-app-kb, 0px)) !important;
}
/* Same call the site already makes for Safari: the bottom tab bar is dead
   weight while typing, and its rows are better spent on the chat. Without
   this the composer stops a tab-bar's height short of the keys. */
html.wpz-app-ios body.wpz-app-kb-open .wpz-mobile-bar,
html.wpz-app-android body.wpz-app-kb-open .wpz-mobile-bar,
html.wpz-app-ios body.wpz-app-kb-open .wpz-swipe-hint,
html.wpz-app-android body.wpz-app-kb-open .wpz-swipe-hint {
  display: none !important;
}


/* ── Donation sheet headroom in the native app (2026-08-19) ──────────────
   app sheet headroom: the sheet is sized bottom-up from the tab bar, and on
   a 874pt screen that put its top edge at ~20px — under the status bar and
   the Dynamic Island, where the topbar's own 54px inset says nothing may
   sit. Take the inset off its max-height so it always starts below. */
html.wpz-app-ios .monthly-support-popup{
  max-height: calc(var(--wpz-vvh, 100svh) - 84px - env(safe-area-inset-bottom, 0px) - max(env(safe-area-inset-top, 0px), 54px)) !important;
}


/* ── Embedded-checkout ✕ in the native app (2026-08-19) ──────────────────
   checkout close headroom. On phones the ✕ floats at top:0/right:0 over
   Stripe's iframe, and that placement is deliberate: the iframe has to sit
   at the viewport origin or Stripe's touch-to-click synthesis mis-registers
   every tap by the height of anything above it (see the note by
   .vip-checkout-head). Fine in a browser, where y=0 is below the URL bar —
   but the app runs fullscreen, so y=0 is UNDER the Dynamic Island and the ✕
   was behind the clock and unreachable. Nobody could leave checkout.
   Move ONLY the ✕ down by the safe-area inset; the card and the iframe stay
   exactly where they are, so the tap-offset fix is untouched. */
html.wpz-app-ios .vip-checkout-head{
  top: max(env(safe-area-inset-top, 0px), 54px) !important;
  right: 6px !important;
}
html.wpz-app-android .vip-checkout-head{
  top: max(env(safe-area-inset-top, 0px), 28px) !important;
  right: 6px !important;
}
/* Bigger than the 34px browser version: it now sits near the island, and
   this is the only way out of a payment screen. */
html.wpz-app-ios .vip-checkout-close,
html.wpz-app-android .vip-checkout-close{
  width: 40px !important;
  height: 40px !important;
  background: rgba(20, 26, 34, 0.72) !important;
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.45) !important;
}


/* ── Embedded checkout as a SHEET, native app only (2026-08-19) ──────────
   checkout sheet presentation. Until now the embedded checkout was styled
   full-bleed — 100svh, no radius, no border, title hidden — so although it
   really is Stripe's embedded iframe inside our own modal, it looked exactly
   like being thrown out to a Stripe page.

   The full-bleed styling was not arbitrary: the note by .vip-checkout-head
   records that Stripe's touch-to-click synthesis assumed the iframe sat at
   the VIEWPORT ORIGIN, and a 59px header above it shifted every tap by 59px.
   Putting the header back in flow is exactly the shape that caused it, so
   this is scoped to the NATIVE APP ONLY — mobile web keeps the proven
   full-bleed layout, and if taps mis-register in the app the blast radius is
   one platform and one revert. */
@media (max-width: 700px) {
  html.wpz-app-ios .vip-checkout-overlay,
  html.wpz-app-android .vip-checkout-overlay {
    /* Backdrop again, so the room is visible behind the sheet — the single
       biggest cue that you never left the app. */
    background: rgba(4, 8, 16, 0.62) !important;
    align-items: flex-end !important;
    justify-content: center !important;
    padding: 0 !important;
  }
  html.wpz-app-ios .vip-checkout-card,
  html.wpz-app-android .vip-checkout-card {
    height: calc(var(--wpz-vvh, 100dvh) - max(env(safe-area-inset-top, 0px), 54px) - 12px) !important;
    border-radius: 18px 18px 0 0 !important;
    overflow: hidden !important;
    box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.5) !important;
  }
  /* Header back in flow, with the title visible: this is the part that says
     "you are still inside Wasted Penguinz". */
  html.wpz-app-ios .vip-checkout-head,
  html.wpz-app-android .vip-checkout-head {
    position: static !important;
    display: flex !important;
    align-items: center !important;
    justify-content: space-between !important;
    gap: 10px !important;
    padding: 10px 12px !important;
    background: rgba(12, 16, 24, 0.96) !important;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
  }
  html.wpz-app-ios .vip-checkout-head .vip-checkout-title,
  html.wpz-app-android .vip-checkout-head .vip-checkout-title {
    display: block !important;
    font-size: 13.5px !important;
    font-weight: 800 !important;
    color: #e6ecf2 !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
  }
  html.wpz-app-ios .vip-checkout-close,
  html.wpz-app-android .vip-checkout-close {
    position: static !important;
    flex: 0 0 auto !important;
  }
}


/* ── XP Shop in the native app (2026-08-19) ──────────────────────────────
   xp shop headroom. The overlay centres its card in the viewport and the
   card is nearly full height on a phone, so its top edge lands ~10px down —
   which in a fullscreen app is behind the status bar and the Dynamic Island:
   the title collided with the clock, the XP balance sat under the island and
   the ✕ was clipped. Anchor the card to the top instead of centring it, and
   take the inset off its height so nothing is hidden. Third overlay tonight
   with this same fullscreen-origin bug — the browser's y=0 is below the URL
   bar, the app's is not. */
@media (max-width: 980px) {
  html.wpz-app-ios .wpz-shop-overlay{
    align-items: flex-start !important;
    padding-top: max(env(safe-area-inset-top, 0px), 54px) !important;
    padding-bottom: max(env(safe-area-inset-bottom, 0px), 12px) !important;
  }
  html.wpz-app-android .wpz-shop-overlay{
    align-items: flex-start !important;
    padding-top: max(env(safe-area-inset-top, 0px), 28px) !important;
    padding-bottom: max(env(safe-area-inset-bottom, 0px), 12px) !important;
  }
  html.wpz-app-ios .wpz-shop-card{
    max-height: calc(var(--wpz-vvh, 100svh) - max(env(safe-area-inset-top, 0px), 54px) - 24px) !important;
  }
  html.wpz-app-android .wpz-shop-card{
    max-height: calc(var(--wpz-vvh, 100svh) - max(env(safe-area-inset-top, 0px), 28px) - 24px) !important;
  }
  /* The header is one nowrap row: title, balance, ✕. The balance pill grows
     with the number (20,451 XP) and was pushing the ✕ off the card edge. */
  html.wpz-app-ios .wpz-shop-head,
  html.wpz-app-android .wpz-shop-head{
    flex-wrap: nowrap !important;
  }
  html.wpz-app-ios .wpz-shop-title,
  html.wpz-app-android .wpz-shop-title{
    min-width: 0 !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
  }
  html.wpz-app-ios .wpz-shop-close,
  html.wpz-app-android .wpz-shop-close{
    flex: 0 0 auto !important;
    margin-left: auto !important;
  }
}


/* ── Pinned band v2 phone (2026-08-19) ───────────────────────────────────
   The phone rules clamped the band to ONE ellipsised 20px line, which is
   why a multi-line pin looked like it had lost its line breaks even after
   the desktop fix. Let it grow to a few lines and scroll past that. */
@media (max-width: 980px) {
  .room-pinned{
    white-space: normal !important;
    text-overflow: clip !important;
    max-height: 96px !important;
    overflow-y: auto !important;
    padding: 7px 8px 7px 10px !important;
    line-height: 1.35 !important;
  }
  .room-pinned #wpzPinnedMsgText{ max-height: none !important; overflow: visible !important; }
}


/* ── Inbox unread badge escape (2026-08-20) ──────────────────────────────
   unread badge escape. The ≤560px block above turns Inbox/Friends/Blocked
   into 34px circles and clips them with `overflow: hidden !important` so a
   stray label cannot spill out of the circle. The unread badge, though, is
   positioned at top:-6px/right:-6px — deliberately OUTSIDE the circle — so
   that same clip sliced it in half and left a red crescent behind the
   envelope (device screenshots 2026-08-20).

   Only #inboxButton is exempted, and only on the phone breakpoint: it is the
   one that carries a badge. Friends/Blocked keep the clip. The label is
   already swapped for a glyph in client.js, so there is nothing left for the
   clip to protect here. Same specificity as the rule above (1,1,0) and later
   in the file, which is what wins the !important tie. */
@media (max-width: 560px) {
  .topbar #inboxButton:not(.hidden) {
    overflow: visible !important;
  }
}


/* ── Inbox + PM in the native app (2026-08-20) ───────────────────────────
   inbox app headroom. TWO paddings were stacking. The app block above pads
   .wpz-inbox itself by max(env, 54px) so the window clears the status bar,
   and the ≤700px block already pads .wpz-inbox-top by env + 10px for the
   same reason. Together they left a tall empty band ABOVE the "Inbox"
   title — the container was pushed down AND the header padded again inside
   it. Pad only the header: it is the thing that must clear the island, and
   padding it keeps the window itself edge-to-edge. */
@media (max-width: 980px) {
  html.wpz-app-ios .wpz-inbox {
    padding-top: 0 !important;
  }
  html.wpz-app-ios .wpz-inbox-top {
    padding-top: calc(max(env(safe-area-inset-top, 0px), 54px) + 6px) !important;
  }
}

/* Keyboard. The inbox and the PM window size themselves from --wpz-vvh,
   which comes from visualViewport — and a WKWebView never shrinks that (see
   the wpzAppKeyboard notes). So in the app the keyboard simply covered the
   composer: you could not see what you were typing. The app publishes the
   real keyboard height as --wpz-app-kb; subtract it here the same way the
   main chat shell does. */
html.wpz-app-ios body.wpz-app-kb-open .wpz-inbox,
html.wpz-app-android body.wpz-app-kb-open .wpz-inbox {
  height: calc(100dvh - var(--wpz-app-kb, 0px)) !important;
  max-height: calc(100dvh - var(--wpz-app-kb, 0px)) !important;
}
html.wpz-app-ios body.wpz-app-kb-open .pm-chat-dialog-modal,
html.wpz-app-android body.wpz-app-kb-open .pm-chat-dialog-modal,
html.wpz-app-ios body.wpz-app-kb-open #socialWindow,
html.wpz-app-android body.wpz-app-kb-open #socialWindow,
html.wpz-app-ios body.wpz-app-kb-open #supportWindow,
html.wpz-app-android body.wpz-app-kb-open #supportWindow {
  max-height: calc(100dvh - var(--wpz-app-kb, 0px)) !important;
}





/* ── App overlay headroom sweep (2026-08-20) ─────────────────────────────
   app overlay headroom sweep. Fourth report of the same bug, so this covers
   the remaining centred overlays rather than waiting to find each one in a
   screenshot.

   The pattern: an overlay is `position: fixed; inset: 0` with
   `align-items: center`, and its card is taller than the viewport — so the
   card overflows equally top and bottom and its header lands UNDER the
   status bar. In a browser y=0 sits below the URL bar and it looks fine; the
   app is fullscreen, so y=0 is the Dynamic Island. The Notifications title
   was rendering on top of the clock.

   Fix is the same one used for the XP shop and the donation sheet: anchor to
   the top instead of centring, reserve the inset, and cap the card's height
   so nothing runs off the bottom either. */
@media (max-width: 980px) {
  html.wpz-app-ios .wpz-notification-panel,
  html.wpz-app-ios .wpz-profile-modal {
    align-items: flex-start !important;
    padding-top: max(env(safe-area-inset-top, 0px), 54px) !important;
    padding-bottom: max(env(safe-area-inset-bottom, 0px), 12px) !important;
  }
  html.wpz-app-android .wpz-notification-panel,
  html.wpz-app-android .wpz-profile-modal {
    align-items: flex-start !important;
    padding-top: max(env(safe-area-inset-top, 0px), 28px) !important;
    padding-bottom: max(env(safe-area-inset-bottom, 0px), 12px) !important;
  }
  html.wpz-app-ios .wpz-notification-panel .wpz-modal-card,
  html.wpz-app-ios .wpz-profile-modal .wpz-modal-card {
    max-height: calc(var(--wpz-vvh, 100svh) - max(env(safe-area-inset-top, 0px), 54px) - 24px) !important;
    overflow-y: auto !important;
  }
  html.wpz-app-android .wpz-notification-panel .wpz-modal-card,
  html.wpz-app-android .wpz-profile-modal .wpz-modal-card {
    max-height: calc(var(--wpz-vvh, 100svh) - max(env(safe-area-inset-top, 0px), 28px) - 24px) !important;
    overflow-y: auto !important;
  }
}


/* ── Notifications / profile card headroom, take two (2026-08-20) ────────
   notif card headroom. The panel-padding approach did not survive contact
   with the device: an in-app probe reported `align-items` applying but
   `padding-top` computing to 0px once the panel carries .is-open, while the
   very same panel reads 62px when closed. Something zeroes the padding on
   open and it is not in any stylesheet the page can enumerate.

   So put the headroom where nothing else is competing — a margin on the CARD
   itself. A panel rule cannot zero a child's margin, whatever sets it.
   Device values from the probe: env(safe-area-inset-top) = 62px (NOT 0 as an
   older note claimed), innerHeight 874, so the card starts at 68px and the
   Dynamic Island is clear. */
@media (max-width: 980px) {
  html.wpz-app-ios .wpz-notification-panel .wpz-modal-card,
  html.wpz-app-ios .wpz-profile-modal .wpz-modal-card {
    margin-top: calc(max(env(safe-area-inset-top, 0px), 54px) + 6px) !important;
    margin-bottom: max(env(safe-area-inset-bottom, 0px), 12px) !important;
  }
  html.wpz-app-android .wpz-notification-panel .wpz-modal-card,
  html.wpz-app-android .wpz-profile-modal .wpz-modal-card {
    margin-top: calc(max(env(safe-area-inset-top, 0px), 28px) + 6px) !important;
    margin-bottom: max(env(safe-area-inset-bottom, 0px), 12px) !important;
  }
}







/* ── PM header ⋯ menu (2026-08-20) ───────────────────────────────────────
   pm more menu. Three actions stay inline as icons; the rest live behind ⋯
   and get their labels back inside the dropdown, where there is room. */
@media (max-width: 700px) {
  .wpz-pm-more-wrap { position: relative !important; flex: 0 0 auto !important; }
  .wpz-pm-more-menu {
    position: absolute !important;
    top: calc(100% + 6px) !important;
    right: 0 !important;
    display: none !important;
    flex-direction: column !important;
    gap: 4px !important;
    min-width: 168px !important;
    padding: 6px !important;
    border-radius: 14px !important;
    background: #1f1f21 !important;
    border: 1px solid rgba(255, 255, 255, 0.10) !important;
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55) !important;
    z-index: 2147483600 !important;
  }
  html[data-theme="light"] .wpz-pm-more-menu {
    background: #ffffff !important;
    border-color: rgba(79, 149, 216, 0.20) !important;
    box-shadow: 0 18px 44px rgba(86, 126, 164, 0.22) !important;
  }
  .wpz-pm-more-wrap.is-open .wpz-pm-more-menu { display: flex !important; }
  /* Inside the menu they are full-width rows WITH labels — the icon-only
     rule above is scoped to the bar, so it has to be undone here. */
  .wpz-pm-more-menu .wpz-inbox-call-pill {
    width: 100% !important;
    min-width: 0 !important;
    height: auto !important;
    padding: 9px 10px !important;
    border-radius: 10px !important;
    justify-content: flex-start !important;
    gap: 8px !important;
    font-size: 12.5px !important;
  }
  .wpz-pm-more-menu .wpz-inbox-call-pill > span:last-child { display: inline !important; }
  /* .hidden must still win inside the menu: the display:inline-flex on the
     pills is !important, and a single-class .hidden would lose to it — the
     same trap that exposed the moderation menu to members this morning.
     Two classes beat one. */
  .wpz-inbox-call-pill.hidden { display: none !important; }
}


/* ── PM conversation header, single definition (2026-08-20) ──────────────
   pm head final. Three earlier blocks in this file each redefined this
   header — a wrapping row, a sideways scroll strip, and a vertical rail —
   and every one of them set BOTH grid-template-columns and grid-column.
   Later rules only overrode the properties they named, so the header ended
   up with the column TRACKS from one block and the item PLACEMENT from
   another: the name and the actions landed in the same cell (the name ran
   underneath the icons) with a leftover empty track on the right. Those
   three blocks are deleted; this is now the only definition.

   Layout: [back] [name, takes the slack] [3 icon actions + ⋯].
   The name gets the 1fr track so it shows in full and only ellipsises when
   it genuinely cannot fit. */
@media (max-width: 700px) {
  .wpz-inbox-conv-head {
    display: grid !important;
    grid-template-columns: auto minmax(0, 1fr) auto !important;
    align-items: center !important;
    column-gap: 8px !important;
    row-gap: 0 !important;
    padding: 8px 10px !important;
  }
  .wpz-inbox-back {
    grid-column: 1 / 2 !important;
    grid-row: 1 !important;
  }
  .wpz-inbox-conv-name {
    grid-column: 2 / 3 !important;
    grid-row: 1 !important;
    min-width: 0 !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
    font-size: 15.5px !important;
  }
  .wpz-inbox-conv-actions {
    grid-column: 3 / 4 !important;
    grid-row: 1 !important;
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    align-items: center !important;
    justify-content: flex-end !important;
    gap: 6px !important;
    width: auto !important;
    max-width: none !important;
    margin-left: 0 !important;
    overflow: visible !important;
    padding: 0 !important;
  }
  /* Icon-only in the bar: the label is always the last span. */
  .wpz-inbox-conv-actions > .wpz-inbox-call-pill > span:last-child {
    display: none !important;
  }
  .wpz-inbox-conv-actions .wpz-inbox-call-pill {
    flex: 0 0 auto !important;
    width: 34px !important;
    min-width: 34px !important;
    height: 34px !important;
    padding: 0 !important;
    border-radius: 999px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    font-size: 15px !important;
  }
  .wpz-inbox-conv-actions .wpz-inbox-call-pill svg { width: 16px !important; height: 16px !important; }
}


/* ── PM header polish (2026-08-20) ───────────────────────────────────────
   pm head polish. Two defects introduced by the consolidated block above.

   1. The ⋯ button rendered as "⋯More". The label-hiding rule uses a CHILD
      combinator (> .wpz-inbox-call-pill > span), and ⋯ lives one level
      deeper inside .wpz-pm-more-wrap, so it never matched.
   2. The dropdown items became 34px circles with their labels spilling out:
      `.wpz-inbox-conv-actions .wpz-inbox-call-pill` and
      `.wpz-pm-more-menu .wpz-inbox-call-pill` are both (0,2,0), and the
      consolidated block was appended later, so it won the tie. Three
      classes beat two — no more relying on file order for this. */
@media (max-width: 700px) {
  .wpz-pm-more > span:last-child { display: none !important; }

  .wpz-inbox-conv-actions .wpz-pm-more-menu .wpz-inbox-call-pill {
    width: 100% !important;
    min-width: 0 !important;
    height: auto !important;
    padding: 9px 10px !important;
    border-radius: 10px !important;
    justify-content: flex-start !important;
    gap: 8px !important;
    font-size: 12.5px !important;
  }
  .wpz-inbox-conv-actions .wpz-pm-more-menu .wpz-inbox-call-pill > span:last-child {
    display: inline !important;
  }
  /* Keep .hidden winning over the display:inline-flex above — non-staff must
     not see SMS / Summon inside the menu. */
  .wpz-inbox-call-pill.hidden { display: none !important; }
}


/* ── Friend list / Blocked: compact on phones (2026-08-20) ───────────────
   social compact. The sheet layout was right but everything inside it was
   still desktop-sized — 10px/12px row padding, 18px radii, 38px avatars and
   a detail card that ate a third of the screen, so five friends and a couple
   of requests filled the whole display.

   Only padding, gaps, radii and font sizes are touched. The grid template of
   .social-item is deliberately LEFT ALONE: an earlier attempt to force the
   avatar-wrap width collapsed those rows into overlapping text, and the note
   from that fix says to touch nothing but padding/fonts here. */
@media (max-width: 700px) {
  .social-window .social-shell { gap: 8px !important; }

  .social-window .social-item {
    padding: 6px 9px !important;
    border-radius: 12px !important;
    gap: 8px !important;
  }
  .social-window .social-name { font-size: 13px !important; }
  .social-window .social-sub { font-size: 10.5px !important; }

  /* Friend requests: five pending rows should not be half the screen. */
  .social-window .wpz-social-reqgroup { padding: 6px 8px !important; gap: 4px !important; border-radius: 10px !important; }
  .social-window .wpz-social-reqrow { padding: 4px 6px !important; font-size: 12px !important; }
  .social-window .wpz-social-reqhead { font-size: 10px !important; }

  /* The detail card for the selected friend. */
  .social-window .social-detail { padding: 8px !important; border-radius: 12px !important; }
  .social-window .social-detail-card { padding: 8px !important; gap: 6px !important; }
  .social-window .social-detail .social-avatar-wrap { transform: scale(0.86); transform-origin: left center; }

  /* Tabs + the add-a-friend row. */
  .social-window .social-tabs { gap: 6px !important; }
  .social-window .social-tab { padding: 6px 12px !important; font-size: 12.5px !important; }
  /* The real add-friend dock: .wpz-social-adddock > form.wpz-social-addbox.
     (.wpz-social-addrow was a guess and matches nothing — verified against
     client.js before shipping.) */
  .social-window .wpz-social-adddock { padding-top: 6px !important; }
  .social-window .wpz-social-addbox { gap: 6px !important; }
  .social-window .wpz-social-addbox input { padding: 7px 10px !important; font-size: 16px !important; }
  .social-window .wpz-social-addbtn,
  .social-window .social-secondary { padding: 7px 11px !important; font-size: 11.5px !important; }
}


/* ── Blocked users popup hugs its content (2026-08-20) ───────────────────
   blocked hug. The popup already had `height: auto`, but the shell inside it
   is `flex: 1 1 auto` (it is the scroller for the full-screen Friends sheet),
   so with an empty list it still stretched toward the max-height and left a
   tall empty box under "No blocked users." Let the shell size to its content
   in blocked mode only — Friends keeps the scroller it needs. */
@media (max-width: 700px) {
  .social-window.blocked-mode { padding: 12px !important; }
  .social-window.blocked-mode .social-shell {
    flex: 0 0 auto !important;
    gap: 8px !important;
    overflow-y: visible !important;
  }
  .social-window.blocked-mode .social-list,
  .social-window.blocked-mode .social-list-panel {
    min-height: 0 !important;
    flex: 0 0 auto !important;
  }
  .social-window.blocked-mode .social-empty,
  .social-window.blocked-mode .wpz-social-empty {
    padding: 10px !important;
    font-size: 12px !important;
  }
}


/* ── Friend list clearances (2026-08-20) ─────────────────────────────────
   social sheet clearances. Two defects visible on device:

   1. The sheet is `height: var(--wpz-vvh)` starting at top 0, but the bottom
      tab bar (.wpz-mobile-bar, z-index above it) sits on top of its last
      ~72pt — so the "Add a friend by username / Send request" row was behind
      the bar and unreachable. Reserve the bar's height in the padding.
   2. The first friend row was sliced in half under the search field: the
      shell scrolls and the search box sits in the flow above it with no
      spacing, so a partially-scrolled row peeked out beneath it. A little
      padding at the top of the scroller keeps rows whole.

   The bar is 52pt of content plus its capped 20pt home-indicator reserve. */
@media (max-width: 700px) {
  .social-window,
  .social-window.blocked-mode {
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 84px) !important;
  }
  /* The blocked popup floats above the bar rather than filling the screen,
     so it does not need the reserve — keep it tight. */
  .social-window.blocked-mode {
    padding-bottom: 12px !important;
    max-height: min(72vh, calc(var(--wpz-vvh, 100svh) - 150px)) !important;
  }
  .social-window .social-shell { padding-top: 2px !important; }
  .social-window .social-list { padding-top: 4px !important; }
}


/* ── Friend list fills the sheet (2026-08-20) ────────────────────────────
   social list fills. Measured on the phone sheet: the shell is 426px tall
   and its children came to 241px — the list panel and the detail card are
   both `flex: 0 0 auto`, so 185px of the sheet was simply empty between the
   last friend and the "Add a friend" dock.

   Give the LIST the slack (the detail card keeps its natural height, so it
   stays put under the list rather than being pushed off the fold). A long
   list still grows past its share and the shell scrolls as before, so the
   one-scroller design is intact. */
@media (max-width: 700px) {
  .social-window .social-list-panel {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    display: flex !important;
    flex-direction: column !important;
  }
  .social-window .social-list {
    flex: 1 1 auto !important;
    min-height: 0 !important;
  }
}


/* ── Friend sheet bottom reserve while typing (2026-08-20) ───────────────
   social kb reserve. The 84px at the foot of the sheet exists to clear the
   bottom tab bar (verified: bar z-index 2147483002 vs the sheet's 72, so the
   bar paints ON TOP and really does cover that strip). But the keyboard rules
   hide the bar while typing — so during that time the reserve is holding
   space for something that is not there, and the "Add a friend" row floats
   above a dead strip. Give it back when the bar is gone. */
html.wpz-app-ios body.wpz-app-kb-open .social-window,
html.wpz-app-android body.wpz-app-kb-open .social-window {
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 12px) !important;
}

/* ── Profile card: kill the stacked headroom (2026-08-23) ────────────────
   The VIP aura sets position:relative on the card, which activates the
   phone block's `top: calc(env(safe-area-inset-top) + 10px)` ON TOP of the
   app margin-top headroom above — env=62 in the app, so the card opened
   ~140pt down with a dead band under the island. The margin is the app's
   one headroom mechanism; zero the relative offset. */
@media (max-width: 980px) {
  html.wpz-app-ios .wpz-profile-modal .wpz-modal-card,
  html.wpz-app-android .wpz-profile-modal .wpz-modal-card {
    top: 0 !important;
  }
}


/* Safe-area inset as a JS-readable var — makeDraggable clamps dragged
   windows below the Dynamic Island with it (env() is not readable from
   JS directly). 0 in browsers, ~62px in the iOS app. */
:root { --wpz-safe-top: env(safe-area-inset-top, 0px); }


/* ── 📱 Mini call bar: three buttons, not ten (2026-08-23) ───────────────
   Docked (.is-mini) the head keeps all 10 controls; on a phone the
   touch-sized buttons make that a ~460pt row — it overlapped the composer
   and ran off the right screen edge. A docked bar needs exactly: Mute,
   Expand (the dock/restore button), Leave. The :not([hidden]) guard keeps
   this from resurrecting buttons client.js hid with the hidden attr. */
@media (max-width: 700px) {
  .vip-call-panel.is-mini .vip-call-controls { flex-wrap: nowrap; }
  .vip-call-panel.is-mini .vip-call-controls .wpz-inbox-icon-btn {
    display: none !important;
  }
  .vip-call-panel.is-mini .vip-call-controls #vipCallMuteBtn:not([hidden]),
  .vip-call-panel.is-mini .vip-call-controls #vipCallHideBtn:not([hidden]),
  .vip-call-panel.is-mini .vip-call-controls #vipCallLeaveBtn:not([hidden]) {
    display: inline-flex !important;
  }
}


/* ── 📱 App-only composer font (2026-08-23) ──────────────────────────────
   The 16px composer floor above exists to stop Safari's zoom-on-focus —
   a WKWebView doesn't do that, so in the app it just looked oversized
   next to the 13px chat bubbles. Match them. Mobile Safari keeps 16px. */
@media (max-width: 980px) {
  html.wpz-app-ios .message-form input[type="text"],
  html.wpz-app-ios .message-form textarea,
  html.wpz-app-ios #messageInput,
  html.wpz-app-android .message-form input[type="text"],
  html.wpz-app-android .message-form textarea,
  html.wpz-app-android #messageInput {
    font-size: 13px !important;
  }
}


/* ── 📱 Admin channel composer: same mobile treatment as the main one ────
   (2026-08-23) The admin channel has its own composer/renderer
   (.admin-channel-form / .admin-channel-log) and never got the mobile
   composer rules. Mirror them: 16px input (Safari zoom-on-focus guard),
   38px tap targets, tighter padding — same breakpoint as the main block. */
@media (max-width: 560px) {
  .admin-channel-form { padding: 5px 6px !important; gap: 5px !important; }
  .admin-channel-form input {
    font-size: 16px !important;
    min-height: 38px !important;
    padding: 7px 10px !important;
  }
  .admin-channel-form button {
    min-width: 38px !important; min-height: 38px !important;
    padding: 6px 10px !important;
  }
  .admin-channel-log { padding: 6px 8px !important; }
}
/* App-only: match the 13px chat bubbles, same as the main composer above
   (WKWebView has no zoom-on-focus, so the 16px floor is browser-only). */
@media (max-width: 980px) {
  /* ID selector on purpose: the blanket anti-zoom rule above is
     input:not()x5 !important (~0,5,1 specificity) and outguns any
     class-only selector — the main composer rule survives it only
     because it rides #messageInput. Same trick here. */
  html.wpz-app-ios #adminChannelInput,
  html.wpz-app-android #adminChannelInput {
    font-size: 13px !important;
  }
}


/* ══ 📱 App Settings page skin (2026-08-23) ═══════════════════════════════
   The profile editor's Settings mode (form.wpz-settings-form) was desktop
   markup squeezed into the phone: some groups carded, some floating, web
   checkboxes, status text and buttons wrapping loosely. App-only skin:
   every group is the same card, checkboxes become iOS switches, each
   status row is text-left / action-right, inputs get a sane size (the
   blanket anti-zoom input rule is (0,5,1) — the :not() chains below are
   there to outgun it, same trap as #adminChannelInput). */
@media (max-width: 980px) {
  html.wpz-app-ios .wpz-settings-form,
  html.wpz-app-android .wpz-settings-form {
    gap: 10px !important;
  }
  /* Uniform cards for every top-level group… */
  html.wpz-app-ios .wpz-settings-form > div,
  html.wpz-app-android .wpz-settings-form > div {
    background: rgba(255, 255, 255, 0.055) !important;
    border: 1px solid rgba(255, 255, 255, 0.08) !important;
    border-radius: 16px !important;
    padding: 14px !important;
    margin: 0 !important;
  }
  /* …except the "⚙ Settings" heading row. */
  html.wpz-app-ios .wpz-settings-form > div:first-child,
  html.wpz-app-android .wpz-settings-form > div:first-child {
    background: none !important;
    border: none !important;
    padding: 2px 2px 0 !important;
  }
  /* Danger zone reads as danger. */
  html.wpz-app-ios .wpz-settings-form > #wpzDangerZone,
  html.wpz-app-android .wpz-settings-form > #wpzDangerZone {
    background: rgba(224, 82, 82, 0.09) !important;
    border-color: rgba(224, 82, 82, 0.28) !important;
  }
  /* Section labels: uniform rhythm. */
  html.wpz-app-ios .wpz-settings-form .wpz-profile-label,
  html.wpz-app-android .wpz-settings-form .wpz-profile-label {
    display: block;
    margin-bottom: 10px;
  }
  /* Security rows: status text left, action buttons pushed right. */
  html.wpz-app-ios .wpz-settings-form #wpz2faStatus,
  html.wpz-app-ios .wpz-settings-form #wpzPhoneStatus,
  html.wpz-app-android .wpz-settings-form #wpz2faStatus,
  html.wpz-app-android .wpz-settings-form #wpzPhoneStatus {
    flex: 1 1 auto;
    min-width: 0;
  }
  /* Inputs / selects / textareas: full-width, phone-sized text. The :not()
     chain matches the blanket anti-zoom rule's specificity — a plain class
     selector silently loses to it. */
  html.wpz-app-ios .wpz-settings-form input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),
  html.wpz-app-android .wpz-settings-form input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),
  html.wpz-app-ios .wpz-settings-form textarea,
  html.wpz-app-ios .wpz-settings-form select,
  html.wpz-app-android .wpz-settings-form textarea,
  html.wpz-app-android .wpz-settings-form select {
    font-size: 14px !important;
    min-height: 40px !important;
    border-radius: 12px !important;
  }
  /* VIP rows: keep the color-picker row on one tidy line. */
  html.wpz-app-ios .wpz-settings-form .wpz-vip-profile-row,
  html.wpz-app-android .wpz-settings-form .wpz-vip-profile-row {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  html.wpz-app-ios .wpz-settings-form .wpz-vip-profile-row > span:first-child,
  html.wpz-app-android .wpz-settings-form .wpz-vip-profile-row > span:first-child {
    flex: 1 1 auto;
    min-width: 0;
  }
  html.wpz-app-ios .wpz-settings-form .wpz-vip-profile-row.is-col,
  html.wpz-app-android .wpz-settings-form .wpz-vip-profile-row.is-col {
    flex-direction: column;
    align-items: stretch;
  }
  /* Checkbox rows → iOS switches: text left, switch right. */
  html.wpz-app-ios .wpz-settings-form .wpz-vip-profile-toggle,
  html.wpz-app-android .wpz-settings-form .wpz-vip-profile-toggle {
    display: flex !important;
    flex-direction: row-reverse;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 2px;
    margin: 0 !important;
  }
  html.wpz-app-ios .wpz-settings-form .wpz-vip-profile-toggle input[type="checkbox"],
  html.wpz-app-android .wpz-settings-form .wpz-vip-profile-toggle input[type="checkbox"] {
    -webkit-appearance: none !important;
    appearance: none !important;
    width: 47px !important;
    height: 28px !important;
    flex: none !important;
    border-radius: 999px !important;
    border: none !important;
    background: rgba(120, 120, 128, 0.34) !important;
    position: relative !important;
    margin: 0 !important;
    transition: background 0.2s ease;
    cursor: pointer;
  }
  html.wpz-app-ios .wpz-settings-form .wpz-vip-profile-toggle input[type="checkbox"]::after,
  html.wpz-app-android .wpz-settings-form .wpz-vip-profile-toggle input[type="checkbox"]::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
    transition: left 0.2s ease;
  }
  html.wpz-app-ios .wpz-settings-form .wpz-vip-profile-toggle input[type="checkbox"]:checked,
  html.wpz-app-android .wpz-settings-form .wpz-vip-profile-toggle input[type="checkbox"]:checked {
    background: #34c759 !important;
  }
  html.wpz-app-ios .wpz-settings-form .wpz-vip-profile-toggle input[type="checkbox"]:checked::after,
  html.wpz-app-android .wpz-settings-form .wpz-vip-profile-toggle input[type="checkbox"]:checked::after {
    left: 21px;
  }
  /* Action buttons: consistent height + radius. */
  html.wpz-app-ios .wpz-settings-form .support-action-btn,
  html.wpz-app-android .wpz-settings-form .support-action-btn {
    min-height: 36px;
    border-radius: 10px !important;
    padding: 8px 14px !important;
  }
}


/* ── 📱 Donation forms vs the app keyboard (2026-08-23) ──────────────────
   The quick-donate bottom sheet is pinned to --wpz-vvh, which tracks
   visualViewport — that shrinks under the keyboard in Safari but NEVER in
   the app's WKWebView, so the sheet (and the message field being typed in)
   sat behind the keyboard. Ride the app's own keyboard signal instead:
   wpzAppKeyboard publishes --wpz-app-kb + body.wpz-app-kb-open. */
@media (max-width: 560px) {
  html.wpz-app-ios body.wpz-app-kb-open .wpz-quickdonate-overlay,
  html.wpz-app-android body.wpz-app-kb-open .wpz-quickdonate-overlay {
    padding-bottom: var(--wpz-app-kb, 0px) !important;
  }
  html.wpz-app-ios body.wpz-app-kb-open .wpz-quickdonate-card,
  html.wpz-app-android body.wpz-app-kb-open .wpz-quickdonate-card {
    max-height: calc(var(--wpz-vvh, 100svh) - var(--wpz-app-kb, 0px)) !important;
  }
  /* Custom-amount donate modal (top-anchored): shorten it so the focused
     field and the Support button stay above the keyboard; it scrolls. */
  html.wpz-app-ios body.wpz-app-kb-open #wpzDonateModal .wpz-confirm-card,
  html.wpz-app-android body.wpz-app-kb-open #wpzDonateModal .wpz-confirm-card {
    max-height: calc(var(--wpz-vvh, 100svh) - var(--wpz-app-kb, 0px) - 24px) !important;
  }
}


/* ── 📱 Minimizable channel header (2026-08-24) ── the "# main" bar
   collapses to one slim line; the chevron toggles and localStorage
   remembers. Wired by wpzRoomHeaderMin in client.js. */
@media (max-width: 980px) {
  #wpzRoomHeaderMinBtn{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 8px;
    flex: none;
    width: 26px;
    height: 26px;
    border-radius: 999px;
    border: 1px solid rgba(120, 150, 190, 0.25);
    background: rgba(120, 150, 190, 0.10);
    color: inherit;
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
    opacity: 0.65;
  }
  body.wpz-room-header-min .room-header{
    padding: 3px 10px !important;
    min-height: 0 !important;
    gap: 6px !important;
  }
  body.wpz-room-header-min .room-avatar,
  body.wpz-room-header-min .room-topic,
  body.wpz-room-header-min .room-badges{
    display: none !important;
  }
  body.wpz-room-header-min .room-title{
    font-size: 12.5px !important;
    line-height: 1.2 !important;
  }
  body.wpz-room-header-min .room-heading{
    align-items: center !important;
  }
}
