/* RustyRoam — dark industrial + blue theme (re-themed from the original
   rust/orange look; CSS vars below are still named --gold/--orange/--rust
   for minimal churn across every file that references them — only their
   hex values changed) */
/* Google Fonts are linked from each page's <head> (with preconnect), not
   @import'ed here -- an @import only starts after this whole file loads. */

:root {
  --bg: #0a0d13;
  --bg-panel: #131826;
  --bg-panel-2: #1a2033;
  --bg-panel-3: #1f2740;
  --deposit-card-bg: #232b42; /* deposit/withdraw cards -- a step above bg-panel-3 so cards separate from the page, dark like the rest of the site (was #41475d, restyled 2026-09-29) */
  --border: #2c3450;
  --border-soft: #212942;
  --gold: #2f7dfd;
  --orange: #1b56c9;
  --rust: #123a7a;
  /* Shared elevation/accent tokens. Before these, every panel/card/tile
     hand-rolled its own shadow + hover glow, which is why surfaces read
     flat and slightly inconsistent from page to page -- one source of
     truth now, so a depth tweak lands everywhere at once. */
  --accent-wash: rgba(47,125,253,0.09);
  --accent-glow: rgba(47,125,253,0.22);
  --edge-hi: rgba(255,255,255,0.07);   /* 1px top highlight -- fakes a lit top edge */
  --elev-1: 0 2px 6px rgba(0,0,0,0.35), 0 10px 28px rgba(0,0,0,0.30);
  --elev-2: 0 4px 10px rgba(0,0,0,0.40), 0 18px 44px rgba(0,0,0,0.38);
  --ring: 0 0 0 2px rgba(47,125,253,0.55), 0 0 0 5px rgba(47,125,253,0.14);
  --text: #eceff5;
  --text-dim: #8a91a6;
  /* Rarity colors: trash brown, common grey, uncommon light green,
     rare light blue, epic purple, legendary orange, mythic gold -- per
     spec. rare/epic/mythic reuse the previous uncommon/rare/epic hexes
     (already proven to read well on this dark background), just shuffled
     to the ranks whose requested color they actually match. */
  --trash: #8b5e34;
  --common: #9e9e9e;
  --uncommon: #6ee86e;
  --rare: #4fc3f7;
  --epic: #b452f0;
  --legendary: #ff9d3d;
  --mythic: #f5c518;
  --good: #35d07f;
  --bad: #ff5c5c;
  --display-font: 'Oswald', 'Segoe UI', Arial, sans-serif;
  --body-font: 'Inter', 'Segoe UI', Arial, sans-serif;
}

* { box-sizing: border-box; }

html { scrollbar-color: var(--rust) var(--bg); }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: #1a2f52; border-radius: 8px; border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--rust); }

body {
  margin: 0;
  font-family: var(--body-font);
  /* Layers, back to front: base vertical gradient, a cool glow under the
     navbar, a warmer counter-glow low-left so the lower half of long pages
     isn't dead flat black, and a fine dot grid instead of the old 2px
     diagonal stripes (the stripes read as moire/noise once a page had any
     real content on it; a 3px dot on a 22px pitch gives the same
     "industrial surface" hint without competing with the UI). */
  background:
    radial-gradient(rgba(255,255,255,0.028) 1px, transparent 1px) 0 0 / 22px 22px,
    radial-gradient(ellipse 700px 520px at 8% 88%, rgba(47,125,253,0.05) 0%, transparent 65%),
    radial-gradient(ellipse 900px 600px at 50% -10%, rgba(47,125,253,0.12) 0%, transparent 60%),
    linear-gradient(180deg, #131826 0%, #0b0e14 45%, #050609 100%);
  background-attachment: fixed;
  color: var(--text);
  min-height: 100vh;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
/* Homepage only: the same diamond plate as .game-tabs-bar behind the whole
   page, dimmed under a dark wash so the games panel's brighter plate still
   reads as the raised centrepiece on top of it. */
body.home {
  background:
    radial-gradient(ellipse 700px 520px at 8% 88%, rgba(47,125,253,0.05) 0%, transparent 65%),
    radial-gradient(ellipse 900px 600px at 50% -10%, rgba(47,125,253,0.12) 0%, transparent 60%),
    linear-gradient(180deg, rgba(10,13,20,.5) 0%, rgba(7,9,14,.68) 45%, rgba(4,5,8,.82) 100%),
    url('/img/textures/diamond-plate.png?v=1') 0 0 / 128px 128px repeat,
    #0b0e14;
  background-attachment: fixed;
}

a { color: var(--gold); text-decoration: none; }

h1, h2, h3 {
  font-family: var(--display-font);
  font-weight: 700;
  /* Oswald is already a condensed face; the old +0.4px tracking on top of
     that made every heading on the site read slightly loose and washed
     out. Tightened, with the size-proportional bump kept only where caps
     genuinely need the air (see .section-title / .hero-home h1). */
  letter-spacing: 0.2px;
  margin: 0 0 8px;
}

/* One keyboard-focus treatment for the whole site. There was none before --
   tabbing through a page gave you the browser default outline on some
   controls and nothing at all on the div-based ones. :focus-visible (not
   :focus) so mouse clicks don't light buttons up. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: none;
  /* box-shadow only -- a ring drawn this way already follows whatever
     border-radius the element has (pills, the round odds button), so no
     radius is forced here. */
  box-shadow: var(--ring);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

.navbar {
  display: flex;
  align-items: center;
  padding: 14px 28px;
  /* Was a flat 2px --gold rule across the whole width, which read as a
     hard divider bar. Now a 1px hairline that's brightest under the logo
     and fades toward both ends, drawn as a ::after so it can be a
     gradient. The navbar also gets a slight blur behind it since it's
     sticky and content scrolls under. */
  /* Stays fully opaque, and deliberately has NO backdrop-filter. .brand-logo
     below relies on mix-blend-mode:screen to make its source PNG's solid
     black background disappear into the bar -- that blend resolves against
     whatever is actually behind the logo, so the moment this bar goes
     translucent (or backdrop-filter gives it its own stacking context) the
     black square stops cancelling out and shows as a grey box around the
     logo. Confirmed live. Frosted glass here costs the logo. */
  background: linear-gradient(180deg, #171d2e 0%, #0d1119 100%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.05) inset, 0 6px 22px rgba(0,0,0,0.55);
  flex-wrap: wrap;
  gap: 10px;
  position: sticky;
  top: 0;
  z-index: 100;
}

.navbar::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 1px;
  background: linear-gradient(90deg, transparent 0%, rgba(47,125,253,0.25) 12%, var(--gold) 50%, rgba(47,125,253,0.25) 88%, transparent 100%);
  box-shadow: 0 0 14px rgba(47,125,253,0.4);
  pointer-events: none;
}

.brand { display: flex; align-items: center; justify-content: center; }
.brand-logo {
  height: 80px; /* 2x (owner request, 2026-09-15; was 40px) */
  width: auto;
  display: block;
  /* the source PNG has a solid black background (no alpha) — screen blend
     makes pure black vanish into the navbar's near-black gradient so only
     the logo mark/text show, instead of a visible dark rectangle */
  mix-blend-mode: screen;
}

/* Wraps nav-links + nav-right so they share the navbar's remaining width as
   their own row, independent of .brand's own width -- see the @media block
   below, where .brand becomes a fixed-width column and this needs to keep
   filling whatever's left of it rather than being squeezed by a 3-way
   space-between across the whole bar. */
.navbar-body { display: flex; align-items: center; justify-content: space-between; flex: 1; gap: 10px; flex-wrap: wrap; min-width: 0; }

.nav-links { display: flex; gap: 22px; flex-wrap: wrap; }
.nav-links a {
  font-family: var(--display-font);
  color: var(--text-dim);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  padding: 6px 2px;
  border-bottom: 2px solid transparent;
  transition: color .12s ease, border-color .12s ease;
}
.nav-links a:hover, .nav-links a.active { color: var(--gold); border-color: var(--gold); }

/* Games dropdown. Eighteen games can't each have a top-level nav link, and
   before this the nav's answer to "where are the games" was Crates and
   nothing else -- every other game was reachable only from the homepage grid
   or by typing the URL, so once you were inside a game there was no way to
   reach another one without going back Home first (owner request,
   2026-09-19). Laid out as a 3-column grid so all eighteen fit in a panel
   that's still shorter than the viewport. */
.nav-games-wrap { position: relative; }
.nav-games-toggle {
  font-family: var(--display-font);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  color: var(--text-dim);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  padding: 6px 2px;
  transition: color .12s ease, border-color .12s ease;
}
.nav-games-toggle::after { content: '\25BE'; margin-left: 5px; font-size: 10px; vertical-align: 1px; }
.nav-games-toggle:hover { color: var(--gold); border-bottom-color: var(--gold); }
.nav-games-menu {
  position: absolute; top: calc(100% + 8px); left: 0;
  display: grid; grid-template-columns: repeat(3, minmax(112px, 1fr)); gap: 2px;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); z-index: 200;
}
/* Overrides .nav-links a above -- these are menu rows, not nav tabs, so they
   drop the uppercase display face and the underline-on-hover. */
.nav-games-menu a {
  font-family: var(--body-font);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 500;
  font-size: 13px;
  color: var(--text);
  padding: 8px 10px;
  border-bottom: none;
  border-radius: 6px;
  white-space: nowrap;
}
.nav-games-menu a:hover { background: var(--bg-panel-3); color: var(--gold); border-color: transparent; }
@media (max-width: 640px) {
  .nav-games-menu { grid-template-columns: repeat(2, minmax(104px, 1fr)); }
}

.nav-right { display: flex; align-items: center; gap: 14px; }

/* Desktop + a chat sidebar actually below it (js/nav.js sets this class off
   the same signed-in check chat.js uses to decide whether to render at all)
   -- the logo moves into the navbar's own strip directly above the sidebar
   (pulled out of the normal flex row so it lands in the 280px column
   #nav-root's negative margin opens up below, rather than after
   .navbar's padding-left like nav-links/nav-right) instead of living inside
   .chat-sidebar itself (see .chat-sidebar-brand below, hidden here so it
   isn't shown twice). .navbar is position:sticky, which -- same as
   relative/absolute/fixed -- still counts as "positioned" and establishes
   the containing block left/top/bottom below measure against, so left:0
   lands at .navbar's own left edge. Narrower screens / signed-out visitors
   keep the navbar's logo exactly as before (normal in-flow flex item),
   since there the sidebar is either collapsed by default (mobile) or never
   rendered at all (signed out) and wouldn't have a strip to line up with.
   Scoped to :not(.chat-collapsed) same as the #nav-root/.navbar rules
   below -- with the sidebar collapsed, .navbar isn't pushed/widened either
   (confirmed live: without this scoping the 280px-wide absolute .brand sat
   on top of "HOME/CRATES/REWARDS" once .navbar's own padding-left shrank
   back to normal), so .brand falls back to its plain in-flow style same as
   the no-sidebar case, and the sidebar's own logo (offscreen anyway while
   collapsed) is left alone rather than force-hidden. */
@media (min-width: 1300px) {
  body.nav-has-sidebar:not(.chat-collapsed) .navbar .brand {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 280px;
  }
  body.nav-has-sidebar:not(.chat-collapsed) .chat-sidebar-brand { display: none; }

  /* .navbar is sticky, not fixed -- it's a normal body child, so body's own
     padding-left:280px reserved for the fixed sidebar (below) shoves the
     WHOLE navbar box (its background/border/shadow included) 280px to the
     right along with it, same as any other body content. The fixed sidebar
     doesn't cover that gap either -- it only starts below the navbar's own
     height (js/chat.js's positionChatBelowNavbar()) -- so the result was a
     blank rectangle at the very top-left, above the sidebar's logo, with
     neither the navbar's background nor the sidebar painted into it
     (confirmed live via a user screenshot). #nav-root breaks back out to
     full viewport width (negative margin cancels the inherited padding,
     width compensates for what that margin ate off the right edge) so the
     navbar's own background/border spans edge-to-edge like before, while
     .navbar's own padding-left keeps its actual content (nav-links etc,
     .brand is hidden above anyway) indented past where the sidebar sits,
     lining up with the page content to its right. Scoped to
     :not(.chat-collapsed) same as the body padding rule itself -- with the
     sidebar collapsed there's no padding to cancel out in the first place. */
  body.nav-has-sidebar:not(.chat-collapsed) #nav-root {
    margin-left: -280px;
    width: calc(100% + 280px);
  }
  body.nav-has-sidebar:not(.chat-collapsed) .navbar {
    padding-left: 308px; /* 280px sidebar width + the navbar's own normal 28px */
  }
}

/* Wallet dropdown (js/nav.js) -- replaces the old plain "Deposit/Withdraw"
   button. Anchored to its own wrapper (position:relative) so the menu
   floats under just the Wallet button, not the whole navbar. */
.wallet-menu-wrap { position: relative; }
.wallet-menu {
  position: absolute; top: calc(100% + 6px); right: 0; min-width: 160px;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 6px; display: flex; flex-direction: column; gap: 2px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5); z-index: 200;
}
/* Deposit/Withdraw are plain nav-style links now (own pages, /deposit and
   /withdraw -- see js/nav.js), not buttons that open a modal -- Free Frags
   is still a real button (opens the Free Frags modal in place), so both
   need to look identical in this dropdown despite being different elements. */
.wallet-menu button, .wallet-menu a {
  display: block;
  background: transparent; border: none; color: var(--text); text-align: left;
  padding: 9px 10px; border-radius: 6px; font-size: 13px; cursor: pointer; font-family: inherit;
  text-decoration: none;
}
.wallet-menu button:hover, .wallet-menu a:hover { background: var(--bg-panel-3); color: var(--gold); }

/* Free Frags modal (js/nav.js) -- the list Wallet -> Free Frags opens into. */
.free-frags-list { display: flex; flex-direction: column; gap: 8px; }
.free-frags-item {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px; width: 100%;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px;
  text-decoration: none; color: var(--text); cursor: pointer; text-align: left; font-family: inherit; font-size: 14px;
}
.free-frags-item:hover:not(.free-frags-item-disabled) { border-color: var(--gold); }
.free-frags-item-disabled { opacity: 0.5; cursor: default; }
.free-frags-item-icon { font-size: 22px; flex-shrink: 0; }

/* Faucet view within the Free Frags modal -- swapped into #free-frags-body
   same as renderPromoCodeEntry() below it, not a separate page anymore (used
   to be testgame/faucet.html/.js, still there for direct links but the nav
   entry point now opens this instead). Text-centered layout matches what
   that standalone page looked like, just without its own <style> block. */
.faucet-modal-amount { font-family: var(--display-font); font-size: 38px; font-weight: 800; color: var(--gold); margin: 8px 0; text-align: center; }
#faucet-modal-claim-btn { width: 100%; margin-top: 14px; font-size: 17px; padding: 14px; }
#faucet-modal-countdown { font-size: 14px; color: var(--text-dim); margin-top: 8px; text-align: center; }
#turnstile-modal-container { display: flex; justify-content: center; margin-top: 12px; }

.balance-pill {
  font-family: var(--display-font);
  background: linear-gradient(180deg, var(--bg-panel-3), var(--bg-panel-2));
  border: 1px solid var(--gold);
  border-radius: 20px;
  padding: 6px 16px;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: var(--gold);
  box-shadow: 0 0 12px rgba(47,125,253,0.2) inset, 0 2px 6px rgba(0,0,0,0.3);
}

/* The little metal-fragment icon fmtFrags() (js/pb.js) puts in place of the
   word "Frags" everywhere a Frags amount is shown -- sized off the
   surrounding text (1em tall) so it scales correctly whether it's sitting
   in a 12px hint or a 24px price tag, rather than a fixed pixel size that'd
   look right in only one of those. Nudged up slightly (vertical-align) since
   mat-metal.webp's own silhouette sits a little low in its frame otherwise. */
.frag-icon {
  height: 1em;
  width: auto;
  vertical-align: -0.1em;
  margin-right: 3px;
}

/* Buttons used to be dark navy text (#051220) on a mid-blue gradient with a
   hard `0 3px 0` offset block underneath. Two problems: the label was only
   ~2.5:1 against its own background (the single worst contrast on the site,
   and it's on every primary action), and the offset block is a flat-design
   tic that dates the whole UI. Now: brighter blue ramp, white label, and
   real layered depth -- a lit top edge inset, a tight contact shadow, and a
   soft accent bloom. The press still travels, it just travels smoothly
   instead of snapping a solid slab in and out. */
.btn {
  position: relative;
  font-family: var(--display-font);
  background: linear-gradient(180deg, #3d88ff 0%, #2266e0 55%, #144aa8 100%);
  color: #fff;
  border: none;
  border-radius: 9px;
  padding: 11px 20px;
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  cursor: pointer;
  text-shadow: 0 1px 1px rgba(3,12,30,0.4);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.28),
    inset 0 -1px 0 rgba(0,0,0,0.25),
    0 2px 5px rgba(4,10,24,0.5),
    0 0 18px rgba(47,125,253,0.18);
  transition: transform .12s ease, box-shadow .12s ease, filter .12s ease;
}
.btn:hover {
  transform: translateY(-1px);
  filter: brightness(1.07);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.34),
    inset 0 -1px 0 rgba(0,0,0,0.25),
    0 5px 14px rgba(4,10,24,0.55),
    0 0 26px rgba(47,125,253,0.42);
}
.btn:active {
  transform: translateY(1px);
  filter: brightness(0.95);
  box-shadow: inset 0 2px 5px rgba(0,0,0,0.45), 0 1px 2px rgba(4,10,24,0.5);
}
.btn.secondary {
  background: linear-gradient(180deg, var(--bg-panel-3), var(--bg-panel-2));
  color: var(--text);
  text-shadow: none;
  border: 1px solid var(--border);
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 5px rgba(0,0,0,0.4);
}
.btn.secondary:hover {
  border-color: var(--gold);
  filter: none;
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 5px 14px rgba(0,0,0,0.45), 0 0 18px rgba(47,125,253,0.25);
}
.btn.danger {
  background: linear-gradient(180deg, #ff6b6b 0%, #e03b3b 55%, #a81f1f 100%);
  color: #fff;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.28),
    inset 0 -1px 0 rgba(0,0,0,0.25),
    0 2px 5px rgba(24,4,4,0.5),
    0 0 18px rgba(255,92,92,0.16);
}
.btn.danger:hover {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.34),
    inset 0 -1px 0 rgba(0,0,0,0.25),
    0 5px 14px rgba(24,4,4,0.55),
    0 0 26px rgba(255,92,92,0.38);
}
/* The diagonal-line texture used to sit on every .btn all the time, purely
   decorative -- now only shows up when a button actually can't be clicked,
   making it a real "this is disabled" signal instead of noise (see
   testgame/mines.js's start-btn double-click fix, which this was covering
   up: the button never visibly looked different while a round was active,
   so there was no cue not to click it again). A ::after overlay rather than
   part of `background` so it layers cleanly over .secondary/.danger's own
   backgrounds too instead of fighting them for the same property. */
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; }
.btn:disabled::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: repeating-linear-gradient(-45deg, rgba(0,0,0,0.12) 0px, rgba(0,0,0,0.12) 6px, transparent 6px, transparent 12px);
  pointer-events: none;
}
.btn-steam {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg, #24354a, #0e141c);
  color: #dbe6f0;
  text-shadow: none;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10), 0 2px 5px rgba(0,0,0,0.5);
}
.btn-steam:hover { box-shadow: inset 0 1px 0 rgba(255,255,255,0.14), 0 5px 14px rgba(0,0,0,0.55); }

.container { max-width: 1100px; margin: 0 auto; padding: 28px 20px 80px; }

/* The surface every page is built out of. `border-top-color` alone wasn't
   enough to separate a panel from the page behind it -- the old #262d40
   border sat about 4% off the panel fill, so on a busy page everything
   melted into one grey mass. Lifted border, a real 1px inner top highlight,
   and layered elevation now do the separating. */
.panel {
  position: relative;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border);
  border-top-color: #3a4463;
  border-radius: 14px;
  padding: 22px;
  margin-bottom: 22px;
  box-shadow: inset 0 1px 0 var(--edge-hi), var(--elev-1);
}

.hero {
  text-align: center;
  padding: 60px 20px;
}
.hero h1 { font-size: 42px; margin-bottom: 6px; }
.hero p { color: var(--text-dim); font-size: 17px; }

.hero-home {
  position: relative;
  overflow: hidden;
  padding: 84px 20px 58px;
  border-bottom: 1px solid var(--border);
  /* real procedurally-generated Rust map (img/rust-map.jpg) as the backdrop,
     darkened so the logo/text/buttons on top stay legible */
  background:
    linear-gradient(180deg, rgba(5,7,12,0.72) 0%, rgba(5,7,12,0.88) 65%, rgba(5,7,12,1) 100%),
    url('/img/rust-map.webp?v=1') center 30% / cover no-repeat;
}
.hero-glow {
  position: absolute;
  top: -220px; left: 50%;
  transform: translateX(-50%);
  width: 900px; height: 420px;
  background: radial-gradient(ellipse at center, rgba(47,125,253,0.28) 0%, rgba(47,125,253,0) 70%);
  pointer-events: none;
}
/* index.html writes the wordmark as Rusty<span style="color:var(--text)">Roam</span>
   -- i.e. the markup has always expected the h1 itself to be the accent
   color, with only "Roam" overridden back to white. No rule ever set that
   color, so "Rusty" just inherited --text and the whole wordmark rendered
   flat white, quietly losing the two-tone lockup. Setting it here restores
   it without touching the markup. */
.hero-home h1 {
  position: relative;
  color: #4a92ff;
  font-size: 58px;
  text-transform: uppercase;
  letter-spacing: 1px;
  text-shadow: 0 2px 24px rgba(0,0,0,0.6), 0 0 34px rgba(47,125,253,0.5);
}
.hero-home p { position: relative; max-width: 560px; margin: 0 auto; }
.hero-home #hero-cta { position: relative; margin-top: 26px; }

/* Home page wins ticker — scrolls continuously (owner request, 2026-09-19:
   the feed should "actually move"). It used to sit still and only animate on
   a genuinely new win, which on a site this size meant it was motionless
   almost always, and the entries past the right edge were simply clipped and
   unreachable. The track holds two identical copies of the sequence and
   translates exactly one copy's width, so the restart lands on a seam that
   looks like more of the same feed rather than a jump. Speed is set per
   render in js/index.js, not here, so a long feed doesn't race and a short
   one doesn't crawl. */
.wins-ticker-wrap {
  display: flex;
  align-items: center;
  gap: 14px;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border);
  border-top-color: #3a4463;
  border-radius: 12px;
  padding: 12px 18px;
  margin-bottom: 22px;
  overflow: hidden;
  box-shadow: inset 0 1px 0 var(--edge-hi), var(--elev-1);
}
.wins-ticker-label {
  flex: 0 0 auto;
  font-family: var(--display-font);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--gold);
  padding-right: 14px;
  border-right: 1px solid var(--border);
}
.wins-ticker {
  flex: 1;
  min-width: 0; /* without this the track's max-content width blows the flex row open */
  overflow: hidden;
  white-space: nowrap;
  /* Fade the entries out at both ends instead of guillotining them at the
     edge -- a half-cut word is what made the old static strip look broken. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 28px, #000 calc(100% - 44px), transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 28px, #000 calc(100% - 44px), transparent 100%);
}
.wins-ticker-track {
  display: flex;
  width: max-content;
  animation-name: wins-marquee;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.wins-ticker-seq { display: flex; align-items: center; gap: 10px; padding-right: 10px; }
@keyframes wins-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Hold still while someone is actually trying to read an entry. */
.wins-ticker-wrap:hover .wins-ticker-track { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .wins-ticker-track { animation: none; }
}
.wins-ticker-empty { color: var(--text-dim); font-size: 13px; }
.win-entry {
  flex: 0 0 auto;
  background: linear-gradient(180deg, var(--bg-panel-3), var(--bg-panel-2));
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 6px 14px;
  font-size: 13px;
  color: var(--text-dim);
  box-shadow: inset 0 1px 0 var(--edge-hi);
}
.win-entry strong { color: var(--text); }
.win-entry-amount { color: var(--good); font-weight: 700; }
/* A newly-landed win glows and fades back to normal. Used to be a slide-in
   from the left, which fought the marquee underneath it -- the entry was
   being translated by its own animation and the track's at the same time. */
.win-entry-new { animation: win-entry-flash 2.6s ease-out; }
@keyframes win-entry-flash {
  0%   { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold), 0 0 20px rgba(233, 176, 74, 0.55); }
  60%  { border-color: var(--gold); box-shadow: 0 0 0 1px rgba(233, 176, 74, 0.5), 0 0 12px rgba(233, 176, 74, 0.25); }
  100% { border-color: var(--border); box-shadow: inset 0 1px 0 var(--edge-hi); }
}

.level-progress-track {
  position: relative;
  background: rgba(5,7,12,0.7);
  border: 1px solid var(--border);
  border-radius: 10px;
  height: 12px;
  overflow: hidden;
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.5);
}
.level-progress-fill {
  position: relative;
  height: 100%;
  background: linear-gradient(90deg, #1b56c9 0%, #2f7dfd 60%, #6fb0ff 100%);
  border-radius: 10px 0 0 10px;
  box-shadow: 0 0 12px rgba(47,125,253,0.55), inset 0 1px 0 rgba(255,255,255,0.25);
  transition: width .45s cubic-bezier(.2,.7,.3,1);
}
/* Slow diagonal hatch crawling along the fill. It's the one animated
   element on an otherwise static progress readout, and it's what makes the
   bar look like accumulating progress rather than a painted rectangle. */
.level-progress-fill::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(115deg, rgba(255,255,255,0.14) 0px, rgba(255,255,255,0.14) 8px, transparent 8px, transparent 18px);
  animation: xp-crawl 1.1s linear infinite;
}
@keyframes xp-crawl { to { background-position: 18px 0; } }

/* Rank badge (owner request, 2026-09-18) -- per-rank artwork (bronze..
   addict, champion split into 5 sub-images) shown large next to the home
   page's level readout, see js/index.js's rankBadgeFile(). */
.rank-badge-row { display: flex; align-items: center; gap: 16px; }
.rank-badge { width: 84px; height: 84px; object-fit: contain; flex-shrink: 0; }
.rank-badge-row > div { flex: 1; min-width: 0; }

/* Level strip (owner request, 2026-09-18) -- every level in the rank ladder
   across the top of the rewards page, current level auto-scrolled to center
   via rewards.js's renderRankPanel(). Left/right padding to let the very
   first/last item reach center is set inline (needs the wrap's measured
   width), not here.
   Owner follow-up request: arrows instead of a scrollbar, and a hover
   tooltip showing what each level pays out -- .level-strip-nav is the
   position:relative host for the single shared #level-strip-tooltip (JS-
   positioned per hover via getBoundingClientRect, not a per-item CSS :hover
   popup, so it isn't clipped by .level-strip-wrap's own overflow-x:auto). */
.level-strip-nav { position: relative; display: flex; align-items: center; gap: 8px; margin-bottom: 18px; }
.level-strip-arrow {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-panel-2);
  color: var(--text);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .15s ease, border-color .15s ease;
}
.level-strip-arrow:hover { background: var(--bg-panel-3); border-color: var(--gold); }
.level-strip-wrap {
  flex: 1;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 10px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.level-strip-wrap::-webkit-scrollbar { display: none; }
.level-strip { display: flex; align-items: center; gap: 6px; width: max-content; }
.level-strip-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 58px;
  flex-shrink: 0;
  opacity: 0.4;
  filter: grayscale(60%);
  cursor: default;
  transition: opacity .2s ease, filter .2s ease, transform .2s ease;
}
.level-strip-item img { width: 34px; height: 34px; object-fit: contain; }
.level-strip-item span { font-size: 10px; color: var(--text-dim); text-align: center; white-space: nowrap; }
.level-strip-item.past { opacity: 0.65; filter: grayscale(20%); }
.level-strip-item.current {
  opacity: 1;
  filter: none;
  transform: scale(1.35);
}
.level-strip-item.current img {
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--bg-panel), 0 0 0 4px var(--gold);
}
.level-strip-item.current span { color: var(--text); font-weight: 700; }

.level-strip-tooltip {
  position: absolute;
  transform: translate(-50%, calc(-100% - 8px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 10px;
  white-space: nowrap;
  color: var(--text);
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease;
  z-index: 30;
  box-shadow: 0 4px 12px rgba(0,0,0,.4);
}
.level-strip-tooltip strong { font-size: 12px; }
.level-strip-tooltip span { color: var(--gold); font-size: 10px; }

/* ---------- XP gain bar ---------- */
/* Slides down from the top of the screen on every bet, showing the XP that
   bet earned plus progress toward the next level. Built and driven entirely
   by js/nav.js (see its "XP gain bar" block) -- no page includes markup for
   it. z-index 90 against .navbar's 100 is load-bearing: it makes the bar
   emerge from BEHIND the navbar instead of sliding over the top of it, which
   is also why .navbar has to stay fully opaque. --xp-bar-top is set from JS
   to the navbar's measured height. */
.xp-gain-bar {
  position: fixed;
  top: var(--xp-bar-top, 0px);
  left: 50%;
  z-index: 90;
  display: flex;
  align-items: center;
  gap: 12px;
  width: min(380px, calc(100vw - 32px));
  padding: 10px 16px 12px;
  border-radius: 0 0 14px 14px;
  background: linear-gradient(180deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border);
  border-top: none;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5), 0 0 26px var(--accent-glow);
  transform: translate(-50%, -140%);
  opacity: 0;
  pointer-events: none;   /* purely informational -- never eats a click meant for the page */
  transition: transform .45s cubic-bezier(.2,.8,.3,1), opacity .25s ease;
}
.xp-gain-bar.show { transform: translate(-50%, 0); opacity: 1; }
.xp-gain-badge { width: 34px; height: 34px; object-fit: contain; flex: 0 0 auto; }
.xp-gain-body { flex: 1; min-width: 0; }
.xp-gain-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 6px; }
.xp-gain-amount {
  font-family: var(--display-font);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.4px;
  color: #6fb0ff;
  text-shadow: 0 0 14px rgba(47,125,253,0.5);
  white-space: nowrap;
}
.xp-gain-meta {
  font-size: 11px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.xp-gain-track {
  height: 6px;
  border-radius: 4px;
  background: rgba(5,7,12,0.7);
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.5);
  overflow: hidden;
}
.xp-gain-fill {
  height: 100%;
  width: 0;
  border-radius: 4px;
  background: linear-gradient(90deg, #1b56c9 0%, #2f7dfd 60%, #6fb0ff 100%);
  box-shadow: 0 0 10px rgba(47,125,253,0.6);
  transition: width .5s cubic-bezier(.2,.7,.3,1);
}
/* Level-up variant -- swaps to gold and lingers ~5s instead of ~3.2s (the
   duration lives in nav.js, not here). */
.xp-gain-bar.levelled {
  border-color: rgba(245,197,24,0.5);
  box-shadow: 0 10px 30px rgba(0,0,0,0.5), 0 0 30px rgba(245,197,24,0.35);
}
.xp-gain-bar.levelled .xp-gain-amount { color: var(--mythic); text-shadow: 0 0 14px rgba(245,197,24,0.55); }
.xp-gain-bar.levelled .xp-gain-fill {
  background: linear-gradient(90deg, #a67c00 0%, var(--mythic) 60%, #ffe27a 100%);
  box-shadow: 0 0 12px rgba(245,197,24,0.6);
}

/* Home page game grid -- the six headline games, three across. Briefly held
   all eighteen; the owner wanted it back to six (2026-09-19). Nothing became
   unreachable in the process: the nav's Games dropdown (js/nav.js) still
   lists every game, which is what actually fixed "you can only get to a game
   from the homepage". */
/* The tiles sit on a diamond-plate steel panel (img/textures/diamond-plate.png,
   a seamless 128px tile). Rivets, sheen and vignette are extra background
   layers rather than ::before/::after -- this is a grid container, and
   pseudo-elements would become grid items. */
.game-tabs-bar {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-bottom: 30px;
  padding: 22px;
  border-radius: 16px;
  border: 1px solid var(--border);
  --rivet: radial-gradient(circle, #56627f 0 2px, #2a3247 2.5px 3.5px, rgba(0,0,0,.55) 4.5px, transparent 6px);
  background:
    var(--rivet) left 3px top 3px / 16px 16px no-repeat,
    var(--rivet) right 3px top 3px / 16px 16px no-repeat,
    var(--rivet) left 3px bottom 3px / 16px 16px no-repeat,
    var(--rivet) right 3px bottom 3px / 16px 16px no-repeat,
    linear-gradient(115deg, rgba(255,255,255,.055) 0%, transparent 32%, transparent 68%, rgba(255,255,255,.03) 100%),
    radial-gradient(130% 100% at 50% 0%, transparent 50%, rgba(0,0,0,.55) 100%),
    url('/img/textures/diamond-plate.png?v=1') 0 0 / 128px 128px repeat,
    #131824;
  box-shadow: inset 0 1px 0 var(--edge-hi), inset 0 0 50px rgba(0,0,0,.45), var(--elev-1);
}
/* Icon over label, not beside it -- at three across these are wide, and a
   single centered row of icon+text left the tile looking empty through the
   middle. Stacked, the glyph gets to be the thing you see first. */
.game-tab-tile {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 132px;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border);
  border-top-color: #3a4463;
  border-radius: 12px;
  padding: 24px 14px;
  font-family: var(--display-font);
  font-size: 16px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  text-align: center;
  color: var(--text);
  box-shadow: inset 0 1px 0 var(--edge-hi), var(--elev-1);
  transition: transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .18s ease, border-color .18s ease, color .18s ease;
}
/* These tiles are the main entry point to every game, so they get the
   strongest hover on the site: the accent wash plus a sheen that sweeps
   across on enter. Contained by overflow:hidden on the tile. */
.game-tab-tile::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(var(--tile-rgb), 0.11) 0%, transparent 60%);
  opacity: 0;
  transition: opacity .18s ease;
  pointer-events: none;
}
.game-tab-tile::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: -60%;
  width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,0.07), transparent);
  transform: skewX(-18deg);
  pointer-events: none;
}
/* Per-game accent, as raw channels rather than a color so one custom
   property can drive the glyph, its bloom, the hover wash and the hover
   border/glow -- otherwise each game would need four vars that have to be
   kept in agreement by hand. Warm hues on the top row, cool on the bottom,
   which is what stops six colors reading as a rainbow. The twelve games not
   currently on the homepage keep their entry here so putting a tile back is
   a markup change and nothing else. */
