/* ============================================================================
   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;
    padding: 6px 8px calc(6px + env(safe-area-inset-bottom));
    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: obvious, centred placement ═══════════════════
   Device report: opening a VIDEO call on a phone was disorienting — the
   window either docked to the tiny bottom bar (body hidden, no video) or
   appeared at the desktop `top:96px;right:26px` anchor, so you couldn't tell
   where it spawned. Pin the full (non-mini) window as a centred sheet just
   below the header with a strong shadow so it's unmistakable, and cap its
   height so it never covers the bottom nav. Centred via left/right margins,
   NOT transform, so the `vipCallSpawn` drop-in animation (which animates
   transform) still plays. Breakpoint matches the ≤700 single-pane switch. */
@media (max-width: 700px) {
  .vip-call-panel:not(.is-mini):not(.hidden) {
    top: calc(env(safe-area-inset-top, 0px) + 56px) !important;
    left: 3vw !important;
    right: 3vw !important;
    bottom: auto !important;
    width: 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;
  }
}

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