/*
 * Shared theme.
 *
 * Every page used to carry its own copy of these tokens and its own hand
 * written navigation, which is why the banners drifted apart -- one said
 * "Balance", another "Wallet", one called the same game "Blackjack" and
 * another "Solo BJ", and the link order changed from page to page. One
 * stylesheet and one nav builder keeps them honest.
 */

:root {
  /* ------------------------------------------------------------- type ---
     The floor was set entirely in a monospace face. That is right for money
     -- a column of figures has to line up, and digits of equal width are the
     only way it does -- but it was doing every other job too, so headings,
     buttons, prose and labels all arrived in the same typewriter voice with
     nothing to tell them apart. Reading the site was harder than it needed
     to be, and no amount of size changes fixed it.

     Two faces now, with one rule for choosing between them:

       --font-ui    everything a person READS: headings, prose, buttons,
                    labels, navigation.
       --font-mono  everything a person COUNTS or CHECKS: balances, stakes,
                    prices, ledger rows, seeds, hashes, times.

     Both are system stacks. Nothing is downloaded, so there is no flash of
     unstyled text, no extra request on a shared host, and no font that fails
     to arrive and takes the layout with it. */
  --font-ui:   system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
               Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;

  --ink:    #0d1117;

  /* ------------------------------------------------------------- glass ---
     Surfaces are translucent so the sky shows through them. `--panel` is the
     token nearly every page already reaches for, so making IT the glass turns
     the whole building at once rather than page by page.

     `--solid` is kept for the few places that genuinely must not be see
     through -- a dropdown over text, a dialog over a busy table -- because
     glass stacked on glass stops being glass and starts being fog.

     The blur is deliberately modest. A heavy blur on a starfield turns the
     stars into a grey wash, which loses the thing worth showing through. */
  --panel:  rgba(19, 25, 34, .58);
  --solid:  #151b23;
  --glass-blur: blur(16px) saturate(135%);
  --line:   rgba(255, 255, 255, .085);
  --bone:   #e8e4dc;
  --muted:  #7d8694;
  /* ------------------------------------------------------------- gold ---
     THERE WERE TWO GOLDS. `--signal` (#d9a441) here, and a hard-coded
     #f0b647 scattered through nine pages and this stylesheet -- a brighter,
     yellower gold that never quite matched the one beside it. Two accents
     doing one job is the single loudest thing that makes a site read as
     assembled from parts.
     
     One gold now, with a lighter step for glows and highlights so the
     brighter tone still exists where it was doing real work. */
  --signal: #d9a441;
  --gold:      #d9a441;
  --gold-lift: #ecc06a;   /* highlights, glows, hover */
  --gold-dim:  #a97f2e;   /* pressed, disabled, hairlines */
  --up:     #4d8b5f;
  --down:   #a3241f;
  --felt:   #20302b;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font: 15px/1.6 var(--font-ui);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings carry the weight and the tightness; the size scale does the rest.
   Tracking goes slightly negative as the size goes up, which is what stops a
   large heading reading as though the letters were spaced by accident. */
h1, h2, h3, h4 { font-family: var(--font-ui); font-weight: 700;
                 letter-spacing: -.012em; line-height: 1.25; }
h1 { font-size: 1.6rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }

/* Anything countable or checkable. Tabular figures so digits sit in columns
   and a changing balance does not make the row twitch. */
.mono, .num, .figure, .amt, .bal, .stake, .price, .odds, .hash, .seed,
code, kbd, samp, pre { font-family: var(--font-mono);
                       font-variant-numeric: tabular-nums; }
table td.num, table th.num, .money, .credits { font-family: var(--font-mono);
                                               font-variant-numeric: tabular-nums; }

/* The figures themselves, wherever the floor puts them. Listed by the classes
   the pages actually use rather than left to each page to remember, because a
   balance set in the reading face is the one place the difference shows: the
   digits change width as the number changes and the row jumps. */
.v, .val, .figure, .balance, .jackval,
#bal, #lastWin, #jackVal, .stat .n, .tier .price .num,
table.daytable td, table.cmp td.val, .feed .amt, .money-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* The small uppercase labels are the site's own voice and they stay -- but
   they belong to the UI face, where the wider tracking reads as deliberate
   rather than as monospace doing an impression of a label. */
.eyebrow, .kicker, .cap, .label { font-family: var(--font-ui);
                                  letter-spacing: .14em;
                                  text-transform: uppercase; }

a { color: var(--signal); }

/* ------------------------------------------------------------- banner --- */

.topbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .75rem 1.25rem;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}

.brand {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  text-decoration: none;
  color: var(--bone);
  white-space: nowrap;
}

.brand .mark {
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .18em;
  color: var(--signal);
}

.brand .page {
  font-size: .68rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--muted);
  padding-left: .5rem;
  border-left: 1px solid var(--line);
}

.topnav {
  display: flex;
  gap: .15rem;
  flex-wrap: wrap;
}

.topnav a {
  padding: .4rem .7rem;
  font-size: .72rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  /* Two-word labels wrap inside the link instead of overflowing it.
     "House Vault", "Live Blackjack" and "Horse Racing" were each wider than
     the sidebar column, so the second word ran out past the border and sat on
     whatever was next to it. Wrapping keeps the text inside its own box; the
     line height keeps a wrapped label from crowding the one below. */
  display: block;
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.35;
  border: 1px solid transparent;
  border-radius: 8px;
}

.topnav a:hover { color: var(--signal); border-color: var(--line); }

/* Text stays inside its own edges, everywhere.
   A label longer than its container used to spill past the border rather than
   wrapping, which reads as a rendering fault rather than as a long word. The
   list is deliberately broad: it is easier to name the few places that must
   NOT wrap (below) than to chase every new component that can hold text. */
button, a, label, .pill, .badge, .chipbtn, .tag, .tagb, .tagj,
th, td, li, p, h1, h2, h3, h4, summary, option, legend, figcaption,
.card, .card2, .note, .logline, .meta, .sub, .who, .msub, .dim,
.stat, .acct, .req, .tk, .fline, .k, .v, .s {
  overflow-wrap: anywhere;
  word-break: normal;
}

/* Anything that holds a single unbroken value keeps its own line: a balance
   split across two lines is harder to read than one that overflows, and these
   are all short enough that they never need to wrap. */
.amt, .big, .jackval, .mfig .v, .acct .v, .stat .v,
[class*="num"], [class*="Num"] {
  overflow-wrap: normal;
  white-space: nowrap;
}

/* Long identifiers -- references, seeds, character ids -- break anywhere,
   because they have no spaces to break at and would otherwise push a whole
   table sideways. */
code, .mono, .seed, .ref, .faircard, .formstr {
  overflow-wrap: anywhere;
  word-break: break-all;
}

button, .pill, .badge, .chipbtn {
  white-space: normal;
}

.topnav a.on {
  color: var(--signal);
  border-color: var(--signal);
  background: #1b212b;
}

.spacer { flex: 1; }

.stat {
  font-size: .6rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.stat b {
  display: block;
  font-size: 1rem;
  letter-spacing: 0;
  color: var(--bone);
}

/* ------------------------------------------------------------ controls --- */

.ghost {
  padding: .45rem .8rem;
  border: 1px solid var(--line);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: .72rem;
  cursor: pointer;
  border-radius: 8px;
  text-decoration: none;
  display: inline-block;
  white-space: nowrap;
}

.ghost:hover:not(:disabled) { border-color: var(--signal); color: var(--signal); }
.ghost:disabled { opacity: .35; cursor: not-allowed; }

.primary {
  padding: .65rem 1.1rem;
  border: none;
  background: var(--signal);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: 8px;
}

.primary:disabled { opacity: .35; cursor: not-allowed; }

.up { color: var(--up); }
.down { color: var(--down); }
.muted { color: var(--muted); }
.hidden { display: none !important; }

/* ---------------------------------------------------------------- gate --- */

.gate {
  max-width: 23rem;
  margin: 4.5rem auto;
  padding: 0 1.5rem;
}

.gate h1 {
  font-size: .8rem;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 .5rem;
}

.gate p.lead {
  color: var(--muted);
  font-size: .78rem;
  margin: 0 0 1.2rem;
}

.gate input {
  width: 100%;
  padding: .65rem;
  margin-bottom: .6rem;
  background: var(--panel);
  border: 1px solid var(--line);
  color: var(--bone);
  font: inherit;
}

.gate .row { display: flex; gap: .6rem; }

.msg { color: var(--down); font-size: .78rem; min-height: 1.2rem; }
.msg.ok { color: var(--signal); }

/* ------------------------------------------------------------- ticker --- */

.ticker {
  overflow: hidden;
  white-space: nowrap;
  /* Glass, like the banner it sits on. It was the one opaque strip left
     across the top of every page. */
  background: rgba(17, 22, 29, .66);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--line);
  height: 1.7rem;
}

.ticker.quiet { display: none; }

.ticker .run {
  display: inline-block;
  padding-left: 100%;
  animation: ticker 38s linear infinite;
}

.ticker:hover .run { animation-play-state: paused; }

.ticker .win {
  display: inline-block;
  padding: .25rem 1.6rem;
  font-size: .72rem;
  color: var(--muted);
  border-right: 1px solid var(--line);
}

.ticker .win b { color: var(--bone); font-weight: 600; }
.ticker .win b.amt { color: var(--signal); }