.game-tab-tile { --tile-rgb: 47,125,253; }   /* fallback: the site accent */
.gt-crates    { --tile-rgb: 245,197,24; }
.gt-mines     { --tile-rgb: 255,92,92; }
.gt-dig       { --tile-rgb: 255,157,61; }
.gt-upgrader  { --tile-rgb: 53,208,127; }
.gt-wheel     { --tile-rgb: 180,82,240; }
.gt-battles   { --tile-rgb: 79,195,247; }
.gt-dice      { --tile-rgb: 110,232,110; }
.gt-crash     { --tile-rgb: 255,120,80; }
.gt-plinko    { --tile-rgb: 120,160,255; }
.gt-roulette  { --tile-rgb: 232,86,120; }
.gt-slots     { --tile-rgb: 245,197,24; }
.gt-blackjack { --tile-rgb: 220,228,240; }
.gt-coinflip  { --tile-rgb: 236,176,74; }
.gt-jackpot   { --tile-rgb: 255,180,40; }
.gt-chaindig  { --tile-rgb: 154,176,200; }
.gt-cratedrop { --tile-rgb: 100,214,190; }
.gt-roam      { --tile-rgb: 126,214,120; }
.gt-faucet    { --tile-rgb: 90,196,255; }
.gt-rocket    { --tile-rgb: 198,140,86; }    /* wood, not another bright */

/* Rockets is the seventh tile and seven divides into neither three columns
   nor two, so instead of leaving one orphan sitting in the last row it takes
   the whole row. 1 / -1 spans whatever the column count currently is, so the
   same rule covers both breakpoints. Full width at the normal 132px would be
   a mostly-empty band, so this one lays the glyph beside the label rather
   than over it and sits shorter -- which also reads as the feature row it is. */
.game-tab-tile.gt-rocket {
  grid-column: 1 / -1;
  flex-direction: row;
  min-height: 84px;
  gap: 14px;
}

/* One geometric line-glyph per game, drawn once as an inline <symbol> sprite
   at the top of index.html and pulled in per tile with <use> -- nothing new
   to host, and one HTTP request fewer than the PNGs it replaces. The sprite
   still carries all eighteen even though six are on the page, for the same
   reason the accents above do. Phase 2's real art can swap them per tile
   without touching this layout. */
.game-tab-tile-icon {
  position: relative;
  width: 46px;
  height: 46px;
  flex-shrink: 0;
  color: rgb(var(--tile-rgb));
  /* A soft bloom in the glyph's own color, so the color reads as something
     lit rather than a flat swatch. drop-shadow follows the stroke outline
     (box-shadow would halo the empty 46px box instead). */
  filter: drop-shadow(0 0 7px rgba(var(--tile-rgb), 0.35));
  /* fill/stroke are inherited properties, so setting them here carries
     through <use>'s shadow content into the symbol -- which is why the
     symbols themselves carry pure geometry and no styling. A shape that
     needs to be solid (dice pips, plinko pegs) sets fill on itself, and a
     declaration on the element always beats an inherited value. */
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: filter .18s ease, transform .18s cubic-bezier(.2,.7,.3,1);
}
.game-tab-tile-label { position: relative; }
.emoji-icon { height: 1em; width: 1em; object-fit: contain; vertical-align: -0.15em; flex-shrink: 0; }
/* The sprite itself is markup, not art -- kept out of flow and out of the
   accessibility tree rather than display:none, which in some browsers stops
   <use> resolving against it at all. */
.svg-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
/* Hover picks up the same accent -- the tile lights in the game's own color
   rather than every tile flashing the site blue. */
.game-tab-tile:hover {
  transform: translateY(-3px);
  color: #fff;
  border-color: rgba(var(--tile-rgb), 0.55);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10), var(--elev-2), 0 0 26px rgba(var(--tile-rgb), 0.24);
}
.game-tab-tile:hover .game-tab-tile-icon {
  transform: translateY(-2px) scale(1.07);
  filter: drop-shadow(0 0 12px rgba(var(--tile-rgb), 0.7));
}
.game-tab-tile:hover::before { opacity: 1; }
.game-tab-tile:hover::after { animation: tile-sheen .7s ease forwards; }
@keyframes tile-sheen { to { left: 115%; } }
@media (prefers-reduced-motion: reduce) {
  .game-tab-tile, .game-tab-tile-icon { transition: filter .18s ease; }
  .game-tab-tile:hover { transform: none; }
  .game-tab-tile:hover .game-tab-tile-icon { transform: none; }
  .game-tab-tile:hover::after { animation: none; }
}
/* 2 across rather than 1 -- six full-width tiles would be most of a phone
   screen's scroll before anything else on the page. */
@media (max-width: 620px) {
  .game-tabs-bar { grid-template-columns: repeat(2, 1fr); gap: 10px; padding: 14px; }
  .game-tab-tile { min-height: 112px; font-size: 14px; padding: 18px 8px; }
  .game-tab-tile-icon { width: 38px; height: 38px; }
}

/* Flat 4px accent bar -> a gradient rule that fades out toward the bottom
   and carries a little bloom, so the heading anchors the section instead of
   looking like a stray border. ::before rather than border-left because a
   border can't be gradient-filled and still follow the rounded ends. */
.section-title {
  position: relative;
  font-family: var(--display-font);
  font-size: 21px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--text);
  padding-left: 14px;
  margin-bottom: 18px;
}
.section-title::before {
  content: '';
  position: absolute;
  left: 0; top: 4%; bottom: 4%;
  width: 4px;
  border-radius: 3px;
  background: linear-gradient(180deg, #5fa0ff 0%, var(--gold) 45%, rgba(47,125,253,0.25) 100%);
  box-shadow: 0 0 12px rgba(47,125,253,0.45);
}

.showcase-strip {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.showcase-strip .card { padding: 14px; }
.showcase-strip img { filter: drop-shadow(0 4px 10px rgba(0,0,0,0.5)); }

.game-card { display: flex; flex-direction: column; }
.game-card .btn { margin-top: auto; }

.odds-btn {
  position: absolute;
  top: 10px; right: 10px;
  z-index: 2;   /* above .crate-card-art and the card's hover wash */
  width: 24px; height: 24px;
  border-radius: 50%;
  /* Sits on top of crate artwork, so it needs to hold its own against
     whatever is behind it -- translucent dark + blur instead of a flat
     panel color that only worked against a plain card. */
  background: rgba(10,14,22,0.68);
  backdrop-filter: blur(4px);
  border: 1px solid rgba(255,255,255,0.14);
  color: var(--text-dim);
  font-family: var(--display-font);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .12s ease, color .12s ease, border-color .12s ease, transform .12s ease;
}
.odds-btn:hover {
  background: var(--gold);
  color: #051220;
  border-color: var(--gold);
  transform: scale(1.12);
  box-shadow: 0 0 14px rgba(47,125,253,0.5);
}

/* true popup overlays for case-opening / odds modals, instead of inline panels */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(4, 6, 10, 0.72);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
  animation: modal-fade .18s ease;
}
@keyframes modal-fade { from { opacity: 0; } }
.modal-box {
  position: relative;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border);
  border-top-color: #3a4463;
  border-radius: 14px;
  padding: 28px;
  max-width: 620px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 24px 70px rgba(0,0,0,0.65), 0 0 0 1px rgba(47,125,253,0.08);
  animation: modal-rise .22s cubic-bezier(.2,.7,.3,1);
}
@keyframes modal-rise { from { opacity: 0; transform: translateY(12px) scale(.985); } }
.modal-box::before {
  content: '';
  position: absolute;
  top: 0; left: 14px; right: 14px;
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
  border-radius: 0 0 3px 3px;
}
.modal-box h3 { font-size: 19px; text-transform: uppercase; letter-spacing: 0.5px; }

/* Settings modal -- sidebar of tabs down the left (reference screenshot the
   user sent) instead of the row-of-buttons style the Deposit/Withdraw modal
   uses. Fixed height (not max-height like the base .modal-box) so the two
   columns hold a stable shape, with .settings-content-area scrolling
   internally instead of the whole box growing/shrinking per tab. */
.settings-modal-box {
  max-width: 820px;
  height: 660px;
  max-height: 88vh;
  padding: 0;
  display: flex;
  overflow: hidden;
}
.settings-sidebar {
  flex: 0 0 200px;
  background: linear-gradient(165deg, var(--bg-panel-3) 0%, var(--bg-panel) 100%);
  border-right: 1px solid var(--border);
  padding: 18px 0;
  overflow-y: auto;
}
.settings-sidebar-item {
  padding: 12px 20px;
  font-family: var(--display-font);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  border-left: 3px solid transparent;
  transition: background .12s ease, color .12s ease;
}
.settings-sidebar-item:hover { color: var(--text); background: rgba(255,255,255,0.03); }
.settings-sidebar-item.active {
  color: var(--gold);
  background: rgba(47,125,253,0.12);
  border-left-color: var(--gold);
}
.settings-content-area {
  flex: 1;
  min-width: 0;
  padding: 24px 28px;
  overflow-y: auto;
}
.settings-content-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.settings-content-header h3 { margin: 0; }
.settings-icon-btn {
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  font-size: 15px;
  cursor: pointer;
  transition: color .12s ease, border-color .12s ease;
}
.settings-icon-btn:hover { color: var(--gold); border-color: var(--gold); }

.grid { display: grid; gap: 18px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }

/* Deposit/Withdraw page (wallet.html, /deposit and /withdraw) -- used to be
   a modal (.deposit-modal-box, wide and short instead of the default tall
   centered modal-box so a whole inventory grid fit without much scrolling);
   as an actual page now it just needs the shared .container widened past
   its normal 1100px cap, same :has() pattern as the wheel page's own
   override below. Deposit tab is a two-column layout: item grid + a running
   "Selected" side panel (see .deposit-content-layout below). */
