/* Pool — Yahoo-inspired layout with the gradient/glass theme. */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }

/* Solid deep-maroon backdrop only on the pool page so the green felt
   has a real contrast colour instead of competing with the site's
   pink/purple gradient. The gradient still shows on every OTHER page. */
body.pool-page {
  background: #2a0a14;          /* dark wine */
  background-image: none;
  animation: none;
}
body.pool-page .sparkles { display: none; }

/* Kill backdrop-filter on every translucent panel for the pool page.
   iOS Safari has to RE-BLUR these layers whenever the canvas behind
   them paints (= every animation frame during a shot), which is a
   well-known per-frame cost spike on the platform. The page is on a
   solid maroon background so the blur effect is invisible anyway —
   pure cost, no visual gain. M3 Max barely notices; iPhone does. */
body.pool-page .seat,
body.pool-page .chat,
body.pool-page .hamburger {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* --- iPhone 15 Pro Max Dynamic Island frame ----------------------- */
/* Disabled. The dark-red pill around the Dynamic Island was useful
   while debugging the landscape-phone UI layout (it made it obvious
   where the notch sat relative to the seat chips / button slots), but
   in everyday play it's just visual noise. Kept commented so it can
   be flipped back on if the layout needs another round of debugging.

body.pool-page::before {
  content: '';
  position: fixed;
  display: none;
  pointer-events: none;
  border: 1.5px solid #7f1d1d;
  border-radius: 22px;
  background: #000;
  box-shadow:
    0 0 6px rgba(127, 29, 29, 0.55),
    inset 0 0 4px rgba(127, 29, 29, 0.35);
  z-index: 1500;
}
body.pool-page[data-notch="landscape-left"]::before {
  display: block;
  left: 9.5px;
  top: calc(100svh - 50lvh);
  transform: translateY(-50%);
  width:  40px;
  height: 129px;
}
*/

/* Pool-page specific overrides for the shared chrome ------------- */
/* On the pool page the global nav is hidden by default and only
   reveals when the hamburger is tapped (body.nav-open). We also drop
   the body padding-top that _nav.php normally reserves for the bar. */
body.pool-page                  { padding-top: 0; }
body.pool-page .barnes-nav      { display: none; }
body.pool-page.nav-open .barnes-nav { display: flex; }

/* Hamburger — fixed top-left, inset by the iPhone notch in landscape
   so it never lands underneath it. */
.hamburger {
  position: fixed;
  /* The top-left CORNER of the screen is always notch-free (notch sits
     in the middle of the long edge in landscape, and in the top-CENTRE
     in portrait), so the button can hug the corner with just a status-
     bar inset on top. */
  top: calc(0.4rem + env(safe-area-inset-top));
  left: 0.4rem;
  z-index: 1100;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: rgba(20, 20, 30, 0.65);
  backdrop-filter: blur(10px) saturate(160%);
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #fff;
  font-size: 1.25rem;
  line-height: 1;
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.hamburger:hover { background: rgba(20, 20, 30, 0.82); }
.hamburger:active { transform: translateY(1px); }

/* Force-rotate overlay — opaque BLACK screen with bright-red warning
   letters. Fires on mobile when the phone is in any orientation other
   than landscape with the notch on the left. Desktop / iPad are not
   constrained — the JS check returns early for non-phone sizes. */
.rotate-overlay {
  position: fixed;
  inset: 0;
  z-index: 2000;
  background: #000;
  color: #ef4444;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.rotate-overlay[hidden] { display: none; }
.rotate-content { text-align: center; max-width: 360px; }
.rotate-icon {
  font-size: 4rem;
  animation: rotateHint 2.4s ease-in-out infinite;
  display: inline-block;
  /* Slight red glow on the emoji so it visually matches the text. */
  filter: drop-shadow(0 0 6px rgba(239, 68, 68, 0.5));
}
@keyframes rotateHint {
  0%, 100% { transform: rotate(0deg); }
  50% { transform: rotate(180deg); }
}
.rotate-content h2 {
  margin: 0.8rem 0 0.5rem;
  font-size: 1.6rem;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #ef4444;
  text-shadow: 0 0 12px rgba(239, 68, 68, 0.45);
}
.rotate-content p {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.5;
  color: #ef4444;
  opacity: 0.85;
  letter-spacing: 0.02em;
}
.rotate-content p strong { color: #fca5a5; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
  color: #fff;
  background:
    linear-gradient(135deg, #ff6ec4, #7873f5, #4ade80, #fcd34d, #f472b6, #60a5fa);
  background-size: 400% 400%;
  background-attachment: fixed;
  animation: gradientShift 14s ease infinite;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.20);
}
@keyframes gradientShift {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; background: rgba(0,0,0,0.22); padding: 0.1em 0.4em; border-radius: 6px; }

/* --- sign-in gate --- */
.pool-gate {
  display: grid; place-items: center;
  padding: 2rem 1rem 4rem;
  min-height: calc(100vh - 4rem);
}
.pool-gate .card {
  max-width: 480px;
  padding: 2rem 1.75rem;
  border-radius: 24px;
  background: rgba(255,255,255,0.12);
  backdrop-filter: blur(10px) saturate(160%);
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  box-shadow: 0 20px 60px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.25);
  text-align: center;
}
.pool-gate h1 { margin: 0 0 0.5rem; }
.pool-gate p { opacity: 0.95; line-height: 1.5; }
.pool-gate .hint { font-size: 0.85rem; opacity: 0.75; }
.pool-gate form { display: flex; gap: 0.5rem; justify-content: center; margin-top: 1rem; }
.pool-gate input {
  flex: 1 1 0; min-width: 0;
  font: inherit; font-size: 1.05rem; color: #fff;
  background: rgba(255,255,255,0.18);
  border: 1px solid rgba(255,255,255,0.30);
  border-radius: 999px;
  padding: 0.65em 1.1em;
  outline: none; text-align: center; caret-color: #fff;
  text-shadow: none;
}
.pool-gate input::placeholder { color: rgba(255,255,255,0.65); }
.pool-gate input:focus {
  background: rgba(255,255,255,0.26);
  border-color: rgba(255,255,255,0.55);
  box-shadow: 0 0 0 4px rgba(255,255,255,0.10);
}
.pool-gate button {
  font: inherit; font-weight: 700; font-size: 1rem;
  color: #2b1b3c;
  background: linear-gradient(135deg, #ffffff, #fde4ec);
  border: 1px solid rgba(255,255,255,0.65);
  border-radius: 999px;
  padding: 0.65em 1.25em;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(0,0,0,0.18);
}

/* --- main app layout --- */
/* Desktop: pool-app fills the viewport (the global nav is hidden on
   the pool page so we don't subtract the 2.4rem reserved by _nav.php). */
.pool-app {
  height: 100vh;
  max-width: none;
  padding: 0.75rem;
  margin: 0;
  box-sizing: border-box;
}
/* Desktop / wide: 3 columns. Seats span all rows on the left, chat all
   on the right; the middle column stacks status / canvas / controls /
   hint. table-wrap is gone — every element is a direct grid child.
   Sidebar widths trimmed a bit so the table gets more horizontal room. */
.pool-grid {
  display: grid;
  grid-template-columns: 200px 1fr 240px;
  grid-template-rows: auto 1fr auto auto;
  grid-template-areas:
    "seats   status  chat"
    "seats   table   chat"
    "seats   ctrls   chat"
    "seats   hint    chat";
  gap: 0.75rem;
  height: 100%;
}
.seats        { grid-area: seats; }
.status       { grid-area: status; }
.canvas-host  { grid-area: table; }
.controls     { grid-area: ctrls; }
.hint         { grid-area: hint; }
.chat         { grid-area: chat; }

/* Portrait (any width up to 1024 in portrait, OR taller-than-540 in
   landscape). Stack everything into a single column. */
@media (max-width: 1024px) {
  html, body {
    overflow: hidden;
    overscroll-behavior: none;
    touch-action: manipulation;
    height: 100dvh;
  }
  .pool-app {
    /* Edge-to-edge: zero horizontal padding so the grid columns
       literally touch the left and right edges of the screen. Top and
       bottom still inset by the iOS safe area so the iPhone status bar
       (portrait) and home indicator (both) don't overlap content. */
    height: 100dvh;
    padding: calc(0.3rem + env(safe-area-inset-top)) 0 calc(0.3rem + env(safe-area-inset-bottom)) 0;
    margin: 0;
    max-width: none;
  }
  .pool-grid {
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
  }
  .seats {
    flex: 0 0 auto;
    flex-direction: row;
    gap: 0.4rem;
  }
  .seat { flex: 1; padding: 0.25rem 0.55rem; min-height: 0; }
  .status { flex: 0 0 auto; font-size: 0.78rem; padding: 0.3em 0.8em; }
  .canvas-host { flex: 0 0 auto; width: 100%; }
  .controls { flex: 0 0 auto; gap: 0.4rem; }
  .controls button { padding: 0.35em 0.85em; font-size: 0.85rem; }
  .hint { display: none; }
  /* body.pool-page prefix bumps specificity above the un-prefixed
     base rules (.chat-log etc.) that appear LATER in the source than
     this media query — without it, the cascade order wins and the
     base 0.86rem font overrides the mobile 6px. */
  body.pool-page .chat {
    flex: 1 1 0;
    min-height: 0;
    max-height: none;
    border-radius: 1px;
  }
  body.pool-page .chat-log {
    font-size: 9px;
    line-height: 1.4;
    padding: 4px 5px;
  }
  body.pool-page #chatForm {
    flex-direction: column;
    gap: 3px;
    padding: 3px;
  }
  body.pool-page #chatInput {
    font-size: 9px;
    padding: 3px 5px;
    border-radius: 1px;
    width: 100%;
  }
  body.pool-page #chatForm button {
    font-size: 9px;
    padding: 3px 5px;
    border-radius: 1px;
    width: 100%;
  }
}