@keyframes ticker {
  0%   { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}

/* Anyone who has asked for less motion should not get a scrolling banner. */
@media (prefers-reduced-motion: reduce) {
  .ticker .run { animation: none; padding-left: 0; }
}

/* ---- sign in / create account -------------------------------------------
   Two panels in one box; only one is ever visible. */
.gate .alt { margin-top: .9rem; font-size: .78rem; color: var(--muted); }
.gate .alt a { color: var(--signal); text-decoration: none;
               border-bottom: 1px solid rgba(217,164,65,.4); }
.gate .alt a:hover { border-bottom-color: var(--signal); }
.gate .hint { font-size: .72rem; color: var(--muted); margin: -.2rem 0 .6rem;
              line-height: 1.5; }
.gate [data-panel] > input { display: block; width: 100%; }
.gate .hidden { display: none; }

/* ---- table chat ---------------------------------------------------------
   Fixed height with its own scroll: a long conversation must not push the
   table off the screen. */
/* ---- chat, everywhere ---------------------------------------------------
   One look for every conversation on the site: the room panel on keno, the
   dock in the corner, the one on the front page and the rail beside the
   slots. They were drifting apart -- the keno panel got a proper header, a
   deeper log and a composer on its own bar, and the rest kept a flat box
   from before. A site with four chats that look like four different products
   is a site that looks unfinished. */
.chat { border: 1px solid var(--line); border-radius: 16px; overflow: hidden;
        display: flex; flex-direction: column;
        background: linear-gradient(178deg,
          rgba(24, 31, 44, .58), rgba(12, 17, 25, .68));
        backdrop-filter: var(--glass-blur);
        -webkit-backdrop-filter: var(--glass-blur);
        box-shadow: inset 0 1px 0 rgba(255,255,255,.06); }
/* Inside a card that already draws its own frame, the chat should not draw a
   second one a pixel inside the first. */
.chatcard .chat { border: 0; border-radius: 0; background: none; }
.chatlog { height: 17rem; overflow-y: auto; padding: .8rem .9rem;
           font-size: .84rem; line-height: 1.6;
           scrollbar-width: thin; scrollbar-color: #2c3542 transparent; }
.chatlog::-webkit-scrollbar { width: 8px; }
.chatlog::-webkit-scrollbar-thumb { background: #2c3542; border-radius: 8px; }
.chatlog::-webkit-scrollbar-track { background: transparent; }
/* One line per remark, the name leading it. Speech is prose, so it is set in
   the reading face; only the timestamp beside it is a figure. */
/* Each remark gets a little room and a hairline between, so a busy room is
   readable instead of a wall. */
.chatlog .line { margin-bottom: .5rem; padding-bottom: .5rem;
                 border-bottom: 1px solid rgba(255,255,255,.04);
                 word-wrap: break-word; overflow-wrap: anywhere; }
.chatlog .line:last-child { border-bottom: none; margin-bottom: 0; }
.chatlog .who { color: var(--signal); font-weight: 700; margin-right: .4rem;
                font-size: .78rem; letter-spacing: .01em; }
.chatlog .who::after { content: ""; }
.chatlog .said { color: var(--bone); }
/* The composer sits on its own bar at the foot, darker than the log so the
   two read as different things. */
.chatbar { display: flex; gap: .45rem; padding: .7rem .8rem; align-items: center;
           border-top: 1px solid var(--line);
           background: rgba(0, 0, 0, .22); }
.chatbar input { flex: 1; min-width: 0; background: rgba(0, 0, 0, .35);
                 border: 1px solid var(--line); color: var(--bone);
                 border-radius: 8px;
                 font: inherit; font-size: .84rem; padding: .55rem .7rem;
                 transition: border-color .15s, box-shadow .15s; }
.chatbar input:focus { outline: none; border-color: var(--signal);
                       box-shadow: 0 0 0 3px rgba(217,164,65,.14); }
.chatbar button { border-radius: 8px; }
.chatbar .emojibtn { font-size: 1.05rem; line-height: 1; padding: .3rem .5rem; }
.emojipad { display: grid; grid-template-columns: repeat(10, 1fr); gap: .1rem;
            padding: .5rem; border-top: 1px solid var(--line);
            background: var(--ink); }
.emojipad .emoji { background: none; border: 1px solid transparent;
                   font-size: 1.1rem; line-height: 1.4; cursor: pointer;
                   border-radius: 8px; padding: .1rem; }
.emojipad .emoji:hover { border-color: var(--signal);
                         background: rgba(217,164,65,.1); }
.chatnote { margin: 0; padding: 0 .6rem .5rem; font-size: .72rem;
            color: var(--down); min-height: 1em; }
.chat .hidden { display: none; }
@media (max-width: 700px) {
  .emojipad { grid-template-columns: repeat(8, 1fr); }
  .chatlog { height: 12rem; }
}

/* ---- global chat dock ---------------------------------------------------
   Fixed to the corner of every page. Collapsed it is a tab and nothing more. */
.gchat { position: fixed; right: 1rem; bottom: 0; z-index: 40;
         width: 22rem; max-width: calc(100vw - 2rem);
         display: flex; flex-direction: column; }
.gchat-tab { display: flex; align-items: center; gap: .5rem;
             align-self: flex-end; background: var(--panel);
             border: 1px solid var(--line); border-bottom: none;
             border-radius: 4px 4px 0 0; color: var(--bone);
             font: inherit; font-size: .72rem; letter-spacing: .14em;
             text-transform: uppercase; padding: .5rem .9rem; cursor: pointer; }
.gchat-tab:hover { border-color: var(--signal); color: var(--signal); }
.gchat-badge { background: var(--signal); color: #14110b; border-radius: 999px;
               font-size: .62rem; letter-spacing: 0; padding: .05rem .38rem;
               font-weight: 700; }
.gchat-body { border: 1px solid var(--line); border-bottom: none;
              border-radius: 4px 0 0 0; overflow: hidden; }
.gchat.closed .gchat-body { display: none; }
.gchat .chat { border: none; }
.gchat .chatlog { height: 13rem; }
.chatlog .line { position: relative; padding-right: 1.1rem; }
.chatlog .line .rm { position: absolute; right: 0; top: 0; background: none;
                     border: none; color: var(--muted); cursor: pointer;
                     font-size: .7rem; line-height: 1.4; opacity: 0; }
.chatlog .line:hover .rm { opacity: 1; }
.chatlog .line .rm:hover { color: var(--down); }
@media (max-width: 620px) {
  .gchat { right: .5rem; width: calc(100vw - 1rem); }
  .gchat .chatlog { height: 10rem; }
}
@media print { .gchat { display: none; } }

/* ================================================== circular card table ===
   Seats sit on an ellipse around a felt oval, the way players sit at a real
   table, rather than in a grid. Positions are computed in JS (CSS has no
   usable trigonometry) and written as inline left/top percentages, so this
   file only has to describe what a seat looks like, not where it goes.

   The centre is left clear for the board and the pot: that is the part
   everyone looks at, so it gets the space and the largest cards. */

.tablewrap { position: relative; width: 100%; max-width: 68rem; margin: 0 auto;
             aspect-ratio: 16 / 9; min-height: 30rem; }

/* Blackjack seats sit in an arc across the bottom rather than all the way
   round, so the felt reads as emptier than poker's. Same box, same size --
   the difference should be the seating, not the table. */
.tablewrap.bj { max-width: 68rem; min-height: 30rem; }

/* The 3D cloth. It sits under everything and the flat .tablefelt stays
   behind it as the fallback for a browser that cannot start WebGL. */
.felt3d { position: absolute; inset: 0; width: 100%; height: 100%;
          display: block; border-radius: 16px; }
.tablewrap.is3d .tablefelt { display: none; }
/* The seat cards STAY VISIBLE in the 3D view.
   This rule used to hide them, on the assumption that the WebGL table would
   draw hole cards on the felt itself. It never did -- table3d.js has no
   hole-card rendering at all -- so in 3D a player simply could not see their
   own hand, and opponents showed no face-down backs either. The table looked
   like it had dealt to nobody.
   The pod cards are drawn above each name plate instead, which reads clearly
   over the 3D felt and needs no WebGL to work. */
.tablewrap.is3d .pod .cards {
  display: flex;
  /* Lifted clear of the plate and given a dark backing so a white card face
     stays legible against the bright green felt underneath. */
  filter: drop-shadow(0 .25rem .5rem rgba(0, 0, 0, .75));
}

.tablefelt {
  position: absolute; inset: 6% 4%;
  border-radius: 50% / 50%;
  background:
    radial-gradient(ellipse at 50% 42%, #1f5641 0%, #17422f 55%, #103324 100%);
  border: .55rem solid #2b1d12;
  box-shadow: inset 0 0 4rem #0009, inset 0 0 0 .35rem #d9a44122,
              0 1.2rem 2.5rem #0008;
}
.tablefelt::after {              /* the inner rail line, as on a real table */
  content: ""; position: absolute; inset: 9%;
  border: 1px solid #ffffff14; border-radius: 50% / 50%; pointer-events: none;
}

/* ---- the middle: board and pot ---- */
.tablemid { position: absolute; left: 50%; top: 47%; transform: translate(-50%, -50%);
            display: flex; flex-direction: column; align-items: center;
            gap: .55rem; z-index: 2; pointer-events: none; }
.tablemid .board { display: flex; gap: .4rem; justify-content: center;
                   min-height: 5.4rem; align-items: center; }
.tablemid .potline { font-size: .64rem; letter-spacing: .18em;
                     text-transform: uppercase; color: #cfe6d8cc;
                     display: flex; align-items: center; gap: .5rem; }
.tablemid .potline b { font-size: 1.1rem; letter-spacing: 0; color: var(--signal);
                       background: #00000055; padding: .1rem .6rem;
                       border-radius: 999px; }
.tablemid .phase { font-size: .6rem; letter-spacing: .18em; color: #cfe6d888;
                   text-transform: uppercase; }

/* ---- a seat ---- */
.pod { position: absolute; transform: translate(-50%, -50%); z-index: 3;
       width: 9.5rem; text-align: center; }
.pod .plate { background: linear-gradient(#1c2530, #151c25);
              border: 1px solid #ffffff1f; border-radius: 8px;
              padding: .4rem .5rem; box-shadow: var(--lift-2); }
.pod .nm { font-size: .74rem; font-weight: 600; color: var(--bone);
           white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pod .stk { font-size: .82rem; color: var(--signal); letter-spacing: .02em; }
.pod .cards { display: flex; gap: .22rem; justify-content: center;
              margin-bottom: .3rem; min-height: 4.4rem; align-items: flex-end; }
/* The seat's bet slot. The chip inside it does all the drawing now -- this
   only positions it and carries the "all in" tag. */
.pod .bet { position: absolute; left: 50%; transform: translateX(-50%);
            bottom: -2.1rem; display: flex; align-items: center; gap: .3rem;
            white-space: nowrap; }
.pod .bet .allin { font-size: .56rem; letter-spacing: .1em;
                   color: var(--signal); text-transform: uppercase; }

.pod.acting .plate { border-color: var(--signal);
                     box-shadow: 0 0 0 2px var(--signal), 0 0 1.6rem #d9a44166; }
.pod.folded { opacity: .42; filter: grayscale(.7); }
.pod .dealerbtn { position: absolute; top: -.45rem; right: .2rem;
                  width: 1.25rem; height: 1.25rem; border-radius: 50%;
                  background: #f2efe8; color: #14181f; font-size: .62rem;
                  font-weight: 700; display: grid; place-items: center;
                  box-shadow: var(--lift-2); }
.pod .tag { font-size: .58rem; letter-spacing: .12em; text-transform: uppercase;
            color: var(--muted); }
.pod .away { font-size: .58rem; letter-spacing: .1em; color: var(--down);
             text-transform: uppercase; }

/* ---- an empty seat ---- */
.pod.free .plate { background: #0f1a16aa; border-style: dashed;
                   border-color: #ffffff2e; }
.pod.free button { background: none; border: none; color: #bcd6c6;
                   font: inherit; font-size: .7rem; letter-spacing: .1em;
                   text-transform: uppercase; cursor: pointer; padding: .25rem .1rem;
                   width: 100%; }
.pod.free button:hover { color: var(--signal); }

/* ---- a countdown ring on whoever is on the clock ---- */
.pod .ring { position: absolute; left: 50%; bottom: -.55rem;
             transform: translateX(-50%); width: 5.5rem; height: .22rem;
             background: #ffffff1f; border-radius: 999px; overflow: hidden; }
.pod .ring i { display: block; height: 100%; background: var(--signal);
               transition: width .9s linear; }

@media (max-width: 820px) {
  .tablewrap { aspect-ratio: 4 / 5; min-height: 34rem; }
  .pod { width: 7.2rem; }
  .pod .cards { min-height: 3.4rem; }
}

/* ---- blackjack: the dealer owns the top of the felt ---- */
.tablewrap.bj .dealerbox { position: absolute; left: 50%; top: 20%;
                           transform: translate(-50%, -50%); text-align: center;
                           z-index: 2; }
.tablewrap.bj .dealerbox .lbl { font-size: .6rem; letter-spacing: .18em;
                                text-transform: uppercase; color: #cfe6d8aa;
                                margin-bottom: .35rem; }
.tablewrap.bj .dealerbox .cards { display: flex; gap: .3rem;
                                  justify-content: center; min-height: 4.4rem; }
.tablewrap.bj .dealerbox .total { font-size: .68rem; color: #cfe6d8cc;
                                  margin-top: .3rem; }
.tablewrap.bj .dealerbox .phase { font-size: .58rem; letter-spacing: .16em;
                                  text-transform: uppercase; color: #cfe6d877;
                                  margin-top: .2rem; }
.bjhand { margin-bottom: .25rem; }
.bjhand.live { outline: 1px solid var(--signal); outline-offset: .18rem;
               border-radius: 8px; }
.bjhand .cards { display: flex; gap: .22rem; justify-content: center; }
.bjhand .tot { font-size: .64rem; color: #cfe6d8cc; margin-top: .15rem; }
.bjhand .outcome { font-size: .56rem; letter-spacing: .12em;
                   text-transform: uppercase; margin-top: .1rem; }
.bjhand .outcome.win, .bjhand .outcome.blackjack { color: var(--signal); }
.bjhand .outcome.lose, .bjhand .outcome.bust { color: var(--muted); }
.bjhand .outcome.push { color: #cfe6d8cc; }

/* ---- admin tabs, game stats and log search ---- */
.tabs { display: flex; gap: .3rem; margin-bottom: 1rem;
        border-bottom: 1px solid var(--line); }
.tabs .tab { background: none; border: none; border-bottom: 2px solid transparent;
             color: var(--muted); font: inherit; font-size: .72rem;
             letter-spacing: .16em; text-transform: uppercase; cursor: pointer;
             padding: .55rem .9rem; }
.tabs .tab:hover { color: var(--bone); }
.tabs .tab.on { color: var(--signal); border-bottom-color: var(--signal); }
.tabpane.hidden { display: none; }

.gamepick { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .9rem; }
.gamepick button { background: var(--ink); border: 1px solid var(--line);
                   color: var(--bone); font: inherit; font-size: .72rem;
                   padding: .35rem .7rem; cursor: pointer; border-radius: 8px; }
.gamepick button.on { border-color: var(--signal); color: var(--signal); }

.edgegrid { display: grid; gap: .7rem;
            grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
.edgecard { border: 1px solid var(--line); padding: .7rem; border-radius: 8px;
            background: var(--ink); }
.edgecard h3 { margin: 0 0 .4rem; font-size: .72rem; letter-spacing: .12em;
               text-transform: uppercase; color: var(--bone); }
.edgecard .row { display: flex; justify-content: space-between; gap: .5rem;
                 font-size: .74rem; padding: .12rem 0; }
.edgecard .row span:first-child { color: var(--muted); }
.edgecard .warn { font-size: .66rem; color: var(--down); margin-top: .4rem;
                  line-height: 1.5; }
.edgecard input, .edgecard select { width: 5.5rem; background: var(--panel);
        border: 1px solid var(--line); color: var(--bone); font: inherit;
        font-size: .74rem; padding: .2rem .35rem; }

.logfilters { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .8rem; }
.logfilters input, .logfilters select { background: var(--ink);
        border: 1px solid var(--line); color: var(--bone); font: inherit;
        font-size: .78rem; padding: .38rem .5rem; }
.logfilters #lgQ { flex: 1; min-width: 12rem; }
.logrow { display: grid; grid-template-columns: 10.5rem 8rem 9rem 1fr;
          gap: .6rem; padding: .4rem 0; border-bottom: 1px dashed var(--line);
          font-size: .74rem; align-items: baseline; }
.logrow .when { color: var(--muted); font-size: .68rem; }
.logrow .act { color: var(--signal); }
.logrow .who { color: var(--bone); }
.logrow .det { color: var(--muted); word-break: break-word; }
.logpager { display: flex; align-items: center; gap: .8rem; margin-top: .8rem; }
@media (max-width: 700px) {
  .logrow { grid-template-columns: 1fr; gap: .1rem; }
}

/* ---- who is online ---- */
.online { display: flex; flex-direction: column; gap: .3rem; }
.online .who { display: flex; align-items: center; gap: .45rem;
               font-size: .78rem; }
.dot { width: .5rem; height: .5rem; border-radius: 50%; background: #3fbf7f;
       box-shadow: 0 0 .35rem #3fbf7f; flex: 0 0 auto; }
.dot.idle { background: var(--muted); box-shadow: none; }
.online .tagadmin { font-size: .58rem; letter-spacing: .1em; color: var(--signal);
                    text-transform: uppercase; }

/* ---- open your own room ---- */
.newroom { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.newroom input { background: var(--ink); border: 1px solid var(--line);
                 color: var(--bone); font: inherit; font-size: .78rem;
                 padding: .38rem .5rem; }
.newroom input[type=number] { width: 5rem; }
.newroom input:first-child { flex: 1; min-width: 9rem; }

/* ---- no sign-in flash between pages -------------------------------------
   casino.js sets data-session on <html> before the body is parsed, so the
   browser never paints the wrong screen first. These rules beat the .hidden
   class the pages ship with, which is why they are !important: the markup's
   default and the session's answer genuinely conflict, and the session wins. */
html[data-session="yes"] #gate { display: none !important; }
html[data-session="yes"] #app  { display: block !important; }
html[data-session="no"]  #app  { display: none !important; }
html[data-session="no"]  #gate { display: block !important; }

/* ---- cash out ---- */
.wdform { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.wdform input { background: var(--ink); border: 1px solid var(--line);
                color: var(--bone); font: inherit; font-size: .78rem;
                padding: .4rem .5rem; }
.wdform input[type=number] { width: 7rem; }
.wdform input:first-child { flex: 1; min-width: 9rem; }
.wdlist { margin-top: .8rem; }
.wdrow { display: grid; grid-template-columns: 1fr auto auto; gap: .8rem;
         align-items: center; padding: .5rem 0;
         border-bottom: 1px dashed var(--line); }
.wdwho { font-size: .8rem; }
.wdamt { font-size: .9rem; color: var(--bone); }
.badge { font-size: .6rem; letter-spacing: .1em; text-transform: uppercase;
         padding: .22rem .55rem; border-radius: 999px; white-space: nowrap;
         border: 1px solid transparent; }
.badge.pending  { color: #e0b341; border-color: #e0b34155; background: #e0b34114; }
.badge.approved { color: #3fbf7f; border-color: #3fbf7f66; background: #3fbf7f18; }
.badge.rejected { color: var(--down); border-color: #b3211c55; background: #b3211c14; }
.wdadmin { display: grid; grid-template-columns: 1fr auto; gap: .6rem 1rem;
           align-items: start; padding: .7rem 0;
           border-bottom: 1px dashed var(--line); }
.wdadmin .wdacts { grid-column: 1 / -1; display: flex; gap: .4rem;
                   flex-wrap: wrap; align-items: center; }
.wdadmin .wdacts input { flex: 1; min-width: 10rem; background: var(--ink);
        border: 1px solid var(--line); color: var(--bone); font: inherit;
        font-size: .76rem; padding: .35rem .5rem; }
.pipbadge { display: inline-grid; place-items: center; min-width: 1.1rem;
            height: 1.1rem; margin-left: .4rem; padding: 0 .25rem;
            border-radius: 999px; background: var(--signal); color: #14181f;
            font-size: .6rem; font-weight: 700; }
.pipbadge.hidden { display: none; }

/* ---- notification centre ---- */
.bellhost { position: relative; display: inline-flex; }
.bell { background: none; border: 1px solid var(--line); color: var(--bone);
        border-radius: 8px; cursor: pointer; padding: .3rem .45rem;
        display: inline-flex; align-items: center; position: relative; }
.bell:hover { border-color: var(--signal); color: var(--signal); }
.bellpip { position: absolute; top: -.35rem; right: -.35rem;
           min-width: 1rem; height: 1rem; padding: 0 .22rem;
           border-radius: 999px; background: var(--signal); color: #14181f;
           font-size: .56rem; font-weight: 700; display: grid;
           place-items: center; }
.bellpip.hidden { display: none; }
/* Fixed, and placed from JS against the bell's own rectangle. It used to be
   absolute inside the banner with two media queries arguing about where it
   should sit; once the banner became glass it could not escape the banner at
   all. */
.bellpanel { position: fixed; width: 22rem;
             max-width: 82vw; max-height: 26rem; overflow-y: auto; z-index: 60;
             background: var(--panel); border: 1px solid var(--line);
             border-radius: 8px; box-shadow: var(--lift-3); }
.bellpanel.hidden { display: none; }
.bellhead { display: flex; align-items: center; justify-content: space-between;
            gap: .5rem; padding: .5rem .6rem; border-bottom: 1px solid var(--line);
            font-size: .64rem; letter-spacing: .14em; text-transform: uppercase;
            color: var(--muted); position: sticky; top: 0;
            background: var(--panel); }
.bellitem { display: block; padding: .55rem .6rem; text-decoration: none;
            border-bottom: 1px dashed var(--line); color: var(--bone); }
.bellitem:hover { background: #ffffff08; }
.bellitem.fresh { border-left: 2px solid var(--signal); }
.bellitem .bt { font-size: .78rem; }
.bellitem .bb { font-size: .7rem; color: var(--muted); margin-top: .12rem;
                line-height: 1.5; }
.bellitem .bw { font-size: .62rem; color: var(--muted); margin-top: .2rem;
                opacity: .7; }
.badge.paid { color: #6ee7a8; border-color: #6ee7a877; background: #6ee7a81f;
              font-weight: 600; }

/* ==========================================================================
   MOBILE
   ==========================================================================
   Phones get a single column, thumb-sized controls and no horizontal scroll.

   The 16px minimum on inputs is not a style choice: iOS Safari zooms the
   whole page when a focused input is smaller than that, and the page never
   zooms back. Every text input on the site therefore holds at 16px on small
   screens regardless of what it looks like on a desktop.                   */

@media (max-width: 760px) {

  html { -webkit-text-size-adjust: 100%; }
  body { overflow-x: hidden; }
  .wrap { padding-left: .7rem; padding-right: .7rem; }

  /* ---- banner: nine nav items will not fit across a phone ----
     Wrapping them makes a three-line banner that pushes the game off screen,
     so the nav becomes one swipeable row on its own line instead. */
  .topbar { gap: .5rem .7rem; padding-left: .7rem; padding-right: .7rem; }
  .topnav {
    order: 10; width: 100%;
    flex-wrap: nowrap; overflow-x: auto;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    padding-bottom: .2rem;
  }
  .topnav::-webkit-scrollbar { display: none; }
  .topnav a { white-space: nowrap; padding: .55rem .6rem; }

  /* Thumbs, not cursors. 44px is the usual floor for a reliable tap. */
  button, .ghost, .primary, .topnav a, .tabs .tab { min-height: 2.75rem; }
  button, .ghost, .primary { padding-left: .8rem; padding-right: .8rem; }

  input, select, textarea { font-size: 16px !important; }

  /* ---- tables and long rows stack ---- */
  .layout, .side, .homegrid, .stats, .heroes, .edgegrid, .picks, .grid {
    grid-template-columns: 1fr !important;
  }
  .bar, .ev, .strip, .leg, .tbl, .lbrow, .wdrow, .wdadmin, .logrow {
    grid-template-columns: 1fr !important;
    gap: .2rem !important;
  }
  .lbrow .rank { display: none; }

  /* Wide data tables scroll sideways rather than squeezing to nothing. */
  .card table, .card2 table, #gameStats table {
    display: block; overflow-x: auto; white-space: nowrap;
  }

  /* ---- the felt ---- */
  .tablewrap { aspect-ratio: auto; min-height: 0; height: 30rem; }
  .tablewrap.bj { height: 26rem; }
  .pod { width: 6.4rem; }
  .pod .plate { padding: .3rem .35rem; }
  .pod .nm { font-size: .64rem; }
  .pod .stk { font-size: .7rem; }
  .pod .cards { min-height: 3rem; }
  .card { width: 2.1rem; height: 3rem; }
  .card .cnr { font-size: .5rem; }
  .card .cnr .s { font-size: .52rem; }
  .card .mid { font-size: .95rem; }
  .tablemid .board { min-height: 3.4rem; }
  .board .card { width: 2.4rem; height: 3.4rem; }

  /* ---- chat dock: a phone-width sheet, not a floating card ---- */
  .gchat { left: 0; right: 0; bottom: 0; width: 100%; max-width: 100%;
           border-radius: 0; border-left: none; border-right: none; }
  .gchat .chatlog { height: 45vh; }

  /* ---- notifications ---- */
  .bellpanel { max-width: calc(100vw - 1rem); max-height: 70vh; }

  /* ---- forms wrap instead of overflowing ---- */
  .logfilters, .newroom, .wdform, .wdadmin .wdacts { flex-direction: column;
                                                      align-items: stretch; }
  .logfilters input, .logfilters select, .newroom input, .wdform input,
  .wdadmin .wdacts input { width: 100%; min-width: 0; }
  .newroom input[type=number], .wdform input[type=number] { width: 100%; }

  /* ---- admin tabs scroll rather than wrap into a tangle ---- */
  .tabs { overflow-x: auto; scrollbar-width: none; }
  .tabs::-webkit-scrollbar { display: none; }
  .tabs .tab { white-space: nowrap; }

  /* ---- the slot cabinet ---- */
  .reel { width: 4rem; height: 4.7rem; }
  .face svg { width: 2.5rem; height: 2.5rem; }
}

@media (max-width: 420px) {
  .pod { width: 5.4rem; }
  .card { width: 1.85rem; height: 2.65rem; }
  .tablewrap { height: 26rem; }
  .reel { width: 3.5rem; height: 4.2rem; }
  .face svg { width: 2.1rem; height: 2.1rem; }
}
.rowbtn.off { border-color: var(--down); color: var(--down); }

/* ==========================================================================
   THE SIGN-IN SCREEN
   ==========================================================================
   A night sky over the desert. The stars are drawn with layered radial
   gradients rather than hundreds of DOM nodes or an image file: it costs one
   element, scales to any screen, needs no download, and cannot go missing.  */

.gate {
  /* The original .gate rule earlier in this file boxes the sign-in column to
     23rem. That is right for the card and wrong for the sky, which was being
     cropped to a narrow strip down the middle of the screen. Reset the box
     here and let the sky be a fixed full-viewport layer instead. */
  max-width: none;
  margin: 0;
  padding: 2rem 1rem;
  position: relative;
  min-height: 100vh;
  display: grid;
  place-items: center;
}

/* Fixed, not absolute: the night sky is behind the whole page rather than
   inside the sign-in box, so it covers the viewport at any scroll position
   and any window size. */
html[data-session="no"] body::before {
  content: "";
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 120% 80% at 50% 108%, #150e21 0%, #0b0716 40%,
                    #050409 70%, #020205 100%);
}

/* The stars are real elements, built in JS, not painted gradients.
   Gradients were cheaper but they can only fade as one flat sheet -- for stars
   that blink independently each one needs its own animation clock. Only
   `opacity` and `transform` are animated, both of which the compositor handles
   without touching layout, so several hundred of them stay cheap. */
.skyfield {
  /* Behind the whole page. It used to sit at z-index 1, which was fine when
     it only ever appeared on the sign-in screen with one card above it; on a
     full page it would have covered the content. */
  position: fixed; inset: -12%; z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

/* Everything the page draws sits above the sky. Stated once here rather than
   left to each page to remember, because a page that forgets disappears.
   ONLY containers that are still statically positioned belong in this list:
   giving `position: relative` to something already fixed or absolute takes it
   out of the corner it was pinned to. That is exactly what happened -- the
   chat dock is `position: fixed; bottom: 0`, and being handed `relative` here
   dropped it into the middle of the page. */
/* Positioned, but deliberately WITHOUT a z-index.
 *
 * `position: relative` alone is enough to lift these above the fixed
 * background, which sits at z-index 0 earlier in the document. Adding a
 * z-index as well turned #app into a stacking context -- and a stacking
 * context is a ceiling: a full-screen modal INSIDE it, however high its own
 * z-index, can never rise above the header. The vault's account picker is
 * exactly that, a fixed sheet at 70 living inside #app. Leaving the z-index
 * off lets it compete globally, as it is written to. */
#app, main, .wrap, .gate { position: relative; }

/* The header sits ABOVE the page, not level with it.
 *
 * It used to share z-index 1 with #app, and a tie is broken by document
 * order -- #app comes second, so every page quietly painted over its own
 * header. Nothing looked wrong until a page put something tall or opaque at
 * the top of its layout: the Boulevard board covered the nav outright, so the
 * Help me button was there, was clickable in theory, and was drawn behind the
 * tiles.
 *
 * It is one number ahead rather than a large one, so anything genuinely
 * meant to cover the whole screen -- the help sheet at 80, the veil at 60 --
 * still covers the header too. */
/* Above ordinary page furniture, below anything meant to cover the screen.
 *
 * 50 sits over the slots wheel overlay at 30 and the chat dock at 40, and
 * under the veils at 60 and 70 and the help sheet at 80 -- all of which are
 * supposed to cover the header. */
#chrome { position: relative; z-index: 50; }

/* Already positioned, so they need the stacking order and nothing else. */
.gchat { z-index: 40; }
.veil, .m-veil { z-index: 60; }
.wheelover { z-index: 30; }
.topbar { z-index: 2; }

/* The glass itself. One list, so a new surface joins the theme by using the
   same class the rest of the site already uses rather than by restating the
   recipe. `backdrop-filter` only does anything where the background is
   translucent, so it is harmless on the few that are not. */
.card, .card2, .panel, .topbar, .chatcard, .hero, .gatecard,
.chat, .bellpanel, .tkpanel {
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
/* Pages with their own namespaced surfaces opt in from their own stylesheet
   using --glass-blur. Naming `.w-panel` or `.m-tier` HERE would mean the
   shared sheet reaching into a page's namespace, which is the same coupling
   the namespacing exists to prevent -- and the collision check rightly
   flagged it when I tried. */

/* A single hairline of light along the top edge. It is what separates a
   translucent panel from a hole cut in the page. */
.card, .card2, .panel, .chatcard, .hero, .gatecard {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06),
              0 .6rem 1.6rem rgba(0, 0, 0, .32);
}

/* Three depth layers. They drift at different rates under the pointer, which
   is what stops the field reading as one flat sheet sliding about. */
.starlayer {
  position: absolute; inset: 0;
  will-change: transform;
  transition: transform .5s cubic-bezier(.22,.61,.36,1);
}

.star {
  position: absolute;
  border-radius: 50%;
  background: #fff;
  opacity: var(--lo);
  animation: blink var(--dur) ease-in-out var(--delay) infinite;
}

/* A handful of brighter ones with a glow, so the field has some hierarchy
   instead of reading as evenly scattered dust. */
.star.bright { box-shadow: 0 0 .35rem .04rem #fffdf2aa; }

/* ---- shooting stars ------------------------------------------------------
   The streak is one element: a gradient that fades from nothing at the tail
   to a bright head, rotated to its heading and then translated along that
   same rotated axis, so it travels the way it points. It grows along its
   length as it goes, which reads as a trail being drawn rather than a dash
   being slid across the sky. */
.shootlayer { position: absolute; inset: 0; overflow: hidden; }

.shooter {
  position: absolute;
  width: var(--len); height: 2px;
  border-radius: 999px;
  background: linear-gradient(90deg,
              #fff0 0%, #ffe9b83d 35%, #fff6e0cc 78%, #fff 100%);
  filter: drop-shadow(0 0 .3rem #ffe6ad);
  transform-origin: 100% 50%;
  opacity: 0;
  animation: shoot var(--dur) cubic-bezier(.28,.62,.35,1) forwards;
}

@keyframes shoot {
  0%   { opacity: 0; transform: rotate(var(--ang)) translateX(0) scaleX(.15); }
  10%  { opacity: 1; }
  76%  { opacity: 1; }
  100% { opacity: 0;
         transform: rotate(var(--ang)) translateX(var(--dist)) scaleX(1); }
}

@keyframes blink {
  0%, 100% { opacity: var(--lo); }
  50%      { opacity: var(--hi); }
}

@media (prefers-reduced-motion: reduce) {
  .star { animation: none; opacity: var(--hi); }
  .starlayer { transition: none; }
}

/* The desert horizon the town sits in. */
.gate::after {
  content: ""; position: fixed; left: 0; right: 0; bottom: 0; height: 30%;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(#0000 0%, #0a0512aa 45%, #06030dee 100%),
    radial-gradient(ellipse 70% 100% at 20% 132%, #150c22, transparent),
    radial-gradient(ellipse 90% 100% at 78% 130%, #110a1c, transparent);
}

.gatecard {
  position: relative; z-index: 3;
  width: min(24rem, 100%);
  padding: 2rem 1.6rem 1.6rem;
  border-radius: 8px;
  background: linear-gradient(#161d2ae8, #0e131de8);
  border: 1px solid #ffffff1a;
  box-shadow: 0 1.6rem 3.5rem #000a, inset 0 0 0 1px #ffffff0a;
  backdrop-filter: blur(3px);
}

/* The sign. Two text shadows: a tight warm core and a wide glow, which is
   roughly how neon actually reads at night. */
.gatecard h1.neon {
  margin: 0 0 .3rem;
  text-align: center;
  font-size: clamp(2.1rem, 9vw, 2.9rem);
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 400;
  color: #ffe9b8;
  text-shadow:
    0 0 .35rem #ffd98a,
    0 0 1.4rem #e8a13c99,
    0 0 3rem #c4761f66;
  animation: sign 5s ease-in-out infinite;
}
@keyframes sign {
  0%, 92%, 100% { opacity: 1; }
  94% { opacity: .72; }
  96% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .gatecard h1.neon { animation: none; } }

.gatecard .lead { text-align: center; color: var(--muted); font-size: .74rem;
                  line-height: 1.6; margin: 0 0 1.2rem; }

/* ---- live table: chat on the left, the log on the right ----
   The felt takes the middle and the widest column, because it is the thing
   being looked at; the rails are readable at a glance without stealing space
   from the cards. */
/* The rails grow with the screen, and stay equal to each other.
 *
 * A flat 17rem was too narrow for a feed line carrying a username and a
 * six-figure win: the amount had nowhere to go and the scrollbar sat on top
 * of it. They widen on a big monitor and hold at 17 on a small one, so the
 * felt still gets the majority of the width either way.
 *
 * BOTH rails, by the same amount. Widening only the one with the feed in it
 * would move the centre of the felt off the centre of the screen, which is
 * the fault that made the slot cabinets look off-centre. */
.tablerig { display: grid; gap: 1rem; align-items: start;
            grid-template-columns: clamp(17rem, 18vw, 20rem) minmax(0, 1fr)
                                   clamp(17rem, 18vw, 20rem); }
.tablerig .rail { display: flex; flex-direction: column; gap: 1rem; }
.tablerig .rail .chatlog { height: 17rem; }
.tablerig .feltcol { min-width: 0; }

@media (max-width: 1180px) {
  /* Not enough width for three columns: keep chat beside the felt and drop
     the log underneath, since the log is reference and the chat is live. */
  .tablerig { grid-template-columns: 15rem minmax(0, 1fr); }
  .tablerig .rail.right { grid-column: 1 / -1; flex-direction: row;
                          flex-wrap: wrap; }
  .tablerig .rail.right > * { flex: 1; min-width: 15rem; }
}
@media (max-width: 860px) {
  .tablerig { grid-template-columns: 1fr; }
  .tablerig .rail.left { order: 3; }
  .tablerig .rail.right { order: 4; flex-direction: column; }
  .tablerig .feltcol { order: 1; }
}

/* ---- who is here, on the front page ---- */
.whogrid { display: flex; flex-wrap: wrap; gap: .35rem; }
.whochip { display: inline-flex; align-items: center; gap: .35rem;
           padding: .22rem .6rem; border-radius: 999px;
           border: 1px solid var(--line); background: var(--ink);
           font-size: .74rem; color: var(--bone); }
.whochip.admin { border-color: #d9a44155; color: var(--signal); }

/* ---- little bar rows in the log summary ---- */
.minirow { display: grid; grid-template-columns: 1fr 4rem 2.6rem; gap: .5rem;
           align-items: center; font-size: .72rem; padding: .14rem 0; }
.minirow > span:first-child { overflow: hidden; text-overflow: ellipsis;
                              white-space: nowrap; color: var(--muted); }
.minirow > span:last-child { text-align: right; color: var(--bone); }
.minibar { display: block; height: .32rem; background: #ffffff12;
           border-radius: 999px; overflow: hidden; }
.minibar i { display: block; height: 100%; background: var(--signal); }
.ghost.warn { border-color: #b3211c66; color: #e08b86; }
.ghost.warn:hover { border-color: var(--down); color: var(--down); }

/* ---- music volume ---- */
.sndwrap { display: inline-flex; align-items: center; gap: .4rem; }
.sndwrap input[type=range] { width: 4.5rem; accent-color: var(--signal);
                             cursor: pointer; }
@media (max-width: 760px) { .sndwrap input[type=range] { width: 6rem; } }

/* ---- one player's whole history ---- */
.dossier { margin-top: 1.2rem; border-top: 1px solid var(--line);
           padding-top: 1.2rem; }
.dossier .dh { font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
               color: var(--muted); margin: 1.4rem 0 .6rem; font-weight: 600; }
.dgrid { display: grid; gap: .8rem;
         grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.dgrid2 { display: grid; gap: .8rem; margin-top: 1.2rem;
          grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); }
/* The dossier cards, given the same lift as the rest of the site: rounded,
   gently graded, with a gold hairline under each heading so the eye can find
   the section it wants without reading every title. */
.dcard { border: 1px solid var(--line); border-radius: 8px; padding: .85rem .9rem;
         background: linear-gradient(160deg, #131922, #0b0f16);
         box-shadow: inset 0 1px 0 #ffffff08; }
.dcard h3 { margin: 0 0 .55rem; padding-bottom: .35rem; font-size: .58rem;
            letter-spacing: .16em; text-transform: uppercase;
            color: var(--muted);
            border-bottom: 1px solid color-mix(in srgb, var(--signal) 28%,
                                               transparent); }
/* Label left, figure right, and the figure never wraps -- a balance split
   across two lines is harder to read than one that runs on. The LABEL wraps
   instead, which is the half that can afford to. */
.drow { display: flex; justify-content: space-between; gap: .7rem;
        align-items: baseline; font-size: .76rem; padding: .22rem 0;
        border-bottom: 1px solid #ffffff06; }
.drow:last-child { border-bottom: 0; }
.drow > span:first-child { color: var(--muted); min-width: 0;
                           overflow-wrap: anywhere; }
.drow > b, .drow > span:last-child { white-space: nowrap; flex: 0 0 auto;
                                     font-variant-numeric: tabular-nums; }
.dossier .up { color: var(--signal); }
.dossier .down { color: var(--down); }

.dh { font-size: .6rem; letter-spacing: .16em; text-transform: uppercase;
      color: var(--signal); margin: 1.4rem 0 .5rem; }

.dscroll { overflow-x: auto; border: 1px solid var(--line); border-radius: 8px; }
.dscroll.tall { max-height: 26rem; overflow-y: auto; }
.dtable { width: 100%; border-collapse: collapse; font-size: .74rem; }
.dtable th { position: sticky; top: 0; background: var(--panel);
             text-align: left; font-weight: 600; font-size: .62rem;
             letter-spacing: .1em; text-transform: uppercase;
             color: var(--muted); padding: .5rem .6rem;
             border-bottom: 1px solid var(--line); white-space: nowrap; }
.dtable td { padding: .42rem .6rem; border-bottom: 1px solid #ffffff08;
             white-space: nowrap; font-variant-numeric: tabular-nums; }
/* The first column is a name or a date and is the one that can be long, so it
   wraps; the number columns stay on one line where they are readable. */
.dtable td:first-child { white-space: normal; overflow-wrap: anywhere;
                         font-variant-numeric: normal; }
.dtable tbody tr:hover { background: #ffffff08; }
.dtable tbody tr:nth-child(even) { background: #ffffff03; }
.dtable .mono { font-family: var(--font-mono);
                font-size: .66rem; }


/* ===================================================== slots lounge (3D) ===
   The lounge is a room seen in perspective rather than a list of panels. The
   player's own cabinet stands front and centre; the others sit further back
   along an arc, smaller and dimmer, and animate when their owner spins. That
   is what makes it feel like sitting next to someone rather than reading a
   feed of their results. */

/* The room is now a WebGL scene drawn into a canvas that fills the floor, with
   a DOM overlay (.cabrow) on top holding the avatars and chat bubbles. The
   old CSS-3D perspective is gone; the depth is real now. */
.floor { position: relative; min-height: 33rem; padding: 0;
         border-radius: 8px; overflow: hidden;
         background:
           radial-gradient(120% 80% at 50% 0%, #1a1226 0%, #0b0812 55%, #05040a 100%); }

/* #roomCanvas was pool's table. Removed with the game. */

/* The carpet is drawn in the 3D scene now; the DOM one is retired but kept
   hidden rather than deleted so nothing referencing it throws. */
.carpet { display: none; }

/* The overlay layer: absolutely fills the floor, sits over the canvas, and
   lets clicks through to nothing (there is nothing under it to click). The
   avatars inside are positioned by JavaScript in pixels each frame. */
.cabrow { position: absolute; inset: 0; pointer-events: none; z-index: 4; }

/* One machine. `--depth` pushes a neighbour back into the room and `--shift`
   slides it sideways, so the row reads as an arc rather than a flat line. */
.cabinet { position: relative; transform-style: preserve-3d; flex: 0 0 auto;
           transform: translateZ(var(--depth, 0px)) translateX(var(--shift, 0))
                      rotateY(var(--turn, 0deg));
           transition: transform .5s ease, filter .5s ease;
           filter: brightness(var(--lit, .62)) saturate(var(--sat, .7)); }
.cabinet.mine { --lit: 1; --sat: 1; z-index: 5; }
.cabinet.spinning { --lit: .95; }

/* The body of the machine: a front panel with a lit bezel, plus a thin side
   face so it reads as a solid object rather than a rectangle. */
.cabbody { position: relative; width: 11rem; border-radius: 12px 12px 6px 6px;
           padding: .55rem; transform-style: preserve-3d;
           background: linear-gradient(#221733, #140d1f 60%, #0c0814);
           border: 1px solid var(--cabedge, #d9a44144);
           box-shadow: 0 .8rem 2rem #0009, inset 0 0 1.6rem var(--cabglow, #d9a4411a); }
.cabinet.mine .cabbody { width: 21rem; padding: .8rem;
                         box-shadow: 0 1.6rem 3.4rem #000b,
                                     inset 0 0 2.4rem var(--cabglow, #d9a44126); }
.cabinet.mine .cabbody { border-color: var(--cabedge, #d9a441); }

.cabside { position: absolute; top: 0; bottom: 0; width: 1.1rem; right: -1.1rem;
           transform: rotateY(72deg); transform-origin: left center;
           background: linear-gradient(#160f22, #0a0710);
           border-radius: 0 6px 6px 0; pointer-events: none; }

/* The pull arm. Mounted on the right cheek of the machine: a chromed base, a
   stick, and a red ball. It lives in front of the side face in Z so it reads
   as sticking out of the cabinet rather than painted on. Purely a flourish --
   the Spin button stays the control -- so it is aria-hidden and ignores
   pointer events. */
.cabarm { position: absolute; top: 42%; right: -1.5rem; width: 1.4rem;
          height: 3.4rem; transform: translateZ(.6rem);
          transform-style: preserve-3d; pointer-events: none; z-index: 4; }
.cabinet.mine .cabarm { right: -2.4rem; width: 2.2rem; height: 6rem;
                        top: 40%; }
.armbase { position: absolute; left: 50%; bottom: 0; width: .8rem; height: .8rem;
           transform: translateX(-50%); border-radius: 50%;
           background: radial-gradient(circle at 35% 30%, #f2f2f5, #6a6a72 70%, #3a3a40);
           box-shadow: var(--lift-1); }
.cabinet.mine .armbase { width: 1.25rem; height: 1.25rem; }
/* The stick pivots at the base. Default resting position is raised and tilted
   back a touch, the way a slot lever sits before you pull it. */
.armstick { position: absolute; left: 50%; bottom: .3rem; width: .28rem;
            height: 2.6rem; transform-origin: bottom center;
            transform: translateX(-50%) rotate(18deg);
            background: linear-gradient(#e8e8ee, #9a9aa2);
            border-radius: 8px; box-shadow: 0 0 .2rem #0006;
            transition: transform .12s ease-out; }
.cabinet.mine .armstick { width: .42rem; height: 4.6rem; bottom: .55rem; }
.armknob { position: absolute; left: 50%; top: -.55rem; width: 1rem; height: 1rem;
           transform: translateX(-50%); border-radius: 50%;
           background: radial-gradient(circle at 35% 30%, #ff8a7a, #c0231a 65%, #7d130d);
           box-shadow: 0 .15rem .4rem #0009, inset 0 0 .2rem #fff4; }
.cabinet.mine .armknob { width: 1.6rem; height: 1.6rem; top: -.9rem; }

/* The pull: down and forward, then a spring back. Driven by a class the spin
   handler adds, so the arm moves exactly when the reels are set going. */
.cabinet.pulling .armstick { animation: armpull .62s cubic-bezier(.3,.9,.3,1); }
@keyframes armpull {
  0%   { transform: translateX(-50%) rotate(18deg); }
  30%  { transform: translateX(-50%) rotate(-78deg); }
  45%  { transform: translateX(-50%) rotate(-78deg); }
  100% { transform: translateX(-50%) rotate(18deg); }
}

/* The crown: the machine's own name and colour, which is how a player tells
   one cabinet from another across the room. */
.cabtop { text-align: center; font-size: .52rem; letter-spacing: .2em;
          text-transform: uppercase; padding: .25rem 0 .4rem;
          color: var(--cabink, #ffe9b8); text-shadow: 0 0 .5rem var(--cabglow, #d9a44166); }
.cabinet.mine .cabtop { font-size: .68rem; padding: .35rem 0 .55rem; }

.cabglass { display: grid; grid-template-columns: repeat(5, 1fr); gap: .18rem;
            background: #05060c; border: 1px solid #ffffff12; border-radius: 8px;
            padding: .18rem; }
.cabinet.mine .cabglass { gap: .4rem; padding: .4rem; border-radius: 8px; }

/* Reel heights are derived from the row count, never guessed.
   These were sized for the old three-row machine and stayed behind when the
   grid went to seven, so the window clipped the reels: a player saw three rows
   while the engine paid lines across all seven. Wins appeared for symbols that
   were never on screen, which reads as the machine paying wrongly -- and from
   the player's side it is indistinguishable from being cheated.
   One row count, one cell height, everything else multiplied from them. */
:root { --slot-rows: 7; --slot-cell: 1.55rem; --slot-cell-mine: 2.7rem; }

.cabreel { position: relative; overflow: hidden;
           height: calc(var(--slot-rows) * var(--slot-cell));
           border-radius: 8px;
           /* A vertical light band down the middle plus darkened top and
              bottom reads as a curved drum catching the light, so the reels
              look like cylinders turning rather than flat strips sliding. */
           background:
             linear-gradient(#000000aa, #00000000 22%, #00000000 78%, #000000aa),
             linear-gradient(90deg, #0a0c14, #1a1e2e 50%, #0a0c14); }
.cabinet.mine .cabreel { height: calc(var(--slot-rows) * var(--slot-cell-mine)); }
.cabstrip { position: absolute; left: 0; right: 0; top: 0;
            display: flex; flex-direction: column; }
.cabcell { height: var(--slot-cell); display: grid; place-items: center; }
.cabinet.mine .cabcell { height: var(--slot-cell-mine); }
/* Symbol art is sized FROM THE CELL, never in absolute units.
   It used to be given a fixed rem size chosen independently of the cell that
   holds it -- 4rem of art in a 2.7rem cell, so every symbol overflowed its
   own row by half again and bled into the ones above and below. The grid then
   looked crooked even though it was not: the win lines and the highlight
   boxes were drawn on the true cell centres, and it was the ARTWORK that sat
   in the wrong place. A percentage cannot drift from the cell whatever the
   row count, the scale, or which cabinet it belongs to. */
.cabcell svg { width: auto; height: 76%; max-width: 86%; }
.cabinet.mine .cabcell svg { width: auto; height: 76%; max-width: 86%; }
.cabcell.hit { background: var(--cabglow, #d9a4411f); border-radius: 8px;
               animation: cellhit .9s ease-in-out 3; }
@keyframes cellhit { 0%,100% { filter: brightness(1); }
                     50% { filter: brightness(1.7); } }

/* A neighbour paying out flashes their whole cabinet, which is visible from
   across the room without being loud enough to distract from your own reels. */
.cabinet.paid .cabbody { animation: cabpaid 1.1s ease-out 2; }
@keyframes cabpaid { 0% { box-shadow: 0 .8rem 2rem #0009, 0 0 0 0 var(--cabglow, #d9a44100); }
                     35% { box-shadow: 0 .8rem 2rem #0009, 0 0 2.4rem .3rem var(--cabglow, #d9a441aa); }
                     100% { box-shadow: 0 .8rem 2rem #0009, 0 0 0 0 #d9a44100; } }
.cabinet.bonusing .cabbody { animation: cabbonus .8s ease-in-out infinite; }
@keyframes cabbonus { 0%,100% { box-shadow: 0 .8rem 2rem #0009, 0 0 1.4rem #d9a44166; }
                      50% { box-shadow: 0 .8rem 2rem #0009, 0 0 3rem .5rem #ffe9b8cc; } }

/* ---- the player above the machine ---- */
/* Positioned in pixels by Room.placeOverlay each frame; left/top are the point
   just above the cabinet's crown, so the avatar is centred on it and lifted
   fully above. */
.avatar { position: absolute; transform: translate(-50%, -100%);
          text-align: center; width: 9rem; pointer-events: none; z-index: 6;
          will-change: left, top; }
.head { width: 1.7rem; height: 1.7rem; border-radius: 50%; margin: 0 auto .2rem;
        background: linear-gradient(var(--cabink, #ffe9b8), var(--cabedge, #d9a441));
        border: 1px solid #0006; box-shadow: var(--lift-2); }
.nametag { font-size: .6rem; letter-spacing: .08em; color: var(--bone);
           white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cabinet.mine .nametag { font-size: .72rem; color: var(--signal); }
.nametag .idle { color: var(--muted); }

/* A speech bubble that rises above the speaker's head and fades. This is the
   whole reason the lounge has faces: you can see WHO said it without reading
   a name in a list. */
.bubble { position: absolute; left: 50%; bottom: 100%; transform: translateX(-50%);
          margin-bottom: .35rem; max-width: 11rem; padding: .35rem .55rem;
          background: #efe7d8; color: #191320; border-radius: 8px;
          font-size: .66rem; line-height: 1.25; text-align: left;
          box-shadow: var(--lift-2); word-break: break-word;
          animation: bubblein .28s ease-out; z-index: 8; }
.bubble::after { content: ""; position: absolute; left: 50%; top: 100%;
                 transform: translateX(-50%);
                 border: .32rem solid transparent; border-top-color: #efe7d8; }
.bubble.fading { animation: bubbleout .5s ease-in forwards; }
@keyframes bubblein { from { opacity: 0; transform: translate(-50%, .4rem); }
                      to { opacity: 1; transform: translate(-50%, 0); } }
@keyframes bubbleout { to { opacity: 0; transform: translate(-50%, -.5rem); } }

/* An empty machine still gets a stool, so the room reads as having space in
   it rather than simply being small. */
.cabinet.empty { --lit: .34; --sat: .25; }
.cabinet.empty .avatar { opacity: .4; }

@media (prefers-reduced-motion: reduce) {
  .cabinet, .cabinet.paid .cabbody, .cabinet.bonusing .cabbody,
  .bubble { animation: none !important; transition: none !important; }
}

@media (max-width: 1180px) {
  .floor { perspective: none; min-height: 0; }
  .carpet { display: none; }
  .cabrow { flex-wrap: wrap; }
  .cabinet { transform: none !important; }
}

/* Marks a figure on the front page as operator-only, so an admin can see at a
   glance that what they are looking at is not what a player sees. */
.adminonly { font-size: .5rem; letter-spacing: .12em; padding: .1rem .3rem;
             border: 1px solid var(--line); border-radius: 8px;
             color: var(--muted); margin-left: .35rem; vertical-align: middle; }

/* A scatter is not a line win, so it gets its own mark: a cool ring rather
   than the warm payline glow, which keeps "you nearly had the bonus" visually
   distinct from "you were paid". */
.cabcell.hit.scat { background: #6fb6d92e; box-shadow: inset 0 0 0 1px #6fb6d977;
                    border-radius: 8px; }


/* ============================================ slots: a wider, brighter room ===
   The lounge lost its left rail, so the floor gets everything except the feed.
   The first cut also had the neighbouring cabinets dimmed almost out of
   existence and small enough to be unreadable -- the room is meant to be
   watchable, not merely present. */

/* Chat on the left, the room in the middle, the feed and paytable on the
   right. The floor keeps the flexible track so it takes every pixel the two
   rails do not need. */
.slotsrig { display: grid; gap: 1.2rem; align-items: start;
            grid-template-columns: 17rem minmax(0, 1fr) 17rem; }
@media (max-width: 1400px) {
  .slotsrig { grid-template-columns: 15rem minmax(0, 1fr); }
  .slotsrig .rail.right { grid-column: 1 / -1; display: grid; gap: 1rem;
                          grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
}
@media (max-width: 900px) {
  .slotsrig { grid-template-columns: 1fr; }
  .slotsrig .rail.left { order: 3; }
  .slotsrig .rail.right { order: 4; grid-template-columns: 1fr; }
}
.slotsrig .rail { display: flex; flex-direction: column; gap: 1rem; }
/* Tall enough to be worth reading, and it scrolls rather than growing. */
.slotsrig .chatcard .chatlog { height: 24rem; }
@media (max-width: 900px) { .slotsrig .chatcard .chatlog { height: 13rem; } }

/* Room for every cabinet at full size, with the outermost ones no longer
   sliding off the edge. */
.slotsrig .floor { min-height: 40rem; padding: 3.2rem 1.5rem 0;
                   overflow: visible; }
.slotsrig .cabrow { gap: 1.6rem; padding-bottom: 2rem; }

/* Neighbours: readable rather than shadowed. Still clearly not YOUR machine,
   but you can see what is on their reels, which is the entire point of
   sitting next to somebody. */
.slotsrig .cabinet { --lit: .92; --sat: .95; }
.slotsrig .cabinet.empty { --lit: .55; --sat: .4; }
.slotsrig .cabinet.mine { --lit: 1; --sat: 1; }

/* Bigger glass all round. */
.slotsrig .cabbody { width: 14.5rem; padding: .7rem; }
.slotsrig .cabinet.mine .cabbody { width: 34rem; padding: 1.1rem; }
.slotsrig .cabreel { height: calc(var(--slot-rows) * var(--slot-cell)); }
.slotsrig .cabinet.mine .cabreel { height: calc(var(--slot-rows) * var(--slot-cell-mine)); }
.slotsrig .cabcell { height: var(--slot-cell); }
.slotsrig .cabinet.mine .cabcell { height: var(--slot-cell-mine); }
.slotsrig .cabcell svg { width: auto; height: 76%; max-width: 86%; }
.slotsrig .cabinet.mine .cabcell svg { width: auto; height: 76%; max-width: 86%; }
.slotsrig .cabtop { font-size: .58rem; }
.slotsrig .cabinet.mine .cabtop { font-size: .78rem; }

/* Pulled back so the deeper cabinets stay legible instead of vanishing into
   the distance. */
.slotsrig .floor { perspective: 2600px; perspective-origin: 50% 42%; }

/* ---- bubbles: the room's only chat, so they have to carry it ---- */
.bubble { max-width: 15rem; padding: .55rem .8rem; font-size: .84rem;
          line-height: 1.35; border-radius: 12px; font-weight: 500;
          box-shadow: var(--lift-3); margin-bottom: .5rem; }
.bubble::after { border-width: .42rem; }
.cabinet.mine .bubble { max-width: 20rem; font-size: .92rem;
                        background: #fff6e2; }

/* Names have to be readable from across the room now that there is no roster
   panel to fall back on. */
.nametag { font-size: .78rem; letter-spacing: .04em; font-weight: 600;
           text-shadow: 0 .1rem .4rem #000c; }
.cabinet.mine .nametag { font-size: .92rem; }
.head { width: 2.2rem; height: 2.2rem; }
.cabinet.mine .head { width: 2.7rem; height: 2.7rem; }
.avatar { width: 13rem; top: -4.4rem; }
.cabinet.mine .avatar { width: 20rem; top: -5rem; }

/* The say bar is the lounge's chat now, so it gets room to be one. */
.loungechat { max-width: 54rem; }
.sayrow input { font-size: .9rem; padding: .6rem .8rem; }

/* ---- casino doors ---- */
.doorrow { display: flex; gap: .8rem; align-items: center; flex-wrap: wrap; }
.doorrow input { flex: 1; min-width: 14rem; }
.doorstate { display: flex; align-items: center; gap: .5rem; font-size: 1.1rem; }
.doorstate .down { color: #c0392b; }
/* The open indicator pulses; the closed one is deliberately steady, because a
   blinking red light reads as an alarm rather than a state. */
.livedot { display: inline-block; width: .5rem; height: .5rem;
           border-radius: 50%; background: #3fbf7f; margin-right: .35rem;
           box-shadow: 0 0 .4rem #3fbf7f;
           animation: dotpulse 1.6s ease-in-out infinite; }
/* Opacity only. box-shadow cannot be composited, so animating it forced the
   browser to repaint this element sixty times a second for as long as any page
   was open -- which, since the live dot sits in the status banner on every
   page, meant the tab could never go idle. The dot looks the same. */
@keyframes dotpulse { 0%, 100% { opacity: 1; }
                      50%      { opacity: .45; } }

/* Closed is steady red rather than blinking: a flashing red light reads as an
   alarm, and a casino being shut for the evening is not an emergency. */
.livedot.off { background: #c0392b; box-shadow: 0 0 .4rem #c0392b88;
               animation: none; }

@media (prefers-reduced-motion: reduce) {
  .livedot { animation: none; }
}

/* ---- open/closed banner on the front page ---- */
/* Sized to be read at a glance from across a desk. The first version was set
   at body-text size and disappeared into the page -- a status line nobody
   notices is the same as no status line. */
.doorbanner { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap;
              padding: 1.1rem 1.3rem; margin: 0 0 1rem; border-radius: 8px;
              border: 1px solid var(--line); background: var(--panel);
              box-shadow: inset 0 0 2.5rem #3fbf7f0f; }
.doorbanner .doortext { display: flex; flex-direction: column; gap: .15rem; }
.doorbanner strong { font-size: 1.45rem; letter-spacing: .04em;
                     line-height: 1.1; }
.doorbanner .up { color: var(--signal); }
.doorbanner .down { color: #e0685a; }
.doorbanner .sub { color: var(--muted); font-size: .84rem; }
.doorbanner .livedot { width: .85rem; height: .85rem; margin: 0; }

.doorbanner.shut { border-color: #c0392b88; background: #c0392b16;
                   box-shadow: inset 0 0 2.5rem #c0392b1a; }

@media (max-width: 560px) {
  .doorbanner { padding: .9rem 1rem; }
  .doorbanner strong { font-size: 1.2rem; }
}

/* ---- brand mark ---- */
.brand .mark { height: 2.6rem; width: auto; display: block;
               filter: drop-shadow(0 .1rem .5rem color-mix(in srgb, var(--gold) 20%, transparent)); }
@media (max-width: 700px) { .brand .mark { height: 2.1rem; } }

/* The login screen leads with the full badge rather than a wordmark. */
/* ---- the sign on the gate ------------------------------------------------
   The neon breathes. The glow is a second image holding only the saturated
   reds -- the lettering and the trim, not the gold bulbs -- blurred and laid
   back over the sign, so what pulses is the red text rather than a halo round
   the whole board. Screen blending means it adds light instead of painting a
   red film over the artwork. */
.gatesign { position: relative; width: min(21rem, 74vw); margin: 0 auto .6rem; }
.gatelogo { display: block; width: 100%; height: auto; }
.gateglow {
  position: absolute; inset: 0; width: 100%; height: auto;
  pointer-events: none; mix-blend-mode: screen;
  animation: neon 3.4s ease-in-out infinite;
  will-change: opacity, filter;
}

@keyframes neon {
  0%, 100% { opacity: .22; filter: blur(1px)   saturate(1.1); }
  50%      { opacity: 1;   filter: blur(2.5px) saturate(1.6); }
}

/* Someone who has asked for less movement gets the sign lit, not flashing. */
@media (prefers-reduced-motion: reduce) {
  .gateglow { animation: none; opacity: .5; }
}

/* ---- staff, marked in chat ----------------------------------------------
   Red plus a crown. Colour alone would be invisible to a colour-blind player
   and would not survive a screenshot being quoted, so the crown carries the
   same information and a screen-reader gets the word "Admin". */
.isadmin { position: relative; color: #e0685a; font-weight: 700; }
.isadmin .crown { position: absolute; top: -.72rem; left: -.28rem;
                  font-size: .62rem; line-height: 1; color: var(--gold);
                  text-shadow: 0 0 .3rem color-mix(in srgb, var(--gold) 53%, transparent); pointer-events: none; }

/* Developers get the penguin, and it sits beside the name rather than above
   it -- the crown is a small mark perched over the text, but an emoji at that
   size is a smudge, so it goes inline at a readable size where it reads as a
   badge. Blue rather than the staff red, so the two are told apart at a
   glance as well as by the glyph. */
/* A game that is shut. It keeps the shape of a nav item so the row does not
   reflow when something is locked, but it is plainly not a link. */
/* The house notice. Amber for an announcement, red once the close is close
   enough to matter -- five minutes is about the point where a player needs to
   decide whether to start another hand. */
.housenotice { padding: .5rem .8rem; margin: 0 0 .6rem; border-radius: 8px;
               font-size: .78rem; line-height: 1.35;
               background: color-mix(in srgb, var(--gold) 12%, transparent); border: 1px solid color-mix(in srgb, var(--gold) 33%, transparent);
               color: #f3d79a; }
.housenotice.urgent { background: #d8352a26; border-color: #d8352a66;
                      color: #f0b3ad; }

/* A locked nav entry: the game's name, dimmed, with a small tag saying why.
   The name is never replaced -- see renderNav for what that cost. */
/* Why you were sent back to the sign-in. Sits above the form, in the gate's
   own stacking, so it cannot be missed and cannot cover the fields. */
.gate .gatewhy { margin: 0 auto 1rem; max-width: 22rem; text-align: center;
                 font-size: .8rem; border-radius: 8px; padding: .55rem .7rem;
                 color: #f3d79a; background: rgba(240,182,71,.12);
                 border: 1px solid rgba(240,182,71,.32); }

.navlocked { cursor: not-allowed; padding: inherit; white-space: nowrap;
             display: flex; align-items: center; gap: .45rem; }
.navlocked-name { opacity: .4; }
.navlocked-tag { font-size: .58rem; letter-spacing: .1em;
                 text-transform: uppercase; padding: .05rem .34rem;
                 border-radius: 8px; opacity: .75;
                 color: #f3d79a; background: rgba(217,164,65,.14);
                 border: 1px solid rgba(217,164,65,.3); }

.isdev { color: #7fb4e8; }
.isdev .crown { position: static; top: auto; left: auto;
                margin-right: .24rem; font-size: .82rem;
                color: inherit; text-shadow: none; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden;
      clip: rect(0 0 0 0); white-space: nowrap; }

/* Room chat lines need headroom so the crown is not clipped by the line above. */
.chatlog .line, .online .who { padding-top: .28rem; }

.roomtable { width: 100%; border-collapse: collapse; }
.roomtable td { padding: .45rem .5rem; border-bottom: 1px solid var(--line);
                font-size: .78rem; vertical-align: middle; }
.roomtable td:last-child { text-align: right; width: 1%; white-space: nowrap; }

/* ---- help sheet ----------------------------------------------------------
   A sheet over the page rather than a route: somebody asking for help is
   usually mid-task and should not lose their place to ask a question. */
.helpbtn { border-color: var(--gold) !important;
           color: var(--gold) !important; }

.helpsheet { position: fixed; inset: 0; z-index: 80; background: #000a;
             display: flex; align-items: flex-start; justify-content: center;
             padding: 4rem 1rem 1rem; overflow-y: auto; }
.helpsheet[hidden] { display: none; }
.helpcard { width: min(38rem, 100%); background: var(--panel);
            border: 1px solid var(--line); border-radius: 8px;
            padding: 1.1rem 1.2rem; box-shadow: var(--lift-3); }
.helphead { display: flex; align-items: center; gap: 1rem; margin-bottom: .8rem; }
.helphead h2 { margin: 0; flex: 1; }

.helpform { display: flex; flex-direction: column; gap: .5rem;
            margin-bottom: 1rem; }
.helpform textarea { resize: vertical; font: inherit; padding: .55rem .7rem;
                     background: var(--ink, #0d1117); color: inherit;
                     border: 1px solid var(--line); border-radius: 8px; }
.helpnote { margin: 0; font-size: .7rem; color: #e0685a; }

.tklist { display: flex; flex-direction: column; gap: .4rem; }
.tkrow { display: grid; grid-template-columns: 1fr auto; gap: .2rem .6rem;
         text-align: left; padding: .6rem .7rem; background: transparent;
         border: 1px solid var(--line); border-radius: 8px; color: inherit;
         cursor: pointer; }
.tkrow:hover { border-color: var(--gold); }
.tksub { font-weight: 600; font-size: .84rem; }
.tkmeta { grid-column: 1; font-size: .68rem; color: var(--muted); }
.tkrow .badge { grid-row: 1 / span 2; align-self: center; }

.tktitle { margin: .6rem 0 .1rem; font-size: 1rem; }
.tkthread { display: flex; flex-direction: column; gap: .6rem;
            margin: .8rem 0; max-height: 22rem; overflow-y: auto; }
.tkmsg { padding: .55rem .7rem; border-radius: 8px;
         border: 1px solid var(--line); background: #0d111755; }
.tkmsg.staff { border-color: var(--gold)44; background: var(--gold)0d; }
.tkwho { font-size: .72rem; margin-bottom: .25rem; }
.tkwho .dim { margin-left: .4rem; font-size: .64rem; }
.tksaid { font-size: .82rem; white-space: pre-wrap; line-height: 1.4; }

.badge.assigned { background: color-mix(in srgb, var(--gold) 12%, transparent); color: var(--gold); }
.badge.open { background: #6fb6d91f; color: #6fb6d9; }
.badge.closed { background: #ffffff12; color: var(--muted); }

/* ==========================================================================
   PHONES  --  Android and iPhone
   ==========================================================================
   The layout already had breakpoints; these are the things that only show up
   on a real handset, and each one is a specific bug rather than a tidy-up.
   ========================================================================== */

/* iOS zooms the whole page in when a focused input's text is under 16px, and
   then does not zoom back out. Every form on the site was doing this. */
@media (max-width: 860px) {
  input, select, textarea, button {
    font-size: 16px;
  }
}

@media (max-width: 860px) {
  /* Apple's own guidance is 44px minimum for anything you tap. Several of
     these controls were nearer 24 and needed a deliberate aim. */
  button, .tab, nav a, [data-join], [data-tab] {
    min-height: 44px;
    padding-top: .55rem;
    padding-bottom: .55rem;
  }

  /* The nav is a dozen games wide. On a phone it wrapped into a wall of text
     that pushed the actual page below the fold; now it scrolls sideways as one
     line, with the scrollbar hidden because a thumb does not need to see it. */
  #chrome nav {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: .15rem;
  }
  #chrome nav::-webkit-scrollbar { display: none; }
  #chrome nav a { white-space: nowrap; flex: 0 0 auto; }

  /* Side rails become sections stacked under the game rather than 20%-wide
     columns squeezed either side of it. */
  .rail, .rail.left, .rail.right {
    width: auto;
    max-width: none;
    float: none;
    position: static;
  }

  /* Anything laid out in columns collapses to one. */
  .stats, .cards, .grid, .row-2, .two-col, .lobby {
    grid-template-columns: 1fr !important;
  }

  /* Wide tables scroll inside their own box instead of stretching the page,
     which is what caused the horizontal drift on every admin screen. */
  table { display: block; overflow-x: auto; white-space: nowrap; }

  /* Long usernames and room names used to push the layout sideways. */
  .logline, .fline, .nametag, td, th { overflow-wrap: anywhere; }
}

/* The slots lounge is drawn in CSS perspective at a fixed width. On a phone the
   cabinets ran off both edges, so the whole room is scaled down to fit rather
   than cropped -- the neighbouring machines are the point of the room. */
@media (max-width: 860px) {
  .room { perspective: 900px; padding-top: 3.2rem; }
  .cabrow { height: 20rem; transform: scale(.62); transform-origin: center top; }
}
@media (max-width: 560px) {
  .cabrow { transform: scale(.46); }
  .room { height: 17rem; }
}

/* Phones in landscape are short, not narrow: the chrome has to give the game
   most of the screen or there is nothing left to play on. */
@media (max-height: 480px) and (orientation: landscape) {
  #chrome .topbar { padding: .2rem .5rem; }
  .room { padding-top: 1.5rem; }
}

/* iPhone notch and home indicator. Without this the bottom row of controls
   sits under the gesture bar and cannot be tapped at all. */
@supports (padding: max(0px)) {
  @media (max-width: 860px) {
    body {
      padding-left: max(0px, env(safe-area-inset-left));
      padding-right: max(0px, env(safe-area-inset-right));
      padding-bottom: max(0px, env(safe-area-inset-bottom));
    }
  }
}

/* A double tap should not zoom when the thing tapped is a game control, and a
   long press should not pop the text-selection menu over the reels. */
@media (max-width: 860px) {
  button, .tab, .cabinet, .cell, .cabcell {
    touch-action: manipulation;
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
  }
}

/* ==========================================================================
   CASINO CHIPS  --  one component, every game
   ==========================================================================
   Chips were drawn three different ways on three pages, which is how the live
   blackjack one ended up white-on-white and the blackjack one did not exist at
   all. This is the only chip on the site now.

   Built to match the house set: coloured body, contrasting edge blocks, a
   lighter inlay carrying the logo, and the denomination beneath it. The edge
   blocks are a repeating conic gradient rather than elements, so a chip is one
   div and can be dropped anywhere without a wrapper.

   --chip sets the body colour and --edge the blocks; both come from
   Casino.chipInk(). Size scales from font-size, so a chip in a cramped seat
   pod and one on an open felt are the same component at different scales.
   ========================================================================== */
.chip {
  --chip: #6b7280;
  --edge: #ffffff;
  position: relative;
  width: 3.4em; height: 3.4em;
  border-radius: 50%;
  display: grid; place-items: center;
  flex: 0 0 auto;
  /* The rim: eight blocks of --edge with the body showing between them. */
  background:
    repeating-conic-gradient(var(--edge) 0deg 16deg,
                             transparent 16deg 45deg),
    radial-gradient(circle at 50% 38%,
                    color-mix(in srgb, var(--chip) 118%, #fff) 0%,
                    var(--chip) 62%,
                    color-mix(in srgb, var(--chip) 72%, #000) 100%);
  box-shadow: inset 0 0 0 .12em color-mix(in srgb, var(--chip) 60%, #000),
    inset 0 .1em .25em #ffffff30,
    0 .18em .4em #000a;
  text-shadow: 0 1px 2px #0009;
}
/* The inlay. Slightly proud of the body so the rim blocks stop at its edge,
   which is what makes a chip read as a chip rather than a striped circle. */
.chip::before {
  content: "";
  position: absolute; inset: 17%;
  border-radius: 50%;
  background:
    url("/static/img/logo.png") center 30% / 58% auto no-repeat,
    radial-gradient(circle at 50% 35%,
                    color-mix(in srgb, var(--chip) 82%, #fff) 0%,
                    color-mix(in srgb, var(--chip) 92%, #000) 100%);
  box-shadow: inset 0 0 0 .06em #00000055;
}
.chip .v {
  position: relative;
  margin-top: 1.15em;              /* below the logo, inside the inlay */
  font-size: .58em; font-weight: 800; letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
  color: #fff;
  text-shadow: 0 1px 2px #000, 0 0 3px #000;
  line-height: 1;
}
/* A chip small enough that the logo would be mud drops it and keeps the
   number, because the value is the part that has to be readable. */
.chip.small::before { background-image: none; }
.chip.small .v { margin-top: 0; font-size: .74em; }

/* ==========================================================================
   THE SHELL  --  sidebar navigation, applied to every page at once
   ==========================================================================
   The nav was a horizontal bar that ran to a dozen games and wrapped onto a
   second line, pushing the actual page below the fold. A casino floor is
   browsed by category, so the categories belong down the side where they stay
   visible while you read the page rather than above it competing for height.

   Done entirely in CSS against the existing chrome markup, so every page picks
   it up without being touched -- which also means nothing can be half
   converted, and a page added tomorrow gets it for free.

   Collapses back to the original top bar under 1000px, because a fixed
   sidebar on a phone is just a wall.
   ========================================================================== */
@media (min-width: 1001px) {
  body { padding-left: 13.5rem; }

  #chrome .topbar {
    position: fixed; left: 0; top: 0; bottom: 0;
    width: 13.5rem;
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    gap: .35rem;
    padding: 1rem .6rem;
    overflow-y: auto;
    border-bottom: none;
    border-right: 1px solid var(--line);
    /* Glass, like every other surface. It was a gradient ending in solid
       #0b0f16, so the bottom two thirds of the shell were opaque and the sky
       stopped at the sidebar's edge. */
    background: linear-gradient(180deg,
                  rgba(21, 27, 36, .62), rgba(11, 15, 22, .74));
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    box-shadow: inset -1px 0 0 rgba(255, 255, 255, .05);
    z-index: 40;
  }

  /* NOTHING in the column may shrink.
     Flex items shrink by default, so once fourteen games and the status block
     were taller than the viewport the browser squeezed them all -- the wallet
     and online chips lost their height, their contents spilled out, and they
     came to rest on top of the Help button. The column scrolls instead, which
     is what the overflow rule above was already there to do. */
  #chrome .topbar > * { flex-shrink: 0; }
  /* And the spacer gives its space up first rather than holding the status
     block off the bottom of a column that has already run out of room. */
  #chrome .topbar .spacer { flex: 1 1 0; min-height: 0; }
  /* Hide the scrollbar: the list rarely overflows and a permanent grey stripe
     down the middle of the shell reads as a seam. */
  #chrome .topbar { scrollbar-width: none; }
  #chrome .topbar::-webkit-scrollbar { display: none; }

  #chrome .brand {
    padding: .2rem .5rem 1rem;
    border-bottom: 1px solid var(--line);
    margin-bottom: .6rem;
  }

  #chrome nav {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    overflow: visible;
  }
  #chrome nav a {
    display: block;
    padding: .5rem .65rem;
    border-radius: 8px;
    font-size: .78rem;
    letter-spacing: .04em;
    border: 1px solid transparent;
  }
  #chrome nav a:hover { background: #ffffff08; }
  /* The current page marked by a lit left edge rather than a box, so the eye
     finds it without the item shouting over its neighbours. */
  #chrome nav a.on {
    background: #ffffff0a;
    border-color: var(--line);
    box-shadow: inset .18rem 0 0 var(--signal);
  }

  /* Wallet, online count, sound and the rest sit at the foot of the column,
     away from the games. They are status, not navigation. */
  #chrome .topbar > :not(.brand):not(nav) {
    font-size: .72rem;
  }

  /* Wallet and Online read as status, so they get a quiet glass pill each
     rather than sitting as loose text between the buttons. Laid out as a
     label above a figure, which is what they already were -- they just had
     nothing holding them together. */
  #chrome .topbar .stat {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: .5rem;
    padding: .45rem .6rem;
    border: 1px solid var(--line); border-radius: 8px;
    background: rgba(255, 255, 255, .035);
  }
  #chrome .topbar .stat b {
    display: inline; font-size: .82rem;
    font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  }
  #chrome .topbar .spacer { flex: 1 1 auto; }

  /* Pages set their own max width; without this they would centre against the
     old full-bleed body and sit off to the right of the new content area. */
  .wrap { margin-left: auto; margin-right: auto; }
}

/* ==========================================================================
   THE GAME GRID  --  the floor, as tiles
   ==========================================================================
   Art is generated rather than drawn. Every tile is a gradient, a large glyph
   and a title, which means adding a game is one line and there are no image
   files to ship, cache-bust or have go missing. It also keeps the grid honest
   on a slow connection: the tiles are there the moment the CSS is, rather than
   a page of grey boxes waiting on downloads.
   ========================================================================== */
.gamegrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: .8rem;
  margin: .6rem 0 1.4rem;
}

.tile {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  border-radius: 8px;
  overflow: hidden;
  text-decoration: none;
  border: 1px solid var(--line);
  background: linear-gradient(160deg, var(--t1, #2b3350), var(--t2, #12161f));
  transition: transform .16s ease, box-shadow .16s ease;
  isolation: isolate;
}
.tile:hover {
  transform: translateY(-3px);
  box-shadow: 0 .6rem 1.4rem #000a, 0 0 0 1px var(--t1, #2b3350);
}

/* The glyph. Oversized and bled off the corner so the tile reads as artwork
   rather than an icon centred in a box. */
.tile .art {
  position: absolute; right: -12%; top: 4%;
  font-size: 6.2rem; line-height: 1;
  opacity: .9;
  filter: drop-shadow(0 .3rem .5rem #0008);
  pointer-events: none;
  transition: transform .25s ease;
}
.tile:hover .art { transform: scale(1.06) rotate(-4deg); }

/* A wash across the lower half so a title always has something to sit on,
   whatever colour the tile happens to be. */
.tile::after {
  content: "";
  position: absolute; inset: auto 0 0 0; height: 62%;
  background: linear-gradient(180deg, transparent, #000000d9 62%);
  pointer-events: none;
}

.tile .cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: .7rem .7rem .75rem;
  z-index: 1;
}
.tile .nm {
  display: block;
  font-size: .92rem; font-weight: 700; letter-spacing: .01em;
  color: #fff; text-shadow: 0 2px 6px #000;
  line-height: 1.15;
}
.tile .sub {
  display: block;
  margin-top: .18rem;
  font-size: .62rem; letter-spacing: .1em; text-transform: uppercase;
  color: #ffffffaa;
}

/* A corner flag for anything worth calling out -- live tables, new machines. */
.tile .flag {
  position: absolute; left: .6rem; top: .6rem; z-index: 1;
  font-size: .55rem; letter-spacing: .12em; text-transform: uppercase;
  padding: .18rem .4rem; border-radius: 999px;
  background: #000000aa; border: 1px solid var(--t1, #2b3350);
  color: #fff;
}
.tile .flag.live { color: var(--up, #4ea88a); border-color: var(--up, #4ea88a); }

/* Filter chips above the grid. */
.gamefilters { display: flex; gap: .35rem; flex-wrap: wrap; margin: .2rem 0 .2rem; }
.gamefilters button {
  font-size: .68rem; padding: .3rem .7rem; border-radius: 999px;
}
.gamefilters button.on { border-color: var(--signal); color: var(--signal); }

@media (max-width: 560px) {
  .gamegrid { grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); }
  .tile .art { font-size: 4.6rem; }
}

/* ==========================================================================
   THE PLAYER RAIL  --  who you are, at the top of the right column
   ==========================================================================
   The balance was only in the top bar, which on the new sidebar layout is a
   narrow column down the left where money is easy to miss. This puts the
   player's own position where their eye already goes on the home page, with
   the daily bonus beside it rather than buried further down the page.
   ========================================================================== */
.rail {
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: .9rem;
  margin-bottom: 1rem;
  /* Both stops translucent. The second used to be flat ink, so the sky
     stopped dead halfway down the rail while the top of it was glass. */
  background:
    radial-gradient(120% 90% at 0% 0%, #ffffff12, transparent 60%),
    linear-gradient(160deg, rgba(21, 27, 37, .58), rgba(11, 15, 22, .66));
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07),
              0 .6rem 1.6rem rgba(0, 0, 0, .3);
}
.rail .who { display: flex; align-items: center; gap: .65rem; }
/* The avatar is the initial on a coloured disc: no upload, no placeholder
   image, and a player still recognises their own row in a chat log. */
.rail .pic {
  width: 2.6rem; height: 2.6rem; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 1.1rem; font-weight: 800; color: #fff;
  background: linear-gradient(150deg, var(--signal), #7a4fd0);
  box-shadow: 0 0 0 2px #0008, 0 .2rem .5rem #0007;
  text-shadow: 0 1px 2px #0009;
}
.rail .nm { font-size: .95rem; font-weight: 700; line-height: 1.1; }
.rail .tier { font-size: .6rem; letter-spacing: .12em; text-transform: uppercase;
              color: var(--signal); }
.rail .tier.none { color: var(--muted); }

.rail .bal { margin-top: .8rem; }
.rail .ballbl { font-size: .56rem; letter-spacing: .16em;
                text-transform: uppercase; color: var(--muted); }
.rail .balval { font-size: 1.45rem; font-weight: 800;
                font-variant-numeric: tabular-nums; color: var(--signal); }

/* Progress toward the next tier. A bar answers "how close am I" without the
   player doing arithmetic against a price list. */
.rail .track { height: .4rem; border-radius: 999px; background: #ffffff12;
               overflow: hidden; margin: .55rem 0 .3rem; }
.rail .fill { height: 100%; border-radius: 999px;
              background: linear-gradient(90deg, var(--signal), #7a4fd0);
              transition: width .4s ease; }
.rail .toward { font-size: .62rem; color: var(--muted); }

.rail .claim {
  display: block; width: 100%; margin-top: .8rem;
  padding: .55rem; border-radius: 8px; text-align: center;
  font-size: .74rem; font-weight: 700; letter-spacing: .04em;
  text-decoration: none;
}
.rail .claim.ready {
  background: linear-gradient(120deg, var(--signal), #d98a41);
  color: #17120a; border: none;
}
.rail .claim.waiting {
  background: #ffffff08; color: var(--muted); border: 1px solid var(--line);
}

/* ==========================================================================
   THE HERO  --  the top of the floor
   ==========================================================================
   Generated rather than illustrated, for the same reason as the tiles: no
   image files to ship or lose, and it is on screen the moment the CSS is.
   The depth comes from layered gradients and a large glyph rather than art.
   ========================================================================== */
.hero {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  padding: 1.6rem 1.5rem;
  margin-bottom: .9rem;
  min-height: 11rem;
  display: flex; flex-direction: column; justify-content: center;
  border: 1px solid var(--line);
  background:
    radial-gradient(90% 140% at 88% 15%, #7a4fd033, transparent 62%),
    radial-gradient(70% 120% at 10% 90%, #d9a44126, transparent 60%),
    linear-gradient(140deg, #1a2135, #0a0d14);
  isolation: isolate;
}
/* The glyph sits behind the words, bled off the right edge, so the panel reads
   as a scene rather than a box with an icon parked in it. */
.hero .glyph {
  position: absolute; right: -2%; top: 50%;
  transform: translateY(-50%);
  font-size: 9rem; line-height: 1;
  opacity: .18;
  pointer-events: none;
  z-index: -1;
}
.hero .kicker {
  font-size: .58rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--signal); margin-bottom: .35rem;
}
.hero h1 { margin: 0 0 .3rem; font-size: 1.75rem; line-height: 1.1; }
/* The hero rotates every few seconds, so the change needs to read as a change
   rather than as text being replaced under the reader. A short fade on the
   whole panel is enough; anything more elaborate turns a status line into a
   slideshow that has to be waited out. */
/* The sign-in reassurance panel. Quiet by design: it is there to be found by
   somebody who wonders, not to sell anything, so it sits below the form in
   the muted colour and only the transport line ever takes a colour of its
   own -- and then only to warn. */
.gatesafe { margin-top: 1.4rem; padding-top: 1rem;
            border-top: 1px solid var(--line); text-align: left; }
.gatesafe .gsline { margin: 0 0 .35rem; font-size: .72rem; line-height: 1.5;
                    color: var(--muted); }
.gatesafe .gsline:last-child { margin-bottom: 0; }
.gatesafe .gsline.ok { color: var(--up); }
.gatesafe .gsline.ok::before { content: "\1F512"; margin-right: .4rem; }
.gatesafe .gsline.warn { color: var(--down); }
.gatesafe .gsline.warn::before { content: "\26A0"; margin-right: .4rem; }
.gatesafe [hidden] { display: none; }

.hero.swap { animation: heroswap .45s ease-out; }
@keyframes heroswap {
  from { opacity: .35; transform: translateY(4px); }
  to   { opacity: 1;   transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .hero.swap { animation: none; }
}
.hdots { display: flex; gap: .4rem; margin-top: 1rem; }
.hdots .hdot { width: 1.5rem; height: 3px; padding: 0; border: 0;
               border-radius: 8px; background: #ffffff26; cursor: pointer;
               transition: background .2s, width .2s; }
.hdots .hdot:hover { background: #ffffff4d; }
.hdots .hdot.on { background: var(--signal); width: 2.2rem; }
.hdots .hdot:focus-visible { outline: 2px solid var(--bone); outline-offset: 2px; }

.hero .lede { font-size: .84rem; color: var(--muted); max-width: 34rem;
              line-height: 1.5; }
.hero .go { display: flex; gap: .5rem; margin-top: .9rem; flex-wrap: wrap;
            align-items: center; }
.hero .go .primary { padding: .5rem 1.4rem; }
.hero .stat2 { font-size: .66rem; color: var(--muted); }
.hero .stat2 b { color: var(--signal); font-variant-numeric: tabular-nums; }

/* ---- promo strip ---- */
.promos {
  display: grid; gap: .7rem; margin-bottom: 1.2rem;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
.promo {
  display: flex; align-items: center; gap: .7rem;
  padding: .7rem .8rem; border-radius: 8px;
  border: 1px solid var(--line);
  background: linear-gradient(150deg, var(--p1, #1d2536), #0c1017);
  text-decoration: none;
  transition: transform .14s ease, border-color .14s ease;
}
.promo:hover { transform: translateY(-2px); border-color: var(--p1, #1d2536); }
.promo .pic2 { font-size: 1.7rem; line-height: 1; flex: 0 0 auto; }
.promo .nm2 { display: block; font-size: .8rem; font-weight: 700;
              color: var(--bone); }
.promo .sub2 { display: block; font-size: .64rem; color: var(--muted);
               margin-top: .1rem; }

@media (max-width: 700px) {
  .hero { padding: 1.2rem 1rem; min-height: 9rem; }
  .hero h1 { font-size: 1.35rem; }
  .hero .glyph { font-size: 6rem; opacity: .13; }
}


/* The casino chat, sitting under the player rail. Themed to match it rather
   than the plain card it used to live in: same gradient, same rounding, so the
   right column reads as one panel about you and the room instead of a stack of
   unrelated boxes. */
.railchat {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: .9rem;
  margin-bottom: 1rem;
  background:
    radial-gradient(120% 90% at 100% 0%, #7a4fd016, transparent 60%),
    linear-gradient(160deg, var(--panel), #0b0f16);
}
.railchat h2 {
  font-size: .62rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 .6rem;
}
/* The chat component draws its own frame; inside this panel that is a second
   border around a border. */
/* The rail's chat is the SAME chat, sitting inside a card that already draws
   its own frame. It says how tall it is and nothing else -- it used to
   restate the padding, the border and the background, which is how the front
   page ended up looking like a different product from the keno room. */
.railchat .chat { border: none; border-radius: 0; background: transparent;
                  padding: 0; box-shadow: none; backdrop-filter: none; }
.railchat .chatlog { height: 17rem; }

/* The notification panel, on the sidebar layout.
   ------------------------------------------------------------------------
   The panel is 22rem wide and was anchored `right: 0` inside its container.
   That was fine when the chrome ran across the top; in a 13.5rem sidebar it
   hangs off the edge and the left half is clipped away.

   Anchored to the sidebar's outer edge instead, so it opens INTO the page
   rather than out of the window. Fixed rather than absolute because the
   sidebar scrolls independently and an absolute panel would scroll away from
   its own bell. */
@media (min-width: 1001px) {
  #chrome .bellhost { position: static; }
  /* Placement is computed in JS from the bell's own rectangle now -- a fixed
     left offset guessed at the rail's width and went wrong the moment the
     rail changed. Only the things that are not position stay here. */
  #chrome .bellpanel {
    max-height: 70vh;
    overflow-y: auto;
    box-shadow: var(--lift-3);
  }
}

/* ==========================================================================
   THE HOUSE STYLE
   --------------------------------------------------------------------------
   One place for the furniture every page builds out of.

   This exists because the floor drifted. Each page grew its own panel, its
   own heading, its own button, and the results were close enough to look
   accidental rather than intentional -- five pages had a proper header block
   and fifteen had none. Twenty pages cannot be kept in step by hand; they can
   be kept in step by inheriting from one sheet.

   Everything here is prefixed `x-` and can be used from any page. Nothing
   here restyles an existing class, so adopting it is opt-in and a page that
   ignores it is unaffected.
   ========================================================================== */

:root {
  /* One ramp for gold, so every accent on the floor is the same gold. It was
     written out by hand in a dozen places, drifting a shade each time. */
  --x-gold-1: #f7e6bd;
  --x-gold-2: var(--gold);
  --x-gold-3: #c99a3c;
  --x-gold-face: linear-gradient(178deg, #f7e0a6, #f0b647 62%, #d0921f);
  --x-ink: #0d1310;

  /* Panels. A single surface recipe, so a card on the wallet and a card in
     the cage are recognisably the same object. */
  --x-panel: linear-gradient(168deg, rgba(24,32,42,.74), rgba(9,13,18,.86));
  --x-panel-line: rgba(255,255,255,.08);
  --x-panel-lift: inset 0 1px 0 rgba(255,255,255,.05),
                  0 .7rem 1.6rem rgba(0,0,0,.4);

  --x-r-sm: 10px;
  --x-r: 14px;
  --x-r-lg: 18px;

  /* One duration, so nothing on the floor moves at a different speed. */
  --x-quick: .16s;
  --x-move: cubic-bezier(.2, .9, .25, 1.03);
}

/* ---- the page header ----------------------------------------------------
   Eyebrow, title, one line of explanation. Five pages had grown this
   independently; the other fifteen opened straight into content with nothing
   to say where you were. */
.x-head { margin: 0 0 1.35rem; }
.x-eyebrow { font-family: var(--font-ui); font-size: .62rem;
             letter-spacing: .22em; text-transform: uppercase;
             color: var(--muted); margin: 0 0 .35rem; font-weight: 600; }
.x-head h1 { margin: 0 0 .35rem; font-size: clamp(1.4rem, 3.2vw, 1.8rem);
             letter-spacing: -.01em; line-height: 1.15;
             background: linear-gradient(92deg, var(--x-gold-1),
                                         var(--x-gold-2) 62%, var(--x-gold-3));
             -webkit-background-clip: text; background-clip: text;
             color: transparent; }
.x-head p { margin: 0; color: var(--muted); font-size: .87rem;
            line-height: 1.6; max-width: 44rem; }

/* ---- panels ------------------------------------------------------------- */
.x-panel { border: 1px solid var(--x-panel-line); border-radius: var(--x-r-lg);
           background: var(--x-panel); box-shadow: var(--x-panel-lift);
           padding: 1.05rem 1.1rem; }
.x-panel--tight { padding: .75rem .8rem; }
.x-panel--lift { transition: transform var(--x-quick),
                             border-color var(--x-quick); }
.x-panel--lift:hover { transform: translateY(-2px);
                       border-color: rgba(240,182,71,.3); }

/* A caption with a rule trailing off it. Cheap, and it does more to make a
   page feel composed than anything else here. */
.x-cap { font-family: var(--font-ui); font-size: .6rem; letter-spacing: .2em;
         text-transform: uppercase; color: var(--muted); font-weight: 600;
         margin: 0 0 .7rem; display: flex; align-items: center; gap: .5rem; }
.x-cap::after { content: ""; flex: 1 1 auto; height: 1px;
                background: linear-gradient(90deg, rgba(240,182,71,.35),
                                            transparent); }

/* ---- buttons ------------------------------------------------------------
   A real press matters more than it sounds: on a dark screen there is up to a
   second between a click and the server answering, and without it the page
   gives nothing back in the meantime. */
.x-btn { font: inherit; font-family: var(--font-ui); font-size: .84rem;
         font-weight: 500; cursor: pointer; color: var(--x-ink);
         padding: .55rem 1.05rem; border-radius: var(--x-r);
         border: 1px solid rgba(240,182,71,.5);
         background: var(--x-gold-face);
         box-shadow: 0 .35rem .8rem rgba(0,0,0,.42),
                     inset 0 1px 0 rgba(255,255,255,.55);
         transition: transform .1s, filter var(--x-quick),
                     box-shadow var(--x-quick); }
.x-btn:hover:not(:disabled) { filter: brightness(1.07); }
.x-btn:active:not(:disabled) { transform: translateY(1px);
  box-shadow: 0 .12rem .35rem rgba(0,0,0,.5),
              inset 0 1px 2px rgba(0,0,0,.24); }
.x-btn:focus-visible { outline: 2px solid var(--x-gold-1); outline-offset: 2px; }
.x-btn:disabled { filter: grayscale(.7) brightness(.62); cursor: default;
                  box-shadow: none; }
.x-btn--quiet { background: rgba(255,255,255,.045); color: var(--bone);
                border-color: rgba(255,255,255,.16); box-shadow: none; }
.x-btn--quiet:hover:not(:disabled) { background: rgba(255,255,255,.09);
                                     filter: none; }
.x-btn--big { width: 100%; font-size: .95rem; font-weight: 600;
              padding: .72rem 1.1rem; }

/* ---- fields ------------------------------------------------------------- */
.x-field { display: grid; gap: .25rem; margin-bottom: .55rem; }
.x-field > label { font-family: var(--font-ui); font-size: .58rem;
                   letter-spacing: .16em; text-transform: uppercase;
                   color: var(--muted); font-weight: 600; }
.x-field input, .x-field select, .x-field textarea {
  width: 100%; font: inherit; font-size: .86rem; color: var(--bone);
  padding: .55rem .7rem; border-radius: var(--x-r);
  background: rgba(8,12,17,.72);
  border: 1px solid rgba(255,255,255,.12);
  transition: border-color var(--x-quick); }
.x-field input:focus, .x-field select:focus, .x-field textarea:focus {
  outline: none; border-color: rgba(240,182,71,.55); }

/* ---- figures ------------------------------------------------------------
   Anything counted or compared. Tabular, because ragged digits cannot be read
   down a column, which is the whole reason to have one. */
.x-fig { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
         color: var(--bone); }
.x-fig--lg { font-size: 1.8rem; font-weight: 600; color: var(--signal);
             line-height: 1; }
.x-tag { font-family: var(--font-ui); font-size: .56rem; letter-spacing: .12em;
         text-transform: uppercase; padding: .12rem .4rem;
         border-radius: var(--x-r-sm); color: var(--muted);
         border: 1px solid rgba(255,255,255,.16); }
.x-tag--live { color: #f3d79a; border-color: rgba(240,182,71,.4);
               background: rgba(240,182,71,.12); }

/* ---- a 3D-feeling stage -------------------------------------------------
   For the boards and tables. A rim, an inner shadow and a floor glow read as
   depth without costing a single polygon. */
.x-stage { position: relative; border-radius: var(--x-r-lg); overflow: hidden;
           background: #080b0a;
           border: 1px solid rgba(255,255,255,.08);
           box-shadow: 0 1.4rem 3.2rem rgba(0,0,0,.55),
                       inset 0 1px 0 rgba(255,255,255,.06); }
.x-stage::after { content: ""; position: absolute; inset: 0;
                  pointer-events: none;
                  background: radial-gradient(120% 80% at 50% 0%,
                              transparent 55%, rgba(0,0,0,.45) 100%); }

/* Everything above respects a reader who has asked for less movement. */
@media (prefers-reduced-motion: reduce) {
  .x-panel--lift, .x-btn { transition: none; }
  .x-panel--lift:hover { transform: none; }
}

/* ---- movement -----------------------------------------------------------
   Small, consistent, and applied from one place. Each of these was already
   happening on a page or two, at a different speed and with a different
   curve, which reads as inconsistency rather than as polish. */

/* A page arriving.
 *
 * Opacity ONLY, and never a transform on #app.
 *
 * A transform -- even one that lasts a quarter of a second -- makes the
 * element a containing block for every `position: fixed` descendant inside
 * it. #app wraps the whole page, so animating it moves the chat dock, the
 * notification panel and anything else pinned to the viewport, and they do
 * not always come back. A fade costs nothing and cannot do that.
 *
 * Opted into per page with .x-fade rather than applied to #app outright: a
 * rule on the page root is the single most dangerous place to put anything,
 * and this one earns its place on a landing page, not on a card table. */
@keyframes x-in { from { opacity: 0; } to { opacity: 1; } }
.x-fade { animation: x-in .28s ease both; }

/* Anything the floor wants you to notice: a jackpot ticking over, a win, a
   turn that is yours. */
@keyframes x-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(240,182,71,.42); }
  55%      { box-shadow: 0 0 0 .5rem rgba(240,182,71,0); }
}
.x-ping { animation: x-pulse 2.6s ease-in-out infinite; }

/* Figures that have just changed. Used by the balance in the banner. */
@keyframes x-bump { 0% { transform: scale(1); }
                    40% { transform: scale(1.09); color: var(--signal); }
                    100% { transform: scale(1); } }
.x-bumped { animation: x-bump .42s var(--x-move); }

/* A gold underline that draws itself, on the nav.
   Written against `#casinoNav a`, which is what the nav actually renders --
   a first attempt styled a `.navlink` class that does not exist anywhere on
   the site, so it looked right in the sheet and did nothing on the screen. */
#casinoNav a { position: relative; }
#casinoNav a::after { content: ""; position: absolute;
                      left: .55rem; right: .55rem; bottom: .28rem; height: 1px;
                      transform: scaleX(0); transform-origin: left;
                      background: linear-gradient(90deg, var(--x-gold-2),
                                                  transparent);
                      transition: transform var(--x-quick) var(--x-move); }
#casinoNav a:hover::after,
#casinoNav a.on::after { transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) {
  .x-fade { animation: none; }
  #app { animation: none; }
  .x-ping, .x-bumped { animation: none; }
  #casinoNav a::after { transition: none; }
}

/* ===========================================================================
   FINISH
   ---------------------------------------------------------------------------
   The palette, the two type faces and the glass were already here and they
   are good. What was missing is the layer above them -- the things that make
   an interface feel built rather than assembled:

     depth      one shadow ladder instead of a different guess per page
     response   surfaces that acknowledge a pointer and a keyboard
     detail     selection, scrollbars, focus -- the parts nobody names but
                everybody notices when they are wrong

   All of it is additive. Nothing here restyles an existing rule, so no page
   can lose its layout to this block.
   ======================================================================== */

:root {
  /* A shadow ladder. Three steps, each roughly doubling, all tinted toward
     the ink rather than pure black -- a black shadow on a blue-black page
     reads as a hole. */
  --lift-1: 0 1px 2px rgba(3, 6, 12, .34);
  --lift-2: 0 6px 18px rgba(3, 6, 12, .42);
  --lift-3: 0 18px 44px rgba(3, 6, 12, .52);
  /* One easing for everything that moves. Mixed easings are the surest way
     to make an interface feel like several interfaces. */
  --ease: cubic-bezier(.22, .61, .36, 1);
}

/* Panels and tiles sit ON the page rather than in it. */
.panel, .tile, .card, .sb-card, .bd-tile {
  box-shadow: var(--lift-2);
}

/* ---- response ----------------------------------------------------------
   A tile lifts toward the reader and warms its edge. The transform is the
   cheap part; the border warming is what makes it feel lit rather than
   merely moved. */
.tile, .sb-card {
  transition: transform .22s var(--ease), box-shadow .22s var(--ease),
              border-color .22s var(--ease);
  will-change: transform;
}
.tile:hover, .sb-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--lift-3);
  border-color: rgba(217, 164, 65, .34);
}
.tile:active { transform: translateY(-1px); }

/* Buttons answer a press. A control that looks identical before and after a
   click leaves a player unsure whether the bet went on. */
button, .btn, .primary {
  transition: transform .12s var(--ease), filter .18s var(--ease),
              box-shadow .18s var(--ease);
}
button:active, .btn:active, .primary:active { transform: translateY(1px); }
button:hover:not(:disabled), .btn:hover, .primary:hover {
  filter: brightness(1.06);
}

/* ---- the keyboard ------------------------------------------------------
   One ring, everywhere, and only for keyboard users. The site had focus
   styles on some pages and none on others, which for anybody navigating by
   tab means the cursor simply disappears at the door of half the rooms. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 2px;
  border-radius: 8px;
}

/* ---- detail ------------------------------------------------------------ */
::selection { background: rgba(217, 164, 65, .3); color: var(--bone); }

/* Scrollbars in the house colours rather than the browser's grey. */
* { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.16) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, .14);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .24);
  background-clip: content-box; }

/* Money should never wrap or jitter as it counts. Tabular figures keep a
   changing balance from shuffling the layout under a player's eyes. */
.bal, .amount, .odds, .price, [data-money] {
  font-variant-numeric: tabular-nums;
}

/* Nothing above is worth motion sickness. */
@media (prefers-reduced-motion: reduce) {
  .tile, .sb-card, button, .btn, .primary { transition: none; }
  .tile:hover, .sb-card:hover { transform: none; }
}

/* ---------------------------------------------------------------- shape ---
   FORTY-THREE DIFFERENT CORNER RADII.
   
   Measured across every page: 2px, 3px, 4px, 6px, 8px, 10px, 11px, 12px,
   14px, 16px, 18px and more, chosen a page at a time. Nobody consciously
   notices a 3px corner beside an 11px one, and everybody notices that the
   site looks like several sites.
   
   Four steps, and a rule for choosing: the smaller the element, the tighter
   the corner. The tokens are here for new work; the rules below pull the
   shared furniture onto the scale without touching any page's own layout. */
:root {
  --r-sm: 8px;    /* chips, tags, small controls */
  --r-md: 12px;   /* buttons, inputs, list rows */
  --r-lg: 16px;   /* cards, tiles, panels */
  --r-xl: 22px;   /* the hero, dialogs, anything that frames a page */
  --r-pill: 999px;
}

/* Shared furniture, pulled onto the scale. Page-local classes keep their
   own radii -- this is a floor, not a decree. */
.panel, .tile, .card { border-radius: var(--r-lg); }
button, .btn, input, select, textarea { border-radius: var(--r-md); }
.chip, .tag, .flag, .badge { border-radius: var(--r-sm); }

/* ---------------------------------------------------------------- type ---
   The scale had 123 distinct font sizes, most of them within a hundredth of
   a rem of each other -- .6, .62, .64, .66, .68, .7, .72. That is not a
   scale, it is a series of small decisions nobody could repeat.
   
   These are the steps to reach for from here. Existing rules are left
   alone: restyling every page's type blind is how a working site becomes a
   broken one. */
:root {
  --t-micro: .62rem;   /* labels, kickers, table headers */
  --t-small: .74rem;   /* secondary prose, captions */
  --t-body:  .88rem;   /* prose */
  --t-lead:  1.05rem;  /* lead paragraphs, card titles */
  --t-h3:    1.3rem;
  --t-h2:    1.75rem;
  --t-h1:    2.4rem;
}

/* One more unification worth making by hand: every page that shows money
   should show it in the same face at the same weight, whatever else it
   does. A balance that changes typeface between rooms reads as two
   different books. */
.bal, .amount, .odds, .price, [data-money], .stake, .returns {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}