.container:has(#deposit-tab-content) { max-width: none; }

/* Wallet page header -- round back button + big uppercase title + dim
   subtitle, both set from JS (js/deposit.js's loadDepositTab()/
   loadWithdrawTab()) since the same header now serves either tab instead of
   the old static "Deposit / Withdraw" heading. history.back() rather than a
   fixed href -- whatever page actually sent them to the Wallet menu is a
   better "back" than always assuming Home. */
.wallet-header { display: flex; align-items: flex-start; gap: 14px; }
.wallet-back-btn {
  width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
  background: var(--bg-panel-2); border: 1px solid var(--border);
  color: var(--text); font-size: 18px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.wallet-back-btn:hover { border-color: var(--gold); color: var(--gold); }
.wallet-page-title { margin: 0; font-size: 21px; letter-spacing: 0.03em; text-transform: uppercase; }
.wallet-page-subtitle { margin: 4px 0 0; color: var(--text-dim); font-size: 13px; }

/* Item-count + search/sort/refresh row above the grid, both tabs -- replaces
   the old "Inventory value" stat, which doesn't fit a marketplace listing
   (Withdraw) the same way it fit a personal inventory (Deposit) anyway. */
.deposit-toolbar-v2 { display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 12px; margin: 16px 0 14px; }
.deposit-toolbar-count { font-weight: 700; font-size: 15px; }
.deposit-toolbar-updated { color: var(--text-dim); font-size: 11px; margin-top: 2px; }
.deposit-toolbar-actions { display: flex; gap: 8px; align-items: center; }
.deposit-search-input { width: 200px; }
.deposit-icon-btn {
  width: 34px; height: 34px; border-radius: 8px; flex-shrink: 0;
  background: var(--bg-panel-2); border: 1px solid var(--border); color: var(--text-dim);
  cursor: pointer; font-size: 15px; display: flex; align-items: center; justify-content: center;
}
.deposit-icon-btn:hover { border-color: var(--gold); color: var(--gold); }

.deposit-content-layout { display: flex; gap: 16px; align-items: flex-start; }
.deposit-content-layout .deposit-grid { flex: 1; min-width: 0; }
/* Withdraw tab (owner request, 2026-09-15 batch-withdraw change) wraps its
   grid in .withdraw-panel-bg for the floor styling below, so THAT'S the
   direct flex child here now, not .deposit-grid itself -- without its own
   flex:1/min-width:0 it sized to its content's max-content width instead of
   the space .deposit-selected-panel left it, so grid-template-columns'
   auto-fill never had a bounded width to wrap against and every card landed
   in one long unbroken row. Deposit tab's own grid has no such wrapper (see
   js/deposit.js's renderDepositLayout()) so it never needed this. */
.deposit-content-layout .withdraw-panel-bg { flex: 1; min-width: 0; }
.deposit-selected-panel {
  flex: 0 0 330px;
  background: rgba(10,14,22,0.5);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  position: sticky;
  top: 0;
}
.deposit-selected-header { display: flex; justify-content: space-between; align-items: center; font-size: 12px; font-weight: 700; margin-bottom: 8px; }
.deposit-clear-btn { background: none; border: none; color: var(--bad); font-size: 11px; cursor: pointer; padding: 0; }
.deposit-clear-btn:hover { text-decoration: underline; }
.deposit-selected-list { max-height: 260px; overflow-y: auto; margin-bottom: 8px; }
.deposit-selected-row {
  display: flex; align-items: center; gap: 8px;
  font-size: 11px; padding: 6px 0; border-bottom: 1px solid var(--border);
}
.deposit-selected-icon { width: 44px; height: 44px; object-fit: contain; flex-shrink: 0; }
.deposit-selected-name { flex: 1; min-width: 0; color: var(--gold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deposit-selected-price { color: #f5c518; flex-shrink: 0; font-size: 16px; font-family: var(--display-font); font-weight: 700; }
.deposit-selected-total {
  display: flex; justify-content: space-between;
  font-size: 13px; font-weight: 700; padding-top: 6px;
}
/* Real green (not --good's usual small accents) so this reads as THE action
   on the page, same reasoning .deposit-card-price already used to break from
   --gold's blue-in-this-theme confusion (see that rule's own comment). */
.deposit-submit-btn { background: linear-gradient(180deg, #35d07f, #0f7a3d); color: #04140a; box-shadow: 0 3px 0 #0a4d27; }
.deposit-submit-btn:hover:not(:disabled) { box-shadow: 0 4px 0 #0a4d27; }
.deposit-submit-btn:active:not(:disabled) { box-shadow: 0 1px 0 #0a4d27; }

/* Withdraw tab's own floor -- the modal and .card both use the same
   bg-panel/bg-panel-2 gradient, so cards barely stood out from the modal
   around them here (nothing else in that tab, like the Deposit side's
   selected-panel, was already breaking that up). --bg is this theme's
   darkest tone, well below either panel shade, so the cards read as
   distinct lighter panels sitting on top of it instead of blending in. */
.withdraw-panel-bg {
  background: var(--bg);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  padding: 14px;
}
/* Every deposit/withdraw card, not just the floor under them (was scoped to
   just .withdraw-panel-bg .card at first, but the Deposit tab's cards have
   the identical problem and never got it -- .card's own gradient fades
   toward bg-panel (#131826), which sits close enough to the modal/floor
   tones around it that the card edge nearly disappeared at that end of the
   gradient, making the item art/text hard to make out). A flat, lighter
   tone keeps the contrast even on all sides instead of fading out on one. */
/* Compound selector (not just .deposit-card) so this reliably beats .card's
   own background rule regardless of which one happens to come later in the
   file -- .card's rule actually sits after this in source order, so a
   same-specificity .deposit-card-only rule here would have silently lost. */
.card.deposit-card { background: var(--deposit-card-bg); }
/* Rarity tint (trash/common/uncommon/rare/epic/legendary/mythic, same
   colors as the rest of the site's rarity system) is a per-card inline
   background instead of a class here -- it depends on which item the card
   is, set from js/deposit.js, and layers a low-alpha wash of that rarity's
   color over .deposit-card-bg above rather than replacing it, so it reads
   as a hint, not a full recolor. Cards for items with no rarity match (most
   real Steam inventory items -- see pb_hooks/deposit.pb.js) just keep the
   plain background above, no inline override applied. */

/* Item grid -- smaller, denser cards than the crate/loot grid, each with the
   site logo behind it as a faint watermark (rustyloot-style reference the
   user sent -- same logo on every card, not each item's own icon), sized to
   just barely peek out from behind the item's own icon rather than looming
   oversized behind the whole card. */
.deposit-grid { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 14px; }
/* Square cells regardless of actual rendered width (grid's 1fr can stretch
   columns wider than the 170px floor above depending on how many fit per
   row) -- aspect-ratio keeps height matching whatever that width ends up
   being, per-card. */
.deposit-card { padding: 16px 9px 11px; overflow: hidden; cursor: pointer; transition: border-color .12s ease, box-shadow .12s ease; aspect-ratio: 1 / 1; display: flex; flex-direction: column; justify-content: center; }
.deposit-card-locked { cursor: default; opacity: 0.75; }
/* Real gold/amber (same literal #f5c518 as .deposit-card-price below), not
   var(--gold) -- var(--gold) is this theme's blue accent (see the :root
   comment above), which reads as just another normal card border, not a
   "this one's picked" signal. */
.deposit-card.selected { border-color: #f5c518; box-shadow: 0 0 0 2px #f5c518 inset; }
/* .deposit-card-icon-wrap is the "box" (80x80, see js/deposit.js) -- kept at
   its original size on purpose. The icon and watermark inside it are each
   33% bigger than that box and positioned absolute+centered so they overflow
   it symmetrically, rather than growing the box itself (which is what
   "square/card size" is set by, above). Both use fixed pixel sizes now
   instead of percentages of the wrap, since a percentage can't exceed 100%
   of its own container. */
.deposit-card-icon-wrap { position: relative; margin: 0 auto 4px; }
.deposit-card-icon {
  position: absolute;
  top: 50%; left: 50%;
  width: 106px; height: 106px;
  transform: translate(-50%, -50%);
  object-fit: contain;
  z-index: 1;
}
.deposit-card-watermark {
  position: absolute;
  top: 50%; left: 50%;
  width: 101px; height: 101px;
  transform: translate(-50%, -50%);
  object-fit: contain;
  filter: grayscale(100%);
  opacity: 0.15;
  z-index: 0;
  pointer-events: none;
}
.deposit-card-fg { position: relative; z-index: 1; }
/* Always visible now -- used to be opacity:0 until :hover (a name-on-hover
   affordance), reported as wanting the always-on-label look of the
   reference site's own item grid instead. */
.deposit-card-fg h3 {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: 14px; margin: 4px 0;
}
/* --gold is this theme's blue accent (see the :root comment up top), not an
   actual gold color, so this uses a real gold/amber tone directly instead --
   same value already proven against this exact dark background as
   --mythic's rarity color. Left-aligned (the card's own text-align:center
   otherwise centers it) and a bigger frag-icon so it reads as the card's
   bottom-left anchor rather than blending into the centered name above it. */
.deposit-card-price {
  color: #f5c518;
  font-weight: 700;
  font-size: 14px;
  margin-top: 4px;
  text-align: left;
  display: flex;
  align-items: center;
}
.deposit-card-price .frag-icon { height: 1.6em; margin-right: 4px; }

/* Stock-count badge, top-right corner of a Withdraw grid card -- shown only
   when a marketplace "stack" (js/deposit.js's groupMarketplaceStacks(), one
   card per distinct market_hash_name instead of one per deposited_items row,
   owner reference screenshot 2026-09-15) actually has more than one unit in
   stock; a single-unit item just looks like it always did, no badge. */
.deposit-stack-badge {
  position: absolute;
  top: 8px; right: 8px;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px 7px;
  font-size: 11px;
  font-weight: 700;
  color: var(--gold);
  z-index: 2;
}
/* "3x selected" tag under the price when a partial batch of a stack is
   picked -- real gold (#f5c518, not var(--gold)) so it reads as the same
   "picked" signal as .deposit-card.selected's own border. */
.deposit-stack-selected-tag { color: #f5c518; font-size: 11px; font-weight: 700; margin-top: 2px; }

/* Withdraw quantity popup (owner reference screenshot, 2026-09-15): opened by
   clicking a Withdraw grid card instead of toggling selection directly --
   picks how many units of that one stacked item to add, since a stack can
   hold several identical deposited_items rows. #withdraw-qty-modal itself
   reuses the shared .modal-overlay backdrop (see .modal-box above); only the
   inner box is bespoke, sized much smaller than the default .modal-box. */
.qty-modal-box {
  position: relative;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid #f5c518;
  border-radius: 14px;
  padding: 24px;
  width: 220px;
  text-align: center;
  box-shadow: 0 24px 70px rgba(0,0,0,0.65);
}
.qty-modal-stock-badge {
  position: absolute;
  top: 10px; right: 10px;
  display: flex; align-items: center; gap: 4px;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px 7px;
  font-size: 11px;
  font-weight: 700;
  color: var(--gold);
}
.qty-modal-stock-dot { width: 8px; height: 8px; border-radius: 2px; background: var(--good); flex-shrink: 0; }
.qty-modal-icon-wrap { position: relative; width: 130px; height: 130px; margin: 8px auto 6px; }
.qty-modal-icon-wrap .deposit-card-icon { width: 120px; height: 120px; }
.qty-modal-icon-wrap .deposit-card-watermark { width: 114px; height: 114px; }
.qty-modal-box h3 { font-size: 14px; margin: 6px 0 2px; text-transform: none; letter-spacing: normal; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.qty-modal-price { color: #f5c518; font-weight: 700; font-size: 15px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; }
.qty-modal-price .frag-icon { height: 1.6em; margin-right: 4px; }
.qty-modal-stepper { display: flex; align-items: center; justify-content: center; gap: 18px; }
.qty-modal-step-btn {
  width: 34px; height: 34px; border-radius: 8px; flex-shrink: 0;
  background: var(--bg-panel-3); border: 1px solid var(--border); color: var(--text);
  font-size: 17px; font-weight: 700; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.qty-modal-step-btn:hover:not(:disabled) { border-color: var(--gold); color: var(--gold); }
.qty-modal-step-btn:disabled { opacity: 0.4; cursor: default; }
.qty-modal-count { font-size: 16px; font-weight: 700; min-width: 30px; }

/* Withdraw grid's inline quantity stepper (deposit-card-stepper) -- NOT the
   same thing as qty-modal above; this is the card-inline version used by
   marketplaceCardHtml() in js/deposit.js and battle-crate-pick-tile in
   testgame/battles-create.js. Restored after this got dropped by another
   edit; keep both -/+ classed distinctly (red minus, blue plus, 30px). */
.deposit-card-stepper { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 4px; }
.deposit-card-step-btn {
  width: 30px; height: 30px; border-radius: 7px; flex-shrink: 0;
  background: var(--bg-panel-3); border: 1px solid var(--border); color: var(--text);
  font-size: 20px; font-weight: 700; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.deposit-card-stepper button:first-of-type { border-color: #ff5c5c; color: #ff5c5c; }
.deposit-card-stepper button:first-of-type:hover:not(:disabled) { background: rgba(255,92,92,0.15); }
.deposit-card-stepper button:last-of-type { border-color: #4da3ff; color: #4da3ff; }
.deposit-card-stepper button:last-of-type:hover:not(:disabled) { background: rgba(77,163,255,0.15); }
.deposit-card-step-btn:disabled { opacity: 0.4; cursor: default; }
.deposit-card-step-count { font-size: 13px; font-weight: 700; min-width: 22px; text-align: center; color: #f5c518; }

.card {
  position: relative;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border);
  border-top-color: #3a4463;
  border-radius: 12px;
  padding: 18px 16px 16px;
  text-align: center;
  box-shadow: inset 0 1px 0 var(--edge-hi), var(--elev-1);
  transition: transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .18s ease, border-color .18s ease;
}
/* The two decorative "screw head" dots that used to sit in the top corners
   are gone -- skeuomorphic detail at 5px reads as dirt on a screen, not as
   hardware, and they collided with the odds button on crate cards. The
   accent wash below replaces them as the card's hover personality: a
   gradient that fades in from the top edge, so a hovered card looks lit
   rather than just outlined. */
.card::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(180deg, var(--accent-wash) 0%, transparent 55%);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease;
}
.card:hover {
  transform: translateY(-3px);
  border-color: rgba(47,125,253,0.55);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10), var(--elev-2), 0 0 24px var(--accent-glow);
}
.card:hover::before { opacity: 1; }
.card h3 {
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.case-emoji, .item-emoji {
  font-size: 30px;
  margin: 0 auto 12px;
  width: 64px; height: 64px;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(circle, rgba(47,125,253,0.16) 0%, rgba(47,125,253,0) 72%);
  border: 1px solid var(--border);
  border-radius: 50%;
}
.item-emoji { background: radial-gradient(circle, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0) 72%); }
.item-emoji img { border-radius: 6px; }
/* Big-photo crate tile (crates grid) -- layered on top of the shared .card
   rather than changing it, since .card is reused by unrelated grids too. */
.crate-card { cursor: pointer; }
.crate-card-art {
  position: relative;
  width: 100%;
  aspect-ratio: 1.344 / 1;
  border-radius: 10px;
  overflow: hidden;
  margin: 0;
  /* A hairline inset ring gives the art a defined edge -- without it the
     crate renders straight onto the card fill and the tile has no focal
     point. */
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05);
  background:
    radial-gradient(circle at 50% 42%, rgba(47,125,253,0.16) 0%, rgba(47,125,253,0) 70%),
    rgba(0,0,0,0.18);
}
/* Art pushes in slightly as the card lifts -- the two moving together read
   as one object rather than a picture sliding around inside a frame. */
.crate-card-art img { transition: transform .35s cubic-bezier(.2,.7,.3,1); }
.crate-card:hover .crate-card-art img { transform: scale(1.06); }
/* caseVisual() bakes in fixed pixel width/height for its other (small,
   fixed-size) callers -- override just here so the art fills the tile
   responsively instead of rendering at its passed-in pixel size. */
.crate-card-art img {
  width: 100% !important;
  height: 100% !important;
  object-fit: cover !important;
  border-radius: 10px !important;
}
.crate-card h3 {
  margin: 0 0 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.crate-risk-bar {
  position: relative;
  width: 100%;
  height: 5px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--bg-panel-3);
  margin: 0 0 10px;
}
/* Full green->yellow->red gradient always spans the track's full width;
   the mask (grey, right-anchored) slides in from riskPct% to hide the
   rest, so the revealed sliver's own color already encodes the position
   instead of needing a second color computed in JS. */
.crate-risk-fill {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, var(--good) 0%, var(--mythic) 55%, var(--bad) 100%);
}
.crate-risk-mask {
  position: absolute;
  top: 0; bottom: 0; right: 0;
  background: var(--bg-panel-3);
}
/* Price was loose blue text floating under the risk bar. As a bordered pill
   it reads as the card's call-to-action and gives the tile a bottom anchor,
   which is what the layout was missing. */
.crate-card .case-price {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 4px 0 0;
  padding: 5px 13px;
  border-radius: 999px;
  font-size: 15px;
  background: linear-gradient(180deg, rgba(47,125,253,0.14), rgba(47,125,253,0.05));
  border: 1px solid rgba(47,125,253,0.28);
  transition: background .18s ease, border-color .18s ease;
}
.crate-card:hover .case-price {
  background: linear-gradient(180deg, rgba(47,125,253,0.26), rgba(47,125,253,0.10));
  border-color: rgba(47,125,253,0.55);
}

.case-price {
  font-family: var(--display-font);
  color: var(--gold);
  font-weight: 700;
  letter-spacing: 0.3px;
  margin: 6px 0 12px;
}

/* small inline coin icon (used where the 🪙 emoji glyph isn't reliably supported) */
.coin-icon-sm {
  width: 30px; height: 30px; border-radius: 50%;
  background: conic-gradient(from 90deg, var(--gold), var(--orange));
  display: flex; align-items: center; justify-content: center;
  font-family: var(--display-font);
  font-weight: 700; font-size: 13px; color: #051220;
  box-shadow: 0 0 10px rgba(47,125,253,0.4);
}

/* rarity colors -- box-shadow rgba triplets have to be hardcoded (can't
   pull alpha out of a CSS variable's hex), so these are kept in sync by
   hand with the --trash/--common/etc. values above whenever those change. */
.rarity-trash { border-color: var(--trash) !important; box-shadow: 0 0 6px rgba(139,94,52,0.3); }
.rarity-common { border-color: var(--common) !important; box-shadow: 0 0 10px rgba(158,158,158,0.35); }
.rarity-uncommon { border-color: var(--uncommon) !important; box-shadow: 0 0 10px rgba(110,232,110,0.4); }
.rarity-rare { border-color: var(--rare) !important; box-shadow: 0 0 10px rgba(79,195,247,0.4); }
.rarity-epic { border-color: var(--epic) !important; box-shadow: 0 0 12px rgba(180,82,240,0.5); }
.rarity-legendary { border-color: var(--legendary) !important; box-shadow: 0 0 16px rgba(255,157,61,0.55); }
.rarity-mythic { border-color: var(--mythic) !important; box-shadow: 0 0 18px rgba(245,197,24,0.6); }

.tag-rarity {
  display: inline-block;
  font-family: var(--display-font);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  font-weight: 700;
  padding: 2px 9px;
  border-radius: 10px;
  margin-top: 6px;
}
.tag-trash { background: rgba(139,94,52,0.25); color: var(--trash); }
.tag-common { background: rgba(158,158,158,0.2); color: var(--common); }
.tag-uncommon { background: rgba(110,232,110,0.2); color: var(--uncommon); }
.tag-rare { background: rgba(79,195,247,0.2); color: var(--rare); }
.tag-epic { background: rgba(180,82,240,0.2); color: var(--epic); }
.tag-legendary { background: rgba(255,157,61,0.2); color: var(--legendary); }
.tag-mythic { background: rgba(245,197,24,0.22); color: var(--mythic); }

/* case opening spinner */
.spinner-viewport {
  position: relative;
  overflow: hidden;
  height: 150px;
  background: linear-gradient(180deg, #0d111c, var(--bg-panel-2));
  border: 1px solid var(--border);
  border-radius: 10px;
  margin: 20px 0;
  box-shadow: inset 0 2px 10px rgba(0,0,0,0.5);
}
.spinner-viewport::before, .spinner-viewport::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  width: 60px;
  z-index: 4;
  pointer-events: none;
}
.spinner-viewport::before { left: 0; background: linear-gradient(90deg, var(--bg-panel-2), transparent); }
.spinner-viewport::after { right: 0; background: linear-gradient(-90deg, var(--bg-panel-2), transparent); }
.spinner-track {
  position: absolute;
  top: 0; left: 0;
  display: flex;
  height: 100%;
  will-change: transform;
}
.spinner-item {
  flex: 0 0 130px;
  margin: 8px 6px;
  background: var(--bg-panel);
  border: 2px solid var(--common);
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 34px;
}
.spinner-pointer {
  position: absolute;
  top: 0; bottom: 0; left: 50%;
  width: 3px;
  background: var(--gold);
  box-shadow: 0 0 10px var(--gold);
  transform: translateX(-50%);
  z-index: 5;
}

.win-banner {
  text-align: center;
  padding: 18px;
  border-radius: 10px;
  margin-top: 14px;
  font-size: 20px;
  font-weight: 800;
}

/* coinflip */
.coin {
  width: 90px; height: 90px; border-radius: 50%;
  background: conic-gradient(from 90deg, var(--gold), var(--orange));
  display: flex; align-items: center; justify-content: center;
  margin: 20px auto;
  font-family: var(--display-font);
  font-weight: 700; font-size: 14px; color: #051220;
  box-shadow: 0 0 20px rgba(47,125,253,0.5), inset 0 -6px 12px rgba(0,0,0,0.2);
}
.coin.flipping { animation: flip 1.1s ease-in-out; }
@keyframes flip {
  0% { transform: rotateY(0deg); }
  100% { transform: rotateY(1800deg); }
}

.flip-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--border); }

/* Settings modal's History/Transactions/Active Trades lists -- capped so a
   long history scrolls inside the modal instead of pushing it past the
   viewport. */
.settings-tx-list { max-height: 340px; overflow-y: auto; }
/* Game History uses pages instead of an inner scrollbar (unlike
   Transactions/Active Trades above, which still just scroll) -- overrides
   the base .settings-tx-list height cap since each page is already sized
   to fit. */
.settings-tx-list-paged { max-height: none; overflow-y: visible; }
.settings-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin-top: 14px;
}
.settings-history-row .flip-row { padding-bottom: 8px; }
.settings-history-detail {
  background: var(--bg);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  padding: 10px 12px;
  margin: -2px 0 10px;
}
/* Whole-board recap grid for a finished Mines/Dig round (see
   renderMiniGrid() in js/settings.js) -- a small self-contained version of
   the tile look, not a reuse of mines.js/dig.js's own full-size classes. */
.settings-mini-grid { display: grid; gap: 3px; max-width: 230px; }
.settings-mini-tile {
  aspect-ratio: 1 / 1;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 2px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.settings-mini-tile.safe { background: rgba(53,208,127,0.15); border-color: var(--good); }
.settings-mini-tile.mine { background: rgba(255,92,92,0.12); border-color: rgba(255,92,92,0.35); }
.settings-mini-tile.hit { background: rgba(255,92,92,0.4); border-color: var(--bad); }

/* jackpot bar */
.jackpot-bar {
  display: flex; height: 34px; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--border); margin: 14px 0;
}
.jackpot-seg {
  display: flex; align-items: center; justify-content: center;
  font-family: var(--display-font);
  font-size: 11px; font-weight: 700; color: #0b0e14;
  white-space: nowrap; overflow: hidden;
}
.player-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border); }
.pct-dot { display:inline-block; width:10px; height:10px; border-radius:50%; margin-right:8px; }

.timer { font-family: var(--display-font); font-size: 30px; font-weight: 700; color: var(--gold); text-align: center; }

/* Inputs read as recessed now (inner shadow, slightly darker than the panel
   they sit on) rather than as another raised surface the same color as
   everything around them -- the old fill was bg-panel-2, identical to the
   panel behind it, so a text field was invisible until you found its
   border. */
input, select, textarea {
  background: rgba(8,11,18,0.55);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 12px;
  border-radius: 8px;
  font-family: var(--body-font);
  font-size: 14px;
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.35);
  transition: border-color .14s ease, box-shadow .14s ease, background .14s ease;
}
input::placeholder, textarea::placeholder { color: #5d657c; }
input:hover, select:hover, textarea:hover { border-color: #3a4463; }
input:focus, select:focus, textarea:focus {
  outline: none;
  background: rgba(8,11,18,0.75);
  border-color: var(--gold);
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.35), 0 0 0 3px rgba(47,125,253,0.18);
}
label {
  display: block; margin: 10px 0 4px;
  color: var(--text-dim); font-size: 12px;
  text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600;
}

.form-panel { max-width: 420px; margin: 60px auto; }

/* Full-saturation 1px borders on status messages were shouting; a left
   accent bar carries the same meaning at a fraction of the visual weight. */
.msg { padding: 11px 14px; border-radius: 8px; margin: 10px 0; font-size: 14px; border-left: 3px solid transparent; }
.msg.error { background: rgba(255,92,92,0.12); color: #ff8b8b; border-left-color: var(--bad); }
.msg.success { background: rgba(53,208,127,0.12); color: #5ee0a0; border-left-color: var(--good); }

table { width: 100%; border-collapse: collapse; }
th {
  font-family: var(--display-font);
  text-transform: uppercase; font-size: 12px; letter-spacing: 0.4px;
  color: var(--text-dim);
}
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--border-soft); font-size: 14px; }
tbody tr { transition: background .12s ease; }
tbody tr:hover { background: rgba(47,125,253,0.05); }

.hidden { display: none !important; }

/* Striped backdrop strip at the top of the homepage, framing the actual
   ROAM button so it reads as a distinct clickable element, not page text. */
.roam-banner-wrap {
  text-align: center;
  padding: 20px;
  background:
    repeating-linear-gradient(-45deg, rgba(47,125,253,0.05) 0px, rgba(47,125,253,0.05) 8px, transparent 8px, transparent 16px),
    #05070c;
  border-bottom: 1px solid var(--border);
}
/* The button itself — same solid-fill/drop-shadow/lift language as .btn,
   just bigger, so it's unmistakably pressable rather than a text banner. */
.roam-banner {
  display: inline-flex;
  align-items: center;
  gap: 16px;
  font-family: var(--display-font);
  font-size: 30px;
  font-weight: 800;
  letter-spacing: 14px;
  text-indent: 14px; /* re-center despite the trailing letter-spacing gap */
  color: #051220;
  background:
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.06) 0px, rgba(0,0,0,0.06) 6px, transparent 6px, transparent 12px),
    linear-gradient(180deg, var(--gold), var(--orange));
  padding: 16px 40px;
  border-radius: 12px;
  box-shadow: 0 5px 0 #082243, 0 0 24px rgba(47,125,253,0.4);
  cursor: pointer;
  transition: transform .1s ease, box-shadow .1s ease;
}
.roam-banner::after {
  content: '→';
  font-size: 24px;
  text-indent: 0;
  letter-spacing: 0;
}
.roam-banner:hover { transform: translateY(-2px); box-shadow: 0 7px 0 #082243, 0 0 32px rgba(47,125,253,0.6); }
.roam-banner:active { transform: translateY(3px); box-shadow: 0 2px 0 #082243; }

/* Manage Crates loot editor — squares instead of a plain list, each showing
   the item's image, value, and live % chance alongside its weight input. */
.loot-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.loot-square { position: relative; padding-top: 30px; }
.loot-remove-btn {
  position: absolute;
  top: 8px; right: 8px;
  padding: 2px 8px !important;
  font-size: 11px !important;
  line-height: 1.6;
  box-shadow: none !important;
}

/* Manage Crates — top-level tab bar (Crates / Items & Prices), reuses
   .btn/.btn.secondary for the active/inactive look, same toggle pattern as
   the Plinko / Crate Drop risk buttons. */
.admin-tabs { display: flex; gap: 10px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }

/* Manage Crates — Item Prices rarity filter pills, reuses .btn/.btn.secondary
   for the active/inactive look, same toggle pattern as the tab bar above. */
.price-rarity-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.price-filter-btn, .catalog-filter-btn, .bet-adjust-btn, .window-btn { padding: 6px 14px !important; font-size: 12px !important; }
.bet-adjust-btn { flex: 1; }

/* Manage Crates — "Edit a Crate" picker rows (click to open the editor) */
.crate-picker-row { cursor: pointer; transition: background .12s ease; }
.crate-picker-row:hover { background: var(--bg-panel-3); }

/* Manage Crates — single-crate editor screen */
.editor-header { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; }
.editor-cover-wrap { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.editor-cover-preview {
  width: 72px; height: 72px; border-radius: 10px;
  background: var(--bg-panel-3); border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center; font-size: 32px; overflow: hidden;
}
.editor-cover-preview img { width: 100%; height: 100%; object-fit: cover; }
.editor-cover-btn { font-size: 11px !important; padding: 6px 10px !important; }
.editor-name-input {
  width: 100%; background: transparent; border: 1px solid transparent; border-radius: 6px;
  padding: 6px 8px; margin: -6px 0 0 -8px;
  font-family: var(--display-font); font-size: 24px; font-weight: 700; color: var(--text);
}
.editor-name-input:hover { border-color: var(--border); }
.editor-name-input:focus { outline: none; border-color: var(--gold); background: var(--bg-panel-3); box-shadow: 0 0 0 3px rgba(47,125,253,0.15); }

.editor-stats-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px;
  margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border);
}
.editor-stat-tile { background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; }
.editor-stat-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 4px; }
.editor-stat-value { font-family: var(--display-font); font-size: 19px; font-weight: 700; color: var(--text); }

/* ---------- Provably fair field grid (Settings > Fairness) ---------- */
/* Same tile shape as .editor-stat-tile above (label over value, dark rounded
   box) -- these values are hashes/seeds though, so monospace + a copy
   button instead of a plain number. */
.pf-field-grid {
  /* minmax(0, 1fr) here would let auto-fit compute far more columns than
     needed (0 is a valid min-width, so the browser packs in many), and once
     a full-span tile (.pf-field-wide, grid-column: 1/-1) touches all of
     them in its own row, auto-fit won't collapse the rest even though later
     rows only use 2-3 -- every non-wide tile ends up squeezed into a single
     sliver column instead of splitting the row. A real minimum keeps the
     column count sane. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px;
  margin-top: 12px;
}
.pf-field-tile { background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
.pf-field-tile.pf-field-wide { grid-column: 1 / -1; }
.pf-field-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 6px; }
.pf-field-row { display: flex; align-items: center; gap: 8px; }
.pf-field-text {
  flex: 1; min-width: 0; font-family: monospace; font-size: 12px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pf-field-input {
  flex: 1; min-width: 0; font-family: monospace; font-size: 12px;
  background: transparent; border: none; padding: 0; color: var(--text);
}
.pf-field-input:focus { outline: none; }
.pf-copy-btn {
  flex-shrink: 0; width: 24px; height: 24px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 5px;
  color: var(--text-dim); font-size: 12px; cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.pf-copy-btn:hover { color: var(--gold); border-color: var(--gold); }

/* Fairness tab layout (owner request, 2026-09-16: "make it look nicer") --
   groups the current-seed status + the rotate control into one panel instead
   of them just sitting loose in the tab, gives the sub-sections their own
   compact heading style (the existing .section-title is sized for full-page
   headers, too heavy in here), and replaces the browser-default <details>
   disclosure with something that matches the rest of the site. */
.pf-status-card {
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px;
}
.pf-status-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.pf-status-badge {
  display: inline-flex; align-items: center; gap: 5px;
  background: rgba(53,208,127,0.12); border: 1px solid var(--good); color: var(--good);
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 3px 8px; border-radius: 20px;
}
.pf-status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--good); animation: pf-pulse 1.6s ease-in-out infinite; }
@keyframes pf-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

.settings-section-title {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-dim); border-left: 3px solid var(--gold); padding-left: 8px;
  margin: 22px 0 10px;
}

.pf-howto { background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 8px; padding: 2px 14px; }
.pf-howto summary {
  cursor: pointer; list-style: none; padding: 10px 0;
  font-size: 13px; font-weight: 600; color: var(--text);
  display: flex; align-items: center; justify-content: space-between;
}
.pf-howto summary::-webkit-details-marker { display: none; }
.pf-howto summary::after { content: '\25be'; color: var(--text-dim); transition: transform .15s ease; }
.pf-howto[open] summary::after { transform: rotate(180deg); }
.pf-howto summary:hover { color: var(--gold); }
.pf-howto-body { padding: 2px 0 14px; border-top: 1px solid var(--border-soft); }
.pf-howto-body p { font-size: 12px; line-height: 1.6; color: var(--text-dim); }
.pf-howto-body code { background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; font-size: 11px; color: var(--text); }

.pf-calc-card { background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 8px; padding: 14px; }
.pf-calc-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* Static Mines board reconstruction in the Draw Calculator -- deliberately
   NOT reusing .mine-tile (that class is coupled to the live game's flip
   animation via .mine-tile-flip/-face-front/-back), just its layout parent
   .mines-grid, with a plain bomb/safe styling language matching .dig-tile's
   own static-reveal treatment below. */
.pf-mine-tile {
  aspect-ratio: 1 / 1;
  font-size: 20px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg-panel-3);
  display: flex; align-items: center; justify-content: center;
}
.pf-mine-tile.pf-mine-bomb { background: rgba(255,92,92,0.28); border-color: var(--bad); }
.pf-mine-tile.pf-mine-safe { background: rgba(53,208,127,0.14); border-color: var(--good); color: var(--good); }

/* ---------- Arcade (testgame/) ---------- */

/* Plinko buckets */
.plinko-bucket {
  flex: 1;
  text-align: center;
  padding: 8px 2px;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: var(--display-font);
  font-size: 12px;
  font-weight: 700;
  color: var(--text-dim);
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.plinko-bucket.landed { background: var(--gold); color: #051220; border-color: var(--gold); box-shadow: 0 0 12px rgba(47,125,253,0.5); }
.plinko-peg {
  position: absolute;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-dim);
  box-shadow: 0 0 4px rgba(255,255,255,0.15);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* Crate Drop (plinko, rarity-themed) buckets */
.cratedrop-bucket {
  flex: 1;
  min-width: 0;
  text-align: center;
  padding: 6px 2px;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  transition: transform .15s ease, box-shadow .15s ease;
}
.cratedrop-bucket img { width: 100%; max-width: 34px; aspect-ratio: 1; object-fit: contain; display: block; margin: 0 auto 4px; }
.cratedrop-bucket-mult { display: block; font-family: var(--display-font); font-size: 11px; font-weight: 700; color: var(--text-dim); }
.cratedrop-bucket.landed { transform: translateY(-3px) scale(1.06); box-shadow: 0 0 14px rgba(255,255,255,0.25); }
.cratedrop-bucket.landed .cratedrop-bucket-mult { color: var(--text); }

/* Radiation Mines grid */
.mines-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
  margin-top: 14px;
}
.mine-tile {
  aspect-ratio: 1 / 1;
  font-size: 22px;
  background: linear-gradient(180deg, #232c46 0%, var(--bg-panel-3) 55%, #192034 100%);
  border: 1px solid var(--border);
  box-shadow: inset 0 1px 0 var(--edge-hi), 0 2px 4px rgba(0,0,0,0.35);
  border-radius: 8px;
  cursor: pointer;
  padding: 0; /* .mine-tile-flip fills the button exactly; default button padding would leave it undersized */
  display: flex; align-items: center; justify-content: center;
  transition: background .12s ease, border-color .14s ease, transform .14s ease, box-shadow .14s ease;
  perspective: 500px; /* gives .mine-tile-flip's rotateY real depth instead of looking flat/squashed */
}
/* Same lifted-card hover as .card (crate/game tiles) -- bigger lift + glow
   than the old 1px nudge so it actually reads as picking the tile up. */
.mine-tile:hover:not(:disabled) { border-color: var(--gold); transform: translateY(-3px); box-shadow: inset 0 1px 0 rgba(255,255,255,0.12), 0 10px 22px rgba(0,0,0,0.45), 0 0 22px rgba(47,125,253,0.32); }
.mine-tile:disabled { cursor: not-allowed; }
/* Reveal flip -- front face is the always-there logo (unchanged markup),
   back face is whatever flipTile() (testgame/mines.js) puts there right
   before adding .flipped. backface-visibility:hidden on both is what makes
   the front vanish and the back appear exactly as the rotation crosses 90
   degrees, instead of both being visible/overlapping mid-spin. */
.mine-tile-flip {
  position: relative;
  width: 100%; height: 100%;
  transform-style: preserve-3d;
  transition: transform .5s cubic-bezier(.3, .1, .2, 1.05);
}
.mine-tile.flipped .mine-tile-flip { transform: rotateY(180deg); }
.mine-tile-face {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  backface-visibility: hidden;
  border-radius: inherit;
}
.mine-tile-face-back { transform: rotateY(180deg); }
/* Unrevealed-tile face -- same black-background-vanishes trick .brand-logo
   uses (screen blend against the tile's dark bg), plus grayscale so it
   doesn't compete in color with the site's blue/gold accents. Revealing a
   tile overwrites this via textContent in reveal(), same as before.
   logo.png is a single flat 1254x1254 image (R-mark + "RUSTYROAM" wordmark
   below it, see img/logo.png) -- there's no separate mark-only asset, so
   this crops to just the R-mark via the standard responsive
   background-position/-size percentage trick instead of showing the whole
   square (which is what .brand-logo in the navbar does). Numbers below are
   derived from the R-mark's actual pixel bounding box, (287,373)-(981,804)
   within the 1254x1254 source, plus a little padding:
     size%  = fullDim / cropDim * 100
     pos%   = cropStart / (fullDim - cropDim) * 100 */
.mine-tile-logo-wrap {
  position: relative; /* anchors the two absolutely-positioned layers below */
  width: 85%;
  aspect-ratio: 660 / 400; /* logo-mark.svg's own viewBox */
  pointer-events: none;
}
/* img/logo-mark.png is a real transparent-background crop of just the
   R-mark (un-matted from logo.png's solid-black square with a small Python/
   PIL script -- alpha = max(r,g,b), then un-premultiply the RGB by that
   alpha -- rather than the black-background original), so this needs
   neither the crop-via-background-position math nor the mix-blend-mode
   trick .brand-logo/the old version of this used. That matters here
   specifically because mask + mix-blend-mode on anything in the same
   subtree turned out not to render at all (tested directly: with both
   present the masked-in region just doesn't paint, like blend-mode
   silently wins) -- so this needed to stop needing blend-mode in the first
   place rather than fighting that interaction. */
.mine-tile-logo, .mine-tile-logo-color, .mine-tile-logo-erase {
  position: absolute;
  inset: 0;
  background-image: url('/img/logo-mark.svg?v=1');
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
}
.mine-tile-logo {
  filter: grayscale(100%);
  opacity: 0.5;
}
/* .mine-tile-logo-erase needs to stay fully OPAQUE (not opacity:0.5 like the
   base layer) even though it should look just as dim -- its whole job is
   to fully occlude the still-100%-opaque color layer once its mask opens,
   and opacity:0.5 can only ever lay a half-see-through grey wash over that
   color, never actually hide it (that was the bug: nothing looked truly
   grey again until the color layer's own keyframe snapped back at the very
   end of the 12s loop, way later than the 2s erase was supposed to take).
   brightness(50%) gets the same dimmed look by darkening the pixels
   themselves instead of adding transparency. */
.mine-tile-logo-erase {
  /* blur spreads this layer's opacity a little into its own transparent
     edges, symmetrically in every direction -- unlike scaling it up
     (tried first: scale transforms from the box's center, which nudged
     the whole silhouette off-register instead of just thickening it, and
     made the edge gap worse, not better) this can't shift anything
     off-center, it only thickens the existing silhouette in place. Needed
     because two independently-rendered copies of the same PNG don't
     necessarily rasterize their anti-aliased edges to the identical
     sub-pixel, and that gap was enough to let a sliver of full-saturation
     blue from the color layer show through right at the letterform edges. */
  filter: grayscale(100%) brightness(50%) blur(1.5px);
}
/* Hover-gated (grey whenever the mouse isn't over the tile): while hovered,
   loops a 12s cycle = 2s reveal + 4s hold-colored + 2s erase + 4s
   hold-grey, then repeats for as long as the hover continues. Leaving mid-
   cycle doesn't linger -- --wipe transitions straight back to -20% (grey)
   over half a second, from wherever it was.
   Two layers share one proven trick (a soft black->transparent band
   sweeping bottom-left -> top-right, see --wipe below) rather than one
   layer trying to do both jobs:
     .mine-tile-logo-color  sweeps IN over the grayscale base (0s -> 2s),
                            then just stays fully revealed.
     .mine-tile-logo-erase  is an identical grayscale copy that stays fully
                            hidden until 6s, then sweeps IN the same
                            bottom-left -> top-right way (6s -> 8s), so it's
                            what visually "erases" the color -- it isn't
                            reversing the color layer's own wipe, which
                            would travel the opposite direction (last-
                            revealed corner erasing first) instead of what
                            was asked for here.
   Both layers snap back to hidden (-20%) at the 12s loop boundary; that's
   seamless rather than a visible jump because with both hidden the
   ever-present grayscale base underneath is exactly what erase was already
   showing a moment before. */
@property --wipe {
  syntax: '<percentage>';
  inherits: false;
  initial-value: -20%;
}
.mine-tile-logo-color, .mine-tile-logo-erase {
  --wipe: -20%;
  mask-image: linear-gradient(45deg, black calc(var(--wipe) - 15%), transparent calc(var(--wipe) + 15%));
  -webkit-mask-image: linear-gradient(45deg, black calc(var(--wipe) - 15%), transparent calc(var(--wipe) + 15%));
  mask-repeat: no-repeat;
  -webkit-mask-repeat: no-repeat;
  transition: --wipe .5s ease; /* only takes effect once the animation below is removed (hover ends) */
}
.mine-tile:hover:not(:disabled) .mine-tile-logo-color { animation: mine-wipe-reveal 12s linear infinite; }
.mine-tile:hover:not(:disabled) .mine-tile-logo-erase { animation: mine-wipe-erase 12s linear infinite; }
@keyframes mine-wipe-reveal {
  0%      { --wipe: -20%; }  /* t=0s */
  16.667% { --wipe: 120%; }  /* t=2s -- reveal done, stays revealed the rest of the cycle */
  100%    { --wipe: 120%; }
}
@keyframes mine-wipe-erase {
  0%      { --wipe: -20%; }  /* stays hidden through the reveal+hold... */
  50%     { --wipe: -20%; }  /* ...until t=6s */
  66.667% { --wipe: 120%; }  /* t=8s -- erase done, fully covers the color layer */
  100%    { --wipe: 120%; }  /* stays covering (grey hold) until the loop resets */
}
.mine-tile.mine-safe { background: rgba(53,208,127,0.18); border-color: var(--good); }
.mine-tile.mine-hit { background: rgba(255,92,92,0.28); border-color: var(--bad); }
.mine-tile.mine-revealed { background: rgba(255,92,92,0.1); border-color: var(--bad); opacity: 0.6; }

/* Excavator Dig grid — same tile-button conventions as Radiation Mines,
   adapted for a 6x6 board and 4 tile types instead of 2 */
.dig-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 8px;
  margin-top: 14px;
}
.dig-tile {
  aspect-ratio: 1 / 1;
  font-size: 20px;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .12s ease, border-color .12s ease, transform .08s ease;
}
.dig-tile:hover:not(:disabled) { border-color: var(--gold); transform: translateY(-1px); }
.dig-tile:disabled { cursor: not-allowed; }
.dig-tile.dig-hit { background: rgba(255,92,92,0.28); border-color: var(--bad); }
.dig-tile.dig-revealed-wood { background: rgba(53,208,127,0.10); border-color: var(--good); }
.dig-tile.dig-revealed-stone { background: rgba(53,208,127,0.16); border-color: var(--good); }
.dig-tile.dig-revealed-metal { background: rgba(53,208,127,0.22); border-color: var(--good); }
.dig-tile.dig-revealed-sulfur { background: rgba(53,208,127,0.30); border-color: var(--good); }
.dig-tile.dig-bust-reveal { background: rgba(255,92,92,0.1); border-color: var(--bad); opacity: 0.6; }
.dig-tile.dig-safe-reveal { background: rgba(53,208,127,0.1); border-color: var(--good); opacity: 0.6; }

/* Chain Dig -- a linear track (not a grid, see chaindig.js's own writeup),
   so this scrolls horizontally instead of wrapping: one row of 20 fixed
   tiles, each carrying its own step multiplier underneath it, wide enough
   that .chaindig-track-wrap's own overflow-x is what actually handles it
   scrolling into view rather than the tiles shrinking to fit. */
.chaindig-track-wrap { overflow-x: auto; margin-top: 14px; }
/* padding-bottom, not the wrap's -- overflow-x:auto above implicitly makes
   the wrap's own overflow-y compute to auto too (not visible), so anything
   positioned outside a tile's own box (the multiplier label below) needs
   real room INSIDE the scrollable content area or it gets clipped instead
   of just scrolled with everything else. */
.chaindig-track { display: flex; gap: 8px; width: max-content; padding-bottom: 20px; }
.chaindig-tile {
  width: 56px; height: 56px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 8px;
  position: relative; font-size: 22px;
  transition: background .12s ease, border-color .12s ease, transform .12s ease;
}
.chaindig-tile-icon { line-height: 1; }
.chaindig-tile-mult {
  position: absolute; bottom: -16px; left: 0; right: 0; text-align: center;
  font-size: 9px; color: var(--text-dim); white-space: nowrap;
}
.chaindig-tile-cut { background: rgba(53,208,127,0.14); border-color: var(--good); }
.chaindig-tile-current { border-color: var(--gold); box-shadow: 0 0 10px rgba(245,197,24,0.45); transform: translateY(-3px); }
.chaindig-tile-current .chaindig-tile-mult { color: var(--gold); font-weight: 700; }
.chaindig-tile-bust { background: rgba(255,92,92,0.28); border-color: var(--bad); }
.dig-tile img { width: 62%; height: 62%; object-fit: contain; pointer-events: none; }
.mat-icon-inline { width: 18px; height: 18px; object-fit: contain; vertical-align: -4px; margin-right: 2px; }

/* "if your next click is..." live preview — styled like the dig grid's
   tiles (same background/border language) rather than plain paragraph text,
   so it reads as part of the game board, not a footnote. */
.dig-legend-tile {
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  margin-top: 14px;
}
.dig-legend-title {
  font-family: var(--display-font);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-dim);
  margin-bottom: 8px;
}
.dig-legend-tabs { display: flex; gap: 8px; }
.dig-legend-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 6px;
  background: var(--bg-panel-2);
  border: 1px solid var(--border);
  border-bottom: 2px solid var(--border);
  border-radius: 8px 8px 4px 4px;
  transition: border-color .12s ease, background .12s ease;
}
.dig-legend-tab:hover { border-color: var(--gold); border-bottom-color: var(--gold); background: var(--bg-panel-3); }
.dig-legend-tab img { width: 26px; height: 26px; object-fit: contain; }
.dig-legend-tab-name { font-family: var(--display-font); font-size: 11px; text-transform: uppercase; letter-spacing: 0.3px; color: var(--text-dim); }
.dig-legend-pct { color: var(--good); font-weight: 700; font-family: var(--display-font); font-size: 13px; }

/* Radiation Mines' "next click pays" -- Dig's version is 4 static tabs (one
   per material, since Dig has 4 different tile types with different odds).
   Mines only has one kind of safe tile, so there's nothing to show side by
   side like that -- instead this is every future step's payout laid out as
   a horizontal ladder, same viewport+track+transform-scroll mechanic as the
   crate-opening reel (case-open.html's .spinner-viewport), just shorter and
   scrolled by exactly one chip-width per reveal instead of landing on a
   random winner. The next click's value rides centered under
   .mine-ladder-pointer; already-clicked steps end up to its left as the
   track scrolls, not-yet-reached ones stay queued to its right. */
.mine-ladder-wrap { margin-top: 14px; }
.mine-ladder-viewport {
  position: relative;
  overflow: hidden;
  height: 54px;
  background: linear-gradient(180deg, #0d111c, var(--bg-panel-2));
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: inset 0 2px 10px rgba(0,0,0,0.5);
}
.mine-ladder-viewport::before, .mine-ladder-viewport::after {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  width: 40px;
  z-index: 2;
  pointer-events: none;
}
.mine-ladder-viewport::before { left: 0; background: linear-gradient(90deg, var(--bg-panel-2), transparent); }
.mine-ladder-viewport::after { right: 0; background: linear-gradient(-90deg, var(--bg-panel-2), transparent); }
.mine-ladder-pointer {
  position: absolute;
  top: -4px; bottom: -4px; left: 50%;
  width: 2px;
  background: var(--gold);
  box-shadow: 0 0 8px var(--gold);
  transform: translateX(-50%);
  z-index: 3;
}
.mine-ladder-track {
  position: absolute;
  top: 0; left: 0;
  display: flex;
  height: 100%;
  transition: transform .45s ease;
  will-change: transform;
}
.mine-ladder-chip {
  flex: 0 0 64px;
  margin: 0 7px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--display-font);
  font-weight: 700;
  font-size: 13px;
  color: var(--text-dim);
  opacity: 0.45;
}
.mine-ladder-chip-done { color: var(--text-dim); opacity: 0.25; text-decoration: line-through; }
.mine-ladder-chip-next { color: var(--gold); opacity: 1; font-size: 16px; text-shadow: 0 0 10px rgba(245,197,24,0.5); }

/* Session log (Radiation Mines / Excavator Dig) -- outside the main game
   panel on purpose, its own card, so it reads as a running record of the
   session rather than part of the board itself. In-memory only (resets on
   reload) -- there's no server-side "session" concept to persist this
   against, and that matches what it's for: a glance-back at the last few
   rounds you just played, not a saved history. */
.session-log-panel { margin-top: 14px; }
.session-log-list { display: flex; flex-direction: column; gap: 6px; max-height: 220px; overflow-y: auto; }
.session-log-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  background: var(--bg-panel-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 13px;
}
.session-log-row span:first-child { color: var(--text-dim); }
.session-log-win span:last-child { color: var(--good); font-weight: 700; }
.session-log-loss span:last-child { color: var(--bad); font-weight: 700; }

/* Airdrop Roulette wheel */
.roulette-slot {
  flex: 1 0 40px;
  text-align: center;
  padding: 10px 4px;
  border-radius: 6px;
  font-family: var(--display-font);
  font-weight: 700;
  font-size: 13px;
  color: #0b0e14;
  transition: transform .15s ease, box-shadow .15s ease;
}
.roulette-gold { background: var(--gold); }
.roulette-green { background: var(--good); }
.roulette-red { background: var(--bad); }
.roulette-slot.landed { transform: translateY(-4px) scale(1.08); box-shadow: 0 0 16px rgba(47,125,253,0.7); outline: 2px solid var(--text); }

/* Roam Mode — travel map grid overlay (rust-map.jpg placeholder; grid
   dimensions come entirely from JS, this CSS makes no assumption about
   GRID_SIZE beyond "however many cells JS renders fills the wrap") */
.roam-map-zoom-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 10px;
}
.roam-map-zoom-controls span {
  font-family: var(--display-font);
  font-size: 13px;
  color: var(--text-dim);
  min-width: 42px;
  text-align: center;
}
/* Fixed-size scrollable window onto the (possibly zoomed-larger) map wrap —
   zoom grows .roam-map-wrap's actual pixel size via JS, this viewport is
   what keeps that from blowing out the page layout. */
.roam-map-viewport {
  max-width: 500px;
  max-height: 500px;
  margin: 14px auto;
  overflow: auto;
  border-radius: 10px;
  border: 1px solid var(--border);
}
.roam-map-wrap {
  position: relative;
  width: 500px;
  height: 500px;
  overflow: hidden;
}
.roam-map-wrap img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.roam-map-grid {
  position: absolute;
  inset: 0;
  display: grid;
}
.roam-map-cell {
  position: relative;
  border: 1px solid rgba(255,255,255,0.08);
  cursor: pointer;
  transition: background .1s ease, border-color .1s ease;
}
.roam-map-cell:hover { background: rgba(47,125,253,0.25); border-color: var(--gold); }
.roam-map-cell.current {
  background: rgba(255,255,255,0.06);
  border-color: var(--orange);
  cursor: default;
}
.roam-map-cell.water {
  background: rgba(4,10,20,0.45);
  cursor: not-allowed;
}
.roam-map-cell.water:hover { background: rgba(4,10,20,0.45); border-color: rgba(255,255,255,0.08); }
.roam-map-cell.current::after {
  content: "";
  position: absolute;
  width: 40%;
  height: 40%;
  top: 30%;
  left: 30%;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 8px var(--gold);
}

/* Dedicated crate-opening page (case-open.html) */
.back-link {
  display: inline-block;
  font-family: var(--display-font);
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  color: var(--text-dim);
  margin-bottom: 16px;
  transition: color .12s ease;
}
.back-link:hover { color: var(--gold); }

.case-hero {
  display: flex;
  align-items: center;
  gap: 22px;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 22px 26px;
  margin-bottom: 20px;
}
.case-hero-image { flex: 0 0 auto; }
.case-hero-image img { border-radius: 10px; }
.case-hero-info h2 { font-size: 25px; margin-bottom: 4px; }

.case-open-panel { text-align: center; }

/* bigger reel for the full page (base .spinner-viewport/.spinner-item from
   the shared modal styles above still apply — these just scale them up) */
.spinner-viewport-lg { height: 190px; }
.spinner-viewport-lg .spinner-item { flex: 0 0 150px; margin: 10px 7px; font-size: 40px; }

/* small top/bottom carets on the pointer line, instead of a bare bar */
.spinner-pointer::before, .spinner-pointer::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  border: 7px solid transparent;
}
.spinner-pointer::before { top: -1px; border-top-color: var(--gold); }
.spinner-pointer::after { bottom: -1px; border-bottom-color: var(--gold); }