/* Landscape phone — UI is structured into four zones around the iPhone
   15 Pro Max Dynamic Island:

     ┌──────┐                      ┌──────┐
     │ UI 1 │  ← above the notch   │      │
     ├──────┴──┐                   │      │
     │ ░notch░ │ UI 3 (top-half)   │ chat │
     │ ░░░░░░░ │ UI 4 (bot-half)   │      │
     ├──────┬──┘                   │      │
     │ UI 2 │  ← below the notch   │      │
     └──────┘                      └──────┘
       100px        (canvas)          100px

   UI 1/UI 2 are seat 0/seat 1 player chips. UI 3 is the Join-match /
   Leave button. UI 4 is New-game (seated-only). All four use position:
   fixed with the same `calc(100svh - 50lvh)` formula as the notch
   frame, so they re-align automatically when the URL bar shows/hides. */
@media (orientation: landscape) and (max-height: 540px) {
  /* Hide chrome that has no role on this layout — turn state is shown
     by the active-seat highlight (UI 1 / UI 2), system messages stream
     into chat as they're emitted. */
  .hamburger,
  .status,
  .hint {
    display: none !important;
  }

  /* Edge-to-edge. The grid only positions canvas + chat now; UI 1–4
     are taken out of flow with position:fixed and don't need cells. */
  .pool-app { padding: 0; }
  .pool-grid {
    display: grid;
    grid-template-columns: 100px 1fr 100px;
    grid-template-rows: 1fr;
    grid-template-areas: ".  table  chat";
    gap: 0;
    height: 100%;
  }
  /* Containers vanish from flow so their fixed-positioned children
     don't get accidentally placed by grid auto-placement. */
  .seats, .controls { display: contents; }

  /* ── UI 1 & UI 2: player chips above / below the notch ─────── */
  /* Min/max width + min/max height clamps the boxes so changes in
     content (name length, group pill, wins, "(you)" tag etc.) cannot
     resize the chip. Overflow: hidden keeps any rare overflow clipped. */
  #seat0, #seat1 {
    position: fixed;
    left: 0;
    width: 100px;
    min-width: 100px;
    max-width: 100px;
    border-radius: 2px;
    padding: 5px 6px;
    background: rgba(255, 255, 255, 0.10);
    border: 1px solid rgba(255, 255, 255, 0.15);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    align-content: center;
    gap: 4px;
    overflow: hidden;
    box-sizing: border-box;
  }
  #seat0 {
    top: 0;
    height:     calc(100svh - 50lvh - 63px);
    min-height: calc(100svh - 50lvh - 63px);
    max-height: calc(100svh - 50lvh - 63px);
  }
  #seat1 {
    top:    calc(100svh - 50lvh + 63px);
    bottom: 0;
  }
  /* Seat-internal sizing — bumped up from 8px for legibility while
     still fitting the locked-size box. Name is the most prominent
     element; supporting pills are slightly smaller. */
  #seat0 .avatar, #seat1 .avatar {
    width: 20px; height: 20px;
    font-size: 11px;
    flex: 0 0 20px;
  }
  #seat0 .info-row, #seat1 .info-row {
    flex: 1 1 auto;
    min-width: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    font-size: 10px;
    line-height: 1.15;
  }
  #seat0 .name, #seat1 .name {
    font-size: 11px;
    font-weight: 700;
    width: 100%;
  }
  #seat0 .group-pill, #seat1 .group-pill,
  #seat0 .wins-tag,   #seat1 .wins-tag {
    font-size: 9px;
    padding: 0.05em 0.4em;
    border-radius: 2px;
    margin: 0;
  }
  #seat0 .turn-mark, #seat1 .turn-mark { font-size: 10px; }
  #seat0 .seat-empty, #seat1 .seat-empty {
    font-size: 10px;
    text-align: center;
    width: 100%;
    font-style: italic;
  }

  /* ── UI 3/4/5: three vertically-stacked button slots to the right of
     the Dynamic Island. All three buttons share the same width / font
     / typography; only their `top` differs.
        Slot A (top)    – claim seat (unseated) or leave seat (seated)
        Slot B (middle) – new game (seated) or take over AI (spectating)
        Slot C (bottom) – play vs AI (seated alone) — empty otherwise
     Slots are 42 px tall × 3 → 126 px total, centred on the notch
     centerline (which spans roughly ctr-63 to ctr+63). */
  #claimBtn, #leaveBtn,
  #restartBtn, #takeoverBtn,
  #aiBtn {
    position: fixed;
    left: 52px;                          /* notch x=11–48 + 4 px gap */
    width: 48px;
    min-width: 48px;
    max-width: 48px;
    height: 42px;
    min-height: 42px;
    max-height: 42px;
    margin: 0;
    padding: 3px 2px;
    border-radius: 2px;
    /* 8 px so the label is readable; wrapping at the natural space lets
       "JOIN MATCH" split to two lines if it can't fit on one. */
    font-size: 8.5px;
    font-weight: 800;
    line-height: 1.05;
    letter-spacing: 0;
    text-align: center;
    overflow: hidden;
    white-space: normal;
    word-break: break-word;
    -webkit-text-size-adjust: none;
    text-size-adjust: none;
  }
  #claimBtn, #leaveBtn               { top: calc(100svh - 50lvh - 63px); }   /* slot A */
  #restartBtn, #takeoverBtn          { top: calc(100svh - 50lvh - 21px); }   /* slot B */
  #aiBtn                             { top: calc(100svh - 50lvh + 21px); }   /* slot C */

  /* ── Canvas (middle column) ─────────────────────────────────── */
  .canvas-host {
    grid-area: table;
    width: auto;
    height: 100%;
    max-width: 100%;
    max-height: 100%;
    aspect-ratio: 2 / 1;
    margin: 0 auto;
    align-self: center;
    justify-self: center;
  }

  /* ── Chat (right column) ────────────────────────────────────── */
  /* The chat-form is forced into a column below (in the mobile @media
     block) — landscape inherits that, so input + send stack on
     separate lines. */
  .chat {
    grid-area: chat;
    min-height: 0;
    max-width: none;
    width: auto;
    flex: none;
    border-radius: 1px;
    -webkit-text-size-adjust: none;
    text-size-adjust: none;
  }
}