/* Multi-open (2-5 crates at once): one vertical reel per crate, side by
   side, instead of the single big horizontal one -- built as its own set of
   classes rather than rotating the horizontal ones with a CSS transform,
   since that would turn the item art and text sideways too. Same visual
   language throughout (fade edges, pointer line + carets, landed pop/glow),
   just with the X/Y axis swapped. See js/case-open.js's runMultiOpen(). */
.multi-spinner-row {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
  margin: 20px 0;
}
.spinner-viewport-vertical {
  position: relative;
  overflow: hidden;
  width: 130px;
  height: 320px;
  background: linear-gradient(180deg, #0d111c, var(--bg-panel-2));
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: inset 0 2px 10px rgba(0,0,0,0.5);
  flex: 0 0 auto;
}
.spinner-viewport-vertical::before, .spinner-viewport-vertical::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: 50px;
  z-index: 4;
  pointer-events: none;
}
.spinner-viewport-vertical::before { top: 0; background: linear-gradient(180deg, var(--bg-panel-2), transparent); }
.spinner-viewport-vertical::after { bottom: 0; background: linear-gradient(0deg, var(--bg-panel-2), transparent); }
.spinner-track-vertical {
  position: absolute;
  top: 0; left: 0;
  display: flex;
  flex-direction: column;
  width: 100%;
  will-change: transform;
}
.spinner-item-vertical {
  flex: 0 0 88px;
  margin: 6px 8px;
  background: var(--bg-panel);
  border: 2px solid var(--common);
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
}
/* Own rule rather than sharing .spinner-item.landed -- different class
   (spinner-item-vertical, not spinner-item), same pop/glow keyframe. */
.spinner-item-vertical.landed {
  animation: spinner-land .45s ease-out;
  box-shadow: 0 0 18px currentColor;
}
.spinner-pointer-horizontal {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 3px;
  background: var(--gold);
  box-shadow: 0 0 10px var(--gold);
  transform: translateY(-50%);
  z-index: 5;
}
.spinner-pointer-horizontal::before, .spinner-pointer-horizontal::after {
  content: '';
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  border: 7px solid transparent;
}
.spinner-pointer-horizontal::before { left: -1px; border-left-color: var(--gold); }
.spinner-pointer-horizontal::after { right: -1px; border-right-color: var(--gold); }

/* Quantity picker (1-5) + the fast-open toggle above the reel(s). */
.open-qty-group { display: inline-flex; gap: 6px; }
.open-qty-btn { min-width: 34px; padding: 8px 10px; }
.fast-open-btn { font-size: 16px; padding: 8px 12px; }
.fast-open-btn.active { background: linear-gradient(180deg, var(--gold), #0d3f8f); }
.roam-spin-btn { font-size: 13px; padding: 8px 12px; }
.roam-spin-btn.active { background: linear-gradient(180deg, var(--gold), #0d3f8f); }

/* Compact result card for multi-open's post-spin summary row -- same
   color-by-rarity language as .win-reveal, just small enough that up to 5
   fit in one row instead of the single big centered card. */
.multi-result-card {
  background: var(--bg-panel);
  border: 2px solid var(--border);
  border-radius: 10px;
  padding: 10px;
  text-align: center;
  width: 110px;
}
.multi-result-name { font-size: 11px; font-weight: 700; margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.multi-result-value { font-size: 11px; color: var(--text-dim); margin-top: 2px; }

/* landing impact on the winning tile — same "pop" language as
   .cratedrop-bucket.landed / .roulette-slot.landed elsewhere in the site */
.spinner-item.landed {
  animation: spinner-land .45s ease-out;
  box-shadow: 0 0 18px currentColor;
}
@keyframes spinner-land {
  0% { transform: scale(1); }
  45% { transform: scale(1.12) translateY(-4px); }
  100% { transform: scale(1); }
}

/* rarity-colored glow burst behind the viewport the instant it lands */
.burst-flash {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  z-index: 3;
}
.burst-flash.burst-active { animation: burst-fade .9s ease-out; }
@keyframes burst-fade {
  0% { opacity: 0; }
  25% { opacity: 1; }
  100% { opacity: 0; }
}

/* whole-screen flash for the big legendary/mythic pulls only */
.screen-flash {
  position: fixed;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  z-index: 2000;
  mix-blend-mode: screen;
}
.screen-flash.flash-active { animation: screen-flash-fade .65s ease-out; }
@keyframes screen-flash-fade {
  0% { opacity: 0; }
  15% { opacity: 0.55; }
  100% { opacity: 0; }
}

.win-reveal { min-height: 0; margin-top: 18px; }
.win-reveal.show {
  position: relative;
  padding: 24px;
  border-width: 2px;
  border-style: solid;
  border-radius: 12px;
  overflow: hidden;
  animation: reveal-pop .5s cubic-bezier(0.26, 1.4, 0.44, 1);
}
@keyframes reveal-pop {
  0% { transform: scale(0.5); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
.win-reveal-icon { margin-bottom: 8px; }
.win-reveal-name { font-family: var(--display-font); font-size: 22px; font-weight: 800; }
.win-reveal-value { color: var(--text-dim); font-size: 13px; margin-top: 8px; }

/* case-open.html's idle reel viewport (before a spin, or settled back after
   one finishes) -- was a static 10-item preview strip, now just the site
   mark until something's actually been opened this page-load, or a recap
   of the last item that was, sized up since it's the only thing in there
   now instead of one of a row of many. */
/* .spinner-idle-logo (the site logo mark, dimmed, shown when a crate had no
   cover_image at all) is gone -- every crate without its own cover_image
   now falls back to img/default-crate.png through this same .spinner-idle-
   cover class instead, see case-open.js's renderIdleDisplay(). */
.spinner-idle-cover { max-height: 85%; max-width: 85%; object-fit: contain; border-radius: 10px; }
.spinner-idle-recap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 22px;
  border: 2px solid var(--common);
  border-radius: 10px;
  background: var(--bg-panel);
}
.spinner-idle-recap-name { font-family: var(--display-font); font-weight: 700; font-size: 15px; }
.spinner-idle-recap-price { color: var(--text-dim); font-size: 13px; }

/* drifting sparkle glyphs over rare+ reveal cards */
.sparkle {
  position: absolute;
  bottom: 14%;
  font-size: 16px;
  animation-name: sparkle-drift;
  animation-timing-function: ease-out;
  animation-fill-mode: forwards;
  pointer-events: none;
}
@keyframes sparkle-drift {
  0% { transform: translateY(0) scale(0.6) rotate(0deg); opacity: 0; }
  20% { opacity: 1; }
  100% { transform: translateY(-90px) scale(1.1) rotate(90deg); opacity: 0; }
}

.case-open-actions { display: flex; gap: 10px; justify-content: center; margin-top: 16px; }
.btn-open-crate { min-width: 230px; font-size: 15px; padding: 13px 24px; }
.btn-open-crate:disabled { opacity: 0.6; cursor: not-allowed; transform: none !important; }

/* ---------- Upgrader (testgame/upgrader.html) ---------- */
/* Selected-target preview panel (left) + the main panel (right) -- same
   sidebar-beside-main-panel convention as .wheel-layout/.wheel-history-panel/
   .wheel-main-panel, just a wider sidebar since this one shows an icon,
   name and rarity badge rather than plain color chips. */
.upgrader-layout { display: flex; gap: 16px; align-items: stretch; justify-content: center; flex-wrap: wrap; }
/* flex column + flex:1 on whichever of empty/chosen is visible -- so the
   preview panel doesn't just match the RIGHT COLUMN's combined height
   (main panel + the item picker below it, both wrapped in
   .upgrader-right-col so align-items:stretch above has a single tall
   sibling to match), it actually fills that full height with its own
   content instead of leaving dead space under a fixed-height box. */
.upgrader-preview-panel { width: 560px; flex-shrink: 0; text-align: center; display: flex; flex-direction: column; }
.upgrader-right-col { display: flex; flex-direction: column; flex: 1 1 0; min-width: 300px; }
.upgrader-main-panel { max-width: 640px; margin: 0; }
.upgrader-preview-empty {
  background: var(--bg-panel-3); border: 1px dashed var(--border); border-radius: 8px;
  padding: 16px 10px;
  flex: 1; display: flex; align-items: center; justify-content: center;
}
/* Up to MAX_TARGETS (4) halos side by side, wrapping to a second row only if
   the panel's own width genuinely can't fit them (ring size already shrinks
   per-count in upgrader.js's HALO_SIZE first, so wrapping is a last resort,
   not the normal case). */
/* Stays a fixed, stable size (fills the "Target" panel) regardless of how
   many items are picked -- the wrapping/shrinking that produces the row/
   triangle/square shapes happens entirely on .upgrader-target-halo-group
   inside it, which has no background/border of its own. Putting that
   shrinking max-width directly on THIS element instead (an earlier version
   did) made the visible lighter panel background resize along with the
   halo count, which read as a layout glitch rather than a shape change. */
.upgrader-preview-chosen {
  background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 8px;
  padding: 24px 14px;
  flex: 1; display: flex; align-items: center; justify-content: center;
}
/* Fixed-size stage the ring lives in, sized generously enough that even the
   4-item 2x2 square (see HALO_SIZE in upgrader.js) sits comfortably inside
   it with room to spare for the ring itself. */
.upgrader-ring-stage { position: relative; width: 300px; height: 300px; margin: 0 auto; border-radius: 50%; transition: box-shadow 0.4s ease-out; }
/* Same landing "thunk" the old .upgrader-bar used, just on the ring's stage
   instead of the bar track -- see upgrader.js's spinRing(). */
.upgrader-ring-stage.landed-flash { box-shadow: 0 0 26px rgba(255,255,255,0.5); }
/* Fixed at the top, never rotates -- same idea as .wheel-pointer, just
   marking where a spin's real landing position always ends up once
   spinRing() finishes (see upgrader.js). */
.upgrader-ring-pointer {
  position: absolute; top: -6px; left: 50%; transform: translateX(-50%);
  width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent;
  border-top: 18px solid var(--gold); z-index: 3;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
}
/* This is what actually rotates during a spin (upgrader.js's spinRing()
   animates a CSS transform on this element directly) -- the pointer above
   and the halo-group below are separate siblings, not children of it, so
   spinning it never touches either of them. */
.upgrader-ring { position: absolute; inset: 0; width: 100%; height: 100%; transform-origin: 50% 50%; }
.upgrader-ring-bg { fill: none; stroke: var(--bad); stroke-width: 16; }
/* Win-chance arc -- 0% at the top (matching the pointer) going clockwise,
   which is why the circle itself carries a -90deg rotation in its own SVG
   transform attribute (SVG circles otherwise start drawing at 3 o'clock).
   stroke-dasharray is set per-update in upgrader.js's setRingFill(), same
   role .upgrader-bar-win's width used to play. */
.upgrader-ring-fill { fill: none; stroke: var(--good); stroke-width: 16; stroke-dasharray: 0 1000; transition: stroke-dasharray 0.2s ease; }
/* Centered in the stage independent of the ring's own rotation (it's a
   sibling, not a child, of .upgrader-ring) -- max-width set per-instance in
   JS (renderChosenTarget()) to fit exactly 2 halos per line, forcing a
   3rd/4th onto their own centered line (align-content:center keeps those
   lines packed together vertically too, not spread across the tall panel).
   Centered via inset:0 + margin:auto, NOT the more common top/left:50% +
   transform:translate(-50%,-50%) -- that pair quietly HALVES the available
   width the browser uses to shrink-to-fit an auto-width absolutely
   positioned box (shrink-to-fit is computed against the space between
   'left' and the containing block's edge, and transform never factors into
   that layout math, only painting), so a 2-item row that should fit
   side-by-side (2 halos + gap, comfortably under the max-width above) was
   being capped to roughly half that and wrapping into a vertical stack
   instead. inset:0 (left/right/top/bottom all 0) gives shrink-to-fit the
   FULL stage width/height to work with, and margin:auto still centers the
   resulting box exactly like the transform trick did. */
.upgrader-target-halo-group {
  position: absolute; inset: 0; margin: auto;
  display: flex; flex-wrap: wrap; align-items: center; align-content: center; justify-content: center; gap: 16px;
}
/* Just the icon, ringed in a halo colored by rarity -- no name or badge
   text in this preview. The ring itself is a plain circle; .rarity-* (the
   same border-color + box-shadow classes .battle-pull-tile and the loot
   grid tiles already use) does the actual coloring once JS adds the right
   one (see upgrader.js's renderChosenTarget()). Width/height set inline per
   instance (HALO_SIZE), this is just the shared shape/border/background.
   No separate remove button -- the halo itself is clickable (toggles the
   item off, same toggleTarget() the main list uses), hence the pointer
   cursor and a hover dim so that's discoverable. */
.upgrader-target-halo {
  margin: 0 auto;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 3px solid var(--border);
  background: radial-gradient(circle, rgba(47,125,253,0.14) 0%, rgba(47,125,253,0) 72%);
  cursor: pointer; transition: opacity 0.15s ease;
}
.upgrader-target-halo:hover { opacity: 0.75; }

/* Target picker (left) + bet amount/adjust buttons (right), side by side --
   flex:1 1 0 (not a fixed 50%/basis) on both sides so they split evenly and
   still shrink correctly; flex-wrap drops to stacked (picker above bet
   controls) once the panel's own width can't fit both comfortably, same
   wrapping convention as .wheel-layout. */
.upgrader-top-grid { display: flex; gap: 16px; margin-top: 14px; flex-wrap: wrap; }
.upgrader-target-picker { flex: 1 1 0; min-width: 220px; margin-top: 0; }
.upgrader-bet-controls { flex: 1 1 0; min-width: 220px; }
/* Standalone now (the full icon/name/rarity card moved out to
   .upgrader-preview-panel) -- just the one line, dimmed until a real target
   is actually picked. */
.upgrader-price-line { font-size: 13px; color: var(--text); background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; }
.upgrader-price-line.empty { color: var(--text-dim); font-size: 12px; }
/* Optional shortcuts, not required -- the reverse of the odds slider: instead
   of picking a bet against an already-chosen target, these pick a TARGET
   whose value best matches bet * multiplier (see upgrader.js's
   pickTargetForMultiplier()), so a target has to not even be selected yet
   for these to make sense. Disabled until a real bet is typed in (there's
   nothing to match against otherwise) -- always visible in the picker
   itself, not nested inside .upgrader-target-chosen, since clicking one
   can change (or set, from empty) the target rather than requiring one
   first. Reuses .bet-adjust-btn's own compact sizing (see
   .price-filter-btn, .catalog-filter-btn, .bet-adjust-btn rule) for a
   consistent look with the 1/2 · 2x · Max · Clear row. */
.upgrader-multiplier-label { font-size: 11px; color: var(--text-dim); margin-top: 10px; }
.upgrader-multiplier-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.upgrader-multiplier-btn { padding: 4px 10px !important; font-size: 11px !important; }
.upgrader-target-tile { cursor: pointer; transition: transform .15s ease, box-shadow .15s ease; }
.upgrader-target-tile:hover { transform: translateY(-3px); box-shadow: 0 10px 22px rgba(47,125,253,0.18); }
/* Picked straight off this always-on list now, not a modal that closes once
   you tap something -- the selected tile needs its own persistent highlight
   so it's obvious which one is currently the target without having to check
   back up at .upgrader-target-chosen. */
.upgrader-target-tile.selected { border-color: var(--gold) !important; box-shadow: 0 0 0 2px var(--gold), 0 10px 22px rgba(245,197,24,0.25) !important; }
.upgrader-target-results { margin-top: 14px; max-height: 420px; overflow-y: auto; }
/* Denser than the shared .loot-grid/.card sizing (crates, inventory, ...) --
   the item icon itself is untouched (still the same 56px itemVisual() in
   the same 64px .item-emoji badge), this just shrinks the tile's own
   padding and the grid's column width, freed up mostly by the name no
   longer sitting inline (see the tooltip rule below). */
.upgrader-target-results .loot-grid { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 8px; }
.upgrader-target-tile { padding: 10px 6px 8px; position: relative; }
/* Name hidden until hover -- shown as a small tooltip instead of inline,
   same reasoning as .wheel-bettor-tooltip: revealing it this way costs no
   layout space the rest of the time. BELOW the tile, not above -- results
   scrolls inside .upgrader-target-results (overflow-y:auto), which clips
   anything positioned past ITS own top edge, so an above-tile tooltip on
   the very first row was invisible (confirmed live: no tooltip at all until
   scrolled past row 1). Below has the same risk only for the last row,
   which is far less commonly hovered mid-scroll. */
.upgrader-target-tile h3 {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  margin-top: 6px; white-space: nowrap; max-width: none;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 4px 8px; font-size: 11px; text-transform: none; letter-spacing: normal;
  opacity: 0; visibility: hidden; transition: opacity 0.12s;
  z-index: 5; pointer-events: none;
}
.upgrader-target-tile:hover h3 { opacity: 1; visibility: visible; }
/* .card only sets position:relative, no z-index -- without this the tile
   below (later in DOM order, same implicit stacking level) painted right
   over the tooltip instead of the tooltip showing on top of it (confirmed
   live: invisible on every row except the very last). z-index on the tile
   itself, not just the tooltip, since a child's z-index only outranks
   OTHER CHILDREN of the same stacking context -- it can't lift itself past
   a whole sibling tile that never opened one of its own. */
.upgrader-target-tile:hover { z-index: 10; }

.upgrader-odds-row { display: flex; justify-content: space-between; align-items: baseline; margin-top: 14px; font-size: 13px; color: var(--text-dim); }
/* Past HIGH_CHANCE_WARN (90%) in upgrader.js -- not a hard block (the bet's
   still perfectly valid), just a nudge that the payout multiplier is barely
   above 1x at this point. --legendary reused as the "caution" tone here
   specifically because it reads as distinct from both --bad (actually
   invalid) and --gold (normal/fine) elsewhere on this same row. */
.upgrader-odds-warning { font-size: 12px; color: var(--legendary); margin-top: 4px; }

/* Win chance now lives entirely on the ring around the target preview
   (below) -- replaces the old linear .upgrader-bar entirely, including its
   own landing animation; see upgrader.js's spinRing(). */


/* Tally (testgame/battle.js) -- replaces the old plain-text win banner.
   Every player's own pulls count up into their own card -- never combined
   into a team number at any point. Then the pot itself moves: frag
   particles fly out of losing players' cards into winning players' cards
   while each number animates to match (losers to 0, winners to their equal
   share of the pot) -- no "Team X wins" text anywhere, watching the frags
   land is what says it. */
.battle-tally { margin: 18px 0; text-align: center; position: relative; }
.tally-status { color: var(--text-dim); font-size: 13px; margin: 0 0 10px; }
.tally-players-row { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
.tally-player-card {
  background: var(--bg-panel-2); border: 2px solid var(--border); border-radius: 12px; padding: 14px 20px; min-width: 130px;
  transition: transform .5s cubic-bezier(.2,.8,.2,1), box-shadow .5s, border-color .5s, opacity .5s, background .5s;
}
.tally-player-name { font-size: 12px; color: var(--text-dim); margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tally-player-value { font-family: var(--display-font); font-weight: 700; font-size: 19px; transition: color .5s, font-size .5s; }
.tally-player-card.tally-winner {
  transform: translateY(-14px) scale(1.12); border-color: var(--gold); z-index: 2;
  box-shadow: 0 0 26px rgba(47,125,253,0.5); background: linear-gradient(180deg, var(--gold)22, var(--bg-panel-2));
}
.tally-player-card.tally-winner .tally-player-value { color: var(--gold); font-size: 23px; }
.tally-player-card.tally-loser { transform: scale(0.92); opacity: 0.5; }
/* Positioned/animated entirely from JS (left/top set on creation, then
   transform+opacity transitioned toward the target card) -- these rules
   just give it a look and keep it out of layout flow. */
.tally-frag-particle {
  position: absolute; width: 20px; height: 20px; margin: -10px 0 0 -10px;
  background: url('/img/mat-metal.webp') center/contain no-repeat;
  pointer-events: none; z-index: 10; filter: drop-shadow(0 0 4px rgba(47,125,253,0.7));
}

/* Outcome strip (testgame/battle.js) -- only runs on an actual tie now,
   to break it before the tally above crowns a winner. A FIXED pointer sits
   still in the middle of the viewport and a long strip of team-colored
   blocks scrolls past it, same mechanic as the crate reels above
   (.spinner-viewport-vertical etc.) just horizontal -- not a bar with a
   moving marker like an earlier version of this. */
.outcome-viewport {
  position: relative; overflow: hidden; height: 56px; margin: 16px 0;
  border-radius: 8px; border: 1px solid var(--border);
  background: linear-gradient(180deg, #0d111c, var(--bg-panel-2));
  box-shadow: inset 0 2px 10px rgba(0,0,0,0.5);
}
.outcome-track { position: absolute; top: 0; left: 0; display: flex; height: 100%; will-change: transform; }
.outcome-block {
  flex: 0 0 90px; margin: 5px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--display-font); font-weight: 700; font-size: 12px; color: #0a0d13;
}
.outcome-block.landed { animation: spinner-land .45s ease-out; box-shadow: 0 0 18px currentColor; }
.outcome-pointer {
  position: absolute; top: 0; bottom: 0; left: 50%; width: 3px;
  background: var(--gold); box-shadow: 0 0 10px var(--gold); transform: translateX(-50%); z-index: 5;
}

/* ---------- Jackpot roll (testgame/battle.js) ---------- */
/* Reuses .outcome-viewport/.outcome-track/.outcome-pointer/.outcome-block
   wholesale (see that section above) -- same scrolling-strip-past-a-fixed-
   pointer mechanic as the tie-break reveal, just with .jackpot-roll-block
   overriding each block's width to its real weighted share instead of the
   tie-break's uniform 90px. flex:0 0 auto is required for the inline width
   to actually take effect -- .outcome-block's own flex:0 0 90px would
   otherwise win via flex-basis regardless of what width is set inline. */
.jackpot-roll-block { flex: 0 0 auto; font-size: 11px; }
.jackpot-wheel-legend { max-width: 320px; margin: 12px auto 0; display: flex; flex-direction: column; gap: 6px; }
.jackpot-wheel-legend-item {
  display: flex; align-items: center; gap: 8px; font-size: 13px;
  padding: 6px 10px; border-radius: 6px; background: var(--bg-panel-2); border: 1px solid var(--border);
}
.jackpot-wheel-swatch { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
/* flex:1 + min-width:0 so a long name (or several, joined for a team battle)
   truncates instead of pushing the %-share span off the edge of the card. */
.jackpot-wheel-legend-names { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jackpot-wheel-legend-winner { border-color: var(--gold); box-shadow: 0 0 10px rgba(47,125,253,0.4); font-weight: 700; }

/* Win-chance slider removed -- odds are now purely a readout (the ring +
   #odds-pct), driven only by bet amount and target picks, not draggable. */

/* ---------- Wheel ---------- */
/* The disc is a self-drawn SVG (built in wheel.js's renderWheelDisc()
   straight from WHEEL_SEGMENTS -- every wedge's angle/color/label comes
   from the same array the spin math uses, so it's exact by construction,
   not a photo that needs measuring). drop-shadow lives on the STATIC
   .wheel-stage wrapper, not on .wheel-disc itself -- a filter on the
   element that actually has transform: rotate() spins right along with it
   (filters apply pre-transform), so the shadow's offset would sweep around
   in a circle every spin and read as the whole wheel shaking. Keeping it on
   the non-rotating parent keeps it fixed in screen space like a real
   shadow, while only the disc's content underneath it turns. */
.wheel-round-status { text-align: center; font-size: 13px; color: var(--text-dim); margin-top: 8px; }
.wheel-round-status strong { color: var(--gold); }
/* Betting-window countdown -- a draining bar instead of a plain "Ns left"
   sentence, same drain-bar idea as .upgrader-bar just time- instead of
   chance-based. Same widget reappears (see .wheel-timer-widget-disabled
   below) while the disc is actually spinning, so its shape stays familiar
   across both phases -- only its state changes. */
.wheel-timer-widget { margin-top: 10px; }
.wheel-timer-bar { position: relative; height: 10px; border-radius: 5px; overflow: hidden; background: var(--bg-panel-3); border: 1px solid var(--border); }
.wheel-timer-bar-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 100%; background: var(--gold); transition: width 0.25s linear, background-color 0.3s; }
.wheel-timer-bar-fill.urgent { background: var(--bad); }
.wheel-timer-row { margin-top: 6px; font-size: 13px; }
.wheel-timer-count { font-family: var(--display-font); font-weight: 700; font-size: 16px; color: var(--gold); }
/* Frozen full + greyed while the disc spins -- there's no countdown to show
   here (the spin animation itself is the thing playing out), the striped
   fill just keeps the bar from reading as "stuck"/broken. */
.wheel-timer-widget-disabled { opacity: 0.45; }
.wheel-timer-widget-disabled .wheel-timer-bar-fill {
  background: repeating-linear-gradient(45deg, var(--text-dim) 0 10px, transparent 10px 20px);
  animation: wheel-timer-stripes 1s linear infinite;
}
@keyframes wheel-timer-stripes { from { background-position: 0 0; } to { background-position: 28px 0; } }
/* Betting's closed but the committed block hasn't landed yet -- unpredictable
   length (real EOS mining, not a fixed timer), so this pulses instead of
   showing progress through anything. */
.wheel-block-wait { text-align: center; margin-top: 10px; }
.wheel-block-wait-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); margin-bottom: 4px; }
.wheel-block-wait-number { font-family: var(--display-font); font-size: 20px; font-weight: 700; animation: wheel-block-pulse 1.4s ease-in-out infinite; }
@keyframes wheel-block-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
/* Click the block number -> pick which real EOS explorer to check it on
   (testgame/wheel.js's eosBlockLink()/EOS_EXPLORERS). */
.eos-block-wrap { position: relative; display: inline-block; }
.eos-block-trigger { color: var(--gold); text-decoration: underline dotted; cursor: pointer; }
.eos-block-menu {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: 6px;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px; display: flex; flex-direction: column; gap: 2px; white-space: nowrap; z-index: 20;
  box-shadow: 0 6px 18px rgba(0,0,0,0.5); text-align: left;
}
.eos-block-menu a { display: block; padding: 6px 10px; border-radius: 6px; color: var(--text); font-size: 12px; text-decoration: none; }
.eos-block-menu a:hover { background: var(--bg-panel-3); color: var(--gold); }
/* Recent Spins + Wheel side by side, history on the left -- align-items:
   stretch (not the usual flex-start) makes the history panel match the
   wheel panel's own height exactly rather than sizing to its own content,
   same idea as a real casino's single-file spin-history rail. Wraps to
   stacked below the wheel on narrow viewports same as every other
   flex-wrap layout on this site, rather than needing its own separate
   mobile breakpoint -- the stretch just stops applying once it's a single
   column and each panel is free to size to its own content instead. */
.wheel-layout { display: flex; gap: 16px; align-items: stretch; justify-content: center; flex-wrap: wrap; }
.wheel-history-panel { width: 100px; flex-shrink: 0; display: flex; flex-direction: column; }
/* flex-basis:0 (not 640px) -- flex-wrap decides whether an item fits a
   line using its BASIS, before any shrinking is applied, so a 640px basis
   still wrapped onto its own line even with flex-shrink allowed, any time
   the remaining space (container minus the 100px history rail) came in
   under 640px, which is most real viewports. Starting from 0 and growing
   to fill whatever room is actually left (capped at max-width) fits
   properly instead. min-width is the real floor below which it gives up
   and wraps to sit under the history rail instead of squeezing further. */
.wheel-main-panel { max-width: 680px; flex: 1 1 0; min-width: 300px; margin: 0; }
/* The site's shared .container caps every page at 1100px -- fine everywhere
   else, but the wheel disc below is now 1.5x its old size (612px) and needs
   more room than that leaves this panel once the history rail + betting
   rail on either side are accounted for. Scoped to just this page via
   :has(), not a change to every other page's .container. */
.container:has(.wheel-layout) { max-width: 1420px; }
/* Betting rail on the right -- same fixed-width-sidebar treatment as
   .wheel-history-panel on the left, just wider since tier squares/chip tray
   need real room. Fixed width (not flex:1) so it doesn't fight the wheel
   panel for space; flex-wrap on .wheel-layout drops it below the wheel on
   narrow viewports same as the history rail does. 580px = 44px padding +
   the chip tray's two 30px page-arrow buttons + their 2*8px flex gaps +
   room for all 6 denomination chips (5*83px + 4*8px gaps = 447px) to sit in
   a single row without the arrows getting squeezed out or the row wrapping;
   comfortably covers the narrower 5*83+4*5=435px the tier squares need too. */
.wheel-betting-panel { width: 580px; flex-shrink: 0; display: flex; flex-direction: column; margin: 0; }
/* Every past spin's actual landed color/number, nothing about anyone's
   bet -- pulled from the real (public, no secret to gate) wheel_rounds
   history on load, then grown live as this browser watches more spins
   resolve (see wheel.js's loadRecentSpinHistory()/pushSpinHistory()).
   Same TIER_COLOR each wedge on the disc itself uses, so a run of colors
   here reads the same as the wedge it landed on. One column wide (not
   wrapped into a grid) -- flex:1 + its own overflow-y so it's exactly as
   tall as the wheel panel next to it and scrolls internally past that
   rather than growing the page. */
.wheel-history-list { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 1; overflow-y: auto; min-height: 0; }
.wheel-history-chip {
  width: 34px; height: 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--display-font); font-weight: 700; font-size: 12px; color: #0a0d13;
  box-shadow: 0 2px 4px rgba(0,0,0,0.4);
  flex-shrink: 0;
}
.wheel-stage { position: relative; width: 100%; max-width: 612px; margin: 18px auto 8px; aspect-ratio: 1 / 1; display: flex; align-items: center; justify-content: center; filter: drop-shadow(0 6px 16px rgba(0,0,0,0.6)); }
.wheel-disc {
  width: 100%; height: 100%;
  transform: rotate(0deg);
  transform-origin: center;
}
.wheel-disc svg { display: block; }
/* Scaled 1.5x along with .wheel-stage (12/20 -> 18/30) so the pointer stays
   in the same proportion to the disc rather than looking undersized next to
   the now-bigger wheel. */
.wheel-pointer { position: absolute; top: -9px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 18px solid transparent; border-right: 18px solid transparent; border-top: 30px solid #c0521f; z-index: 2; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.6)); }

/* Drop targets -- one square per tier. Staking happens by dragging a chip
   from .wheel-chip-tray onto one of these (see wheel.js's
   initChipInteractions()), not by typing into a field. --tier-color is set
   per-square inline (the same TIER_COLOR used on the wheel itself). Fixed
   83px columns (not 1fr) -- matches --wheel-tile-size on .wheel-chip below
   exactly, so the two stay visually the same size rather than each being
   whatever its own layout happens to compute. justify-content:center is a
   safety margin in case the panel is ever a hair wider than the 5 tiles'
   exact total need. */
.wheel-tier-squares { display: grid; grid-template-columns: repeat(5, 83px); justify-content: center; gap: 5px; margin-top: 12px; }
.wheel-tier-square {
  position: relative; aspect-ratio: 1 / 1; border-radius: 8px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  background: linear-gradient(160deg, color-mix(in srgb, var(--tier-color) 18%, var(--bg-panel-2)), var(--bg-panel-2));
  border: 2px solid color-mix(in srgb, var(--tier-color) 45%, var(--border));
  transition: box-shadow 0.15s, border-color 0.15s, transform 0.1s;
}
.wheel-tier-square.has-stake { border-color: var(--tier-color); box-shadow: 0 0 12px color-mix(in srgb, var(--tier-color) 40%, transparent); }
.wheel-tier-square.drag-over { border-color: var(--tier-color); box-shadow: 0 0 0 3px color-mix(in srgb, var(--tier-color) 55%, transparent); transform: scale(1.05); }
.wheel-tier-clear {
  position: absolute; top: 2px; right: 2px; width: 15px; height: 15px; border-radius: 50%;
  background: var(--bad); color: #fff; border: none; font-size: 10px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.wheel-tier-mult { font-family: var(--display-font); font-weight: 700; font-size: 14px; color: var(--tier-color); }
.wheel-tier-odds { color: var(--text-dim); font-size: 8px; }
.wheel-tier-stake { font-weight: 700; font-size: 10px; color: var(--text); }

/* Draggable Frags, not poker chips -- the real mat-metal.webp icon used
   everywhere else on the site (fmtFrags()'s own icon), sized up, with the
   denomination underneath. Pointer-events driven drag (mouse/touch/pen
   alike), not the HTML5 native drag-and-drop API. touch-action:none stops
   the page from scrolling under a touch-drag. */
.wheel-chip-tray { display: flex; align-items: center; gap: 8px; margin-top: 16px; }
/* Betting's closed -- same greyed-out look .btn:disabled already uses
   elsewhere on this site, applied to the tier squares + chip tray instead
   of a button since there's no "place bet" button anymore to disable
   (wheel.js's renderRoundStatus() toggles this the instant status isn't
   "open"). pointer-events:none backs up onChipPointerDown's own status
   check so a drag can't even start, on top of it. */
.wheel-tier-squares.wheel-betting-disabled,
.wheel-chip-tray.wheel-betting-disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}
/* Every player with money on the current round -- just avatars in a row,
   hover one to see exactly what they staked (wheel.js's renderBettors(),
   fed by wheel-status's own bettors list). Kept deliberately quiet -- small
   avatars, small tooltip text -- this is a glance, not a leaderboard. */
.wheel-bettors { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 12px; }
.wheel-bettors-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim); margin-bottom: 8px; }
.wheel-bettors-row { display: flex; flex-wrap: wrap; gap: 8px; min-height: 30px; }
.wheel-bettor { position: relative; }
.wheel-bettor-avatar { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; display: block; border: 2px solid var(--border); cursor: default; }
.wheel-bettor-avatar-blank { background: var(--bg-panel-3); }
.wheel-bettor-tooltip {
  position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 8px;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 8px; white-space: nowrap; z-index: 30;
  opacity: 0; visibility: hidden; transition: opacity 0.12s;
  box-shadow: 0 6px 18px rgba(0,0,0,0.5);
}
.wheel-bettor:hover .wheel-bettor-tooltip { opacity: 1; visibility: visible; }
.wheel-bettor-name { font-size: 11px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
.wheel-bettor-bets-list { display: flex; flex-direction: column; gap: 2px; }
.wheel-bettor-bet { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-dim); }
.wheel-bettor-swatch { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
.wheel-chip-row { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; flex: 1; }
.wheel-chip-page-btn {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  background: var(--bg-panel-2); border: 1px solid var(--border); color: var(--text);
  font-size: 18px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.wheel-chip-page-btn:disabled { opacity: 0.3; cursor: default; }
.wheel-chip-page-btn:not(:disabled):hover { border-color: var(--gold); color: var(--gold); }
/* Square "scrap stack" style tiles (bigger than the old round chips) --
   the mat-metal icon fills the whole tile as a faded watermark behind the
   denomination, same idea as a Rust-skin site's stack-of-scrap betting
   tiles. .wheel-chip-icon is absolutely positioned to cover the tile;
   .wheel-chip-value needs its own stacking context (position:relative) or
   it would paint BEHIND the absolutely-positioned icon regardless of DOM
   order (positioned elements always paint after static ones). 83px to
   match .wheel-tier-squares' own column size exactly -- the betting spots
   and the chips you drag onto them read as one consistent size. */
.wheel-chip {
  position: relative; overflow: hidden;
  width: 83px; height: 83px; border-radius: 10px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, var(--bg-panel-3), var(--bg-panel-2) 75%);
  border: 2px solid var(--gold);
  box-shadow: 0 3px 8px rgba(0,0,0,0.5);
  font-family: var(--display-font); font-weight: 700; font-size: 16px; color: var(--gold);
  cursor: grab; touch-action: none; user-select: none;
  transition: transform 0.1s;
}
.wheel-chip-icon { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.5; pointer-events: none; }
.wheel-chip-value { position: relative; z-index: 1; text-shadow: 0 2px 5px rgba(0,0,0,0.9), 0 0 3px rgba(0,0,0,0.9); }
.wheel-chip:hover { transform: translateY(-2px); }
.wheel-chip.dragging-source { opacity: 0.35; }
.wheel-chip-ghost { position: fixed; z-index: 999; pointer-events: none; transform: translate(-50%, -50%) scale(1.15); filter: drop-shadow(0 6px 10px rgba(0,0,0,0.6)); }

.wheel-total-row { display: flex; align-items: center; gap: 10px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-soft); font-size: 13px; color: var(--text-dim); }
#wheel-total-staked { color: var(--gold); font-weight: 700; }

/* ---------- Global chat sidebar (js/chat.js) ---------- */
/* Fixed to the left edge, full remaining viewport height below the navbar.
   `top` is set inline by chat.js's positionChatBelowNavbar() (measured
   against the real navbar height, not a hardcoded guess -- .navbar can wrap
   taller on narrow screens) rather than here, since below the 1300px
   breakpoint .navbar isn't pushed aside and would otherwise render on top
   of the sidebar's own header (.navbar is z-index:100, above this). Collapses
   by sliding off -100% rather than unmounting -- keeps the realtime
   subscription alive while collapsed instead of tearing it down and missing
   messages sent while closed. */
.chat-sidebar {
  position: fixed; left: 0; top: 68px; bottom: 0; width: 280px; z-index: 80;
  background: var(--bg-panel); border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  box-shadow: 4px 0 16px rgba(0,0,0,0.4);
  transition: transform 0.2s ease;
}
body.chat-collapsed .chat-sidebar { transform: translateX(-100%); }

/* Logo, above the chat panel itself -- first child of .chat-sidebar, above
   .chat-sidebar-header. The navbar's own logo is hidden whenever this one's
   showing (see the .nav-has-sidebar rule near .brand) so there's only ever
   one on screen. */
.chat-sidebar-brand { display: flex; align-items: center; justify-content: center; padding: 12px 16px; border-bottom: 1px solid var(--border); flex: 0 0 auto; }
/* img/logo-crop.png -- a tighter crop of img/logo.png (see the _crop_logo.js
   script this was generated with) trimmed down to just the mark+wordmark.
   The original is a square canvas with the actual logo occupying only the
   middle ~40% of its height (confirmed by scanning its pixels) -- fine
   inline in the busy navbar where it's one item among several, but alone
   in its own dedicated row here that much built-in blank canvas read as a
   literal blank bar. Same mix-blend-mode:screen trick still applies since
   this crop is still on the same solid-black (no alpha) background. */
.chat-sidebar-brand-logo { height: 56px; width: auto; display: block; }

.chat-sidebar-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--border); flex: 0 0 auto;
  font-family: var(--display-font); font-weight: 700; color: var(--gold);
}
.chat-collapse-btn { background: none; border: none; color: var(--text-dim); font-size: 20px; cursor: pointer; line-height: 1; padding: 0; }
.chat-collapse-btn:hover { color: var(--text); }

.chat-messages { flex: 1; overflow-y: auto; padding: 10px 12px; display: flex; flex-direction: column; gap: 10px; }
.chat-msg { display: flex; gap: 8px; align-items: flex-start; }
.chat-msg-avatar { width: 26px; height: 26px; border-radius: 50%; flex: 0 0 auto; object-fit: cover; }
.chat-msg-avatar-blank { background: var(--bg-panel-3); }
.chat-msg-body { min-width: 0; display: flex; flex-direction: column; }
.chat-msg-name { font-size: 11px; font-weight: 700; color: var(--gold); }
.chat-msg-text { font-size: 13px; color: var(--text); word-break: break-word; }

.chat-input-row { display: flex; gap: 6px; padding: 10px 12px; border-top: 1px solid var(--border); flex: 0 0 auto; }
.chat-input-row input { flex: 1; min-width: 0; }
.chat-input-row .btn { padding: 8px 14px; flex: 0 0 auto; }

/* Re-open tab -- only shown while collapsed (see body.chat-collapsed
   below). Vertical text so a thin edge tab still reads as "Chat". */
.chat-toggle-tab {
  display: none; position: fixed; left: 0; top: 50%; transform: translateY(-50%);
  z-index: 79; background: var(--bg-panel-2); border: 1px solid var(--gold); border-left: none;
  border-radius: 0 8px 8px 0; padding: 12px 8px; color: var(--gold); font-weight: 700; font-size: 12px;
  cursor: pointer; writing-mode: vertical-rl;
}
body.chat-collapsed .chat-toggle-tab { display: block; }

/* Only reserve real layout space for the sidebar on wide-enough desktop
   windows -- on anything narrower it stays a slide-in overlay (see
   chat.js's initChatSidebar(), which also defaults to collapsed below this
   same breakpoint) so it never eats into a laptop/tablet-width page.
   Gated on .nav-has-sidebar (js/nav.js, set for signed-in users only --
   same condition chat.js's initChatSidebar() itself gates on) so signed-out
   visitors don't get this padding reserved for a sidebar that never
   actually renders. Used to be plain `body:not(.chat-collapsed)`, which
   matched by default for anyone chat.js had never touched at all (i.e.
   every signed-out visitor, since initChatSidebar() returns before adding
   either class) -- confirmed live as a permanent blank 280px gutter with
   nothing in it. */
@media (min-width: 1300px) {
  body.nav-has-sidebar:not(.chat-collapsed) { padding-left: 280px; }
}

/* ---------- Case Battles (testgame/battles.js) ---------- */
.battle-format-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.battle-format-btn { padding: 8px 16px; }

.battle-crate-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; min-height: 48px; align-items: center; }
.battle-crate-chip { position: relative; width: 48px; height: 48px; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); background: var(--bg-panel-2); display: flex; align-items: center; justify-content: center; }
.battle-crate-chip-remove {
  position: absolute; top: -4px; right: -4px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--bad); color: #fff; border: none; font-size: 11px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.battle-crate-pick-tile { cursor: pointer; }

/* ---------- Create Battle (testgame/battles-create.js) ---------- */
.battle-create-header { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 4px; }
.battle-create-back { color: var(--text-dim); font-size: 13px; text-decoration: none; white-space: nowrap; }
.battle-create-back:hover { color: var(--text); }

.battle-section-label { display: block; font-family: var(--display-font); font-weight: 600; font-size: 13px; letter-spacing: 0.02em; text-transform: uppercase; color: var(--text); margin-top: 20px; }
.battle-section-hint { color: var(--text-dim); font-size: 12px; margin: 4px 0 0; }

.battle-config-cards { display: flex; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
.battle-config-card {
  flex: 1 1 220px; background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px; transition: border-color 0.15s, background 0.15s;
}
/* Regular Battle carries 4 pills (1v1/1v1v1/4-way/6-way) to the other two
   cards' 3-4 -- a plain equal flex-basis left "6-way" wrapping onto its own
   row underneath the first three. Weighted wider instead of just raising
   the shared 220px floor, so Team Battle/Group Unbox don't get pulled
   wider than they need along with it. */
#config-card-regular { flex: 1.35 1 260px; }
.battle-config-card.active { border-color: var(--gold); background: var(--bg-panel-3); }
.battle-config-card-disabled { opacity: 0.45; }
.battle-pill:disabled { cursor: not-allowed; opacity: 0.6; }
.battle-pill:disabled:hover { border-color: var(--border); color: var(--text-dim); }
/* Dimmed by color, not opacity -- opacity would also fade its ::after tooltip. */
.battle-pill.battle-pill-blocked,
.battle-pill.battle-pill-blocked:hover {
  cursor: not-allowed;
  color: color-mix(in srgb, var(--text-dim) 55%, transparent);
  border-color: color-mix(in srgb, var(--border) 60%, transparent);
}
/* Instant on-brand tooltip for formats blocked by the pot limit (data-tip is
   set in battles-create.js's renderPillGroup). */
.battle-pill[data-tip] { position: relative; }
.battle-pill[data-tip]:hover::after {
  content: attr(data-tip); position: absolute; left: 50%; bottom: calc(100% + 8px); transform: translateX(-50%);
  width: max-content; max-width: 240px; white-space: normal; text-align: center; z-index: 20; pointer-events: none;
  padding: 7px 10px; border-radius: 8px; background: var(--bg-panel-3); border: 1px solid var(--border);
  color: var(--text); font-size: 12px; font-weight: 600; line-height: 1.35; box-shadow: var(--elev-1);
}
.battle-config-card-head { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 14px; }
.battle-config-card-info { color: var(--text-dim); font-size: 12px; font-weight: 400; cursor: help; margin-left: -2px; }
.battle-pill-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.battle-pill {
  padding: 6px 12px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-panel);
  color: var(--text-dim); font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.15s;
}
.battle-pill:hover { border-color: var(--gold); color: var(--text); }
.battle-pill.selected { background: var(--gold); border-color: var(--gold); color: #fff; }

.battle-crates-area { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; align-items: center; }
.battle-add-crate-card {
  width: 64px; height: 64px; border-radius: 10px; border: 1px dashed var(--border);
  background: transparent; color: var(--text-dim); cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  transition: border-color 0.15s, color 0.15s;
}
.battle-add-crate-card:hover { border-color: var(--gold); color: var(--gold); }
/* Crate picker grid -- its own class, not .loot-grid, which the Rockets panel
   redefines as a fixed 6-column grid further down this file. */
.crate-pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
/* Crate that would push the battle past the pot limit (battles-create.js). */
.battle-crate-pick-tile.blocked { opacity: .35; filter: grayscale(1); cursor: not-allowed; }
.battle-crate-pick-tile.blocked:hover { transform: none; border-color: var(--border); }
.battle-add-crate-plus { font-size: 18px; line-height: 1; }
.battle-add-crate-label { font-size: 9px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; }

.battle-modifiers-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; }
@media (max-width: 640px) { .battle-modifiers-grid { grid-template-columns: 1fr; } }
.battle-modifier-card {
  display: flex; align-items: center; gap: 10px; background: var(--bg-panel-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px; cursor: pointer;
}
.battle-modifier-icon { font-size: 18px; width: 28px; text-align: center; flex-shrink: 0; }
/* Mode glyphs, img/modes/*.svg -- each carries its own color. */
.battle-mode-icon { width: 22px; height: 22px; flex-shrink: 0; display: block; }
.battle-modifier-icon .battle-mode-icon { width: 28px; height: 28px; margin: 0 auto; }
.battle-modifier-text { flex: 1; min-width: 0; }
.battle-modifier-title { font-size: 13px; font-weight: 600; }
.battle-modifier-desc { font-size: 11px; color: var(--text-dim); margin-top: 2px; line-height: 1.4; }

/* Checkbox-driven switch -- the input stays in the DOM (so the label's own
   click toggles it and :checked is just plain CSS) but is visually hidden
   rather than display:none, which some browsers exclude from tab order. */
.battle-toggle-input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.battle-toggle {
  flex-shrink: 0; width: 36px; height: 20px; border-radius: 10px; background: var(--bg-panel-3);
  border: 1px solid var(--border); position: relative; transition: background 0.15s;
}
.battle-toggle::before {
  content: ''; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--text-dim); transition: transform 0.15s, background 0.15s;
}
.battle-toggle-input:checked + .battle-toggle { background: var(--gold); border-color: var(--gold); }
.battle-toggle-input:checked + .battle-toggle::before { transform: translateX(16px); background: #fff; }
.battle-toggle-input:focus-visible + .battle-toggle { outline: 2px solid var(--gold); outline-offset: 2px; }

/* ---------- Battles lobby (testgame/battles.html) ---------- */
.bl-hero, .bl-panel { max-width: 860px; margin: 0 auto 20px; }
.bl-hero {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  background: radial-gradient(120% 140% at 0% 0%, var(--accent-glow), transparent 55%), var(--bg-panel);
}
.bl-hero-text { flex: 1 1 320px; }
.bl-hero-text p { color: var(--text-dim); font-size: 13px; margin: -8px 0 0; line-height: 1.5; }
.bl-hero-btn { flex: 0 0 auto; padding: 14px 26px; font-size: 15px; }
.bl-panel-title { margin: 0 0 14px; display: flex; align-items: center; gap: 10px; }
.bl-count {
  font-family: var(--body-font); font-size: 12px; font-weight: 700; color: #fff;
  background: var(--gold); border-radius: 999px; padding: 2px 9px;
}
.bl-count:empty { display: none; }

/* Open battles are a <a> (link to their own battle page) or, for private
   battles seen by strangers, an inert <div> -- see battles.js. */
.bl-card {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  grid-template-areas: "head teams side" "crates teams side";
  align-items: center; gap: 10px 22px;
  background: linear-gradient(180deg, var(--bg-panel-2), var(--bg-panel));
  border: 1px solid var(--border); border-radius: 12px; box-shadow: inset 0 1px 0 var(--edge-hi);
  padding: 14px 16px; margin-bottom: 10px; text-decoration: none; color: inherit;
  transition: border-color .15s, transform .15s, box-shadow .15s;
}
a.bl-card:hover { border-color: var(--gold); transform: translateY(-2px); box-shadow: var(--elev-1), 0 0 0 1px var(--accent-glow); }
.bl-card-locked { opacity: 0.7; cursor: default; }
.bl-card-head { grid-area: head; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.bl-format {
  font-family: var(--display-font); font-weight: 700; font-size: 15px; letter-spacing: .02em;
  color: var(--gold); background: var(--accent-wash); border: 1px solid var(--accent-glow);
  border-radius: 6px; padding: 2px 9px;
}
.bl-mod { width: 20px; height: 20px; cursor: help; display: block; }
.bl-meta { color: var(--text-dim); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.bl-crates { grid-area: crates; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.bl-crate {
  display: inline-flex; border-radius: 8px; overflow: hidden; background: var(--bg-panel-3);
  border: 1px solid var(--border-soft);
}
.bl-crate-more { color: var(--text-dim); font-size: 12px; font-weight: 700; padding: 0 4px; }

.bl-teams { grid-area: teams; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: center; }
.bl-team { display: inline-flex; gap: 4px; padding: 3px; border-radius: 999px; border: 1px solid transparent; }
.bl-team-won { border-color: rgba(53,208,127,.55); background: rgba(53,208,127,.1); }
.bl-team-lost { opacity: .45; }
.bl-vs { font-family: var(--display-font); font-size: 11px; font-weight: 700; color: var(--text-dim); letter-spacing: .05em; }
.bl-avatar {
  width: 44px; height: 44px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center; font-size: 20px;
  background: var(--bg-panel-3); border: 2px solid var(--border);
}
.bl-team-won .bl-avatar { border-color: var(--good); }
.bl-avatar-empty { border-style: dashed; background: transparent; }

.bl-side { grid-area: side; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; min-width: 104px; }
.bl-side-label { font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-dim); }
.bl-cost { font-family: var(--display-font); font-size: 18px; font-weight: 700; color: var(--text); white-space: nowrap; }
.bl-fill { font-size: 11px; color: var(--text-dim); }
.bl-btn { pointer-events: none; margin-top: 4px; padding: 7px 18px; }

.bl-card-compact { padding: 10px 14px; gap: 6px 18px; }
.bl-card-compact .bl-avatar { width: 38px; height: 38px; font-size: 17px; }
.bl-card-compact .bl-cost { font-size: 15px; }
.bl-card-cancelled { opacity: .55; }
.bl-status { font-size: 11px; font-weight: 700; color: var(--text-dim); white-space: nowrap; }
.bl-status-won { color: var(--good); }
.bl-status-cancelled { color: var(--bad); }
.bl-status-live { color: #ff5c5c; animation: bl-live-pulse 1.4s ease-in-out infinite; }
@keyframes bl-live-pulse { 50% { opacity: .45; } }

.bl-empty {
  display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 26px 12px;
  color: var(--text-dim); font-size: 13px; border: 1px dashed var(--border); border-radius: 12px;
}
.bl-empty-icon { font-size: 30px; opacity: .6; }

@media (max-width: 640px) {
  .bl-card { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "head side" "crates side" "teams teams"; }
  .bl-teams { justify-content: flex-start; }
  .bl-hero-btn { width: 100%; text-align: center; }
}

/* Standin for the reel view for the one viewer whose own click is
   resolving the battle, while it's waiting on the EOS block committed to
   at creation -- see battle.js's showWaitingForBlock(). */
.eos-wait-spinner {
  width: 32px; height: 32px; margin: 0 auto 12px;
  border: 3px solid var(--border); border-top-color: var(--gold); border-radius: 50%;
  animation: eos-wait-spin 0.8s linear infinite;
}
@keyframes eos-wait-spin { to { transform: rotate(360deg); } }

/* Battle detail -- one column per team, members stacked, each member's
   pulls as a small row of tiles colored by rarity (reuses the site's
   existing .rarity-* border classes). */
/* ---------- Battle page (testgame/battle.html) ---------- */
.bt-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; color: var(--text-dim); font-size: 13px; margin-top: -6px; }
.bt-meta-dot { opacity: .5; }
.bt-format {
  font-family: var(--display-font); font-weight: 700; font-size: 14px; color: var(--gold);
  background: var(--accent-wash); border: 1px solid var(--accent-glow); border-radius: 6px; padding: 1px 8px;
}
.bt-mode {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 700; color: var(--text); background: var(--bg-panel-3);
  border: 1px solid var(--border); border-radius: 999px; padding: 2px 9px 2px 5px;
}
.bt-mode img { width: 16px; height: 16px; }
/* One tile per round, in opening order. Single scrolling row rather than a
   wrap -- a 22-round battle would otherwise push the results off screen. */
.bt-rounds { display: flex; gap: 8px; overflow-x: auto; padding: 12px 2px 6px; margin-bottom: 8px; scrollbar-width: thin; }
.bt-round {
  position: relative; flex: 0 0 auto; display: inline-flex; padding: 4px; border-radius: 10px;
  background: var(--bg-panel-2); border: 1px solid var(--border);
}
.bt-round-num {
  position: absolute; top: -7px; left: -6px; min-width: 18px; height: 18px; padding: 0 4px; border-radius: 9px;
  background: var(--gold); color: #fff; font-size: 10px; font-weight: 800; line-height: 18px; text-align: center;
}

.bt-pf { background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px; margin: 4px 0 16px; }
.bt-pf summary { cursor: pointer; font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-dim); list-style: none; }
.bt-pf summary::-webkit-details-marker { display: none; }
.bt-pf summary::after { content: '▾'; float: right; transition: transform .15s; }
.bt-pf[open] summary::after { transform: rotate(180deg); }
.bt-pf[open] summary { margin-bottom: 10px; }
.bt-pf-hint { text-transform: none; letter-spacing: 0; font-weight: 400; opacity: .7; margin-left: 6px; }

.win-reveal.show.bt-win {
  display: flex; align-items: center; gap: 16px; margin: 0 0 16px; border-color: var(--good);
  background: radial-gradient(120% 160% at 0% 0%, rgba(53,208,127,.22), transparent 60%), var(--bg-panel-2);
  box-shadow: 0 0 28px rgba(53,208,127,.18);
}
.bt-win-trophy { font-size: 40px; line-height: 1; filter: drop-shadow(0 0 12px rgba(245,197,24,.45)); }
.bt-win-title { color: var(--text); }
.bt-win-sub { margin-top: 4px; }

.bt-teams { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; }
.bt-team {
  background: linear-gradient(180deg, var(--bg-panel-2), var(--bg-panel)); border: 1px solid var(--border);
  border-radius: 12px; padding: 14px; box-shadow: inset 0 1px 0 var(--edge-hi);
}
.bt-team-won { border-color: var(--good); box-shadow: 0 0 0 1px rgba(53,208,127,.35), 0 0 26px rgba(53,208,127,.16); }
.bt-team-lost { opacity: .72; }
.bt-team-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.bt-team-label { font-family: var(--display-font); font-size: 16px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
.bt-winner-badge {
  font-size: 11px; font-weight: 800; color: var(--good); background: rgba(53,208,127,.12);
  border: 1px solid rgba(53,208,127,.4); border-radius: 999px; padding: 2px 9px;
}
.bt-team-total { margin-left: auto; font-family: var(--display-font); font-size: 18px; font-weight: 700; white-space: nowrap; }
.bt-team-won .bt-team-total { color: var(--good); }
.bt-member + .bt-member { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-soft); }
.bt-member-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; font-size: 13px; }
.bt-avatar { display: inline-flex; flex-shrink: 0; }
.bt-avatar .battle-joinbar-avatar { width: 40px; height: 40px; font-size: 19px; }
/* min-width:0 so the ellipsis can actually kick in inside the flex row;
   the total keeps flex-shrink:0 so it never gets squeezed instead. */
.bt-member-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-weight: 600; }
.bt-member-total { margin-left: auto; flex-shrink: 0; color: var(--gold); font-weight: 700; white-space: nowrap; }
.bt-empty { color: var(--text-dim); font-size: 12px; margin: 0; }

.bt-pulls { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; }
/* --rc is the pull's rarity color, set inline from battle.js's rarityColor(). */
.bt-pull {
  display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 6px 7px; border-radius: 10px;
  background: radial-gradient(90% 70% at 50% 35%, color-mix(in srgb, var(--rc) 26%, transparent), transparent 70%), var(--bg-panel);
  border: 1px solid color-mix(in srgb, var(--rc) 35%, var(--border));
  border-bottom: 3px solid var(--rc); min-width: 0;
}
.bt-pull-icon { display: flex; align-items: center; justify-content: center; height: 56px; }
.bt-pull-icon img { filter: drop-shadow(0 4px 8px rgba(0,0,0,.45)); }
.bt-pull-name { font-size: 10px; color: var(--text-dim); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bt-pull-value { font-size: 12px; font-weight: 700; white-space: nowrap; }

/* Join bar -- shown only while a battle is still "waiting" (see
   renderBattleHtml's isOpen branch), replacing the single full-width
   "Join for $X" button with one team-colored chip per open seat, grouped
   by team and separated by a VS divider (2 teams) or a subtler dot
   separator (3+ teams). The vertical .battle-team-col view above stays
   completely untouched for complete/cancelled battles. */
.battle-joinbar { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px; margin: 4px 0 8px; padding: 14px 10px; background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px; }
.battle-joinbar-group { display: flex; flex-direction: column; gap: 8px; align-items: center; }
.battle-joinbar-group-label { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.battle-joinbar-group-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }
.battle-joinbar-vs { font-size: 15px; font-weight: 800; color: var(--text-dim); padding: 0 4px; }
.battle-joinbar-sep { width: 1px; align-self: stretch; background: var(--border); margin: 0 1px; }
/* Padding tightens (not the border/gap/font) as team count climbs, purely
   via how many direct chip/sep children share the row -- CSS can't key off
   sibling count on its own, so battle.js's buildJoinBarHtml adds
   .battle-joinbar-tight (5+ groups) / .battle-joinbar-tighter (6+ groups)
   itself, same "compute it once in JS, just toggle a class" approach as
   every other conditional style in this file. Keeps 1v1/2v2/etc at their
   normal roomier size and only tightens up once a row actually needs it
   (4-way, 6-way, 2v2v2...) to still fit on one line instead of wrapping. */
.battle-joinbar-chip { display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 2px solid var(--border); border-radius: 999px; background: var(--bg-panel); min-height: 40px; }
.battle-joinbar-tight .battle-joinbar-chip { padding: 6px 9px; gap: 6px; }
.battle-joinbar-tighter .battle-joinbar-chip { padding: 5px 8px; gap: 5px; }
.battle-joinbar-tighter .battle-joinbar-avatar,
.battle-joinbar-tighter .battle-joinbar-avatar-fallback,
.battle-joinbar-tighter .battle-joinbar-avatar-bot { width: 22px; height: 22px; }
.battle-joinbar-tighter .battle-joinbar-name { max-width: 82px; font-size: 12px; }
.battle-joinbar-chip-filled { font-size: 13px; font-weight: 600; }
.battle-joinbar-chip-empty { border-style: dashed; background: transparent; padding: 4px 4px 4px 12px; }
.battle-joinbar-chip-empty .battle-join-btn { padding: 6px 14px; border-radius: 999px; font-size: 13px; white-space: nowrap; }
.battle-joinbar-empty-label { font-size: 12px; color: var(--text-dim); padding: 0 8px; }
/* max-width caps how wide a single long name can push the pill before
   ellipsis kicks in; min-width:0 is required alongside it since this is a
   flex child of .battle-joinbar-chip -- flex items default to min-width:auto
   (their content's natural size), which would silently defeat the ellipsis
   entirely without this. */
.battle-joinbar-name { max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.battle-joinbar-avatar { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; }
.battle-joinbar-avatar-fallback, .battle-joinbar-avatar-bot { width: 26px; height: 26px; border-radius: 50%; background: var(--bg-panel-2); display: flex; align-items: center; justify-content: center; font-size: 13px; }
/* --team is the seat's team color, set inline by buildJoinBarHtml. */
.battle-joinbar-roamer-btn {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 14px; flex-shrink: 0;
  border-radius: 999px; border: 1px solid color-mix(in srgb, var(--team, var(--gold)) 70%, transparent);
  background: color-mix(in srgb, var(--team, var(--gold)) 14%, var(--bg-panel));
  color: var(--text); font-family: var(--body-font); font-size: 13px; font-weight: 700; white-space: nowrap;
  cursor: pointer; transition: background .15s, box-shadow .15s, transform .15s;
}
.battle-joinbar-roamer-btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--team, var(--gold)) 26%, var(--bg-panel));
  box-shadow: 0 0 12px color-mix(in srgb, var(--team, var(--gold)) 40%, transparent);
  transform: translateY(-1px);
}
.battle-joinbar-roamer-btn:disabled { opacity: .65; cursor: wait; }
/* A seat that only holds the roamer button (the creator's view) -- even padding. */
.battle-joinbar-chip-roamer { padding: 4px; }
.battle-joinbar-tighter .battle-joinbar-roamer-btn { padding: 0 10px; font-size: 12px; }

/* Reveal area -- sits below the Provably Fair box and above the row of
   player chips (see renderBattleHtml/buildPreRevealShellHtml in battle.js).
   Empty (still just waiting on players), it takes up no space at all; the
   :not(:empty) box only appears once showAllSlotsFilled/showWaitingForBlock/
   playCountdown/playBattleReveal actually put something in it, so the whole
   "everyone's in, resolving, here's the reels" sequence plays in this one
   spot instead of replacing the entire screen. */
#battle-reveal-area:not(:empty) { margin: 10px 0 16px; padding: 16px; background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px; }
.battle-pull-row { display: flex; flex-wrap: wrap; gap: 6px; }
.battle-pull-tile { width: 52px; border: 2px solid var(--border); border-radius: 6px; padding: 4px; text-align: center; background: var(--bg-panel); }
/* NOT .item-emoji -- that's a fixed 64x64 circular badge (.case-emoji,
   .item-emoji in the cards section above), sized for wide card layouts.
   This tile is only 52px wide (44px of content once its own padding/border
   are subtracted) -- wrapping itemVisual()'s 44px icon in that badge made
   it overflow the tile on every side instead of sitting inside it. This
   tile's own icon wrapper just centers the image at its actual requested
   size, no forced badge size of its own. */
.battle-pull-icon { display: flex; align-items: center; justify-content: center; }
.battle-pull-icon img { border-radius: 6px; }
.battle-pull-value { font-size: 10px; color: var(--text-dim); margin-top: 2px; }

/* Live reveal's growing haul (#battle-reel-results, inside the shared
   .multi-spinner-row flex container) -- one column per player, lined up
   under that same player's reel above, listing every .battle-pull-tile
   they've pulled so far this battle (wraps onto more rows as more crates
   land) instead of being overwritten each round by only the latest one. */