.seats { display: flex; flex-direction: column; gap: 0.4rem; }
.seat {
  border-radius: 999px;
  padding: 0.25rem 0.55rem;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.15);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; gap: 0.45rem;
  min-height: 0;
  min-width: 0;
}
.seat.active {
  background: rgba(74, 222, 128, 0.22);
  border-color: rgba(74, 222, 128, 0.55);
  box-shadow: 0 0 0 2px rgba(74, 222, 128, 0.15);
}
.seat.you { border-color: rgba(252, 211, 77, 0.65); }
.seat-empty { opacity: 0.7; font-style: italic; font-size: 0.78rem; padding: 0.1rem 0.3rem; }
.seat .avatar {
  width: 22px; height: 22px; border-radius: 50%;
  background: rgba(255,255,255,0.18);
  display: grid; place-items: center;
  font-size: 0.72rem; font-weight: 700;
  flex: 0 0 22px;
}
.seat .info-row {
  display: flex; align-items: center; gap: 0.4rem;
  flex: 1; min-width: 0;
  font-size: 0.78rem; line-height: 1.15;
}
.seat .name {
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.seat .name .uid,
.chat-log .who .uid { font-family: ui-monospace, monospace; font-weight: 400; opacity: 0.55; font-size: 0.78em; }
.seat .name .you-tag { font-weight: 500; opacity: 0.75; font-size: 0.85em; }
.seat .turn-mark {
  color: #4ade80;
  font-size: 0.62rem;
  line-height: 1;
  text-shadow: 0 0 6px rgba(74, 222, 128, 0.65);
}
.seat .group-pill {
  margin-left: auto;
  font-size: 0.66rem;
  padding: 0.1em 0.5em;
  border-radius: 999px;
  background: rgba(0,0,0,0.30);
  font-family: ui-monospace, monospace;
  flex: 0 0 auto;
}
.seat .wins-tag {
  font-size: 0.66rem;
  font-family: ui-monospace, monospace;
  background: rgba(252, 211, 77, 0.22);
  padding: 0.1em 0.45em;
  border-radius: 999px;
  flex: 0 0 auto;
}
.status {
  width: 100%;
  text-align: center;
  background: rgba(0,0,0,0.30);
  padding: 0.45em 1em;
  border-radius: 999px;
  font-size: 0.9rem;
  align-self: center;
  /* Pin to a single line so changes in the status text never push the
     rest of the layout around. Long messages truncate with an ellipsis. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.canvas-host {
  /* Default (mobile + landscape-phone): grid item with aspect-ratio.
     2 rem of internal padding on every side gives the table room to
     breathe — the canvas inside fills the content area, and the maroon
     page background shows through the padding. */
  width: 100%;
  aspect-ratio: 2 / 1;
  position: relative;
  margin: 0 auto;
  align-self: center;
  justify-self: center;
  max-height: 100%;
  padding: 2rem;
  box-sizing: border-box;
}

/* Fullscreen overlay for cue-stick rendering. Sits above all UI (chat,
   seats, buttons) so the stick can extend across the whole page; below
   the notch frame and the rotate prompt. pointer-events:none lets
   touches pass through to the elements below it.

   Both inset:0 AND explicit 100vw/100vh because <canvas> is a replaced
   element — without an explicit width/height it falls back to its
   intrinsic 300×150 default even when inset:0 is set. With both, the
   canvas reliably fills the layout viewport. */
.pool-overlay {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 1400;
}

/* Desktop / wide layout.
   The previous version pinned the canvas to viewport coordinates with
   position:fixed. That kept it from jittering when surrounding rows
   resized, but it also left a big empty strip above the table (status
   anchored to the top of the column) and below it (controls anchored
   near the bottom), plus a too-short chat panel. Switch back to
   in-grid placement with auto-packed rows so status / table / ctrls /
   hint cluster together around the centred table, then centre that
   whole stack vertically. Chat is allowed to fill the column, and
   seats are vertically centred so all three columns visually align
   around the table's midline. */
@media (min-width: 1025px) {
  .pool-grid {
    /* All auto rows — the table sizes itself from its aspect-ratio,
       not a 1fr filler that was inflating the row to viewport height
       and pushing status/ctrls to the column edges. */
    grid-template-rows: auto auto auto auto;
    align-content: center;
    row-gap: 0.6rem;
    /* Slightly wider chat sidebar so the panel reads as a real column
       instead of a narrow strip on big monitors. */
    grid-template-columns: 200px 1fr 300px;
  }
  .canvas-host {
    position: static;
    transform: none;
    aspect-ratio: 2 / 1;
    width: 100%;
    height: auto;
    /* Leave room for status (~2.5rem), ctrls (~3.5rem), hint (~1.5rem),
       plus gaps and pool-app padding. */
    max-height: calc(100vh - 9rem);
    max-width: 100%;
    margin: 0 auto;
    /* A bit of breathing room around the table so the rail doesn't
       sit flush against the maroon backdrop. */
    padding: 0.75rem;
    align-self: center;
    justify-self: center;
  }
  /* Chat fills the full column height on desktop — the 460 px cap was
     leaving a strip of dead maroon below it on tall windows. */
  body.pool-page .chat {
    max-height: none;
    min-height: 0;
    height: 100%;
  }
  /* Seats vertically centred to align with the table's midline rather
     than top-anchored against an empty column. */
  .seats {
    justify-content: center;
  }
  /* Slightly more visual weight on each seat chip so they don't look
     like tiny stragglers in a wide column. */
  .seat { padding: 0.45rem 0.75rem; }
  .seat .avatar { width: 26px; height: 26px; font-size: 0.82rem; flex: 0 0 26px; }
  .seat .info-row { font-size: 0.85rem; gap: 0.5rem; }
  /* Tight status text right above the table, tighter hint just under
     controls. min-height keeps the rows the same size when the text
     goes empty (hint blanks during shot animation) so the table
     doesn't shift up/down between game states. */
  .status { margin: 0 auto; min-height: 2.1em; }
  .hint   { margin: 0 auto; min-height: 1.4em; }
  .controls { min-height: 2.6em; }
}
/* Inside canvas-host, both #pool (2D static) and #poolBalls (WebGL)
   are absolute-positioned and fill the stack so they sit precisely
   atop each other. The .canvas-stack wrapper exists so they fill the
   content box (inside canvas-host's 1.5 rem padding) rather than the
   padding box. */
.canvas-stack {
  position: relative;
  width: 100%;
  height: 100%;
}
#pool, .balls-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: 14px;
  display: block;
}
#pool {
  background: #0a1810;
  box-shadow: 0 12px 40px rgba(0,0,0,0.35);
  touch-action: none;
}
.balls-canvas {
  z-index: 1;            /* drawn above #pool */
  pointer-events: none;  /* clicks/touches go to #pool below */
}
.controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  justify-content: center;
  width: 100%;
}
.power-wrap { display: flex; align-items: center; gap: 0.5rem; flex: 1 1 200px; min-width: 0; }
.power-wrap input[type="range"] { flex: 1 1 0; min-width: 0; accent-color: #fde4ec; }
.power-wrap label { font-size: 0.85rem; opacity: 0.9; }
#powerLabel { font-family: ui-monospace, monospace; font-size: 0.85rem; min-width: 3.2em; text-align: right; }
.controls button {
  font: inherit; font-weight: 600; font-size: 0.95rem;
  color: #2b1b3c;
  background: linear-gradient(135deg, #ffffff, #fde4ec);
  border: 1px solid rgba(255,255,255,0.65);
  border-radius: 999px;
  padding: 0.55em 1.1em;
  cursor: pointer;
  text-shadow: none;
  transition: transform 0.1s ease, opacity 0.15s ease;
}
.controls button:disabled { opacity: 0.45; cursor: not-allowed; }
.controls button:not(:disabled):active { transform: translateY(1px); }
.hint { font-size: 0.8rem; opacity: 0.78; text-align: center; max-width: 720px; }

.chat {
  grid-area: chat;
  display: flex; flex-direction: column;
  border-radius: 16px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.15);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  min-height: 280px;
  max-height: 460px;
  overflow: hidden;
}
.chat-log {
  flex: 1; overflow-y: auto;
  padding: 0.6rem 0.75rem;
  font-size: 0.86rem;
  line-height: 1.4;
}
.chat-log .msg { margin-bottom: 0.3rem; word-wrap: break-word; }
.chat-log .msg .who { font-weight: 700; }
/* Game messages (joins, leaves, turn announcements, win/loss, group
   assigned, etc.). Yellow so they're visually distinct from player
   chat. */
.chat-log .msg.system { opacity: 0.95; font-style: italic; color: #ffd966; }
.chat-log .msg.system .who { display: none; }
/* Warn-kind system messages. Used when the shooter pockets the
   opponent's ball ("X sank Y's 7-ball") — coloured red so it stands
   out from the regular yellow game-event line. */
.chat-log .msg.system.kind-warn { color: #fca5a5; }
#chatForm {
  display: flex;
  gap: 0.4rem;
  padding: 0.55rem;
  border-top: 1px solid rgba(255,255,255,0.10);
}
#chatInput {
  flex: 1; min-width: 0;
  font: inherit; font-size: 0.9rem; color: #fff;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.20);
  border-radius: 999px;
  padding: 0.45em 0.9em;
  outline: none; caret-color: #fff;
  text-shadow: none;
}
#chatInput::placeholder { color: rgba(255,255,255,0.55); }
#chatInput:focus {
  background: rgba(255,255,255,0.18);
  border-color: rgba(255,255,255,0.40);
}
#chatForm button {
  font: inherit; font-weight: 600; font-size: 0.85rem;
  color: #2b1b3c;
  background: linear-gradient(135deg, #ffffff, #fde4ec);
  border: 1px solid rgba(255,255,255,0.65);
  border-radius: 999px;
  padding: 0.4em 0.95em;
  cursor: pointer;
  text-shadow: none;
}