.multi-result-col { display: flex; flex-direction: column; align-items: center; gap: 6px; width: 130px; }
.multi-result-col-name { font-size: 12px; color: var(--text-dim); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.multi-result-col-items { display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; }

/* Reveal reels (testgame/battle.js's playBattleReveal()) -- one small
   vertical reel per player slot, reusing case-open.js's own
   .spinner-viewport-vertical/.spinner-track-vertical/.spinner-item-vertical
   sizing and animation byte-for-byte, all in one flat side-by-side row
   (labeled with each player's name + team) instead of case-open's plain
   0..N-1 multi-open row. */
.battle-reel-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.battle-reel-col { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.battle-reel-name { font-size: 12px; color: var(--text-dim); max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- Blackjack ---------- */
.bj-table { max-width: 640px; margin: 0 auto; text-align: center; }
.bj-dealer { margin-bottom: 22px; }
.bj-dealer h3, .bj-hand-label { font-family: var(--display-font); font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); margin: 0 0 8px; }
.bj-cards { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; min-height: 84px; }
.bj-card {
  width: 56px; height: 78px; border-radius: 8px; background: #eceff5; color: #0a0d13;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  font-family: var(--display-font); font-weight: 700; box-shadow: 0 3px 0 rgba(0,0,0,0.35); flex-shrink: 0;
  animation: bj-deal 0.25s ease-out;
}
@keyframes bj-deal { from { transform: translateY(-14px) scale(0.9); opacity: 0; } to { transform: none; opacity: 1; } }
.bj-card.red { color: var(--bad); }
.bj-card-rank { font-size: 20px; line-height: 1; }
.bj-card-suit { font-size: 22px; line-height: 1.3; }
.bj-card-back {
  background: linear-gradient(135deg, var(--rust), var(--orange));
  border: 2px solid var(--border);
}
.bj-total { font-family: var(--display-font); font-weight: 700; color: var(--gold); margin-top: 8px; min-height: 20px; }
.bj-hands { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; margin-bottom: 14px; }
.bj-hand {
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px; min-width: 170px; transition: border-color 0.15s, box-shadow 0.15s;
}
.bj-hand-active { border-color: var(--gold); box-shadow: 0 0 16px rgba(47,125,253,0.35); }
.bj-hand-total { font-family: var(--display-font); font-weight: 700; margin-top: 6px; }
.bj-hand-bet { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
.bj-hand-badge { display: inline-block; margin-top: 6px; padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.4px; }
.bj-badge-active { background: rgba(47,125,253,0.18); color: var(--gold); }
.bj-badge-stand { background: rgba(138,145,166,0.18); color: var(--text-dim); }
.bj-badge-bust, .bj-badge-lose { background: rgba(255,92,92,0.15); color: var(--bad); }
.bj-badge-win, .bj-badge-blackjack { background: rgba(53,208,127,0.15); color: var(--good); }
.bj-badge-push { background: rgba(197,164,25,0.15); color: var(--mythic); }
.bj-controls { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 6px; }
.bj-controls .btn { min-width: 90px; }

/* ---------- Autobet ---------- */
/* Injected by js/autobet.js into whatever #autobet-root the page provides, so
   these rules are shared by all five games that offer it -- see that file for
   why the engine owns its own markup instead of it being copied into five
   HTML pages. Sits visually *under* the game's own controls: it's a secondary
   mode, and a panel as loud as the game itself would read as the main event. */
.autobet-panel {
  background: var(--bg-panel-2);
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  padding: 14px 16px 16px;
  margin-top: 18px;
}
.autobet-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px; }
.autobet-title {
  font-family: var(--display-font); font-weight: 700; font-size: 15px;
  text-transform: uppercase; letter-spacing: 0.6px;
}
.autobet-status { font-size: 12px; color: var(--text-dim); }
/* auto-fit rather than a fixed column count: the same panel has to sit in
   Dice's 520px column and under Plinko's full-width board. The two
   win/loss rules get their own wider track so they always land beside each
   other -- in one shared grid the third stop-field would pair "Stop on loss"
   with "On win" on the same row, which reads as though they belong together. */
.ab-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0 12px; }
.ab-grid-rules { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.ab-field label { margin-top: 10px; }
.ab-field input { width: 100%; }
.ab-seg { display: flex; gap: 6px; }
.ab-seg .btn { flex: 1; padding: 8px 4px; font-size: 12px; }
/* The percent box only exists in "Increase" mode; reserving its height in
   "Reset" mode would leave a hole under half the fields at all times. */
#ab-win-pct, #ab-loss-pct { margin-top: 6px; }

.ab-readout {
  display: flex; gap: 10px; margin: 14px 0 2px;
  background: var(--bg-panel); border: 1px solid var(--border-soft);
  border-radius: 10px; padding: 10px 12px;
}
.ab-readout > div { flex: 1; text-align: center; }
.ab-readout span {
  display: block; font-size: 10px; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 3px;
}
.ab-readout strong { font-family: var(--display-font); font-size: 17px; font-weight: 700; }

.autobet-note { color: var(--text-dim); font-size: 11px; line-height: 1.5; margin: 10px 0 0; text-align: center; }

/* ---------- Boxes ---------- */
/* Rust's large-wooden-box loot panel, as close as CSS gets: a dark inset
   frame with a "LOOT" header and a 6x8 grid of square slots. Everything
   here is sized off the grid rather than fixed px so the same markup works
   at 660px and at 340px -- aspect-ratio keeps the slots square at whatever
   width six columns happen to divide into. */
.box-tier-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin: 8px 0 16px;
}
/* The tier buttons carry two lines (name over price), which .btn's own
   single-line centering doesn't allow for. */
.box-tier-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 9px 6px;
  line-height: 1.25;
}
.box-tier-name { font-size: 12px; }
.box-tier-price { font-size: 11px; opacity: 0.85; display: flex; align-items: center; gap: 3px; }

/* The spin strip (Boxes rules_version 2). An open lands on exactly one of
   eight boxes before any slot is rolled, so the page finally has something
   to spin onto. Built from the same inset-dark-frame vocabulary as
   .loot-panel right below rather than a new look: same background, same
   border, same inset shadow, so the two panels read as one machine. The
   strip is a flex row of square icon tiles inside an overflow-hidden window
   with a centre marker; testgame/boxes.js measures the real tile width and
   gap off the DOM and animates translateX, so the numbers here can change
   without desyncing the landing. */
.box-spin {
  background: rgba(0,0,0,0.42);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px;
  margin-top: 14px;
  box-shadow: inset 0 2px 10px rgba(0,0,0,0.55);
}
.box-spin-window {
  position: relative;
  overflow: hidden;
  border-radius: 4px;
  /* Fixed to 3 tiles wide (2 * gap + 3 * tile, matching .box-spin-tile below)
     so this reads as a peephole with one framed centre tile and two fading
     neighbours -- not full-panel-width, which just shows a loose row of
     box photos with no obvious "this one" focal point. Mobile's media query
     below re-derives this at its own smaller tile size. */
  width: 228px;
  margin: 0 auto;
  /* Feathered edges so tiles enter and leave the window instead of being
     visibly clipped by a hard border -- the same trick the reel needs at
     speed for the motion to read as continuous. */
  mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}
.box-spin-strip {
  display: flex;
  gap: 6px;
  /* Animated via the Web Animations API, not a CSS transition -- the reel
     needs two chained phases with different easings (see runSpin), and
     will-change keeps the whole strip on its own layer for the fast one. */
  will-change: transform;
}
.box-spin-tile {
  flex: 0 0 auto;
  width: 72px;
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255,255,255,0.035);
  border: 1px solid rgba(255,255,255,0.07);
  border-radius: 3px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
}
.box-spin-icon {
  width: 88%;
  height: 88%;
  object-fit: contain;
  /* 512px source art at 63px -- dimmed slightly so the landed tile's own
     full-brightness state below is a visible change, not a guess. */
  opacity: 0.72;
  -webkit-user-drag: none;
  user-select: none;
}
/* Centre pointer -- where the strip is going to stop. */
.box-spin-marker {
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: 50%;
  width: 78px;              /* tile + gap, so it frames exactly one tile */
  transform: translateX(-50%);
  border-left: 1px solid rgba(255,255,255,0.18);
  border-right: 1px solid rgba(255,255,255,0.18);
  background: linear-gradient(to bottom, rgba(245,197,24,0.10), rgba(245,197,24,0));
  pointer-events: none;
}
.box-spin.landed .box-spin-marker { border-color: rgba(245,197,24,0.55); }
.box-spin-landed .box-spin-icon { opacity: 1; }
.box-spin-landed {
  background: rgba(245,197,24,0.10);
  border-color: rgba(245,197,24,0.45);
  animation: loot-pop .22s cubic-bezier(.2,.8,.3,1.2);
}
.box-spin-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin-top: 8px;
  min-height: 30px;         /* reserved, so the panel doesn't jump when the label swaps */
  text-align: center;
}
.box-spin-status {
  font-family: var(--display-font);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--text-dim);
}
.box-spin-status-fire { color: #f5c518; }
.box-spin-landed-name {
  font-family: var(--display-font);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.4px;
  color: var(--text);
}
.box-spin-landed-sub { font-size: 11px; color: var(--text-dim); }

.loot-panel {
  background: rgba(0,0,0,0.42);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px;
  margin: 0 0 14px;
  box-shadow: inset 0 2px 10px rgba(0,0,0,0.55);
}
.loot-panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 8px;
  font-family: var(--display-font);
  text-transform: uppercase;
  letter-spacing: 1.4px;
}
.loot-panel-title { font-size: 13px; font-weight: 700; color: var(--text-dim); }
.loot-panel-count { font-size: 11px; letter-spacing: 0.6px; color: var(--text-dim); }

.loot-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; }
.loot-slot {
  position: relative;
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  background: rgba(255,255,255,0.035);
  border: 1px solid rgba(255,255,255,0.07);
  border-radius: 3px;
  /* Empty is the resting state and has to look deliberate rather than
     unloaded, so the slots keep a visible bevel whether or not anything
     is in them. */
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
}
/* .rarity-* (shared with case-open.js's items) supplies the border color and
   glow, so this only has to handle the fill and the pop. */
.loot-slot.filled {
  background: rgba(255,255,255,0.08);
  animation: loot-pop .22s cubic-bezier(.2,.8,.3,1.2);
}
@keyframes loot-pop {
  from { transform: scale(.55); opacity: 0; }
  to   { transform: scale(1);   opacity: 1; }
}
.loot-slot-emoji { font-size: 22px; line-height: 1; }
.resource-icon-lg, .resource-icon-md, .resource-icon-sm { object-fit: contain; vertical-align: middle; }
.resource-icon-lg { width: 60px; height: 60px; }
.resource-icon-md { width: 22px; height: 22px; }
.resource-icon-sm { width: 20px; height: 20px; }
/* Full brightness, not --text-dim: the emoji says what the item is but this
   is the only place the slot says what it's worth, so it isn't secondary. */
.loot-slot-value {
  font-family: 'Roboto Condensed', var(--display-font);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.2px;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  opacity: 0.7;
  text-align: right;
  align-self: flex-end;
}
/* Boxes rules_version 8: a slot can hold several copies of the same skin, so
   it needs a multiplier badge between the icon and the value. Dimmer and
   smaller than .loot-slot-value on purpose -- "how many" is secondary to
   "what it's worth", and the value shown is already the whole slot. */
.loot-slot-copies {
  font-family: var(--display-font);
  font-size: 9px;
  font-weight: 700;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.box-note { color: var(--text-dim); font-size: 12px; margin-top: 10px; line-height: 1.6; }

.box-paytable { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 12px; }
.box-pay-head { color: var(--text-dim); font-size: 12px; line-height: 1.6; margin-bottom: 10px; }
.box-pay-head strong { color: var(--text); }
/* Three columns shared by the header row and every item row, so the numbers
   line up without a <table>. */
.box-pay-cols, .box-pay-row {
  display: grid;
  grid-template-columns: 1fr auto 92px;
  gap: 10px;
  align-items: center;
}
.box-pay-cols {
  font-family: var(--display-font);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-dim);
  padding-bottom: 6px;
}
.box-pay-row { padding: 5px 0; border-top: 1px solid rgba(255,255,255,0.05); font-size: 13px; }
/* One of these sits above each named box's row group -- an open rolls the
   box first and then rolls inside only that one, so the paytable reads the
   same way: which box lands, then what that box pays. Flex rather than plain
   text since it now carries the box's own icon on the left and its
   fill/dry/landing numbers pushed out to the right. */
.box-pay-section {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  font-family: var(--display-font);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
  color: var(--text);
  margin-top: 12px;
  padding-bottom: 4px;
  border-bottom: 1px solid rgba(255,255,255,0.08);
}
.box-pay-section:first-of-type { margin-top: 0; }
/* Per-box numbers that belong to the section header rather than any one row
   (landing chance, this box's own fill rate, how often it comes out dry). */
.box-pay-section-note {
  margin-left: auto;
  font-family: inherit;
  font-weight: 400;
  font-size: 10px;
  letter-spacing: 0.2px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
/* The same 512px spin art the reel uses, at row scale, so the "which box"
   table and the reel are unmistakably the same eight things. */
.box-pay-icon {
  width: 20px;
  height: 20px;
  object-fit: contain;
  flex-shrink: 0;
}
.box-pay-item { display: flex; align-items: center; gap: 8px; min-width: 0; }
.box-pay-emoji { font-size: 16px; flex-shrink: 0; }
/* .tag and .tag-rarity both carry a 6px top margin for the stacked card
   layouts they were written for; in a row it just knocks the text off centre. */
.box-pay-row .tag, .box-pay-row .tag-rarity { margin-top: 0; }
.box-pay-val, .box-pay-odds { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
.box-pay-odds { justify-content: flex-end; color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* Rockets' post-run summary -- one chip per resource (stacked total across
   every hit) plus one per skin won, sitting in #loot-totals' flex-wrap row. */
.rocket-total-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  padding: 4px 10px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 560px) {
  .box-tier-row { grid-template-columns: repeat(2, 1fr); }
  .loot-slot-emoji { font-size: 18px; }
  .resource-icon-lg { width: 60px; height: 60px; }
  .loot-slot-value { font-size: 14px; }
  .loot-slot-copies { font-size: 7px; }
  .box-pay-cols, .box-pay-row { grid-template-columns: 1fr auto 74px; gap: 7px; }
  /* Boxes' item names always had a space to wrap on if the column got
     tight; Rockets' single-word resource names (Charcoal, Sulfur, ...)
     don't, so a long one runs past its own grid column and straight over
     the value column next to it. Let it break mid-word as a last resort,
     and let the value text wrap too instead of forcing it onto one line. */
  .box-pay-item span:last-child { overflow-wrap: anywhere; }
  .box-pay-val { white-space: normal; text-align: right; justify-content: flex-end; }
  /* Same 3-tile-wide peephole as desktop, just re-derived at the smaller
     mobile tile size (2 * gap + 3 * tile). The marker has to track the tile
     width by hand -- it's tile + gap, same as above. */
  .box-spin-tile { width: 54px; height: 54px; }
  .box-spin-window { width: 174px; }
  .box-spin-marker { width: 60px; }
  /* Drops under its own row on narrow screens rather than squeezing the box
     name to nothing. */
  .box-pay-section-note { margin-left: 0; flex-basis: 100%; }
}

/* ---------- Icon set (img/icons/, img/modes/) ----------
   Our own line icons in place of emoji -- emoji render differently on every
   OS (and as tofu on some), these don't. Inline by default: sized off the
   surrounding text so the same <img class="ico"> works in a button, a
   heading or a status pill. Containers that used to hold a lone big emoji
   size the image themselves below. */
.ico { display: inline-block; width: 1.15em; height: 1.15em; vertical-align: -0.2em; flex-shrink: 0; }
.section-title .ico { width: 1.25em; height: 1.25em; vertical-align: -0.24em; margin-right: 4px; }
.modal-box h3 .ico { width: 1.2em; height: 1.2em; }
.free-frags-item-icon { display: flex; }
.free-frags-item-icon .ico { width: 24px; height: 24px; }
.fast-open-btn .ico { width: 18px; height: 18px; vertical-align: -3px; }
.bl-avatar-bot .ico, .bl-avatar-fallback .ico { width: 60%; height: 60%; vertical-align: 0; }
.battle-joinbar-avatar-bot .ico, .battle-joinbar-avatar-fallback .ico { width: 64%; height: 64%; vertical-align: 0; }
.bl-empty-icon .ico { width: 40px; height: 40px; }
.bt-win-trophy img { width: 46px; height: 46px; display: block; }

/* ---------- Navbar: SVG logo, game icons, phone drawer ----------
   img/logo.svg is traced from the old 454KB logo.png and has a real
   transparent background, so the screen-blend trick the PNG needed (see
   .navbar's own comment) no longer applies. */
.brand-logo { mix-blend-mode: normal; height: 72px; width: auto; }
.nav-user { color: var(--text-dim); font-size: 13px; white-space: nowrap; }
/* The SVG logo is wider than the old square PNG was, which pushed the bar
   onto two rows at ~1280px; long display names were the rest of it. */
.nav-right .nav-user { max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
@media (min-width: 761px) { .nav-links { gap: 18px; } .nav-right { gap: 10px; } }
.nav-games-menu a { display: flex; align-items: center; gap: 8px; }
.nav-games-menu a .ico { width: 18px; height: 18px; vertical-align: 0; }
.nav-admin-dot { width: 18px; height: 18px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; }
.nav-admin-dot::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: #ff6b6b; box-shadow: 0 0 6px rgba(255,107,107,.6); }

.nav-burger, .nav-drawer { display: none; }
.nav-burger {
  width: 42px; height: 40px; flex-shrink: 0; padding: 0;
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  background: linear-gradient(180deg, var(--bg-panel-3), var(--bg-panel-2));
  border: 1px solid var(--border); border-radius: 9px; cursor: pointer;
}
.nav-burger span { display: block; width: 18px; height: 2px; border-radius: 2px; background: var(--text); transition: transform .18s ease, opacity .18s ease; }
.navbar.nav-open .nav-burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.navbar.nav-open .nav-burger span:nth-child(2) { opacity: 0; }
.navbar.nav-open .nav-burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 760px) {
  .navbar { padding: 8px 12px; flex-wrap: nowrap; gap: 8px; }
  .brand-logo { height: 40px; }
  .navbar-body { flex-wrap: nowrap; justify-content: flex-end; gap: 8px; }
  .nav-links, .nav-desktop-only { display: none !important; }
  .nav-right { gap: 8px; }
  .nav-right .balance-pill { padding: 5px 11px; font-size: 14px; white-space: nowrap; }
  .nav-right .btn { padding: 9px 12px; font-size: 13px; }
  .nav-burger { display: flex; }
  .nav-drawer {
    position: absolute; top: 100%; left: 0; right: 0;
    max-height: calc(100vh - 60px); overflow-y: auto;
    padding: 14px 16px 18px;
    background: linear-gradient(180deg, #151b2b 0%, #0d1119 100%);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 18px 40px rgba(0,0,0,.6);
  }
  .navbar.nav-open .nav-drawer { display: block; animation: nav-drawer-in .16s ease-out; }
  .nav-drawer-links { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
  .nav-drawer-links a, .nav-drawer-games a {
    display: flex; align-items: center; gap: 8px;
    padding: 10px 12px; border-radius: 8px;
    background: var(--bg-panel-2); border: 1px solid var(--border-soft);
    color: var(--text); font-size: 14px; font-weight: 600;
  }
  .nav-drawer-links a { font-family: var(--display-font); text-transform: uppercase; letter-spacing: .4px; }
  .nav-drawer-links a.active { border-color: var(--gold); color: var(--gold); }
  .nav-drawer-label { margin: 14px 0 6px; font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); }
  .nav-drawer-games { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
  .nav-drawer-games a .ico { width: 18px; height: 18px; vertical-align: 0; }
  .nav-drawer-account { display: flex; align-items: center; gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-soft); }
  .nav-drawer-account .nav-user { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
}
@media (max-width: 380px) {
  .brand-logo { height: 34px; }
  .nav-right .btn { padding: 8px 10px; }
}
@keyframes nav-drawer-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ---------- Site-wide backdrop ----------
   The homepage's dimmed diamond plate, now behind every page -- the plain
   gradient everywhere else made the game pages feel like a different, less
   finished site than the homepage. Slightly heavier wash than the homepage
   so busy game boards on top keep their contrast. */
body:not(.home) {
  background:
    radial-gradient(ellipse 700px 520px at 8% 88%, rgba(47,125,253,0.05) 0%, transparent 65%),
    radial-gradient(ellipse 1000px 620px at 50% -12%, rgba(47,125,253,0.13) 0%, transparent 62%),
    linear-gradient(180deg, rgba(10,13,20,.62) 0%, rgba(7,9,14,.78) 45%, rgba(4,5,8,.9) 100%),
    url('/img/textures/diamond-plate.png?v=1') 0 0 / 128px 128px repeat,
    #0b0e14;
  background-attachment: fixed;
}
/* Short pages (Faucet, an empty Stash) otherwise leave the footer floating
   halfway up the screen. Not a flex column on body -- auto-margined children
   like .container would shrink-wrap in one. */
body > .container { min-height: calc(100vh - 420px); padding-bottom: 36px; }

/* ---------- Site footer (js/nav.js renderSiteFooter) ---------- */
.site-footer {
  margin-top: 48px;
  background: linear-gradient(180deg, rgba(13,17,25,.92), #07090e);
  border-top: 1px solid var(--border);
  box-shadow: 0 -1px 0 rgba(255,255,255,.03) inset;
  color: var(--text-dim);
  font-size: 13px;
}
.site-footer-inner {
  max-width: 1180px; margin: 0 auto; padding: 34px 24px 22px;
  display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr 1.6fr; gap: 24px;
}
.site-footer-brand img { height: 56px; width: auto; display: block; margin-bottom: 10px; }
.site-footer-brand p { margin: 0; max-width: 260px; line-height: 1.55; }
.site-footer h4 {
  font-family: var(--display-font); font-size: 13px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text); margin: 0 0 10px;
}
.site-footer-col a { display: block; color: var(--text-dim); padding: 3px 0; transition: color .12s ease; }
.site-footer-col a:hover { color: var(--gold); }
.site-footer-note p { margin: 0 0 8px; line-height: 1.5; font-size: 12px; }
.site-footer-bottom {
  border-top: 1px solid var(--border-soft);
  text-align: center; padding: 14px 16px; font-size: 12px; color: #5d6479;
}
@media (max-width: 760px) {
  .site-footer-inner { grid-template-columns: 1fr 1fr; gap: 22px; padding: 26px 18px 18px; }
  .site-footer-brand, .site-footer-note { grid-column: 1 / -1; }
}
body.nav-has-sidebar:not(.chat-collapsed) .site-footer { position: relative; z-index: 1; }

/* ---------- Game shell: controls column + board (Mines, Dig) ----------
   Both used to be one 520-560px column centred in a 1100px page, so on a
   desktop two thirds of the screen was empty either side of the board.
   Controls on the left, the board gets the rest, the way every real
   casino lays these out. Collapses back to one column on narrow screens. */
.game-shell {
  display: grid;
  grid-template-columns: 320px minmax(0, 1fr);
  gap: 16px;
  align-items: stretch;
  max-width: 1080px;
  margin: 0 auto;
}
.game-shell > .panel { margin: 0; }
.game-controls { display: flex; flex-direction: column; }
.game-controls .section-title { margin-bottom: 14px; }
.game-bet-adjust { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; margin: 8px 0 12px; }
.game-bet-adjust .btn { padding: 8px 0; font-size: 12px; }
.game-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 14px 0 12px; }
.game-actions .btn { padding: 12px 8px; }
.game-actions-main { grid-column: 1 / -1; }
/* Big readout at the foot of the controls -- it is the number the whole
   round is about, and it fills the column the board sets the height of. */
.game-mult {
  margin-top: auto; display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 14px 16px; border-radius: 10px;
  background: linear-gradient(180deg, var(--bg-panel-3), var(--bg-panel-2));
  border: 1px solid var(--border);
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-dim);
}
.game-mult #current-mult { font-family: var(--display-font); font-size: 34px; line-height: 1.1; letter-spacing: .5px; }
.game-hint { color: var(--text-dim); font-size: 12px; line-height: 1.6; margin: 12px 0 0; }
.game-maxwin { text-align: center; color: var(--text-dim); font-size: 12px; margin: 10px 0 0; }
.game-stage {
  display: flex; flex-direction: column; justify-content: center;
  background:
    radial-gradient(ellipse 70% 60% at 50% 40%, rgba(47,125,253,0.08) 0%, transparent 70%),
    linear-gradient(180deg, var(--bg-panel) 0%, #0f1320 100%);
}
.game-stage .mines-grid, .game-stage .dig-grid,
.game-stage .mine-ladder-wrap, .game-stage .dig-legend-tile { width: 100%; max-width: 600px; margin-left: auto; margin-right: auto; box-sizing: border-box; }
.game-stage .mines-grid, .game-stage .dig-grid { margin-top: 0; }
.game-shell-log { max-width: 1080px; margin: 16px auto 0; }
@media (max-width: 900px) {
  .game-shell { grid-template-columns: 1fr; }
  .game-mult { margin-top: 4px; }
}

/* Faucet's panel centres its text; the heading's accent bar is absolutely
   placed at the heading's left edge, so a full-width heading put the bar
   out at the panel edge far from the centred words. */
.faucet-panel .section-title { display: inline-block; }

/* ---------- Homepage hero v2 ----------
   Headline + CTAs on the left, three real crate covers fanned on the right
   (js/index.js). Replaces the centred wordmark-only hero: the logo is
   already in the navbar right above it, so the hero now says what the site
   is and gives two ways in. */
.hero-home { text-align: left; padding: 56px 24px 52px; }
.hero-inner {
  position: relative; max-width: 1100px; margin: 0 auto;
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 32px; align-items: center;
}
.hero-kicker {
  margin: 0 0 10px !important; font-size: 13px !important; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: #6fa8ff !important;
}
.hero-home .hero-copy h1 {
  font-size: 60px; line-height: 1.02; margin: 0 0 16px; color: var(--text);
  letter-spacing: 1px; text-shadow: 0 3px 24px rgba(0,0,0,.65);
}
.hero-home .hero-copy h1 span { color: #4a92ff; text-shadow: 0 0 34px rgba(47,125,253,.5), 0 3px 24px rgba(0,0,0,.6); }
.hero-sub { font-size: 16px !important; line-height: 1.6; max-width: 460px !important; margin: 0 !important; color: #aab2c6 !important; }
.hero-ctas { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 24px; }
.hero-cta { display: inline-flex; align-items: center; gap: 8px; padding: 13px 22px; font-size: 15px; }
.hero-cta .ico { width: 20px; height: 20px; vertical-align: 0; }
/* Three crates on a lit stage: the feature crate front and centre, the two
   flankers pushed well out to the sides (they were tucked behind it, so
   their names got cut off), angled in, set back and dimmed for depth. All
   three bob out of step, stand on a pool of light with a faint reflection,
   and fan further apart on hover. The float uses the separate `translate`
   property so it stacks on top of each crate's own placement transform. */
.hero-art { position: relative; height: 290px; --cw: clamp(140px, 16.5vw, 210px); }
.hero-crate {
  position: absolute; left: 50%; bottom: 44px; width: var(--cw); margin-left: calc(var(--cw) / -2);
  transition: transform .45s cubic-bezier(.2,.8,.25,1), filter .45s ease;
}
.hero-crate img {
  width: 100% !important; height: auto !important; aspect-ratio: 1; display: block;
  border-radius: 0 !important; object-fit: contain !important;
  filter: drop-shadow(0 20px 22px rgba(0,0,0,.6));
  -webkit-box-reflect: below -4px linear-gradient(transparent 72%, rgba(255,255,255,.13));
}
.hero-crate-0 { z-index: 3; transform: scale(1.06); }
.hero-crate-1 { z-index: 2; transform: translateX(-78%) rotate(-7deg) scale(.78); filter: brightness(.72) saturate(.9); }
.hero-crate-2 { z-index: 1; transform: translateX(78%) rotate(7deg) scale(.78); filter: brightness(.72) saturate(.9); }
.hero-art:hover .hero-crate-1 { transform: translateX(-92%) rotate(-10deg) scale(.8); filter: brightness(.9); }
.hero-art:hover .hero-crate-2 { transform: translateX(92%) rotate(10deg) scale(.8); filter: brightness(.9); }
.hero-art:hover .hero-crate-0 { transform: scale(1.1); }
/* Backlight behind the stack, and the pool of light they stand on. */
.hero-art::before {
  content: ''; position: absolute; left: 12%; right: 12%; top: 4%; bottom: 22%;
  background: radial-gradient(ellipse at center, rgba(47,125,253,.42) 0%, rgba(47,125,253,.12) 45%, transparent 72%);
  filter: blur(12px);
}
.hero-art::after {
  content: ''; position: absolute; left: 8%; right: 8%; bottom: 26px; height: 46px;
  background: radial-gradient(ellipse at center, rgba(90,150,255,.38) 0%, rgba(47,125,253,.14) 40%, transparent 70%);
  filter: blur(4px);
}
@media (prefers-reduced-motion: no-preference) {
  .hero-crate { animation: hero-bob 5.2s ease-in-out infinite; }
  .hero-crate-1 { animation-delay: -1.7s; }
  .hero-crate-2 { animation-delay: -3.4s; }
}
@keyframes hero-bob {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 -9px; }
}
@media (max-width: 860px) {
  .hero-home { padding: 36px 18px 30px; text-align: center; }
  .hero-inner { grid-template-columns: 1fr; gap: 12px; }
  .hero-home .hero-copy h1 { font-size: 42px; }
  .hero-sub { margin: 0 auto !important; }
  .hero-ctas { justify-content: center; }
  .hero-art { height: 210px; order: -1; }
  .hero-art:empty { display: none; }
  .hero-art { --cw: 150px; }
  .hero-crate { bottom: 34px; }
  .hero-art::after { bottom: 20px; height: 34px; }
}

/* Perks strip under the game tiles -- four true things the site does. */
.home-perks { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 22px; }
.home-perk {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border); border-radius: 12px; box-shadow: inset 0 1px 0 var(--edge-hi);
}
.home-perk img { width: 30px; height: 30px; flex-shrink: 0; }
.home-perk strong { display: block; font-family: var(--display-font); font-size: 14px; letter-spacing: .4px; text-transform: uppercase; color: var(--text); }
.home-perk span { display: block; font-size: 12px; color: var(--text-dim); }
@media (max-width: 900px) { .home-perks { grid-template-columns: 1fr 1fr; } }
@media (max-width: 420px) { .home-perk { padding: 12px; gap: 10px; } .home-perk strong { font-size: 13px; } }

/* Featured Crates row */
.home-section { margin-top: 28px; }
.home-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.home-section-more { font-size: 13px; font-weight: 600; white-space: nowrap; }
.home-crates { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; }
.home-crate {
  display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 12px 10px 14px;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px solid var(--border); border-radius: 12px; color: var(--text);
  box-shadow: inset 0 1px 0 var(--edge-hi), var(--elev-1);
  transition: transform .15s ease, border-color .15s ease;
}
.home-crate:hover { transform: translateY(-3px); border-color: var(--gold); }
.home-crate-art img { width: 100% !important; max-width: 150px; height: auto !important; aspect-ratio: 1; display: block; }
.home-crate-name { font-family: var(--display-font); font-size: 13px; text-transform: uppercase; letter-spacing: .4px; text-align: center; line-height: 1.2; }
.home-crate-price { font-family: var(--display-font); font-weight: 700; color: var(--gold); font-size: 14px; }
@media (max-width: 900px) { .home-crates { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 480px) { .home-crates { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Empty states (Stash, Rewards): a proper card with an icon and a way
   forward instead of one grey sentence in the corner of an empty page. */
.empty-state {
  grid-column: 1 / -1;
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px;
  padding: 38px 20px;
  background: linear-gradient(165deg, var(--bg-panel-2) 0%, var(--bg-panel) 100%);
  border: 1px dashed var(--border); border-radius: 14px;
}
.empty-state img { width: 52px; height: 52px; margin-bottom: 6px; opacity: .9; }
.empty-state strong { font-family: var(--display-font); font-size: 18px; letter-spacing: .5px; text-transform: uppercase; color: var(--text); }
.empty-state span { color: var(--text-dim); font-size: 14px; max-width: 380px; }
.empty-state .btn { margin-top: 12px; }

/* ---------- Loading screen (#rr-loader, top of every page's <body>) ----------
   The traced logo builds itself: the three speed bars shoot in from the
   left one after another, the R wipes on behind them, the wordmark rises,
   then a light sweep loops across it with the bars twitching forward until
   js/pb.js fades the whole thing out. Only the first page of a visit plays
   the full intro (html.rr-quick after that): later navigations keep it
   invisible unless the page is genuinely still loading 350ms in. */
.rr-loader {
  position: fixed; inset: 0; z-index: 10000;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 26px;
  background:
    radial-gradient(ellipse 60% 45% at 50% 45%, rgba(47,125,253,.16) 0%, transparent 70%),
    #07090e;
  transition: opacity .38s ease, visibility .38s;
  /* Failsafe: gone after 6s even if no script ever runs. */
  animation: rr-loader-failsafe 0s linear 6s forwards;
}
.rr-loader.rr-loader-out { opacity: 0; visibility: hidden; pointer-events: none; }
html.rr-quick .rr-loader { opacity: 0; animation: rr-loader-late .25s ease .35s forwards, rr-loader-failsafe 0s linear 6s forwards; }
html.rr-quick .rr-loader.rr-loader-out { animation: none; opacity: 0; }
@keyframes rr-loader-late { to { opacity: 1; } }
@keyframes rr-loader-failsafe { to { opacity: 0; visibility: hidden; pointer-events: none; } }

.rr-loader-stage { position: relative; width: min(340px, 70vw); }
.rr-loader-logo { display: block; width: 100%; height: auto; overflow: visible; }
.rr-loader-logo path, .rr-loader-logo g { transform-box: fill-box; }
.rr-loader .rr-bar { opacity: 0; animation: rr-bar-in .5s cubic-bezier(.2,.9,.25,1.15) forwards, rr-bar-idle 1.4s ease-in-out 1.3s infinite; }
.rr-loader .rr-bar1 { animation-delay: .05s, 1.3s; }
.rr-loader .rr-bar2 { animation-delay: .16s, 1.45s; }
.rr-loader .rr-bar3 { animation-delay: .27s, 1.6s; }
.rr-loader .rr-r { clip-path: inset(0 100% 0 0); animation: rr-r-in .55s cubic-bezier(.5,0,.2,1) .38s forwards; }
.rr-loader .rr-word { opacity: 0; animation: rr-word-in .45s ease-out .72s forwards; }
@keyframes rr-bar-in { from { opacity: 0; transform: translateX(-140px); } to { opacity: 1; transform: none; } }
@keyframes rr-bar-idle { 0%, 100% { transform: none; } 45% { transform: translateX(14px); } }
@keyframes rr-r-in { to { clip-path: inset(0 0 0 0); } }
@keyframes rr-word-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* Light sweep, clipped to the logo's own shape. */
.rr-loader-shine {
  position: absolute; inset: 0; pointer-events: none;
  -webkit-mask: url('/img/logo.svg?v=1') center / 100% 100% no-repeat;
          mask: url('/img/logo.svg?v=1') center / 100% 100% no-repeat;
  background: linear-gradient(105deg, transparent 38%, rgba(255,255,255,.55) 50%, transparent 62%) no-repeat;
  background-size: 260% 100%;
  background-position: 130% 0;
  animation: rr-shine 1.8s ease-in-out 1.15s infinite;
  mix-blend-mode: overlay;
}
@keyframes rr-shine { from { background-position: 130% 0; } to { background-position: -30% 0; } }

.rr-loader-bar {
  width: min(220px, 50vw); height: 3px; border-radius: 3px; overflow: hidden;
  background: rgba(47,125,253,.16);
  opacity: 0; animation: rr-word-in .3s ease-out .8s forwards;
}
.rr-loader-bar span {
  display: block; width: 40%; height: 100%; border-radius: 3px;
  background: linear-gradient(90deg, transparent, #4a92ff, #9cc4ff, #4a92ff, transparent);
  animation: rr-bar-scan 1.1s ease-in-out infinite;
}
@keyframes rr-bar-scan { from { transform: translateX(-110%); } to { transform: translateX(260%); } }

@media (prefers-reduced-motion: reduce) {
  .rr-loader .rr-bar, .rr-loader .rr-r, .rr-loader .rr-word { animation: none; opacity: 1; clip-path: none; transform: none; }
  .rr-loader-shine { display: none; }
}

/* ---------- Log in / Register ----------
   Brand panel beside the form instead of a lone 420px box floating in an
   empty page -- the first thing a new visitor sees should look like the
   rest of the site. */
.auth-shell {
  display: grid; grid-template-columns: minmax(0, 1fr) 420px; gap: 18px;
  max-width: 900px; margin: 36px auto 0; align-items: stretch;
}
.auth-shell .form-panel { margin: 0; max-width: none; }
.auth-brand {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center;
  padding: 34px 34px 30px; border-radius: 14px; border: 1px solid var(--border);
  background:
    radial-gradient(ellipse 80% 60% at 20% 10%, rgba(47,125,253,.22) 0%, transparent 65%),
    linear-gradient(180deg, rgba(10,13,20,.55), rgba(7,9,14,.85)),
    url('/img/rust-map.webp?v=1') center / cover no-repeat;
  box-shadow: inset 0 1px 0 var(--edge-hi), var(--elev-1);
}
.auth-logo { width: 170px; height: auto; margin-bottom: 22px; }
.auth-brand h1 { font-size: 44px; line-height: 1.02; text-transform: uppercase; letter-spacing: 1px; margin: 0 0 20px; }
.auth-brand h1 span { color: #4a92ff; }
.auth-perks { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; }
.auth-perks li { display: flex; align-items: center; gap: 10px; color: #c3cbe0; font-size: 14px; }
.auth-perks img { width: 22px; height: 22px; flex-shrink: 0; }
.auth-panel h2 { margin-top: 4px; }
@media (max-width: 820px) {
  .auth-shell { grid-template-columns: 1fr; max-width: 440px; margin-top: 16px; }
  .auth-brand { padding: 22px; align-items: center; text-align: center; }
  .auth-brand h1, .auth-perks { display: none; }
  .auth-logo { margin: 0; width: 140px; }
}

/* Rockets loot grid on phones: slots hold a 72px skin image / 60px resource
   icon, but at 375px a slot is ~48px wide. Grid columns can't shrink below
   their content (1fr's minimum is min-content), so the whole 6-wide grid
   grew past the screen edge mid-open. Columns may now shrink to nothing,
   the art scales to the slot, and the stack count sits over the icon's
   bottom-right corner the way Rust's own inventory shows it. */
.loot-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.loot-slot { min-width: 0; overflow: hidden; }
.loot-slot-emoji { display: flex; align-items: center; justify-content: center; width: 82%; height: 82%; }
.loot-slot-emoji img { width: 100% !important; height: 100% !important; object-fit: contain; }
.loot-slot-value {
  position: absolute; right: 3px; bottom: 1px;
  font-size: clamp(9px, 2.8vw, 16px); line-height: 1.1;
  text-shadow: 0 1px 2px rgba(0,0,0,.9);
}
.loot-slot-copies { position: absolute; top: 2px; left: 3px; text-shadow: 0 1px 2px rgba(0,0,0,.9); }
@media (max-width: 560px) {
  /* Every px of side padding here comes out of six loot slots. */
  .rockets-panel { padding-left: 12px; padding-right: 12px; }
  .rockets-panel .loot-panel { padding: 8px 6px; }
  .rockets-panel .loot-grid { gap: 3px; }
}

/* Crates page on phones: three crates a row instead of one or two. The grid's
   220px minimum put a single huge crate per row at 375px; the cards shed
   padding and shrink their text so the name and price still fit ~105px. */
@media (max-width: 600px) {
  #cases-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
  #cases-grid .crate-card { padding: 6px 6px 8px; border-radius: 10px; }
  #cases-grid .crate-card-art, #cases-grid .crate-card-art img { border-radius: 7px !important; }
  #cases-grid .crate-card h3 { font-size: 11px; margin: 6px 0 5px; letter-spacing: .2px; }
  #cases-grid .crate-risk-bar { height: 3px; margin-bottom: 6px; }
  #cases-grid .crate-card .case-price { font-size: 11px; padding: 3px 7px; gap: 3px; margin-top: 0; }
  #cases-grid .odds-btn { top: 4px; right: 4px; width: 18px; height: 18px; font-size: 11px; }
}
/* Perk cards are links now (js/index.js wires the two that open in place). */
a.home-perk { color: inherit; cursor: pointer; transition: transform .15s ease, border-color .15s ease, box-shadow .15s ease; }
a.home-perk:hover { transform: translateY(-2px); border-color: var(--gold); box-shadow: inset 0 1px 0 var(--edge-hi), 0 0 18px rgba(47,125,253,.22); }
a.home-perk:hover strong { color: var(--gold); }

/* ---------- Self-exclusion (Settings tab + site-wide notice) ---------- */
.excl-status {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px; margin-bottom: 14px;
  background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px;
}
.excl-status img { width: 30px; height: 30px; flex-shrink: 0; }
.excl-status strong { display: block; font-family: var(--display-font); font-size: 15px; letter-spacing: .4px; text-transform: uppercase; }
.excl-status span { display: block; font-size: 12px; color: var(--text-dim); }
.excl-status-on { border-color: rgba(255,180,40,.55); background: linear-gradient(180deg, rgba(255,180,40,.10), rgba(255,180,40,.03)); }
.excl-rules { margin: 0 0 16px; padding-left: 18px; font-size: 13px; color: var(--text-dim); line-height: 1.6; }
.excl-rules strong { color: var(--text); }
.excl-label { font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 8px; }
.excl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; }
.excl-opt {
  padding: 11px 8px; border-radius: 9px; cursor: pointer;
  background: linear-gradient(180deg, var(--bg-panel-3), var(--bg-panel-2)); border: 1px solid var(--border);
  color: var(--text); font-family: var(--display-font); font-size: 14px; font-weight: 600; letter-spacing: .3px;
}
.excl-opt:hover { border-color: var(--gold); }
.excl-opt.active { border-color: var(--gold); box-shadow: var(--ring); }
.excl-opt-perm { color: #ff8a8a; }
.excl-opt-perm.active { border-color: var(--bad); box-shadow: 0 0 0 2px rgba(255,92,92,.5); }
.excl-confirm-box { margin-top: 14px; padding: 14px; border-radius: 10px; background: var(--bg-panel-2); border: 1px solid var(--border); }
.excl-confirm-box p { margin: 0 0 10px; font-size: 13px; line-height: 1.55; }
.excl-confirm-perm { border-color: rgba(255,92,92,.55); }
.excl-check { display: flex; align-items: center; gap: 8px; font-size: 13px; cursor: pointer; }
.excl-banner {
  display: flex; align-items: center; justify-content: center; gap: 8px; padding: 9px 16px;
  background: linear-gradient(180deg, rgba(255,180,40,.16), rgba(255,180,40,.07));
  border-bottom: 1px solid rgba(255,180,40,.35);
  color: #ffd98a; font-size: 13px; font-weight: 600; text-align: center;
}

/* Faucet rules checklist (js/nav.js faucetRulesHtml) -- Faucet page + Free Frags modal. */
.faucet-rules { margin-top: 16px; text-align: left; border-top: 1px solid var(--border-soft); padding-top: 12px; }
.faucet-rules-title { font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 8px; }
.faucet-rule { display: flex; align-items: flex-start; gap: 10px; padding: 7px 0; }
.faucet-rule + .faucet-rule { border-top: 1px solid rgba(255,255,255,.04); }
.faucet-rule-mark {
  flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 800; margin-top: 1px;
}
.faucet-rule-ok .faucet-rule-mark { background: rgba(53,208,127,.16); color: var(--good); }
.faucet-rule-no .faucet-rule-mark { background: rgba(255,92,92,.16); color: var(--bad); }
.faucet-rule-info .faucet-rule-mark { background: rgba(138,145,166,.16); color: var(--text-dim); }
.faucet-rule-text strong { display: block; font-size: 13px; font-weight: 600; color: var(--text); }
.faucet-rule-text span { display: block; font-size: 12px; color: var(--text-dim); word-break: break-word; }
.faucet-rule-no .faucet-rule-text strong { color: #ff9a9a; }

/* ---------- Rakeback view (Wallet -> Free Frags -> Rakeback) ---------- */
.rb-kicker { display: block; font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim); }
.rb-hero {
  display: flex; align-items: center; gap: 14px; padding: 14px 16px; border-radius: 12px;
  background: radial-gradient(ellipse 70% 120% at 0% 50%, rgba(47,125,253,.18), transparent 70%), var(--bg-panel-2);
  border: 1px solid var(--border);
}
.rb-badge { width: 58px; height: 58px; object-fit: contain; flex-shrink: 0; filter: drop-shadow(0 4px 10px rgba(0,0,0,.5)); }
.rb-hero-text { flex: 1; min-width: 0; }
.rb-hero-text strong { display: block; font-family: var(--display-font); font-size: 22px; letter-spacing: .5px; text-transform: uppercase; line-height: 1.15; }
.rb-next { font-size: 12px; color: var(--text-dim); }
.rb-rate { text-align: right; }
.rb-rate strong { display: block; font-family: var(--display-font); font-size: 34px; line-height: 1; color: var(--good); text-shadow: 0 0 18px rgba(53,208,127,.35); }
.rb-rate span { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--text-dim); }
.rb-claim {
  margin-top: 12px; padding: 16px; border-radius: 12px; text-align: center;
  background: linear-gradient(180deg, var(--bg-panel-3), var(--bg-panel-2)); border: 1px solid var(--border);
}
.rb-claim-ready { border-color: rgba(53,208,127,.5); box-shadow: 0 0 22px rgba(53,208,127,.14); }
.rb-amount { font-family: var(--display-font); font-size: 36px; font-weight: 800; color: var(--gold); margin: 2px 0 10px; }
.rb-claim .btn { width: 100%; }
.rb-claim-ready .btn { background: linear-gradient(180deg, #3fe08e 0%, #22b566 55%, #15834a 100%); }
.rb-progress { height: 6px; border-radius: 6px; background: var(--bg-panel); overflow: hidden; margin: 0 0 6px; }
.rb-progress span { display: block; height: 100%; border-radius: 6px; background: linear-gradient(90deg, #2f7dfd, #6fb0ff); }
.rb-progress-label { font-size: 12px; color: var(--text-dim); margin-bottom: 12px; }
#rakeback-claim-msg { font-size: 12px; margin-top: 8px; }
#rakeback-claim-msg:empty { display: none; }
.rb-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 12px; }
.rb-stats div { padding: 10px 12px; border-radius: 10px; background: var(--bg-panel-2); border: 1px solid var(--border-soft); }
.rb-stats span { display: block; font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .06em; }
.rb-stats strong { font-family: var(--display-font); font-size: 17px; }
.rb-tiers { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.rb-tier {
  display: flex; align-items: center; gap: 6px; padding: 7px 8px; border-radius: 8px;
  background: var(--bg-panel-2); border: 1px solid var(--border-soft); font-size: 12px; opacity: .6;
}
.rb-tier img { width: 22px; height: 22px; object-fit: contain; }
.rb-tier span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rb-tier strong { color: var(--good); font-size: 12px; }
.rb-tier.passed { opacity: .85; }
.rb-tier.current { opacity: 1; border-color: var(--gold); box-shadow: var(--ring); background: linear-gradient(180deg, rgba(47,125,253,.16), var(--bg-panel-2)); }
.rb-how { font-size: 12px; color: var(--text-dim); line-height: 1.55; margin: 14px 0 0; }
@media (max-width: 420px) { .rb-tiers { grid-template-columns: repeat(2, 1fr); } .rb-rate strong { font-size: 28px; } }
.rb-formula { margin-top: 14px; padding: 12px 14px; border-radius: 10px; background: var(--bg-panel-2); border: 1px solid var(--border-soft); text-align: center; }
.rb-formula-eq { font-family: var(--display-font); font-size: 18px; letter-spacing: .4px; margin-top: 4px; }
.rb-formula-eq b { color: var(--gold); margin: 0 4px; }
.rb-formula-ex { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.rb-rtp { margin: 12px 0 0; font-size: 13px; font-weight: 600; text-align: center; }
.rb-rtp-note { display: block; font-size: 11px; font-weight: 400; color: var(--text-dim); margin-top: 2px; }
.rb-more { margin: 8px 0 0; font-size: 12px; color: var(--text-dim); text-align: center; display: flex; align-items: center; justify-content: center; gap: 6px; }
.rb-info-btn {
  width: 18px; height: 18px; padding: 0; border-radius: 50%; cursor: pointer; flex-shrink: 0;
  border: 1px solid var(--gold); background: rgba(47,125,253,.12); color: var(--gold);
  font: italic 700 11px/1 Georgia, serif;
}
.rb-info-btn:hover { background: rgba(47,125,253,.3); }
.rb-info { margin-top: 10px; padding: 12px 14px; border-radius: 10px; background: var(--bg-panel-2); border: 1px solid var(--border); font-size: 12px; line-height: 1.55; color: var(--text-dim); }
.rb-info p { margin: 0 0 8px; }
.rb-info p:last-child { margin-bottom: 0; }
.rb-info strong { color: var(--text); }

/* ---------- Affiliate view (shares the .rb-* rakeback pieces) ---------- */
.af-intro .rb-hero-text strong { font-size: 19px; }
.af-perks { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.af-perks li { display: flex; align-items: center; gap: 10px; font-size: 13px; color: #c3cbe0; }
.af-perks img { width: 20px; height: 20px; flex-shrink: 0; }
.af-code-row, .af-link-row { display: flex; gap: 8px; align-items: stretch; }
.af-code-row { background: var(--bg-panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 4px 4px 4px 12px; }
.af-code-prefix { align-self: center; color: var(--text-dim); font-size: 13px; font-family: ui-monospace, Consolas, monospace; }
.af-code-row input { flex: 1; min-width: 0; border: none; background: transparent; padding-left: 2px; font-family: ui-monospace, Consolas, monospace; }
.af-code-row input:focus { box-shadow: none; outline: none; }
.af-hint { font-size: 11px; color: var(--text-dim); margin-top: 6px; }
.af-link-row input { flex: 1; min-width: 0; font-family: ui-monospace, Consolas, monospace; font-size: 12px; }
.af-link-row + .rb-claim { margin-top: 12px; }
.af-tiers { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }
.af-tier {
  display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 8px 4px; border-radius: 8px;
  background: var(--bg-panel-2); border: 1px solid var(--border-soft); opacity: .6; text-align: center;
}
.af-tier strong { font-family: var(--display-font); font-size: 16px; color: var(--good); }
.af-tier span { font-size: 11px; color: var(--text-dim); white-space: nowrap; }
.af-tier.passed { opacity: .85; }
.af-tier.current { opacity: 1; border-color: var(--gold); box-shadow: var(--ring); background: linear-gradient(180deg, rgba(47,125,253,.16), var(--bg-panel-2)); }
.af-tier span .frag-icon { height: .9em; }
.rb-formula-ex { line-height: 1.5; }
#affiliate-claim-msg { font-size: 12px; margin-top: 8px; }
#affiliate-claim-msg:empty { display: none; }
@media (max-width: 420px) { .af-tier strong { font-size: 14px; } .af-tier span { font-size: 10px; } }
/* Affiliate: two commission streams */
.af-rates { display: flex; flex-direction: column; gap: 4px; text-align: right; }
.af-rates strong { font-family: var(--display-font); font-size: 22px; line-height: 1; color: var(--good); }
.af-rates span { display: block; font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); }
.af-stats { grid-template-columns: 1fr 1fr; }
.rb-formula-plus { font-family: var(--display-font); font-size: 20px; color: var(--gold); margin: 6px 0 2px; }

/* ---------- Enter Referral Code / Promo Code views ---------- */
.code-row input { font-size: 15px; letter-spacing: .04em; }
.code-row-promo input { text-transform: uppercase; letter-spacing: .12em; font-weight: 700; }
.code-row-promo input::placeholder, .code-row input::placeholder { text-transform: none; letter-spacing: .04em; font-weight: 400; }
.code-row .btn { white-space: nowrap; }
.code-msg { margin-top: 8px; }
.code-msg:empty { display: none; }
.code-msg .msg { margin: 0; }
.code-nocode { margin-top: 10px; font-size: 12px; color: var(--text-dim); text-align: center; }
.code-nocode button { background: none; border: none; padding: 0; color: var(--gold); font: inherit; font-weight: 600; cursor: pointer; }
.code-nocode button:hover { text-decoration: underline; }
.code-rules { margin: 14px 0 0; padding: 12px 14px 12px 30px; border-radius: 10px; background: var(--bg-panel-2); border: 1px solid var(--border-soft); font-size: 12px; line-height: 1.6; color: var(--text-dim); }
.code-rules strong { color: var(--text); }
.code-done {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 4px;
  padding: 22px 18px; border-radius: 12px; background: var(--bg-panel-2); border: 1px solid var(--border);
}
.code-done strong { font-family: var(--display-font); font-size: 20px; letter-spacing: .5px; text-transform: uppercase; }
.code-done span { font-size: 13px; color: var(--text-dim); max-width: 320px; }
.code-done-mark {
  width: 44px; height: 44px; border-radius: 50%; margin-bottom: 6px;
  display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 800;
  background: rgba(47,125,253,.16); color: var(--gold);
}
.code-done-win { border-color: rgba(53,208,127,.5); box-shadow: 0 0 22px rgba(53,208,127,.14); animation: code-pop .35s cubic-bezier(.2,.8,.3,1.2); }
.code-done-win .code-done-mark { background: rgba(53,208,127,.18); color: var(--good); }
.code-done-amount { font-family: var(--display-font); font-size: 34px; font-weight: 800; color: var(--good); line-height: 1.1; }
.code-done-inline { margin-top: 4px; padding: 16px; }
@keyframes code-pop { from { transform: scale(.92); opacity: 0; } to { transform: none; opacity: 1; } }

/* One-click provably-fair verify (js/settings.js verifyHistoryRow) */
.pf-verify-btn { margin-top: 10px; width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.pf-verify { margin-top: 10px; padding: 10px 12px; border-radius: 10px; background: var(--bg-panel-2); border: 1px solid var(--border); }
.pf-verify-ok { border-color: rgba(53,208,127,.5); }
.pf-verify-bad { border-color: rgba(255,92,92,.55); }
.pf-verify-title { font-family: var(--display-font); font-size: 14px; letter-spacing: .5px; text-transform: uppercase; margin-bottom: 4px; }
.pf-verify-ok .pf-verify-title { color: var(--good); }
.pf-verify-bad .pf-verify-title { color: var(--bad); }
.pf-verify-note { font-size: 11px; color: var(--text-dim); margin-top: 6px; line-height: 1.5; }

/* ---------- /transparency ---------- */
.tp-lead { color: var(--text-dim); font-size: 14px; max-width: 760px; margin: -6px 0 16px; line-height: 1.6; }
.tp-lead strong { color: var(--text); }
.tp-tabs { display: inline-flex; gap: 4px; padding: 4px; border-radius: 10px; background: var(--bg-panel-2); border: 1px solid var(--border); margin-bottom: 14px; }
.tp-tabs button { background: none; border: none; color: var(--text-dim); font-family: var(--display-font); font-size: 13px; font-weight: 600; letter-spacing: .4px; text-transform: uppercase; padding: 7px 14px; border-radius: 7px; cursor: pointer; }
.tp-tabs button.active { background: linear-gradient(180deg, #3d88ff, #2266e0); color: #fff; }
.tp-summary { display: grid; grid-template-columns: 1.3fr 1fr 1fr 1fr; gap: 12px; margin-bottom: 16px; }
.tp-summary .panel { grid-column: 1 / -1; margin: 0; }
.tp-stat { padding: 16px 18px; border-radius: 12px; background: linear-gradient(165deg, var(--bg-panel-2), var(--bg-panel)); border: 1px solid var(--border); box-shadow: inset 0 1px 0 var(--edge-hi); }
.tp-stat span { display: block; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-dim); }
.tp-stat strong { display: block; font-family: var(--display-font); font-size: 24px; margin-top: 4px; }
.tp-stat-main strong { font-size: 38px; line-height: 1.05; }
.tp-stat em { font-style: normal; font-size: 12px; color: var(--text-dim); }
.tp-near { color: var(--good); }
.tp-mid { color: #ffb428; }
.tp-far { color: #ff8a5c; }
.tp-h3 { margin: 0 0 12px; font-size: 16px; }
.tp-row { display: grid; grid-template-columns: 1.4fr .7fr 1fr 1fr 1.6fr; gap: 10px; align-items: center; padding: 10px 0; border-top: 1px solid var(--border-soft); font-size: 13px; }
.tp-row-head { border-top: none; padding-top: 0; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--text-dim); }
.tp-game { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.tp-game img { width: 22px; height: 22px; }
.tp-rtp { display: flex; align-items: center; gap: 10px; }
.tp-rtp b { min-width: 52px; text-align: right; font-family: var(--display-font); font-size: 15px; }
.tp-bar { position: relative; flex: 1; height: 8px; border-radius: 8px; background: var(--bg-panel-3); }
.tp-bar-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 8px; background: currentColor; opacity: .85; }
.tp-bar-target { position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; background: #fff; border-radius: 2px; box-shadow: 0 0 6px rgba(255,255,255,.6); }
.tp-note { font-size: 12px; color: var(--text-dim); line-height: 1.6; margin: 14px 0 0; }
.tp-two { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px; }
.tp-two .panel { margin: 0; }
.tp-win { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-top: 1px solid var(--border-soft); }
.tp-win:first-child { border-top: none; }
.tp-win-rank { width: 22px; font-family: var(--display-font); color: var(--text-dim); text-align: center; }
.tp-win img { width: 22px; height: 22px; }
.tp-win-who { flex: 1; min-width: 0; }
.tp-win-who strong { display: block; font-size: 13px; }
.tp-win-who em { display: block; font-style: normal; font-size: 11px; color: var(--text-dim); }
.tp-win-amt { font-family: var(--display-font); font-weight: 700; color: var(--good); }
.tp-fair p, .tp-fair ol { font-size: 13px; color: var(--text-dim); line-height: 1.6; }
.tp-fair strong { color: var(--text); }
.tp-fair ol { padding-left: 18px; margin: 0 0 14px; }
.tp-updated { font-size: 11px; color: var(--text-dim); margin-top: 12px; text-align: right; }
@media (max-width: 760px) {
  .tp-summary { grid-template-columns: 1fr 1fr; }
  .tp-stat-main { grid-column: 1 / -1; }
  .tp-two { grid-template-columns: 1fr; }
  .tp-row-head { display: none; }
  .tp-row { grid-template-columns: 1fr 1fr 1fr; row-gap: 6px; }
  .tp-game, .tp-rtp { grid-column: 1 / -1; }
  .tp-row span[data-l]::before { content: attr(data-l); display: block; font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-dim); }
}

/* ---------- Play Limits tab + reality-check pop-up ---------- */
.lim-intro { font-size: 13px; color: var(--text-dim); line-height: 1.55; margin: 0 0 12px; }
.lim-section { padding: 12px 14px; border-radius: 12px; background: var(--bg-panel-2); border: 1px solid var(--border); margin-bottom: 10px; }
.lim-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.lim-head img { width: 26px; height: 26px; }
.lim-head strong { display: block; font-family: var(--display-font); font-size: 15px; letter-spacing: .4px; text-transform: uppercase; }
.lim-head span { display: block; font-size: 12px; color: var(--text-dim); }
.lim-row { padding: 8px 0; border-top: 1px solid var(--border-soft); }
.lim-row:first-of-type { border-top: none; }
.lim-row-top { display: flex; align-items: center; gap: 10px; }
.lim-label { width: 70px; font-size: 13px; font-weight: 600; }
.lim-used { flex: 1; font-size: 12px; color: var(--text-dim); }
.lim-input { width: 110px; text-align: right; }
.lim-row .rb-progress { margin: 6px 0 0; }
.lim-pending { font-size: 11px; color: #ffb428; margin-top: 4px; }
.lim-reality { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 6px; }
.lim-foot { font-size: 12px; color: var(--text-dim); text-align: center; margin: 12px 0 0; }
.rc-time { font-family: var(--display-font); font-size: 30px; font-weight: 700; }
.rc-delta { font-family: var(--display-font); font-size: 26px; font-weight: 700; }
@media (max-width: 480px) { .lim-row-top { flex-wrap: wrap; } .lim-used { order: 3; flex-basis: 100%; } .lim-input { margin-left: auto; } }

/* Wipe Day banner + modal (js/nav.js renderWipeBanner / openWipeModal). */
.wipe-banner {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 6px 12px;
  width: 100%; padding: 9px 16px; border: 0; border-bottom: 1px solid rgba(206,66,43,.35); cursor: pointer;
  background: linear-gradient(90deg, rgba(206,66,43,.10), rgba(206,66,43,.22), rgba(206,66,43,.10));
  color: #ffd2c4; font: inherit; font-size: 13px; text-align: center;
}
.wipe-banner.live { background: linear-gradient(90deg, rgba(206,66,43,.22), rgba(255,120,40,.38), rgba(206,66,43,.22)); color: #fff; animation: wipe-pulse 2.4s ease-in-out infinite; }
.wipe-banner .ico { width: 18px; height: 18px; }
.wipe-banner strong { letter-spacing: .06em; }
.wipe-banner .wipe-perks { opacity: .85; }
.wipe-banner .wipe-clock { font-variant-numeric: tabular-nums; font-weight: 700; background: rgba(0,0,0,.25); padding: 2px 8px; border-radius: 6px; }
.wipe-banner .wipe-more { opacity: .7; }
.wipe-banner:hover .wipe-more { opacity: 1; }
@keyframes wipe-pulse { 0%, 100% { box-shadow: inset 0 0 0 rgba(255,120,40,0); } 50% { box-shadow: inset 0 0 24px rgba(255,120,40,.35); } }
@media (prefers-reduced-motion: reduce) { .wipe-banner.live { animation: none; } }
@media (max-width: 600px) { .wipe-banner .wipe-perks, .wipe-banner .wipe-more { display: none; } }

.wipe-modal-box { max-width: 480px; width: calc(100% - 32px); }
.wipe-modal-box .modal-close { position: absolute; top: 10px; right: 12px; background: none; border: 0; color: var(--text-dim); font-size: 26px; line-height: 1; cursor: pointer; }
.wipe-modal-box .modal-close:hover { color: var(--text); }
.wipe-hero { display: flex; align-items: center; gap: 14px; }
.wipe-hero img { width: 52px; height: 52px; padding: 8px; border-radius: 12px; background: rgba(206,66,43,.18); border: 1px solid rgba(206,66,43,.4); }
.wipe-hero h3 { margin: 0; font-size: 22px; }
.wipe-hero p { margin: 2px 0 0; color: var(--text-dim); font-size: 13px; }
.wipe-sub { color: var(--text-dim); font-size: 13px; margin: 14px 0; }
.wipe-perk-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.wipe-perk { display: flex; flex-direction: column; gap: 4px; padding: 12px; border-radius: 10px; background: rgba(255,255,255,.03); border: 1px solid var(--border-soft); }
.wipe-perk img { width: 24px; height: 24px; }
.wipe-perk strong { font-size: 15px; }
.wipe-perk span { color: var(--text-dim); font-size: 12px; line-height: 1.4; }
.wipe-rules { margin-top: 12px; font-size: 13px; }
.wipe-rules summary { cursor: pointer; color: var(--text-dim); }
.wipe-rules ul { margin: 8px 0 0; padding-left: 18px; color: var(--text-dim); line-height: 1.5; }
.wipe-lb-title { display: flex; align-items: center; gap: 6px; margin: 18px 0 8px; font-size: 14px; }
.wipe-lb { list-style: none; margin: 0; padding: 0; }
.wipe-lb li { display: flex; align-items: center; gap: 10px; padding: 7px 10px; border-radius: 8px; font-size: 13px; }
.wipe-lb li:nth-child(odd) { background: rgba(255,255,255,.03); }
.wipe-rank { width: 22px; text-align: center; font-weight: 700; color: var(--text-dim); }
.wipe-lb li:nth-child(1) .wipe-rank { color: #ffd257; }
.wipe-lb li:nth-child(2) .wipe-rank { color: #cfd6e4; }
.wipe-lb li:nth-child(3) .wipe-rank { color: #d99a5b; }
.wipe-name { flex: 1; }
.wipe-amt { font-variant-numeric: tabular-nums; font-weight: 600; }
.wipe-empty { color: var(--text-dim); font-size: 13px; }
@media (max-width: 480px) { .wipe-perk-grid { grid-template-columns: 1fr; } }

/* Battle creator funding (battles-create / battle / lobby, 2026-09-29). Text only, no icons. */
.bf-card { background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
.bf-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; }
.bf-title { font-weight: 700; }
.bf-desc { color: var(--text-dim); font-size: 13px; margin-top: 3px; line-height: 1.4; }
.bf-pct { flex-shrink: 0; font-family: var(--display-font); font-size: 20px; font-weight: 700; color: var(--gold); white-space: nowrap; }
.bf-range { width: 100%; margin: 14px 0 2px; accent-color: var(--gold); cursor: pointer; }
.bf-scale { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-dim); }
.bf-ranks { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 14px; }
.bf-ranks label { font-size: 12px; color: var(--text-dim); margin: 0 0 4px; display: block; }
.bf-ranks select { width: 100%; }
.bf-ranks select:disabled { opacity: .45; cursor: not-allowed; }
.bf-note { color: var(--text-dim); font-size: 12px; margin: 10px 0 0; line-height: 1.4; }
.bf-summary { margin-top: 16px; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.bf-line { display: flex; justify-content: space-between; gap: 10px; padding: 9px 14px; font-size: 14px; }
.bf-line + .bf-line { border-top: 1px solid var(--border-soft); }
.bf-line span:last-child { font-variant-numeric: tabular-nums; font-weight: 600; }
.bf-you { background: rgba(47,125,253,.07); font-weight: 700; }
.bf-you span:last-child { color: var(--gold); font-size: 16px; }
.bf-banner { color: var(--gold); font-size: 13px; margin: 4px 0 0; }
.bf-banner-rank { color: var(--text); }
.bl-cost-was { font-size: 12px; color: var(--text-dim); text-decoration: line-through; }
.bf-tags { display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; margin: 4px 0; }
.bf-tag { font-size: 10px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; padding: 2px 6px; border-radius: 4px; background: rgba(47,125,253,.14); color: var(--gold); white-space: nowrap; }
.bf-tag-rank { background: rgba(255,255,255,.07); color: var(--text); }
@media (max-width: 520px) { .bf-ranks { grid-template-columns: 1fr; } }

/* Battles lobby v2 (testgame/battles.html, owner reference 2026-09-29). */
.bl2-page { max-width: 1340px; margin: 0 auto; }
.container:has(> .bl2-page) { max-width: 1380px; }
.bl2-top { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; margin: 6px 0 18px; }
.bl2-title { font-family: var(--display-font); font-size: 28px; margin: 0; }
.bl2-sub { color: var(--text-dim); margin: 4px 0 0; font-size: 15px; }
.bl2-top-actions { display: flex; align-items: center; gap: 18px; }
.bl2-show { display: flex; align-items: center; gap: 8px; margin: 0; font-weight: 700; font-size: 14px; color: var(--text); }
.bl2-show select { width: auto; padding: 6px 28px 6px 10px; }
.bl2-create { padding: 13px 26px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; font-size: 14px; }
.bl2-section { font-family: var(--display-font); font-size: 18px; margin: 28px 0 12px; color: var(--text-dim); }
/* One stacked card per row at every size (owner preference, 2026-09-29). */
.bl2-list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
.bl2-list > .bl2-loading, .bl2-list > .bl2-empty { grid-column: 1 / -1; }
.bl2-loading, .bl2-empty { color: var(--text-dim); font-size: 14px; }
.bl2-empty { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 36px 0; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 6px; }

.bl2-row {
  display: flex; flex-direction: column; gap: 12px; min-width: 0;
  padding: 14px 16px; background: var(--bg-panel); border: 1px solid var(--border);
  border-radius: 6px; color: var(--text); text-decoration: none; transition: background .15s, border-color .15s;
}
a.bl2-row:hover { background: var(--bg-panel-2); border-color: #3a4463; }
.bl2-row-locked { cursor: default; }
.bl2-row-cancelled { opacity: .55; }

.bl2-left { min-width: 0; }
.bl2-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 10px; }
.bl2-value { font-family: var(--display-font); font-size: 17px; font-weight: 800; white-space: nowrap; }
.bl2-was { font-size: 12px; color: var(--text-dim); text-decoration: line-through; }
.bl2-mod { width: 16px; height: 16px; }
.bl2-mode { font-weight: 700; font-size: 14px; color: var(--text-dim); white-space: nowrap; }
.bl2-mode-jackpot { color: #a98bff; }
.bl2-mode-cursed { color: #ff6b6b; }
.bl2-mode-group { color: #4dd0e1; }
.bl2-players { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.bl2-team { display: flex; gap: 4px; }
.bl2-vs { color: var(--text-dim); font-size: 12px; font-weight: 700; margin: 0 6px; }
.bl2-tile {
  position: relative; width: 52px; height: 52px; border-radius: 3px; overflow: hidden; background: var(--bg-panel-3);
  box-shadow: inset 0 -4px 0 var(--tc); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.bl2-tile > img { width: 100%; height: calc(100% - 4px); align-self: flex-start; object-fit: cover; }
.bl2-tile > img.bl2-tile-ico { width: 26px; height: 26px; align-self: center; margin-bottom: 4px; }
.bl2-tile-open { font-family: var(--display-font); font-size: 24px; font-weight: 800; color: var(--text-dim); margin-bottom: 4px; }
.bl2-tile-empty { background: rgba(255,255,255,.03); }
.bl2-tile-dim { opacity: .4; }

.bl2-crates { position: relative; display: flex; align-items: center; gap: 12px; min-width: 0; padding: 18px 8px 8px; overflow-x: auto; scrollbar-width: thin; }
/* Crate art is 4:3, so the box is 4:3 and uses contain -- caseVisual's
   default square + cover was cropping both sides off. */
.bl2-crates .bl2-crate-img img { width: 84px !important; height: 63px !important; object-fit: contain !important; border-radius: 0 !important; }
@media (min-width: 900px) {
  .bl2-crates { overflow: visible; gap: 16px; padding: 20px 10px 10px; }
  .bl2-crates .bl2-crate-img img { width: 128px !important; height: 96px !important; }
  .bl2-tile { width: 64px; height: 64px; }
  .bl2-tile > img.bl2-tile-ico { width: 32px; height: 32px; }
  .bl2-value { font-size: 19px; }
  .bl2-mode { font-size: 15px; }
  .bl2-qty { min-width: 30px; height: 30px; font-size: 13px; }
}
.bl2-crate { position: relative; flex-shrink: 0; display: inline-flex; transition: opacity .3s, transform .3s; }
.bl2-crate-img { display: inline-flex; }
.bl2-crate-done { opacity: .28; }
.bl2-crate-now { transform: scale(1.12); }
.bl2-crate-now .bl2-crate-img img { filter: drop-shadow(0 0 10px rgba(47,125,253,.55)); }
.bl2-marker {
  position: absolute; top: -12px; left: 50%; transform: translateX(-50%); width: 0; height: 0; z-index: 1;
  border-left: 8px solid transparent; border-right: 8px solid transparent; border-top: 10px solid var(--gold);
}
.bl2-qty {
  position: absolute; right: -6px; bottom: -4px; min-width: 26px; height: 26px; padding: 0 5px; border-radius: 13px;
  background: rgba(20,24,38,.92); border: 1px solid var(--border); color: var(--text); font-size: 12px; font-weight: 800;
  display: inline-flex; align-items: center; justify-content: center;
}
.bl2-more { color: var(--text-dim); font-weight: 700; font-size: 14px; }

.bl2-right { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: auto; }
.bl2-status-wrap { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left; }
.bl2-status { font-family: var(--display-font); font-weight: 800; font-size: 17px; color: var(--text); white-space: nowrap; }
.bl2-dim { color: var(--text-dim); }
.bl2-fill { font-size: 12px; color: var(--text-dim); white-space: nowrap; }
.bl2-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 132px; padding: 12px 16px;
  font-size: 12px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; border-radius: 3px; white-space: nowrap;
}
.bl2-btn-watch { background: #3a4156; color: #d9deea; }
a.bl2-row:hover .bl2-btn-watch { background: #4a5270; }
.bl2-btn-join { background: var(--gold); color: #fff; }
a.bl2-row:hover .bl2-btn-join { filter: brightness(1.12); }
.bl2-head .bf-tag { font-size: 9px; }

@media (max-width: 520px) {
  .bl2-tile { width: 46px; height: 46px; }
  .bl2-top-actions { width: 100%; justify-content: space-between; }
}

/* Players beside the crates on wider screens (owner request 2026-09-29). */
.bl2-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
@media (min-width: 1000px) {
  .bl2-body { flex-direction: row; align-items: center; gap: 28px; }
  .bl2-body .bl2-players { flex-shrink: 0; flex-wrap: nowrap; }
  .bl2-body .bl2-crates { flex: 1; }
  .bl2-head { margin-bottom: 0; }
}

/* ---- Wheel restyle (2026-09-29) ---- */
/* The disc spins as a rotated square; its corners pushed the page wider than
   the screen. Clip them -- the round disc itself always fits. */
.wheel-main-panel { overflow: clip; }
/* Recent-spins rail no longer sets the row height (it made the wheel panel
   tall and mostly empty): the list is taken out of flow and scrolls inside
   whatever height the wheel panel gives it. */
.wheel-history-panel { position: relative; min-height: 260px; }
.wheel-history-panel .wheel-history-list { position: absolute; top: 70px; left: 0; right: 0; bottom: 12px; overflow-y: auto; align-items: center; scrollbar-width: none; }
.wheel-history-panel .wheel-history-list::-webkit-scrollbar { display: none; }
.wheel-main-panel { align-self: flex-start; }
.wheel-layout { align-items: flex-start; }
.wheel-history-panel { align-self: stretch; }
.wheel-round-status { margin-top: 0; }
.wheel-timer-bar { height: 14px; border-radius: 7px; }
.wheel-timer-row { margin-top: 8px; font-size: 14px; }
.wheel-timer-count { font-size: 26px; vertical-align: -3px; margin-right: 2px; }
.wheel-bet-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
.wheel-bet-head h3 { margin: 0; font-size: 16px; }
.wheel-bet-hint { font-size: 12px; color: var(--text-dim); }
.wheel-tier-squares.chip-armed .wheel-tier-square { cursor: pointer; }
.wheel-tier-squares.chip-armed .wheel-tier-square:hover { border-color: var(--tier-color); transform: translateY(-2px); }
.wheel-tier-square { transition: transform .12s, border-color .12s; }
.wheel-chip-tray { margin-top: 14px; }
/* Round casino chips instead of a busy image with a number on top. */
.wheel-chip {
  width: 74px; height: 74px; border-radius: 50%; border: 0; overflow: visible;
  background:
    radial-gradient(circle, #1c2335 0 55%, transparent 56%),
    repeating-conic-gradient(var(--chip, var(--gold)) 0 22.5deg, #e9edf5 22.5deg 30deg, var(--chip, var(--gold)) 30deg 45deg);
  box-shadow: 0 0 0 3px var(--chip, var(--gold)) inset, 0 4px 10px rgba(0,0,0,.55);
  color: #fff; font-size: 17px;
}
.wheel-chip::after { content: ''; position: absolute; inset: 13px; border-radius: 50%; border: 2px dashed color-mix(in srgb, var(--chip, var(--gold)) 70%, transparent); pointer-events: none; }
.wheel-chip-icon { display: none; }
.wheel-chip-value { text-shadow: 0 1px 3px rgba(0,0,0,.8); }
.wheel-chip.selected { transform: translateY(-5px); box-shadow: 0 0 0 3px var(--chip, var(--gold)) inset, 0 0 0 3px #fff, 0 8px 18px rgba(0,0,0,.6); }
.wheel-fair { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border-soft); }
.wheel-fair .wheel-bettors-label { margin-bottom: 8px; }
@media (max-width: 520px) {
  .wheel-chip { width: 58px; height: 58px; font-size: 14px; }
  .wheel-chip::after { inset: 10px; }
}

/* ---- Upgrader restyle (2026-09-29) ---- */
/* Panels line up at every width: preview + controls side by side when there's
   room, both full width when stacked. */
.upgrader-preview-panel { width: auto; flex: 0 1 540px; min-width: min(100%, 340px); }
.upgrader-right-col { flex: 1 1 420px; }
.upgrader-right-col > .panel { max-width: none !important; }
.upgrader-preview-empty { border-style: solid; }
.upg-empty { display: flex; flex-direction: column; align-items: center; gap: 22px; padding: 10px 0; }
.upg-empty-ring { position: relative; width: 240px; height: 240px; }
.upg-empty-ring svg { width: 100%; height: 100%; animation: upg-spin 18s linear infinite; }
.upg-empty-track { fill: none; stroke: var(--bg-panel-2); stroke-width: 16; }
.upg-empty-dash { fill: none; stroke: rgba(47,125,253,.4); stroke-width: 16; stroke-dasharray: 18 14; }
@keyframes upg-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .upg-empty-ring svg { animation: none; } }
@media (max-width: 1000px) { .upgrader-preview-panel { flex-basis: 100%; } }
.upg-empty-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; }
.upg-empty-center img { width: 40px; height: 40px; opacity: .8; margin-bottom: 4px; }
.upg-empty-center strong { font-family: var(--display-font); font-size: 18px; }
.upg-empty-center span { color: var(--text-dim); font-size: 12px; }
.upg-steps { list-style: none; counter-reset: upg; margin: 0; padding: 0; display: grid; gap: 10px; text-align: left; width: min(100%, 320px); }
.upg-steps li { counter-increment: upg; display: grid; grid-template-columns: 28px 1fr; column-gap: 10px; align-items: center; }
.upg-steps li::before { content: counter(upg); grid-row: span 2; width: 28px; height: 28px; border-radius: 50%; background: rgba(47,125,253,.15); color: var(--gold); font-weight: 800; display: flex; align-items: center; justify-content: center; font-size: 13px; }
.upg-steps strong { font-size: 13px; }
.upg-steps span { font-size: 12px; color: var(--text-dim); }
/* Item names shown on the tile (the hover tooltip left no name on touch
   screens, and its hidden nowrap text made the grid scroll sideways). */
.upgrader-target-tile h3 {
  position: static; transform: none; margin: 6px 0 4px; white-space: normal; background: none; border: 0; padding: 0;
  opacity: 1; visibility: visible; font-size: 11px; line-height: 1.25; min-height: 2.5em; pointer-events: auto;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; word-break: break-word;
}
.upgrader-target-results { overflow-x: hidden; }
.upgrader-target-results .loot-grid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); }

/* ---- Settings restyle (2026-09-29) ---- */
.acct-card { display: flex; align-items: center; gap: 14px; padding: 14px; border-radius: 12px; background: linear-gradient(135deg, rgba(47,125,253,.14), rgba(47,125,253,.03)); border: 1px solid var(--border); }
.acct-avatar { width: 60px; height: 60px; border-radius: 12px; object-fit: cover; flex-shrink: 0; }
.acct-avatar-blank { background: var(--bg-panel-3); }
.acct-id { flex: 1; min-width: 0; }
.acct-name { font-family: var(--display-font); font-size: 19px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.acct-steam { color: var(--text-dim); font-size: 12px; margin-top: 2px; }
.acct-badge { width: 54px; height: 54px; object-fit: contain; flex-shrink: 0; }
.acct-rank { margin-top: 14px; }
.acct-rank-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; font-size: 13px; }
.acct-rank-top strong { font-family: var(--display-font); font-size: 15px; }
.acct-rank-top span { color: var(--text-dim); font-size: 12px; }
.acct-rank-link { display: inline-block; margin-top: 6px; font-size: 12px; }
.acct-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 16px; }
.acct-tile { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px; border-radius: 10px; background: var(--bg-panel-3); border: 1px solid var(--border); min-width: 0; }
.acct-tile span { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); }
.acct-tile strong { font-size: 16px; }
.acct-tile em { font-style: normal; font-size: 11px; color: var(--text-dim); }
.acct-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.acct-actions .btn { flex: 1; text-align: center; padding: 10px 12px; }
.acct-logout { display: block; margin: 18px auto 0; background: none; border: 0; color: var(--bad); font-weight: 700; font-size: 13px; cursor: pointer; padding: 6px 10px; }
.acct-logout:hover { text-decoration: underline; }
/* Lists fill the panel (it scrolls as a whole) instead of a short inner box. */
.settings-tx-list { max-height: none; overflow: visible; }
@media (max-width: 560px) { .acct-tiles { grid-template-columns: 1fr; } }

/* ---- Dig restyle (2026-09-29) ---- */
/* Pre-round tiles look like packed earth instead of "?" boxes. */
.dig-tile.dig-idle {
  background:
    radial-gradient(circle at 22% 30%, rgba(255,255,255,.05) 0 2px, transparent 3px),
    radial-gradient(circle at 70% 64%, rgba(0,0,0,.28) 0 3px, transparent 4px),
    radial-gradient(circle at 44% 80%, rgba(255,255,255,.04) 0 2px, transparent 3px),
    radial-gradient(circle at 82% 22%, rgba(0,0,0,.22) 0 2px, transparent 3px),
    linear-gradient(160deg, #3a2e24 0%, #2a211a 55%, #211a15 100%);
  border-color: #4a3a2c;
  box-shadow: inset 0 2px 0 rgba(255,255,255,.04), inset 0 -3px 0 rgba(0,0,0,.25);
}
.dig-tile.dig-idle:nth-child(3n) { background-position: 8px 4px, -6px 3px, 5px -4px, 0 0, 0 0; }
.dig-tile.dig-idle:nth-child(4n+1) { filter: brightness(1.08); }
/* The "if your next click is" guide now sits in the controls column. */
.game-controls .dig-legend-tile { width: 100%; max-width: none; margin: 2px 0 14px; }
.game-controls .dig-legend-tabs { gap: 6px; }
.game-controls .dig-legend-tab { flex: 1; min-width: 0; }

/* ---- Rockets restyle (2026-09-29) ---- */
.box-summary { color: var(--text-dim); font-size: 13px; line-height: 1.55; margin: 14px 0 0; }
.box-facts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.box-facts span { font-size: 12px; color: var(--text-dim); background: var(--bg-panel-3); border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px; }
.box-facts strong { color: var(--text); }
.box-how { margin-top: 14px; border: 1px solid var(--border); border-radius: 10px; background: rgba(0,0,0,.12); }
.box-how > summary { cursor: pointer; list-style: none; padding: 12px 16px; font-weight: 700; font-size: 13px; display: flex; justify-content: space-between; align-items: center; }
.box-how > summary::-webkit-details-marker { display: none; }
.box-how > summary::after { content: ''; width: 8px; height: 8px; border-right: 2px solid var(--text-dim); border-bottom: 2px solid var(--text-dim); transform: rotate(45deg); transition: transform .15s; margin-left: 12px; }
.box-how[open] > summary::after { transform: rotate(-135deg); }
.box-how .box-paytable { margin: 0; border-top: 1px solid var(--border); padding: 12px 16px 14px; }

/* ---- Mines retouch (2026-09-29) ---- */
.mine-count-row { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)) 84px; gap: 6px; margin: 8px 0 4px; align-items: center; }
.mine-count-row .btn { padding: 9px 0; font-size: 13px; }
.mine-count-row input { width: 100%; min-width: 0; }
@media (max-width: 420px) { .mine-count-row { grid-template-columns: repeat(6, minmax(0, 1fr)); } .mine-count-row input { grid-column: 1 / -1; } }

/* ---- Deposit / Withdraw restyle (2026-09-29) ---- */
/* Dark cards like the rest of the site (the rarity tint stays), and narrower
   so more fit on a row. */
.deposit-grid { grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 12px; }
.deposit-grid .deposit-card { background-color: var(--bg-panel-2); border-color: var(--border); }
.deposit-grid .deposit-card:hover { border-color: #3a4463; box-shadow: 0 8px 18px rgba(0,0,0,.35); }
.deposit-grid .deposit-card.selected { border-color: #f5c518; box-shadow: 0 0 0 2px #f5c518 inset; }
.deposit-card-fg h3 { font-size: 13px; }
.deposit-selected-panel { background: var(--bg-panel); top: 12px; }
@media (max-width: 900px) {
  /* Summary above the items instead of a fixed 330px column squeezing the
     grid down to two cards a row. */
  .deposit-content-layout { flex-direction: column; align-items: stretch; }
  .deposit-selected-panel { order: -1; flex: none; width: 100%; position: static; box-sizing: border-box; }
  .deposit-selected-list { max-height: 160px; }
}

/* ---- Wipe Day loss-back link + panel (wallet page, 2026-09-29) ---- */
.lossback-link { margin-left: auto; display: inline-flex; align-items: center; gap: 7px; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--gold); background: rgba(47,125,253,.12); color: var(--text); font-weight: 700; font-size: 13px; cursor: pointer; white-space: nowrap; }
.lossback-link .ico { width: 16px; height: 16px; }
.lossback-link:hover { background: rgba(47,125,253,.22); }
.lossback-link.off { border-color: var(--border); background: transparent; color: var(--text-dim); opacity: .55; }
.lossback-link.off .ico { filter: grayscale(1); }
.lossback-link.off:hover { opacity: .8; background: transparent; }
@media (max-width: 520px) { .lossback-link { padding: 8px 10px; font-size: 12px; } }
.lb-card { margin-top: 14px; padding: 14px; border-radius: 10px; background: rgba(255,255,255,.03); border: 1px solid var(--border-soft); }
.lb-row { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--text-dim); padding: 4px 0; }
.lb-row strong { color: var(--text); }
.lb-row.lb-total { border-top: 1px solid var(--border-soft); margin-top: 6px; padding-top: 10px; font-size: 14px; }
.lb-bar { height: 6px; border-radius: 3px; background: rgba(255,255,255,.07); overflow: hidden; margin: 2px 0 6px; }
.lb-bar i { display: block; height: 100%; background: var(--gold); border-radius: 3px; }
.lb-status { margin: 8px 0 0; font-size: 13px; color: var(--text-dim); }
.lb-ok { color: #3ecf6e; font-weight: 700; }
.lb-note { margin-top: 12px; font-size: 13px; color: var(--text-dim); }
.lb-how-title { margin: 16px 0 6px; font-size: 14px; }
.lb-how { margin: 0; padding-left: 18px; color: var(--text-dim); font-size: 13px; line-height: 1.55; }
.lb-how li + li { margin-top: 4px; }

/* ---- Steam-only login/register (2026-09-29) ---- */
.auth-steam-note { text-align: center; margin: 16px 0 0; color: var(--text-dim); font-size: 13px; line-height: 1.5; }

/* ---- Rewards lock card (Steam check, 2026-09-29) ---- */
.rw-lock { margin: 14px 0; padding: 16px; border-radius: 12px; border: 1px solid rgba(47,125,253,.35); background: linear-gradient(180deg, rgba(47,125,253,.10), rgba(47,125,253,.03)); }
.rw-lock-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.rw-lock-head strong { font-size: 15px; }
.rw-lock-head span { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); border: 1px solid var(--border); border-radius: 999px; padding: 3px 9px; }
.rw-lock p { margin: 8px 0 12px; color: var(--text-dim); font-size: 13px; line-height: 1.5; }
.rw-reqs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.rw-req { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; border-radius: 10px; background: rgba(0,0,0,.18); border: 1px solid var(--border-soft); }
.rw-req span { font-size: 12px; color: var(--text-dim); }
.rw-req strong { font-size: 15px; }
.rw-req.ok strong { color: #3ecf6e; }
.rw-req.no strong { color: #ff8a65; }
.rw-req em { font-style: normal; font-size: 11px; color: var(--text-dim); }
.rw-lock-foot { display: flex; align-items: center; gap: 12px; margin-top: 12px; flex-wrap: wrap; }
.rw-lock-msg { font-size: 12px; color: var(--text-dim); }
@media (max-width: 420px) { .rw-reqs { grid-template-columns: 1fr; } }

/* ---- Battle funding: off by default behind a toggle (2026-09-29) ---- */
.bf-switch { position: relative; flex-shrink: 0; width: 46px; height: 26px; cursor: pointer; }
.bf-switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.bf-switch span { position: absolute; inset: 0; border-radius: 999px; background: rgba(255,255,255,.12); border: 1px solid var(--border); transition: background .15s; pointer-events: none; }
.bf-switch span::after { content: ''; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: #cfd6e4; transition: transform .15s; }
.bf-switch input:checked + span { background: var(--gold); border-color: var(--gold); }
.bf-switch input:checked + span::after { transform: translateX(20px); background: #fff; }
.bf-switch input:focus-visible + span { outline: 2px solid var(--gold); outline-offset: 2px; }
.bf-body { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-soft); }
.bf-body[hidden] { display: none; }
.bf-amount { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; font-size: 13px; color: var(--text-dim); }

/* ---- Rainback box above chat (2026-09-29) ---- */
.rain-box { margin: 8px 10px 0; padding: 12px; border-radius: 12px; background: var(--bg-panel-3); border: 1px solid var(--border); font-size: 12px; }
.rain-box.raining { border-color: var(--gold); background: linear-gradient(180deg, rgba(47,125,253,.18), rgba(47,125,253,.05)); box-shadow: 0 0 0 1px rgba(47,125,253,.25), 0 6px 18px rgba(47,125,253,.15); }
.rain-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.rain-title { font-family: var(--display-font); font-weight: 700; font-size: 14px; text-transform: uppercase; letter-spacing: .04em; }
.rain-box.raining .rain-title { color: var(--gold); }
.rain-sub { color: var(--text-dim); margin-top: 2px; }
.rain-pot { font-family: var(--display-font); font-weight: 700; font-size: 18px; color: var(--gold); white-space: nowrap; }
.rain-action { margin-top: 10px; }
.rain-join-btn { width: 100%; padding: 9px 0; }
.rain-joined { display: block; text-align: center; padding: 8px 0; border-radius: 8px; background: rgba(62,207,110,.12); color: #3ecf6e; font-weight: 700; }
.rain-blocked { display: block; color: var(--text-dim); line-height: 1.45; }
.rain-last { margin-top: 8px; color: var(--text-dim); }
.rain-last strong { color: #3ecf6e; }
.rain-links { display: flex; justify-content: space-between; gap: 8px; margin-top: 10px; }
.rain-link { background: none; border: 0; padding: 0; color: var(--gold); font-size: 12px; font-weight: 600; cursor: pointer; }
.rain-link:hover { text-decoration: underline; }
.rain-note { color: var(--text-dim); font-size: 11px; }
.rain-tip-form { display: flex; gap: 6px; margin: 8px 0 4px; }
.rain-tip-form input { flex: 1; min-width: 0; }
.rain-tip-form .btn { padding: 6px 14px; }
.rain-how { margin-top: 8px; color: var(--text-dim); line-height: 1.5; }
.rain-how p { margin: 6px 0; }
.rain-how ul { margin: 4px 0; padding-left: 16px; }
.rain-how strong { color: var(--text); }
.rain-msg { margin-top: 6px; font-size: 11px; color: #3ecf6e; }
.rain-msg.error { color: #ff8a65; }
.rain-msg:empty { display: none; }

/* ---- Chat 50% wider on desktop (owner request, 2026-09-30): 280px -> 420px.
   Every rule that reserves or lines up with the sidebar's width is restated
   here at the same specificity, so they all move together. Below 1300px the
   sidebar is an overlay and keeps its 280px. ---- */
@media (min-width: 1300px) {
  .chat-sidebar { width: 420px; }
  body.nav-has-sidebar:not(.chat-collapsed) { padding-left: 420px; }
  body.nav-has-sidebar:not(.chat-collapsed) .navbar .brand { width: 420px; }
  body.nav-has-sidebar:not(.chat-collapsed) #nav-root { margin-left: -420px; width: calc(100% + 420px); }
  body.nav-has-sidebar:not(.chat-collapsed) .navbar { padding-left: 448px; }
}

/* ---- Rainback: art + under-chat placement + tip announcements (2026-09-30) ---- */
.rain-box { margin: 0 10px 10px; flex: 0 0 auto; }
.rain-top { justify-content: flex-start; }
.rain-art { width: 44px; height: 44px; flex-shrink: 0; filter: drop-shadow(0 3px 6px rgba(47,125,253,.35)); }
.rain-head { flex: 1; min-width: 0; }
.rain-box.raining .rain-art { animation: rain-bob 2.4s ease-in-out infinite; }
@keyframes rain-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
.chat-msg-rain { background: linear-gradient(90deg, rgba(47,125,253,.16), rgba(47,125,253,.04)); border: 1px solid rgba(47,125,253,.3); border-radius: 10px; padding: 6px 8px; }
.chat-msg-rain .chat-msg-rain-icon { background: none; border-radius: 0; object-fit: contain; }
.chat-msg-rain .chat-msg-text { color: #8ab6f3; font-weight: 600; }

/* Rainback: "Tip the rain" is a real button (2026-09-30). */
.rain-links { align-items: center; }
.rain-tip-btn { padding: 7px 14px; font-size: 12px; }

/* ---- Rainback tip pop-up (2026-09-30) ---- */
.rain-tip-modal { max-width: 400px; width: calc(100% - 32px); position: relative; }
.rain-tip-modal .modal-close { position: absolute; top: 10px; right: 12px; background: none; border: 0; color: var(--text-dim); font-size: 26px; line-height: 1; cursor: pointer; }
.rain-tip-modal .modal-close:hover { color: var(--text); }
.rain-tip-hero { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; }
.rain-tip-hero > img { width: 56px; height: 56px; filter: drop-shadow(0 3px 8px rgba(47,125,253,.35)); }
.rain-tip-hero h3 { margin: 0; }
.rain-tip-hero p { margin: 4px 0 0; color: var(--text-dim); font-size: 13px; }
.rain-tip-hero strong { color: var(--gold); }
.rain-tip-quick { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-bottom: 12px; }
.rain-tip-quick .btn { padding: 8px 0; font-size: 13px; }
#rain-tip-form label { font-size: 12px; color: var(--text-dim); }
.rain-tip-row { display: flex; gap: 8px; margin-top: 4px; }
.rain-tip-row input { flex: 1; min-width: 0; }
.rain-tip-meta { display: flex; justify-content: space-between; margin-top: 6px; font-size: 12px; color: var(--text-dim); }
.rain-tip-note { margin: 12px 0 0; font-size: 12px; color: var(--text-dim); line-height: 1.5; }

/* ---- Player profile pop-up (2026-09-30) ---- */
[data-profile] { cursor: pointer; }
.chat-msg-name[data-profile]:hover { text-decoration: underline; }
.profile-modal { max-width: 460px; width: calc(100% - 32px); position: relative; }
.profile-modal .modal-close { position: absolute; top: 10px; right: 12px; background: none; border: 0; color: var(--text-dim); font-size: 26px; line-height: 1; cursor: pointer; }
.profile-modal .modal-close:hover { color: var(--text); }
.profile-loading { color: var(--text-dim); margin: 20px 0; text-align: center; }
.profile-head { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; padding-right: 24px; }
.profile-avatar { width: 64px; height: 64px; border-radius: 12px; object-fit: cover; flex-shrink: 0; border: 2px solid var(--border); }
.profile-avatar-blank { background: var(--bg-panel-3); }
.profile-id { flex: 1; min-width: 0; }
.profile-id h3 { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.profile-sub { margin: 4px 0 0; color: var(--text-dim); font-size: 13px; }
.profile-badge { width: 56px; height: 56px; object-fit: contain; flex-shrink: 0; }
.profile-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.profile-tile { display: flex; flex-direction: column; gap: 3px; padding: 12px; border-radius: 10px; background: rgba(255,255,255,.03); border: 1px solid var(--border-soft); min-width: 0; }
.profile-tile span { font-size: 12px; color: var(--text-dim); }
.profile-tile strong { font-size: 17px; }
.profile-tile em { font-style: normal; font-size: 11px; color: var(--text-dim); line-height: 1.4; }
.profile-section { margin: 18px 0 8px; font-size: 14px; }
.profile-games { display: flex; flex-direction: column; gap: 8px; max-height: 240px; overflow-y: auto; }
.profile-game-top { display: flex; justify-content: space-between; font-size: 13px; color: var(--text-dim); margin-bottom: 4px; }
.profile-game-top strong { color: var(--text); }
.profile-game-bar { height: 6px; border-radius: 3px; background: rgba(255,255,255,.06); overflow: hidden; }
.profile-game-bar i { display: block; height: 100%; background: var(--gold); border-radius: 3px; }
.profile-note { color: var(--text-dim); font-size: 13px; }
@media (max-width: 420px) { .profile-tiles { grid-template-columns: 1fr; } }

/* ---- Staff name colours (2026-09-30): admins red, mods green ---- */
:root { --name-admin: #ff5c5c; --name-mod: #3ecf6e; }
.name-admin { color: var(--name-admin) !important; }
.name-mod { color: var(--name-mod) !important; }
.profile-role { font-weight: 700; }

/* ---- Admin names: animated rainbow (owner request, 2026-09-30) ----
   Overrides the red .name-admin above. The gradient is clipped to the text
   and slid sideways so the colours flow through the name. */
.name-admin {
  background: linear-gradient(90deg, #ff4d4d, #ff9f1a, #ffe14d, #3ecf6e, #2f9bff, #8f5bff, #ff4dd2, #ff4d4d);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent !important;
  -webkit-text-fill-color: transparent;
  animation: name-rainbow 4s linear infinite;
}
@keyframes name-rainbow { from { background-position: 0% 50%; } to { background-position: 200% 50%; } }
@media (prefers-reduced-motion: reduce) { .name-admin { animation: none; } }

/* ---- Chat moderation (2026-09-30) ---- */
.chat-msg { position: relative; }
.chat-mod-tools { position: absolute; top: 2px; right: 4px; display: none; gap: 4px; z-index: 2; }
.chat-msg:hover .chat-mod-tools, .chat-msg:focus-within .chat-mod-tools { display: flex; }
@media (hover: none) { .chat-mod-tools { display: flex; opacity: .75; } }
.chat-mod-btn { background: var(--bg-panel-3); border: 1px solid var(--border); color: var(--text-dim); font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 6px; cursor: pointer; text-transform: uppercase; letter-spacing: .03em; }
.chat-mod-btn:hover { color: #ff8a65; border-color: #ff8a65; }
.chat-error { margin: 0 12px 6px; padding: 6px 10px; border-radius: 8px; background: rgba(255,138,101,.12); color: #ff8a65; font-size: 12px; }
.mute-modal { max-width: 380px; width: calc(100% - 32px); position: relative; }
.mute-modal .modal-close { position: absolute; top: 10px; right: 12px; background: none; border: 0; color: var(--text-dim); font-size: 26px; line-height: 1; cursor: pointer; }
.mute-modal h3 { margin: 0 24px 4px 0; }
.mute-sub { margin: 0 0 14px; color: var(--text-dim); font-size: 13px; }
.mute-opts { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.mute-opts .btn:last-child { grid-column: 1 / -1; }
.mute-unmute { width: 100%; margin-top: 10px; }