/* --- Lounge / room picker -------------------------------------------- */
/* Visible only when body.in-lounge. Sits in the same grid-area as the
   table (`table`) so the layout doesn't reshuffle when switching modes
   — the picker just replaces the table area, and the surrounding
   chat/status/seats either reuse themselves or hide. */
.room-picker {
  grid-area: table;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1rem;
  gap: 1rem;
}
.room-picker-head h2 {
  margin: 0 0 0.25rem 0;
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}
.room-picker-sub {
  margin: 0;
  font-size: 0.9rem;
  opacity: 0.7;
}
.room-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.75rem;
  width: 100%;
  max-width: 720px;
}
.room-btn {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "name  seats"
    "status count";
  gap: 0.3rem 0.6rem;
  text-align: left;
  font: inherit;
  color: inherit;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.18);
  border-radius: 14px;
  padding: 0.85rem 1rem;
  cursor: pointer;
  transition: transform 0.08s ease, background 0.12s ease, border-color 0.12s ease;
}
.room-btn:hover {
  background: rgba(255,255,255,0.18);
  border-color: rgba(255,255,255,0.35);
}
.room-btn:active { transform: translateY(1px); }
.room-name {
  grid-area: name;
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.01em;
}
.room-seats {
  grid-area: seats;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}
.room-status {
  grid-area: status;
  font-size: 0.8rem;
  opacity: 0.78;
}
.room-count {
  grid-area: count;
  font-size: 0.78rem;
  opacity: 0.6;
  text-align: right;
}
.room-dot {
  display: inline-block;
  width: 12px; height: 12px;
  border-radius: 50%;
  box-sizing: border-box;
}
.room-dot.empty  { background: transparent; border: 1.5px solid rgba(255,255,255,0.45); }
.room-dot.filled { background: #4ade80;     border: 1.5px solid rgba(74,222,128,0.85); box-shadow: 0 0 4px rgba(74,222,128,0.5); }
.room-dot.ai     { font-size: 0.55rem; font-weight: 700; color: #1d1226; background: #facc15; border-radius: 4px; padding: 1px 4px; width: auto; height: auto; line-height: 1; }

/* When in lounge, hide the table + game-specific chrome. The room
   picker takes the table area; seats + controls + hint + status row
   collapse out of the way. Chat stays visible (it's the Lounge chat). */
body.in-lounge .canvas-host,
body.in-lounge .seats,
body.in-lounge .controls,
body.in-lounge .hint,
body.in-lounge .pool-overlay { display: none; }
body.in-lounge .status {
  font-size: 0.85rem;
  opacity: 0.85;
}
/* And conversely: when NOT in lounge, hide the picker. */
body:not(.in-lounge) .room-picker { display: none; }

/* Desktop Lounge: switch the grid to a clean 2-column layout —
   the picker on the left where the table used to be, the chat on
   the right at full column height. Removes the wasted vertical
   space above/below the picker that the standard 4-row template
   left behind. */
@media (min-width: 1025px) {
  body.in-lounge .pool-grid {
    grid-template-columns: 1fr 320px;
    grid-template-rows: 1fr;
    grid-template-areas: "picker chat";
    align-content: stretch;
    row-gap: 0;
  }
  body.in-lounge .room-picker {
    grid-area: picker;
    height: 100%;
    justify-content: flex-start;
    padding-top: 2rem;
    gap: 1.5rem;
  }
  body.in-lounge .room-picker-head h2 { font-size: 2rem; }
  body.in-lounge .room-list {
    max-width: 720px;
  }
  body.in-lounge .room-btn { padding: 1.1rem 1.2rem; }
  body.in-lounge .room-name { font-size: 1.15rem; }
  body.in-lounge .chat {
    grid-area: chat;
    height: 100%;
    max-height: none;
  }
}

/* Mark the AI seat visibly different — soft amber tint so it reads as
   "this seat is being played by a bot." */
.seat.ai {
  background: rgba(252, 211, 77, 0.15);
  border-color: rgba(252, 211, 77, 0.50);
}
.seat.ai .avatar {
  background: rgba(252, 211, 77, 0.35);
  font-size: 0.95rem;
}

/* --- Connection diagnostics ------------------------------------------ */
/* The button is a pure dot pinned to the top-right of the canvas-host
   (i.e. corner of the table closest to the chat). No text — colour and
   pulse animation convey state. Tap to open the diag modal. */
.diag-btn {
  position: absolute;
  /* Sit just inside the canvas-host's 1.5rem padding, so the dot floats
     in the felt area's top-right corner. */
  top: 0.45rem;
  right: 0.45rem;
  width: 18px;
  height: 18px;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.diag-btn:focus { outline: none; }
.diag-btn:focus-visible .diag-dot { box-shadow: 0 0 0 2px rgba(255,255,255,0.6); }

/* Room label — small uppercase identifier centered above the felt.
   Lives inside the canvas-host's top padding so it sits just above
   the brown rail. Hidden in the Lounge (no current room to name). */
.room-label {
  position: absolute;
  top: 0.5rem;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-weight: 700;
  color: rgba(255, 217, 176, 0.72);
  pointer-events: none;
  white-space: nowrap;
  z-index: 4;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
}
.room-label:empty { display: none; }
body.in-lounge .room-label { display: none; }
/* Prefix lowercase letter(s) — only the case differs from the rest of
   the label. The parent's font-size + letter-spacing carry through
   unchanged, so the M and the surname caps stay identical to every
   other letter; the "c" renders at the parent's font-size but at
   lowercase x-height (naturally shorter than cap-height). That's
   what produces "McGIRR'S" — same geometry as every other letter
   except the c is mixed-case. */
.room-label .name-prefix {
  text-transform: none;
}
/* Landscape phone: keep the label visible but tuck it tighter to the
   rail so it doesn't compete with the seat chips that float around
   the notch on the left. */
@media (orientation: landscape) and (max-height: 540px) {
  .room-label {
    top: 0.2rem;
    font-size: 0.55rem;
    letter-spacing: 0.18em;
  }
}

/* Lobby icon — pinned to the top-right of the canvas-host, just to the
   LEFT of the diag dot. Visible only when the player is inside a named
   room (not the Lounge itself). Tap to return to the Lounge. */
.lobby-btn {
  position: absolute;
  top: 0.3rem;
  /* Sit just inside the canvas-host's right padding, with enough room
     between the lobby icon and the diag dot to be unambiguously two
     separate tap targets. */
  right: 1.6rem;
  width: 26px;
  height: 26px;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.78);
  -webkit-tap-highlight-color: transparent;
  transition: color 0.12s ease;
}
.lobby-btn:hover { color: #fff; }
.lobby-btn:active { transform: translateY(1px); }
.lobby-btn[hidden] { display: none; }
.lobby-btn svg { display: block; width: 18px; height: 18px; }
.diag-dot {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #888;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.35);
  transition: background 0.2s ease;
}
.diag-dot[data-state="ok"]   { background: #25c557; box-shadow: 0 0 6px rgba(37,197,87,0.7); }
.diag-dot[data-state="warn"] { background: #f5b942; box-shadow: 0 0 6px rgba(245,185,66,0.7); animation: diag-pulse 1.4s ease-in-out infinite; }
.diag-dot[data-state="err"]  { background: #ef4444; box-shadow: 0 0 8px rgba(239,68,68,0.85);  animation: diag-pulse 0.9s ease-in-out infinite; }
@keyframes diag-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}

/* The modal itself. Full-viewport scrim + centred card. Body gains the
   .diag-open class while open so we can stop background pointer events
   bleeding through to the canvas during a touch-drag. */
.diag-modal {
  position: fixed;
  inset: 0;
  background: rgba(8, 4, 14, 0.74);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  padding: 1rem;
  /* Lift over iPhone safe-area so the close button is reachable. */
  padding-top: calc(1rem + env(safe-area-inset-top));
  padding-bottom: calc(1rem + env(safe-area-inset-bottom));
}
.diag-modal[hidden] { display: none; }
.diag-card {
  width: min(680px, 100%);
  max-height: 100%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: #14101e;
  color: #f4f0ff;
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 14px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
}
.diag-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid rgba(255,255,255,0.10);
  background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0));
}
.diag-h-title {
  font-weight: 700;
  flex: 1;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
}
.diag-copy, .diag-close, .diag-force {
  font: inherit; font-size: 0.85rem; font-weight: 600;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.18);
  color: inherit;
  border-radius: 999px;
  padding: 0.3em 0.85em;
  cursor: pointer;
}
.diag-close {
  width: 2rem;
  padding: 0;
  font-size: 1.3rem;
  line-height: 1;
  text-align: center;
}
.diag-copy:hover, .diag-close:hover, .diag-force:hover { background: rgba(255,255,255,0.18); }
.diag-force { color: #ffe1c8; }
.diag-body {
  overflow-y: auto;
  padding: 0.85rem 1rem 1rem;
  font-size: 0.86rem;
  line-height: 1.4;
}
.diag-pill {
  display: inline-block;
  margin-bottom: 0.7rem;
  padding: 0.18em 0.7em;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.diag-pill.ok   { background: rgba(37,197,87,0.18);  color: #8cf0a8; border: 1px solid rgba(37,197,87,0.5); }
.diag-pill.warn { background: rgba(245,185,66,0.18); color: #ffd680; border: 1px solid rgba(245,185,66,0.5); }
.diag-pill.err  { background: rgba(239,68,68,0.18);  color: #ffb6b6; border: 1px solid rgba(239,68,68,0.55); }
.diag-section {
  margin-bottom: 0.9rem;
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 8px;
  padding: 0.55rem 0.75rem;
}
.diag-title {
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.72);
  margin-bottom: 0.4rem;
}
.diag-row {
  display: flex;
  justify-content: space-between;
  gap: 0.8rem;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 0.82rem;
  padding: 0.1rem 0;
  border-top: 1px dotted rgba(255,255,255,0.06);
}
.diag-row:first-child { border-top: none; }
.diag-k { color: rgba(255,255,255,0.62); }
.diag-v { text-align: right; word-break: break-word; }
.diag-v .err { color: #ff8a8a; }
.diag-sub { color: rgba(255,255,255,0.45); font-weight: 400; }
.diag-empty { color: rgba(255,255,255,0.45); font-style: italic; }
.diag-cands {
  margin: 0; padding: 0; list-style: none;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 0.78rem;
  text-align: right;
}
.diag-cands li { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.diag-log {
  max-height: 240px;
  overflow-y: auto;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 0.78rem;
  background: rgba(0,0,0,0.30);
  border-radius: 6px;
  padding: 0.4rem 0.55rem;
}
.diag-log-row {
  display: flex;
  gap: 0.6rem;
  padding: 0.05rem 0;
  line-height: 1.35;
}
.diag-log-row .t { color: rgba(255,255,255,0.45); white-space: nowrap; }
.diag-log-row .m { word-break: break-word; }
.diag-log-row.warn  .m { color: #ffe1a8; }
.diag-log-row.error .m { color: #ff9b9b; }

/* Landscape phone: shrink padding & font so the modal still fits. */
@media (orientation: landscape) and (max-height: 540px) {
  .diag-card { max-height: 96vh; }
  .diag-body { padding: 0.5rem 0.7rem 0.7rem; font-size: 0.78rem; }
  .diag-log  { max-height: 140px; }
}

/* iPad / iPhone landscape: Safari's URL bar sits ON TOP of the layout
   viewport (env(safe-area-inset-top) is 0 in landscape because the
   notch is sideways), so the modal header was getting clipped behind
   it. Push the modal down enough to clear the bar in either compact
   or full Safari toolbar mode. Touch-only — desktop monitors in
   landscape don't have this problem. */
@media (orientation: landscape) and (hover: none) and (pointer: coarse) {
  .diag-modal {
    padding-top: calc(env(safe-area-inset-top) + 4.5rem);
    padding-bottom: calc(env(safe-area-inset-bottom) + 1.25rem);
  }
}
