/* The player's surface. Arcade furniture, not a control panel.

   ALWAYS DARK: the till has a light mode because it is read in daylight
   through a glare, and an arcade is a dark room with lit cabinets in it. The
   tokens from console.css still apply, so the two products stay related --
   this is the same gold on the same near-black, arranged for a room rather
   than a desk. */

body.arcade {
  /* THE ROOM'S OWN TOKENS. Everything below builds its metal and its wells
     out of these three, so a season retints the furniture by setting them
     rather than by restating a background it will drift from. */
  --room-glow: #203222;          /* the wash off the sign overhead */
  /* THE METAL IS MADE FROM --accent, not beside it. A fixed brass would go
     wrong the first time a season moved the gold, and it would go wrong
     quietly -- red marquee, gold rivets. Highlight to shadow over the
     season's own colour. */
  --brass: linear-gradient(180deg,
             rgb(255 255 255 / .55) 0%, rgb(255 255 255 / .12) 38%,
             rgb(0 0 0 / .35) 100%), var(--accent);

  min-height: 100vh;
  display: flex; flex-direction: column;
  /* FOUR LAYERS, and the order is top-down. A single flat fill is what made
     this read as a dashboard: a room has a lit end, a dark floor and a
     surface underfoot, and none of those cost an image. */
  background:
    /* the sign overhead */
    radial-gradient(1200px 600px at 50% -10%, var(--room-glow) 0%, transparent 60%),
    /* the corners fall away, so the lit cabinets are the brightest thing */
    radial-gradient(150% 110% at 50% 38%,
      rgb(0 0 0 / 0) 42%, rgb(0 0 0 / .5) 100%),
    /* the floor */
    radial-gradient(900px 500px at 50% 120%, rgb(0 0 0 / .55) 0%, transparent 70%),
    /* something underfoot */
    repeating-linear-gradient(45deg,
      rgb(255 255 255 / .014) 0 2px, rgb(0 0 0 / 0) 2px 7px),
    var(--bg);
  /* The room stays put while the cabinets scroll past it. */
  background-attachment: fixed, fixed, fixed, fixed, fixed;
}

/* ---------------------------------------------------------- the two rails

   A MARQUEE OVERHEAD AND A DECK UNDERFOOT, not two grey strips. Both were a
   flat --panel with a 1px line, which is how a control panel is built and
   why the whole surface read as one. What separates them now is light: the
   marquee is lit from above and spills onto the room below it, the deck is
   in shadow and sits under the floor plane. */
.arcade-top, .arcade-bottom {
  position: relative; z-index: 5;
  display: flex; align-items: center; gap: 16px;
  padding: 12px 20px;
  /* Room for the painted mark, which is a stacked lock-up rather than a line
     of type. Without this the rail keeps its text height and the logo is
     squeezed into it. */
  min-height: 66px;
  background: linear-gradient(180deg, #203222 0%, #131e14 100%);
}
/* The brass edge. A gradient rather than a border-color, because a 1px line
   is a line and a bevel is what a lit sign actually has -- and it is drawn
   from --accent, so it turns red in December with everything else. */
.arcade-top::after, .arcade-bottom::before {
  content: ""; position: absolute; left: 0; right: 0; height: 2px;
  background: var(--brass); opacity: .5;
}
.arcade-top::after  { bottom: 0; }
.arcade-bottom::before { top: 0; }

.arcade-top {
  /* STICKY, because the pot is the reason to look up and scrolling a lobby
     should not take it away. */
  position: sticky; top: 0;
  box-shadow: 0 18px 40px -26px var(--rail-glow), 0 2px 10px rgb(0 0 0 / .6);
}
.arcade-bottom {
  border-bottom: 0; margin-top: auto;
  background: linear-gradient(180deg, #131e14 0%, #0b130c 100%);
  box-shadow: inset 0 8px 18px -12px rgb(0 0 0 / .9);
}

.arcade-top .winners { font-weight: 800; letter-spacing: .10em;
                       color: var(--text); text-decoration: none;
                       /* Lit, like the sign it is. */
                       text-shadow: 0 0 14px var(--rail-glow); }
.arcade-top .pot { margin: 0 auto; display: flex; align-items: baseline;
                   gap: 10px; }
/* Upper-cased in the STYLESHEET rather than the template, so the rail
   keeps its look while the text stays the pool's actual name. */
.pot-name { font-size: 10px; letter-spacing: .16em; color: var(--muted);
            text-transform: uppercase; line-height: 1; }
/* One block per pot, because there can be two. Side by side rather than
   stacked: the rail is one line tall and a pot that pushes it taller moves
   every cabinet down.

   AN ODOMETER, NOT A STAT. This is the one number the room is arranged
   around and it was set like a figure on a dashboard -- a label, a space, a
   value. It is now what it is on a real machine: digits lit inside a
   recessed window, sunk into the marquee rather than printed on it. The
   depth is an inset shadow and the window is --well; no image, and nothing
   here is a colour a season cannot move. */
.pot-one { display: inline-flex; flex-direction: column; align-items: center;
           gap: 3px; margin-left: 14px;
           padding: 5px 14px 6px; border-radius: 9px;
           background: var(--well); position: relative; overflow: hidden;
           box-shadow: inset 0 3px 8px rgb(0 0 0 / .9),
                       inset 0 0 0 1px rgb(255 255 255 / .05),
                       0 1px 0 rgb(255 255 255 / .04); }
/* The digit cells, suggested rather than drawn: a hairline every 14px behind
   tabular numerals, which is what makes a run of figures read as a counter
   instead of as text. */
.pot-one::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(90deg,
    rgb(255 255 255 / .05) 0 1px, rgb(0 0 0 / 0) 1px 14px);
}
.pot-one:first-child { margin-left: 0; }
.pot-figure { font-size: 20px; font-weight: 800; color: var(--accent);
              font-variant-numeric: tabular-nums; line-height: 1.05;
              letter-spacing: .02em;
              text-shadow: 0 0 16px var(--rail-glow); }
.arcade-top .exit { color: var(--muted); text-decoration: none;
                    font-weight: 700; }
.arcade-top .exit:hover { color: var(--warn); }

.arcade-bottom .who { display: flex; align-items: center; gap: 10px; }
.arcade-bottom .avatar {
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: var(--on-accent); font-weight: 800;
}
.arcade-bottom .name { font-weight: 700; }
.arcade-bottom .cid { color: var(--muted); font-variant-numeric: tabular-nums; }
/* The purse gets the same window as the pot -- it is the same kind of number,
   read the same way, and two treatments for one idea is how a surface starts
   looking assembled rather than designed. */
.arcade-bottom .purse { margin-left: auto; display: flex; gap: 12px; }
.wallet { display: flex; flex-direction: column; align-items: center; gap: 3px;
          padding: 4px 14px 5px; border-radius: 9px; background: var(--well);
          box-shadow: inset 0 3px 8px rgb(0 0 0 / .9),
                      inset 0 0 0 1px rgb(255 255 255 / .05); }
.wallet .label { font-size: 10px; letter-spacing: .14em; color: var(--muted);
                 line-height: 1; }
.wallet .figure { font-size: 18px; font-weight: 800; line-height: 1.05;
                  font-variant-numeric: tabular-nums; }
/* The avatar is a struck coin rather than a flat disc. */
.arcade-bottom .avatar {
  background: var(--brass);
  box-shadow: inset 0 -2px 4px rgb(0 0 0 / .35), 0 2px 6px rgb(0 0 0 / .5);
}

/* A COLUMN, so the lobby inside it has a height to divide up. It was a plain
   block, which left every child sizing to its own content and the band with
   nothing to fill. */
.arcade-body { flex: 1; padding: 20px; max-width: 1320px; width: 100%;
               margin: 0 auto; display: flex; flex-direction: column;
               min-height: 0; }

/* ------------------------------------------------------------ signing in */
.signin { max-width: 360px; margin: 10vh auto; text-align: center; }
.signin h1 { font-size: 30px; letter-spacing: .04em; margin-bottom: 6px; }
.tagline { color: var(--muted); margin: 0 0 18px; }
.signin label { display: block; text-align: left; margin-bottom: 14px;
                color: var(--muted); max-width: none; }
.signin input { display: block; width: 100%; margin-top: 4px;
                font-size: 20px; letter-spacing: .18em; text-align: center; }
.signin button[type=submit] { width: 100%; padding: 12px; font-size: 16px; }
.signin-error { color: var(--warn); margin: 0 0 14px; }
.signin a { color: var(--accent); }

/* The sign-in panel now lives in console.css and applies to every surface --
   see "the front door" there. It was here, conditional on a backdrop being
   present, which defended contrast in one state and not the other. */

/* ----------------------------------------------------------- the lobby */
/* align-items: flex-start so the rail stays its own height rather than
   stretching to the wall beside it. The band opts out with align-self, which
   is what gives it a height to fill. */
/* THE RAIL SITS UNDER THE WALL, at every width.

   It was a column down the left, which is Golden Dragon's shape. The
   reference we are following puts the categories along the bottom, centred,
   and gives the whole width to the machines -- which is the right trade on a
   phone held in one hand and still the right one on a desktop: the wall is
   the product, and a 120px column beside it is 120px of lobby spent on five
   words.

   ORDER RATHER THAN MOVING THE MARKUP. The nav stays first in the document,
   where a screen reader and a stylesheet-less browser both want it, and the
   stylesheet is what puts it at the bottom. Under 640px this used to be a
   special case; it is now simply what the lobby is. */
.lobby { display: flex; flex-direction: column; gap: 16px;
         align-items: stretch;
         flex: 1; min-height: 0; position: relative; }
.rail { display: flex; flex-direction: row; flex-wrap: wrap; gap: 6px;
        justify-content: center; flex: 0 0 auto; order: 2; }

/* THE CUSTOMER NUMBER, top-left above the wall, the way the reference has it.
   order: 0 so it leads the column -- the wall is 1 and the rail is 2. */
.player-id { order: 0; margin: 0; display: flex; align-items: center; gap: 8px;
             font-variant-numeric: tabular-nums; }
.player-id-label { font-size: 11px; font-weight: 800; letter-spacing: .14em;
                   color: var(--muted); }
.player-id-number { font-size: 15px; font-weight: 800; color: var(--text); }
.player-id-copy {
  padding: 3px 10px 4px; border: 0; border-radius: 7px; cursor: pointer;
  font: inherit; font-size: 11px; font-weight: 800; letter-spacing: .08em;
  background: linear-gradient(180deg, #3b4b66 0%, #1d2838 100%);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .14), 0 2px 0 rgb(0 0 0 / .5);
}
.player-id-copy:active { transform: translateY(2px);
                         box-shadow: inset 0 1px 0 rgb(255 255 255 / .14); }
.player-id-copy.done { background: linear-gradient(180deg, #3f6b45, #21402a); }

/* ONE NUMBER ON THE SCREEN, NOT TWO. The bottom rail carries the customer
   number on every play page, which is right everywhere except here -- the
   lobby now shows it where the reference does, and the same seven digits
   twice on one screen is just noise. Hidden rather than removed from the
   markup, because every other page still wants it. */
body.lobby-page .arcade-bottom .cid { display: none; }
/* LIT TABS DOWN THE SIDE. They were three grey pills floating beside the
   grid with nothing to attach them to; a rail is a thing you press, so each
   one now has a face, an edge that lights when it is the live one, and a
   press that actually moves. */
.rail a {
  position: relative;
  padding: 10px 12px 10px 16px; border: 0; border-radius: 8px;
  background: linear-gradient(180deg, #223324 0%, #172519 100%);
  color: var(--muted); text-decoration: none;
  font-size: 13px; font-weight: 700; letter-spacing: .04em; text-align: center;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .06),
              0 2px 0 rgb(0 0 0 / .55);
  transition: color .12s ease, transform .1s ease, box-shadow .1s ease;
}
/* The lit edge, on the side the grid is. */
.rail a::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px;
  border-radius: 3px; background: var(--brass); opacity: .18;
  transition: opacity .12s ease;
}
.rail a.on, .rail a:hover { color: var(--text); }
.rail a.on::before, .rail a:hover::before { opacity: 1; }
.rail a.on { box-shadow: inset 0 1px 0 rgb(255 255 255 / .1),
                         0 2px 0 rgb(0 0 0 / .55),
                         0 0 22px -6px var(--rail-glow); }
/* Pressed: the tab goes down onto its shadow, which is the whole illusion. */
.rail a:active { transform: translateY(2px);
                 box-shadow: inset 0 1px 0 rgb(255 255 255 / .06); }

/* A WALL OF MACHINES YOU PAN ALONG, not a page you scroll down.
   Every platform in this category does it this way and it is not a stylistic
   copy: a lobby that scrolls down reads as a website with games on it, and
   one that scrolls sideways reads as a room with cabinets in it. The
   reference puts two rows in a band and lets them run off the right edge.

   `grid-auto-flow: column` with fixed rows is what turns the same markup
   sideways -- the cabinets are unchanged and nothing in the template moved.

   min-width: 0 IS LOAD-BEARING. This is a flex child, and a flex item's
   default min-width is auto, which is its content -- so without this the
   track simply grows to fit every cabinet and the page scrolls sideways
   instead of the band doing it. */
/* THE FRAME THE BAND MOVES INSIDE. It holds the height and the arrows; the
   band holds the scrolling. They were one box until the arrows arrived, and
   a control inside an `overflow: auto` box scrolls away with the content it
   is meant to be moving. */
/* PINNED TO THE VIEWPORT, lobby only. Every box between the body and the band
   needs a definite height for the band's `max-height: 100%` to resolve against
   -- without one it measured itself against a parent whose height came FROM
   it, which is circular, so nothing ever capped and the machines ran off the
   bottom of the screen. */
body.arcade.lobby-page { height: 100dvh; }
body.arcade.lobby-page .arcade-body { overflow: hidden; }

.wall {
  position: relative;
  /* FIRST IN THE COLUMN, with the rail under it. See .lobby: the nav stays
     first in the markup, where a reader wants it, and order is what puts the
     categories along the bottom the way the reference does. */
  order: 1;
  flex: 1; min-width: 0;
  /* STRETCH, never center. Stretch is the one alignment that means the right
     thing on both axes: down the row in the wide layout, across it when the
     lobby stacks into a column. `center` sized this box to its content in the
     stacked case -- 2,820px of cabinets with nothing left to clip. */
  align-self: stretch;
  /* NO align-self HERE, and that is the point. It said `align-self: center`,
     meaning "sit in the middle of the row" -- true in the wide layout, and in
     the stacked one the cross axis is the WIDTH, so the frame sized itself to
     its content: 2,820px of sixteen cabinets, nothing left to clip, and the
     page scrolling sideways instead of the band.

     The band carries its own height now (see below), so this box has no
     opinion about either axis beyond filling the row it is in. */
  --tile: clamp(190px, 22vw, 360px);
  min-height: 0;
  display: flex; align-items: flex-start;
}

.cabinets {
  flex: 1; min-width: 0;
  /* A HEIGHT OF ITS OWN, not a share of somebody else's.

     This was `align-self: center; height: 100%`, which reads as "fill the row
     and centre in it" -- and is only that in the WIDE layout. Under 640px the
     lobby stacks into a column, and a column flex's cross axis is the WIDTH,
     so centring sized the band to its content: all 2,820px of sixteen
     cabinets, with nothing left over to scroll and the page sliding sideways
     instead. That is the same trap `align-items: stretch` was added to the
     stacked lobby to fix, met again one box further in.

     A definite height dodges the axis question entirely. Two and a half times
     the tile's own width, capped by whatever the room actually has: filling
     every pixel of height is right on a laptop and wrong on a phone held
     upright, where it gave 1:4.7 tiles -- a column of slots rather than a
     cabinet, against the reference's 1:2.

     --tile is in vw rather than % because it is needed on BOTH axes, and a
     custom property is substituted as text: a percentage would resolve
     against the width in one place and the height in the other. */
  /* AS TALL AS A CABINET IS, not a fraction of the room. The height comes
     from the tile now -- its art at 3/4 plus its plate -- so the only thing
     capping it is the space actually available. */
  /* THE BAND TAKES THE ROOM AND THE ROW IS THE BAND. With an `auto` row the
     row sized itself to the tile, so a cabinet taller than the space just
     hung out of the bottom with its name plate cut off -- `max-height` on the
     tile could not help, because it was measuring against a row that had
     already agreed to be 550px tall. A definite row is what gives the art
     something to give way to. */
  height: 100%;
  min-height: 0;
  display: grid; gap: 16px;
  grid-auto-flow: column;
  grid-template-rows: minmax(0, 1fr);
  /* A FRACTION OF THE BAND, NOT A PIXEL WIDTH. This is why the reference
     shows about four cabinets on any screen it runs on: the tile is sized
     against what is visible, so it grows with the window instead of tiling
     more and smaller copies into it. Clamped at both ends so a phone does not
     get one enormous cabinet and a wide monitor does not get twelve stamps. */
  grid-auto-columns: var(--tile);
  overflow-x: auto;
  /* SAYS IT MOVES. A wall of machines that pans has to look like something
     you can take hold of; a mouse has no other way to find out. */
  cursor: grab;
  overscroll-behavior-x: contain;   /* not the browser's back gesture */
  scroll-snap-type: x proximity;
  padding-bottom: 10px;             /* room for the scrollbar, not under a tile */
}
/* THE SLOT holds a cabinet and the heart that belongs to it. It exists
   because a button inside an anchor is invalid and a press on it would open
   the machine as well as mark it -- so the heart is a sibling, placed over
   the plate from here. */
.slot { position: relative; display: flex; min-width: 0; }
.slot > .cabinet { flex: 1; min-width: 0; }

/* THE HEART. Over the plate, where the reference has it. */
.fave { position: absolute; right: 8px; bottom: 10px; z-index: 2; margin: 0; }
.fave button {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: rgb(0 0 0 / .42);
  color: #ffd0d8; font-size: 20px; line-height: 1;
  text-shadow: 0 1px 2px rgb(0 0 0 / .8);
  transition: transform .1s ease, color .12s ease;
}
.fave button[aria-pressed="true"] { color: #ff5c7a; }
.fave button:hover { color: #ff8fa3; }
.fave button:active { transform: scale(.88); }

/* THE ART KEEPS ITS OWN SHAPE, which is what makes it fill.
   
   This was `aspect-ratio: auto` with the art flexing to whatever height the
   tile had, and the tile's height came from a fixed multiple of its width.
   On a wide screen that multiple gave a 300x660 art box -- 0.45 -- for a
   source that is 900x1200, or 0.75. `cover` then scales until the height is
   covered and throws the sides away, so the picture on screen was a narrow
   vertical slice of the tile: the character squeezed into the top and the
   blurred bed underneath it filling the rest.
   
   Held at the source's own ratio there is nothing to crop and nothing left
   over. The tile is then as tall as its art plus its plate, and the band is
   as tall as the tile -- no multiplier anywhere, and the shape stops changing
   with the width of the window. */
.cabinets .cabinet img,
.cabinets .cabinet .noart {
  /* 3/4 is what it WANTS; flex lets it give way. When the room is shorter
     than a whole cabinet -- a laptop in landscape, a phone with the address
     bar showing -- the art gives up a little height and `cover` takes it off
     the top and bottom, which is a crop nobody notices. Held rigid it would
     overflow the band and the plate would be cut off instead, which anybody
     would. */
  /* `0 1 auto`: takes the size its ratio asks for, never grows past it, and
     gives way when the row is shorter than that. Growing is what produced the
     original fault -- art stretched to whatever height the tile had, which on
     a wide screen was 0.45 for a 0.75 picture. */
  aspect-ratio: 3 / 4; flex: 0 1 auto; min-height: 0;
  object-fit: cover;
}
/* Centred in its row rather than stretched, so spare height sits above and
   below a cabinet instead of inside one. */
.slot { align-self: center; max-height: 100%; }
.slot > .cabinet { max-height: 100%; overflow: hidden; }
.cabinets > * { scroll-snap-align: start; }

/* Mid-drag. The grabbing hand, and nothing selectable underneath it: without
   this a drag across two name plates highlights both of them instead. */
.cabinets.dragging { cursor: grabbing; user-select: none; scroll-snap-type: none; }
.cabinets.dragging .cabinet { pointer-events: none; }

/* THE ARROWS. Anchored to the frame's edges and floating over the band, so
   they take no width from it -- a control that pushed the cabinets inward
   would make the wall narrower on exactly the screens that have least room.

   Deliberately quiet. The reference's is a saturated violet slab and it is
   the loudest thing on its lobby, which is the wrong weight here: the
   machines are the product and this is a way to see more of them. */
.pan {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  /* OFF BY DEFAULT, and turned on for a fine pointer below. Most players are
     on a phone, where the band is already swiped and the partly-visible
     cabinet at the edge says the row continues -- and where two of these
     cover about a quarter of a 375px screen, sit on top of both visible
     machines, and turn a tap near a tile's inner edge into a pan instead of
     opening the game. */
  display: none; align-items: center; justify-content: center;
  /* Sized for a thumb, like the wheel's controls: most players are on a
     phone and this sits at the edge of the screen where a hand already is. */
  width: 46px; height: 72px;
  border: 0; border-radius: 10px; cursor: pointer;
  background: linear-gradient(180deg, rgb(16 56 28 / .92), rgb(7 19 9 / .92));
  color: var(--accent); font-size: 30px; line-height: 1;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .14), 0 6px 16px -8px rgb(0 0 0 / .9);
  transition: opacity .12s ease, transform .1s ease;
}
.pan-left  { left: -6px; }
.pan-right { right: -6px; }
.pan:hover { color: var(--text); }
.pan:active { transform: translateY(-50%) scale(.94); }

/* NOT OVER THE SCROLLBAR. The band keeps a slim rail along its bottom edge
   and an arrow centred on the box overlaps it, so the last few pixels of a
   drag land on a button instead. */
.pan { margin-bottom: 10px; }

/* WHERE THEY EARN THEIR SPACE. A mouse has no gesture for this -- a wheel
   mostly cannot scroll sideways and nothing on screen says the row goes on --
   so the arrows are drawn for a pointer and left out where a thumb already
   does the job better.

   `hover: hover` as well as `pointer: fine`, because a stylus reports a fine
   pointer and cannot hover; it swipes like a finger and wants what a finger
   gets. */
@media (hover: hover) and (pointer: fine) {
  .pan { display: flex; }
}

/* A slim rail rather than the platform's default slab, which on a dark room
   is a bright grey bar across the floor. */
.cabinets::-webkit-scrollbar { height: 8px; }
.cabinets::-webkit-scrollbar-track { background: rgb(0 0 0 / .35); border-radius: 4px; }
.cabinets::-webkit-scrollbar-thumb {
  background: var(--rail); border-radius: 4px;
}
.cabinets { scrollbar-width: thin; scrollbar-color: var(--rail) rgb(0 0 0 / .35); }

/* TWO ROWS WHEN THE ROOM IS TALL. A cabinet is limited by the WIDTH of the
   band -- that is what keeps four of them on screen -- so on a tall display
   the wall came out as one short row of small machines with the floor showing
   under it. A tablet held upright is the obvious case and it looked empty.

   The band was two rows once and went to one because a phone has no height
   for two. This is that rule, stated the right way round: two when there is
   room, one when there is not, rather than one always. 900px is enough for
   two cabinets and a gap; a phone in portrait is 812 and stays on one. */

@media (min-height: 900px) {
  .cabinets { grid-template-rows: repeat(2, minmax(0, 1fr)); }

  /* A FEATURED TILE IS TWO ROWS TALL HERE, AND THIS REVERSES A DECISION.
     The rule read "two columns wide, never two rows tall -- the second row is
     for a second cabinet, not for stretching the first", and that was a
     merchandising judgement rather than a bug guard.

     The reference disagrees, and it does so in every capture there is: its
     wall is a two-row band of ordinary cabinets with the featured ones
     standing full height beside them, one column wide. Seen in the static
     screenshots and confirmed on the live site at both desktop and phone
     landscape. A cabinet is a portrait object; two columns wide and one row
     tall is the one shape that makes it stop being one.

     The column span goes with it -- span both and a featured tile is a
     quarter of the wall. Outside this block the band is ONE row, and there a
     featured tile still takes two columns, because two rows of a one-row band
     is the fault its own test is named for. */
  body.arcade .cabinets .slot.featured {
    grid-row: span 2; grid-column: span 1;
    /* AND IT ACTUALLY STANDS IN THEM. Spanning two rows only reserves the
       space; `.slot` is `align-self: center` with a content height, so the
       tile came out 234x312 floating in the middle of a 548px column. The
       reference's featured cabinet is full height, which is the whole point
       of it being featured. */
    height: 100%; align-self: stretch;
  }
  /* AND THE HOLE IT LEAVES GETS BACKFILLED. A two-row tile that will not fit
     in what is left of a column starts a new one, and the half-column behind
     it stays empty -- which showed as a gap in the middle of the wall the
     moment the featured tile grew. `dense` puts the next cabinet that fits
     into it, which is why the reference's wall has no holes in it.

     It changes where a tile is DRAWN and not what order it is in: the
     document order is untouched, so a screen reader and the keyboard still
     walk the wall in the merchandised order, and the tests that read the wall
     by name are reading that order rather than this one. */
  body.arcade.loud .cabinets { grid-auto-flow: column dense; }

  /* THE TILE TAKES ITS WIDTH FROM THE ROW'S HEIGHT, and this is the one
     place in the file where it can.

     `--tile` is 22vw. On a wide short desktop that is bigger than the row is
     tall, so the tile came out 309x249 -- LANDSCAPE -- and a 3:4 portrait
     picture inside it had its top and bottom thrown away by `cover`. Orca's
     ordinary tile is about 1:1.15 and it is a wall of upright machines;
     measured beside it, ours was a shelf of postcards.

     Here the row has a definite height (two rows of a band that is itself
     capped), so a grid item with an aspect-ratio resolves its own width from
     it -- no vw, no vh, and nothing to keep in step with the height of the
     rails above and below. `grid-auto-columns: auto` is what lets the column
     take that width instead of imposing --tile on it.

     The ceiling is not decoration either: on a tall narrow screen -- a tablet
     held upright -- half the band is 430px, and a tile sized purely off that
     would be 370px wide and two of them would fill the room.

     FIRST ATTEMPT, AND WHY IT IS NOT WHAT IS HERE: `grid-auto-columns: auto`
     with `aspect-ratio` and `height: 100%` on the slot. That is circular and
     the browser is right to refuse it -- a column's width is resolved before
     the row's height is known, so the track came out narrow, the slots kept
     the width their ratio asked for, and sixteen cabinets overlapped each
     other across the band. Measured, not guessed: they visibly sat on top of
     one another in the capture.

     So the tile is read off the viewport's HEIGHT instead, which is the move
     the sideways block makes and for the same reason -- height is the scarce
     axis whenever the band has to fit between two rails. 26vh puts a 234px
     tile in a 290px row at 1440x900, which is the upright machine the
     reference has. Clamped at both ends so a very tall display does not
     produce four enormous cabinets.

     BOUNDED ON BOTH AXES, and the first version was not. Read off the height
     alone, 26vh of a tablet held upright is 307px and the wall came back as
     two and a half enormous cabinets -- measured on an 820x1180 viewport,
     where it had been four. A tile is limited by whichever axis runs out
     first, which is the height on a wide desktop and the WIDTH on a tall
     tablet, so it is the smaller of the two and not either one alone. */
  body.arcade.loud .wall { --tile: clamp(160px, min(26vh, 22vw), 300px); }

  /* AND THE ART FILLS THE ROW IT IS GIVEN. Bounded as above, the tile can
     never be wider than about 26vh while its row is about 30vh tall, so the
     picture can be allowed to grow into the space without the letterboxing
     that made `flex: 0 1 auto` necessary in the first place -- the comment on
     that rule describes a 0.45 art box on a wide screen, and that could only
     happen while the tile's width came from 22vw alone.

     Held at 3:4 in a 430px row a 180px tile came out 330 tall and sat in the
     middle of it, so the wall had a hundred pixels of floor showing between
     the two rows. Orca's picture IS the tile, top to bottom. */
  body.arcade.loud .cabinets .cabinet img,
  body.arcade.loud .cabinets .cabinet .noart {
  /* A RATIO, NOT `auto`. THIS WAS THE BLANK LOBBY.
     `auto` means take your height from the image; the image is lazy, so it
     only loads once it is in the viewport; a tile 0 tall is never in the
     viewport, so it never loaded and stayed 0. Every tile, forever. The
     ratio is a floor -- flex and height:100% still win wherever the row has
     a definite height, so the picture fills the tile exactly as before. */
    aspect-ratio: 3 / 4; flex: 1 1 auto;
  }
  body.arcade.loud .cabinets .slot { align-self: stretch; }
}

/* FEWER AND BIGGER ON A PHONE. Most players are on one, and 21% of a 375px
   band is a 79px cabinet -- five thumbnails where the reference shows two and
   a half machines. The fraction is what changes; the row does not. */
@media (max-width: 700px) {
  .wall { --tile: clamp(150px, 44vw, 260px); }
  .cabinets { gap: 12px; }
}



@media (prefers-reduced-motion: no-preference) {
  .cabinets { scroll-behavior: smooth; }
}
/* A cabinet is portrait, like the reference's. The art is the product; the
   plate underneath is only there so a game with none is still a game.

   IT IS A MACHINE STANDING ON A FLOOR, not a picture in a card. The tile was
   a 1px border around an image -- so the only thing on the page with any
   depth was the art itself, and the page around it read as a catalogue. What
   makes it a cabinet: a moulded surround with a highlight along the top edge
   and shadow at the bottom, the art recessed INTO that surround behind glass,
   and a shadow cast down onto the floor it is standing on. */
.cabinet {
  display: flex; flex-direction: column;
  border: 0; border-radius: 14px; padding: 7px 7px 0;
  background:
    linear-gradient(180deg, rgb(255 255 255 / .13) 0%,
                            rgb(255 255 255 / .02) 34%,
                            rgb(0 0 0 / .45) 100%),
    var(--panel);
  text-decoration: none; color: var(--text); position: relative;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / .10),        /* the top of the moulding */
    0 0 0 1px rgb(0 0 0 / .55),                  /* its outside edge        */
    0 16px 22px -16px rgb(0 0 0 / .95);          /* onto the floor          */
  transition: transform .14s ease, box-shadow .14s ease;
}
/* Lit when you reach for it: the machine lifts, its shadow lengthens, and
   the season's colour comes up around the edge. */
.cabinet:hover {
  transform: translateY(-4px);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / .16),
    0 0 0 1px var(--accent),
    0 26px 30px -20px rgb(0 0 0 / .95),
    0 0 34px -10px var(--rail-glow);
}
.cabinet img, .cabinet .noart {
  display: block; width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  border-radius: 9px;
  /* Recessed. The art sits BEHIND the surround, not on top of it. */
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / .8),
              0 2px 10px rgb(0 0 0 / .55) inset;
}
/* The glass. One diagonal highlight across the top corner, at four percent --
   any more and it stops being glass and starts being a gradient. ::before so
   it paints UNDER the badges, which come later in the markup. */
.cabinet::before {
  content: ""; position: absolute; left: 7px; right: 7px; top: 7px;
  height: 46%; border-radius: 9px 9px 0 0; pointer-events: none;
  background: linear-gradient(146deg,
    rgb(255 255 255 / .085) 0%, rgb(255 255 255 / .02) 38%,
    rgb(255 255 255 / 0) 62%);
}
.cabinet .noart {
  display: grid; place-items: center; background: var(--sunken-2);
  font-size: 56px; font-weight: 800; color: var(--muted);
}
/* THE NAME PLATE. Engraved into the surround: sunk, hairline-lit along its
   top edge, and pushed to the bottom of the tile so a two-line name and a
   one-line name still leave two cabinets the same height. */
.cabinet .plate {
  display: block; margin: 8px -7px 0; padding: 9px 13px 11px;
  margin-top: auto;
  background: linear-gradient(180deg, rgb(0 0 0 / .35), rgb(0 0 0 / .12));
  border-radius: 0 0 14px 14px;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .06);
}
.cabinet .plate strong { display: block; letter-spacing: .01em; }
.cabinet .kind { font-size: 12px; color: var(--muted); }
/* Quieter than the game's own name, because it is not the game's name -- it
   is what the game is underneath. */
.cabinet .plate .maths-tag { display: block; font-size: 10px;
                             letter-spacing: .04em; color: var(--muted);
                             opacity: .8; }
/* Coming soon is shown and not openable -- dimmed, and it says which. */
.cabinet.soon { cursor: default; }
.cabinet.soon:hover { transform: none; border-color: var(--line); }
.cabinet.soon img, .cabinet.soon .noart { opacity: .35; }
.nothing { color: var(--muted); }

/* --------------------------------------------------------- the machine

   THE CABINET ITSELF. The reels had chrome and the thing holding them had
   none: five bordered boxes and a button, centred on the page background,
   with nothing to say where the machine stopped and the room started. It is
   now a moulded body -- lit along the top edge, in shadow at the bottom,
   standing on the floor -- with the reels recessed into it and the controls
   on a deck below. Same three ingredients as the lobby tiles, at the size a
   machine is, so the two surfaces are recognisably the same furniture. */
/* THE CABINET TAKES THE ROOM. It was 560px wide -- a postcard in the middle
   of a 27-inch screen, with the reels squeezed into a box a third the width
   of the furniture around them. A slot machine is the thing you are looking
   at, not a widget on a page.

   The canvas measures its own parent on resize, so widening this is all it
   takes for the reels to grow with it -- no JS number to keep in step. */
.machine {
  /* 100%, NOT 96vw. A viewport unit does not know about the 20px of padding
     its parent has, so on a 375px screen this asked for 360 inside a 335 slot
     and hung five pixels off the right edge -- with the page scrolling
     sideways on every game screen to show them.

     Latent until `.arcade-body` became a flex column for the lobby's height
     chain: `margin: 0 auto` on a flex item SUPPRESSES the stretch that had
     been quietly holding this to the available width, and the box fell back
     to its own max-width. Percent is measured against the parent's content
     box, which is the thing that actually has room in it. */
  max-width: min(1180px, 100%); margin: 0 auto; text-align: center;
  padding: 18px 18px 20px; border-radius: 20px;
  background:
    linear-gradient(180deg, rgb(255 255 255 / .12) 0%,
                            rgb(255 255 255 / .02) 22%,
                            rgb(0 0 0 / .45) 100%),
    var(--panel);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / .12),
    0 0 0 1px rgb(0 0 0 / .55),
    0 30px 40px -28px rgb(0 0 0 / .95);
}
/* WRAPS, because the maths line made this a three-item row on a surface
   that is mainly read on a phone. Back-link, title and disclosure in one
   non-wrapping flex row crowd at 360px, and the thing that loses is the
   title. The maths line takes a full basis below so it lands on its own row
   deterministically rather than wrapping only on the narrow screens somebody
   remembered to check. */
.machine-top { display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
               margin-bottom: 18px; }
.machine-top .back { color: var(--muted); text-decoration: none; }
/* The top glass: the game's name lit across the head of the machine. */
.machine-top h1 { margin: 0 auto; font-size: 22px; letter-spacing: .06em;
                  text-shadow: 0 0 18px var(--rail-glow); }

/* The maths line. QUIET ON PURPOSE -- this is a disclosure, not a promotion.
   Rendering a return in the accent at the size of a win would be advertising
   it, and a number that is honest at 13px becomes a claim at 24. */
/* THE DISCLOSURE. Scoped to `.machine-top` until now, which is where it used
   to live -- the block was moved below the machine and this selector was left
   behind, so it matched nothing and seven spans ran together into one
   unreadable sentence. Unscoped, and a column: one fact per line. */
.maths { display: flex; flex-direction: column; gap: 5px; margin: 12px 0 0;
         font-size: 12px; color: var(--muted);
         /* Ranged left, explicitly. The cabinet centres its own text and a
            wrapped qualifier inherited it, so the second line of "not a
            session" sat in the middle of the column under the first. */
         text-align: left; }
.maths-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 7px; }
/* The percentage and the words that say what it does not promise, on one
   line and at the size that makes the pair read as one statement. */
.maths-return { align-items: baseline; }
.maths-name { font-weight: 700; color: var(--text); }
.maths-ver  { font-variant-numeric: tabular-nums; }
.maths-rtp  { font-variant-numeric: tabular-nums; font-weight: 700;
              color: var(--text); }
.maths-win  { font-variant-numeric: tabular-nums; color: var(--text); }
/* The push rate is DIMMER than the winning rate, not hidden. Getting your
   stake back is a real part of how the machine feels and belongs on the page;
   it is simply not the headline, and setting it at the same weight as the
   winning rate would let it read as one. */
.maths-push,
/* Dimmer than the winning rate for the same reason the push is: both are
   real and neither is the headline. Pays-less-than-your-stake especially --
   set at the winning rate's weight it would read as a second kind of win. */
.maths-partial { font-size: 11px; }
.maths-note { font-size: 11px; flex: 1 1 12ch; }

.reels {
  display: flex; gap: 10px; justify-content: center; margin: 10px 0 16px;
}
/* A WINDOW ONTO A STRIP, which is what a reel is. The overflow:hidden is the
   whole mechanism -- the strip is taller than this and script slides it. */
.reel {
  flex: 1; max-width: 150px; aspect-ratio: 1;
  position: relative; overflow: hidden;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--sunken);
  color: var(--text);
}
/* inset:0 gives the strip a DEFINITE height, which is what makes flex-basis
   100% on a cell mean "one reel tall" rather than "as tall as the content".
   Without it the resting cell collapses and every cell is a different size. */
.reel .strip {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; will-change: transform;
}
.reel .cell {
  flex: 0 0 100%;
  display: grid; place-items: center; gap: 2px;
  font-size: 15px; font-weight: 800; letter-spacing: .06em;
}
/* Big. Emoji render well under their declared size, and a symbol that does
   not fill its window reads as a label rather than as the thing that landed. */
.reel .cell .face { font-size: 52px; line-height: 1; letter-spacing: 0; }
/* DRAWN ART, sized off the reel rather than fixed, so it stays right at
   whatever width the column gives the cell. The emoji rule above still
   applies to any symbol nobody has drawn. */
.reel .cell .face img { display: block; width: min(58%, 92px); height: auto; }
.reel .cell .name { font-size: 10px; color: var(--muted); }

/* The blur. Exactly one repeat of the strip per cycle, so it loops without a
   seam -- the 6 here is BLUR_REPEAT in play.js and the two must agree.
   Linear because a reel at speed is at speed; the easing belongs in the
   landing, where the drama is.
   The 0px fallback means an unmeasured reel simply does not blur, rather than
   the whole rule going invalid. */
@keyframes reel-blur {
  from { transform: translateY(0); }
  to   { transform: translateY(calc(var(--cell, 0px) * -6)); }
}
.reel.spinning .strip { animation: reel-blur 300ms linear infinite; }
/* OVERSHOOTS AND SETTLES. The final control point above 1 carries the strip
   just past its stop and lets it come back, which is what gives a reel weight
   -- a reel that decelerates smoothly to rest looks like a list scrolling.
   play.js puts one spare cell below the answer so there is something under
   the overshoot rather than the end of the strip. */
.reel.landing .strip {
  transition: transform var(--land, 800ms) cubic-bezier(.16, .84, .34, 1.08);
}

/* THE HELD REEL. Lit while everything waits on it.
   Fires whenever the first two reels match -- on the near miss exactly as
   often as on the win, because until this one lands they are the same
   picture. See `holding` in play.js for why that matters. */
.reel.holding { border-color: var(--accent); }
.reel.holding::after {
  content: ""; position: absolute; inset: 0; border-radius: 12px;
  box-shadow: inset 0 0 0 1px var(--accent);
  animation: reel-hold 620ms ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes reel-hold { from { opacity: .15; } to { opacity: .9; } }
.reel.win { border-color: var(--accent); background: var(--warn-bg); }
.reel.win .cell { color: var(--accent); }

/* STILL, for anybody who has asked their machine for that. The result is the
   point; the spinning is decoration, and decoration is the part that comes off
   when somebody says motion makes them ill. */
@media (prefers-reduced-motion: reduce) {
  /* The presses stay -- a button that does not move is a button you cannot
     tell you have hit -- but they stop being animated. */
  .controls .stake, .controls .spin, .cabinet, .rail a { transition: none; }
  .reel.spinning .strip { animation: none; }
  .reel.landing .strip { transition: none; }
  /* The hold is suspense, which is motion by another name. The border stays
     so the reel is still marked; the pulse goes. */
  .reel.holding::after { animation: none; opacity: .5; }
}

.outcome { min-height: 24px; color: var(--muted); margin: 0 0 14px; }
.outcome.won { color: var(--accent); font-weight: 700;
               text-shadow: 0 0 16px var(--rail-glow); }

/* THE CONTROL DECK. A row of buttons became a panel you put your hands on:
   sunk into the front of the cabinet, its own surface, the way the deck on a
   machine is a separate plane from the glass above it. */
.controls { display: flex; gap: 12px; align-items: center;
            justify-content: center;
            margin: 0 -18px -20px; padding: 14px 18px 16px;
            border-radius: 0 0 20px 20px;
            background: linear-gradient(180deg, rgb(0 0 0 / .38), rgb(0 0 0 / .14));
            box-shadow: inset 0 1px 0 rgb(255 255 255 / .07); }
/* SPIN TAKES ITS OWN ROW ON A PHONE. The four controls come to 361px of
   content in a 360px box, and with nowrap the last one -- SPIN -- hangs five
   pixels past the right edge of the screen. Wrapping it to a full-width row
   fixes the overflow AND is the better control: the thing a player presses
   most, at the width of their thumb, which is roughly where the reference
   puts it too. */
@media (max-width: 420px) {
  .controls { flex-wrap: wrap; }
  .controls .spin { flex: 1 0 100%; order: 2; }
}

/* The stake keys: round, domed, and they go DOWN when pressed. A key that
   does not move is a key you cannot tell you have hit on a phone. */
.controls .stake {
  /* flex-basis as well as width: in a flex row these were being squashed to
     ellipses, and a circle drawn with border-radius: 50% turns into an oval
     the moment its box stops being square. */
  flex: 0 0 46px; width: 46px; height: 46px; border-radius: 50%; border: 0;
  background: linear-gradient(180deg, #2c432f 0%, #1a281c 100%);
  color: var(--text); font-size: 20px; font-weight: 700; cursor: pointer;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .14),
              0 3px 0 rgb(0 0 0 / .6), 0 5px 10px rgb(0 0 0 / .5);
  transition: transform .07s ease, box-shadow .07s ease;
}
.controls .stake:hover { color: var(--accent); }
.controls .stake:active {
  transform: translateY(3px);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .1), 0 0 0 rgb(0 0 0 / .6);
}
/* The bet is a reading, so it gets the room's window like every other one. */
.bet { min-width: 128px; font-size: 22px; font-weight: 800;
       font-variant-numeric: tabular-nums;
       display: inline-flex; flex-direction: column; align-items: center;
       padding: 6px 14px 7px; border-radius: 10px; background: var(--well);
       box-shadow: inset 0 3px 8px rgb(0 0 0 / .9),
                   inset 0 0 0 1px rgb(255 255 255 / .05); }
.bet .cur { color: var(--muted); font-size: 15px; margin-right: 2px; }
/* THE SPIN BUTTON IS THE ONE THING ON THE PAGE THAT GETS TO SHOUT. Struck
   from the same brass as the marquee, domed, and it travels when pressed --
   the press is the point. */
.controls .spin {
  padding: 14px 32px; border: 0; border-radius: 12px;
  background: var(--brass); color: var(--on-accent);
  font: inherit; font-size: 17px; font-weight: 800; letter-spacing: .10em;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5),
              inset 0 -2px 4px rgb(0 0 0 / .3),
              0 4px 0 rgb(0 0 0 / .55),
              0 8px 20px -6px var(--rail-glow);
  transition: transform .07s ease, box-shadow .07s ease;
}
.controls .spin:active {
  transform: translateY(4px);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .4),
              inset 0 -2px 4px rgb(0 0 0 / .3);
}
/* Disabled means MID-SPIN, so it is held down rather than greyed out: the
   machine is working, not broken. */
.controls .spin[disabled] {
  opacity: .55; cursor: default; transform: translateY(4px);
  box-shadow: inset 0 2px 6px rgb(0 0 0 / .5);
}

.paytable { margin-top: 26px; text-align: left; }
.paytable summary { cursor: pointer; color: var(--muted); }
.paytable table { margin-top: 10px; }
.paytable .face { font-size: 17px; margin-right: 7px; }

/* ------------------------------------------------------- recent winners
   Third column, as the reference has it: kinds on the left, cabinets in the
   middle, who has been winning on the right. Drawn only when there are some. */
/* THE TICKER. Recessed like the pot and the purse, for the same reason: it
   is a run of figures, and the room only has one way of showing those. */
/* THE WINNERS BUTTON, where the reference has it: on the bar, not in the
   room. The list opens over the lobby instead of standing beside it. */
/* ABOVE THE WALL IT OPENS OVER.
 *
 * This sat on z-index 4 and so do the HOT and NEW badges, and the flyout is a
 * sibling of the tiles rather than of the header -- it lives in lobby.html,
 * inside `main`. Equal z-index means DOM order decides, the tiles come later,
 * and the badges painted straight through the winners list.
 *
 * Twelve, not a hundred: it has to clear the badges (4) and the favourite
 * heart (3) and nothing else. The mail panels (20), the wheel stage (40) and
 * the loading screen (60) must all still cover it. */
.winners-list { position: absolute; top: -6px; right: 0; z-index: 12; }
.winners-list > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 34px; padding: 6px 14px; border-radius: 9px;
  background: linear-gradient(180deg, #2b442e 0%, #172719 100%);
  color: #cfe8d6; font-size: 11px; font-weight: 800; letter-spacing: .14em;
  text-transform: uppercase;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .1), 0 2px 0 rgb(0 0 0 / .5);
}
.winners-list > summary::-webkit-details-marker { display: none; }
.winners-list > summary::before { content: "\1F451"; font-size: 13px; }
.winners-list > summary:hover { color: var(--text); }
.winners-list[open] > summary { color: var(--text); }

.winners-panel {
  position: absolute; top: 42px; right: 0; z-index: 12;
  width: min(300px, calc(100vw - 32px));
  max-height: min(58vh, 420px); overflow-y: auto;
  border: 0; border-radius: 14px;
  background: var(--sunken); padding: 14px 16px 16px;
  box-shadow: inset 0 3px 10px rgb(0 0 0 / .8),
              inset 0 0 0 1px rgb(255 255 255 / .05),
              0 1px 0 rgb(255 255 255 / .04);
}
/* A brass rule under the heading, the same metal as the marquee's edge. */
.winners-panel h2 {
  margin: 0 0 4px; padding-bottom: 10px; font-size: 12px; letter-spacing: .16em;
  color: var(--muted); text-transform: uppercase;
}
.winners-panel h2::after {
  content: ""; display: block; height: 2px; margin: 10px -16px 0;
  background: var(--brass); opacity: .35;
}
.winners-panel ol { list-style: none; margin: 0; padding: 0; }
.winners-panel li {
  display: grid; grid-template-columns: 1fr auto; gap: 0 8px;
  padding: 9px 0; border-top: 1px solid var(--line);
}
.winners-panel li:first-child { border-top: 0; }
/* THE AMOUNT IS THE ROW. It was 13px -- the same size as the name beside it --
   which undersold the one number anybody reads and put gold text at a size
   that wants 4.5:1. Measured against --sunken across every season, two of the
   twelve accents land under that: xmas at 4.46 and memorial at 4.19.
   At 19px/800 this is large text, where the floor is 3.0 and the worst season
   clears it comfortably. Bigger was the better answer anyway. */
.winners-panel .won {
  grid-column: 1; font-size: 19px; font-weight: 800; color: var(--accent);
  font-variant-numeric: tabular-nums; line-height: 1.15;
}
.winners-panel .won::before { content: "$"; font-size: 11px; color: var(--muted); }
.winners-panel .when { grid-column: 2; font-size: 11px; color: var(--muted); }
.winners-panel .whom { grid-column: 1 / -1; font-size: 13px; }
.winners-panel .what { grid-column: 1 / -1; font-size: 11px; color: var(--muted); }
.winners-panel .tagline { margin: 12px 0 0; font-size: 11px; color: var(--muted); }

@media (max-width: 640px) {
  /* STACKED, AND STRETCHED. `align-items: flex-start` is right for the wide
     layout -- it stops the rail growing to the height of the wall beside it.
     Turned on its side it means something else entirely: a column flex sizes
     its children to their CONTENT width, so the band measured the full width
     of every cabinet in it (3120px for sixteen) and there was nothing left
     over to scroll. min-width: 0 cannot reach that; it clamps a row flex.
     Stretching is what gives the band a width to be clipped against. */
  /* The lobby stacks at every width now -- see .lobby above. What is left
     here is only what a narrow screen needs on top of that. */
  .arcade-bottom { flex-wrap: wrap; }
  .winners-panel { flex-basis: auto; }

  /* WHO YOU ARE TAKES A QUARTER OF A PHONE, and it was taking it from the
     buttons. An avatar, a display name and a tier chip is 250 of 375 pixels,
     so the rail behind them wrapped one control at a time and read as a pile.
     The reference does not carry a name here at all; ours keeps it and gives
     it a ceiling, which is the smaller change and loses nothing -- the name
     is the player's own and they know it. */
  /* THREE TIDY LINES INSTEAD OF A RAGGED THREE. Who you are, then the
     buttons, then the purse. It wrapped one control at a time before -- a
     name, a tier and whichever button happened to fit beside them -- so the
     rail read as a pile. The reference stacks the same way: a cluster of
     buttons with the two readouts under it. */
  .arcade-bottom .who { flex-basis: 100%; }
  .arcade-bottom .name {
    /* inline-block, or max-width does nothing to a span and the ceiling is
       decorative. */
    display: inline-block; max-width: 14ch; overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom;
  }
  .arcade-bottom .tier { margin-left: 6px; padding: 2px 7px 3px;
                         font-size: 9px; }
  /* Smaller boxes, so more of the rail fits on a line. */
  .arcade-bottom .tasks,
  .arcade-bottom .mailbox,
  .arcade-bottom .cashback,
  .arcade-bottom .services,
  .arcade-bottom .news,
  .arcade-bottom .dailywheel,
  .arcade-bottom .bonus {
    min-width: 56px; min-height: 44px; padding: 4px 7px 5px;
    /* margin on both sides, so a wrapped row of buttons is spaced evenly
       rather than shunted left by a left-only margin. */
    margin: 0 3px;
  }
  /* THE PURSE GETS ITS OWN LINE AND THE WHOLE OF IT. It was pushed right by
     `margin-left: auto`, which on a wrapped row means a line with two
     readouts jammed against the right edge and nothing on the left. The
     reference stacks its two under the buttons; this centres them across. */
  .arcade-bottom .purse {
    margin-left: 0; flex-basis: 100%; justify-content: center;
  }

  /* THE POT WINDOWS PUSHED "EXIT" OFF THE SCREEN. Two recessed readouts on
     one line need more width than a phone has, and what got squeezed out was
     the way out of the game -- clipped past the right edge, on the surface
     where somebody most wants it. The pot takes its own line; the brand and
     the exit keep the first one to themselves. */
  .arcade-top { flex-wrap: wrap; padding: 10px 14px; }
  .arcade-top .winners { white-space: nowrap; }
  .arcade-top .pot { order: 3; flex-basis: 100%; margin: 8px 0 0;
                     justify-content: center; }
  .pot-one { flex: 0 1 auto; min-width: 0; padding: 4px 12px 5px; }
  .pot-figure { font-size: 18px; }
  /* The featured tile's phone behaviour lives at the FOOT of this file --
     these selectors tie with the desktop rule and lost to it on position.
     See the block down there for what that cost. */

  /* The deck is the width of the phone; the keys can have the room. */
  .controls { gap: 10px; }
  .machine { padding: 14px 12px 16px; }
  .controls { margin: 0 -12px -16px; padding: 12px 12px 14px; }
}

/* ------------------------------------------------------------- badges
   Top-left of the cabinet, as the reference has them. One at a time: a tile
   wearing HOT and NEW at once is a tile shouting, and the newer claim is the
   weaker one -- so HOT wins where both are true. */
.cabinet .badge {
  position: absolute; top: 14px; left: 0;
  padding: 4px 10px 4px 12px; border-radius: 0 4px 4px 0;
  font-size: 11px; font-weight: 800; letter-spacing: .08em;
  /* A TAG ON THE MACHINE, not a sticker on a photo. Square against the left
     edge and rounded away from it, so it reads as fixed to the cabinet. */
  box-shadow: 0 2px 8px rgb(0 0 0 / .6),
              inset 0 1px 0 rgb(255 255 255 / .3);
}
.cabinet .badge.hot { background: var(--warn); color: var(--on-accent); }
.cabinet .badge.new { background: var(--accent); color: var(--on-accent); }

/* The house's own pick, and it must not look like the measured badges.
   Different word, different weight, outlined rather than filled -- HOT and
   NEW are facts and are allowed to shout; this is a recommendation. */
.cabinet .badge.featured {
  background: rgb(0 0 0 / .55); border: 1px solid var(--accent);
  color: var(--accent); font-weight: 700; letter-spacing: .10em;
  box-shadow: none;
  /* Under any measured badge rather than beside it, so the two never read as
     one row of equal claims. */
  top: 44px;
}

/* A wider tile for the featured game. TOKENS ARE NOT ENOUGH HERE -- this one
   genuinely changes layout, which is why it is a merchandising feature and
   not a season. Featuring everything features nothing; that is a judgement
   the screen cannot enforce and the operator has to make. */
/* WIDER, AND ON THE SLOT, because the slot is what the grid lays out. This
   used to span two ROWS as well, which was right when the band had two of
   them; the band is one row now, and a tile asking for two opens an implicit
   second one and drags the band's height along with it.

   It was also on `.cabinet` until the heart needed somewhere to live outside
   the link -- a span on a box the grid is not laying out does nothing at all,
   quietly. */
.cabinets .slot.featured { grid-column: span 2; }
/* A featured tile is twice as wide and no taller, so its art covers rather
   than letterboxing -- which crops a little off a picture that is symmetrical
   about its middle. */
.cabinets .cabinet.featured img,
.cabinets .cabinet.featured .noart {
  /* A RATIO, NOT `auto`. THIS WAS THE BLANK LOBBY.
     `auto` means take your height from the image; the image is lazy, so it
     only loads once it is in the viewport; a tile 0 tall is never in the
     viewport, so it never loaded and stayed 0. Every tile, forever. The
     ratio is a floor -- flex and height:100% still win wherever the row has
     a definite height, so the picture fills the tile exactly as before. */
  aspect-ratio: 3 / 2; flex: 1 1 auto; min-height: 0;
}

/* A featured cabinet is lit before anybody touches it. Not a different tile:
   the same machine with its lights on, which is what being on the end of the
   row is supposed to mean. */
.cabinets .cabinet.featured {
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / .16),
    0 0 0 1px rgb(0 0 0 / .55),
    0 16px 22px -16px rgb(0 0 0 / .95),
    0 0 40px -14px var(--rail-glow);
}

/* ------------------------------------------------------------------ keno
   Eighty numbers on one screen. A grid rather than a scrolling list, because
   a ticket is chosen by looking at the whole board at once. */
.keno { max-width: 640px; margin: 0 auto; text-align: center; }
.board {
  display: grid; grid-template-columns: repeat(10, 1fr); gap: 5px;
  margin: 4px 0 16px;
}
.spot {
  aspect-ratio: 1; min-width: 0; padding: 0;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--sunken); color: var(--muted);
  font: inherit; font-size: 13px; font-weight: 700;
  font-variant-numeric: tabular-nums;
  cursor: pointer; transition: transform .08s ease, background .12s ease;
}
.spot:hover { border-color: var(--accent); color: var(--text); }
/* MARKED is the player's doing; DRAWN is the house's; HIT is where they meet
   and is the only one that pays. Three states, three appearances -- marked
   and drawn looking alike is how a player miscounts their own ticket. */
.spot.marked { background: var(--field); border-color: var(--accent);
               color: var(--accent); }
.spot.drawn  { background: var(--sunken-2); color: var(--text);
               border-color: var(--muted); }
.spot.hit    { background: var(--accent); border-color: var(--accent);
               color: var(--on-accent); transform: scale(1.06); }

.keno .controls .clear {
  padding: 10px 16px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--field); color: var(--muted); font: inherit; cursor: pointer;
}
.keno .controls .clear:hover { border-color: var(--warn); color: var(--warn); }
/* NOT .marked. That is what a marked NUMBER is called, and this paragraph
   sits inside .keno too -- so `.keno .marked` matched every marked spot on the
   board at the same specificity as `.spot.marked` and, being later in the
   file, quietly repainted their text back to muted. Gold numerals on a gold
   cell: the hits were unreadable and every rule involved looked correct on
   its own. One class name, one meaning. */
.keno .tally { margin: 12px 0 0; color: var(--muted); font-size: 13px; }
.keno .tally .cur { font-size: 11px; }

/* One card per pick count. The one matching what is marked comes forward --
   before script runs they are all simply readable, which is the point. */
.paycard { border: 1px solid var(--line); border-radius: 10px;
           padding: 10px 14px; margin-bottom: 8px; opacity: .5; }
.paycard.on { opacity: 1; border-color: var(--accent);
              background: var(--sunken); }
.paycard h3 { margin: 0 0 6px; font-size: 14px; display: flex;
              flex-wrap: wrap; gap: 10px; align-items: baseline; }
.paycard .rtp, .paycard .ceiling {
  font-size: 11px; font-weight: 600; color: var(--muted);
  letter-spacing: .04em;
}
.paycard table { margin: 0; }

@media (max-width: 520px) {
  .spot { font-size: 11px; border-radius: 6px; }
  .board { gap: 3px; }
}

/* ------------------------------------------------------- the seasons

   TOKENS ONLY. Each block below redefines colour custom properties and
   nothing else -- no positioning, no sizing, no display. A seasonal skin that
   moves furniture is a skin that breaks a layout nobody re-tested for that
   fortnight, and it will break it in the fortnight when the most people are
   looking.

   Every one stays DARK. The room is still an arcade in December. These shift
   the accent and the ambient wash; they do not turn the lights on.

   The accent carries the jackpot figure and the buttons, so every value here
   has to stay legible against --panel. A season that makes the pot hard to
   read is a season that made the product worse.

   EVERY ACCENT BELOW CLEARS 4.5:1 AGAINST --panel, not the 3.0 that a 20px
   bold figure would need. The pot is large; the winners panel is not -- it
   sets amounts in --accent at 13px/800, which is body size and wants the
   higher bar. The first pass of these reds sat at 3.74 and 3.98: fine for the
   pot, and quietly under the line for every winner's name beside it. The
   body-size use is the one to check a colour against, because it is the one
   that is easy to forget is there. */

/* A SEASON SETS TOKENS AND NOTHING ELSE. Every block below used to restate
   `background:` in full, so the moment the room grew a second layer -- a
   floor, a texture, a vignette -- eleven of the twelve seasons would have
   quietly dropped it, and the default would have been the only skin that
   looked right. It would have shipped in August and broken on 1 December.
   They set --room-glow now; the room composes itself once, above. */

body.arcade[data-season="xmas"] {
  --accent: #e05555;                    /* red on the near-black, not green */
  --rail: #7d3333;
  --room-glow: #33202a;
}

body.arcade[data-season="newyear"] {
  --accent: #d4af37;                    /* the gold, pushed brighter */
  --room-glow: #2b2740;
}

/* Valentine's. A deeper red than Mother's Day's pink on purpose: the two are
   the only warm-pink skins on the calendar and a player who saw both should be
   able to tell them apart. */
body.arcade[data-season="valentine"] {
  --accent: #e8687f;
  --rail: #7d3a49;
  --room-glow: #33191f;
}

body.arcade[data-season="patrick"] {
  --accent: #4caf72;
  --rail: #336b47;
  --room-glow: #1d3327;
}

body.arcade[data-season="easter"] {
  --accent: #c79ad8;
  --rail: #6b5a7d;
  --room-glow: #2e2438;
}

body.arcade[data-season="mother"] {
  --accent: #e08fa8;
  --rail: #7d4f5e;
  --room-glow: #33222a;
}

body.arcade[data-season="father"] {
  --accent: #5b8fc7;
  --rail: #3a5a7d;
  --room-glow: #1f2a38;
}

body.arcade[data-season="memorial"],
body.arcade[data-season="july4"] {
  --accent: #e05555;
  --rail: #3a4a7d;
  --room-glow: #232a40;
}

body.arcade[data-season="summer"] {
  --accent: #e0a84a;
  --room-glow: #332c1f;
}

body.arcade[data-season="labor"] {
  --accent: #b98a4a;
  --room-glow: #2c2620;
}

body.arcade[data-season="halloween"] {
  --accent: #e07b2a;
  --rail: #7d4a1c;
  --room-glow: #33241a;
}

body.arcade[data-season="thxgiv"] {
  --accent: #c9762f;
  --rail: #7d5227;
  --room-glow: #2e2419;
}

/* No block for "default" -- it is the stylesheet above this comment. A skin
   that has to restate the base is a skin that will drift from it. */

/* Sibling skins, under the maths line. Quiet: a fact a player is entitled to,
   not a cross-sell. */
.also-played { margin: 6px 0 0; font-size: 12px; }
.also-played a { color: var(--accent); }

/* ------------------------------------------------- the five-reel cabinet

   A GRID, not a row. Five windows of three rows each, and the whole thing
   sizes off its width so a phone gets a cabinet rather than a scrollbar. */
/* ONE WINDOW WITH FIVE REELS BEHIND IT, not five separate boxes sitting on a
   page. The glass is a single recess cut into the cabinet; the reels are
   what you see through it. */
/* NO GAP AND NO PADDING BETWEEN THE COLUMNS. The reels butt together and
   the divider is drawn on the field -- see CELL_ASPECT in reels-canvas.js
   and the three originals it was measured off. A gap here let the scene
   behind show through in four stripes, which is what made one window read
   as five boxes. */
.reels5 .window {
  /* AS MANY COLUMNS AS THE MACHINE HAS REELS. This was `repeat(5, 1fr)`, so
     Bite of London's sixth reel wrapped onto a second row -- invisible while
     the canvas runs, and exactly wrong for the reader without script, who
     gets these spans and nothing else. `--reels` comes from the template;
     `.linegrid-cells` already counts its columns this way, so this is the
     idiom the file has rather than a second one. */
  display: grid; grid-template-columns: repeat(var(--reels, 5), 1fr); gap: 0;
  margin: 10px 0 16px; padding: 0; border-radius: 12px; overflow: hidden;
  background: var(--well);
  box-shadow: inset 0 4px 14px rgb(0 0 0 / .95),
              inset 0 0 0 1px rgb(255 255 255 / .06),
              0 1px 0 rgb(255 255 255 / .05);
}
.reels5 .reel {
  position: relative; overflow: hidden;
  /* Three rows tall, and a row is NOT as tall as the column is wide. The
     originals measure about 1.2 across for every 1 down (Ancient Maya
     485x402, Golden Treasures ~205x170, Lucky Fortune 3 530x446), so three
     rows come to 3 / 1.2 = 2.5 column widths. aspect-ratio rather than a
     height, so it still falls out of the column width and nothing is
     measured twice.

     THE CANVAS RUNTIME COMPUTES THE SAME THING FROM CELL_ASPECT, and the
     two must not disagree: this is what a browser without canvas gets, and
     a fallback that lays out differently is a second cabinet.

     AND IT FOLLOWS THE ROW COUNT. A column is one wide and `rows / 1.2`
     tall, which is `1.2 / rows` written as a ratio -- 1.2/3 for the common
     machine, and 1.2/4 for Mexico Hot and Bite of London, which a literal
     `1 / 2.5` had a third too short. */
  aspect-ratio: 1.2 / var(--rows, 3);
  border: 0; border-radius: 0;
  background: linear-gradient(180deg, #131f15 0%, #0c160e 50%, #131f15 100%);
  box-shadow: inset -1px 0 0 rgb(0 0 0 / .55);
}
.reels5 .reel:last-child { box-shadow: none; }
/* The curve of the drum: each reel is darker at top and bottom, because a
   real one is turning away from you at both ends. One gradient, and it is
   what stops five flat rectangles reading as five flat rectangles. */
.reels5 .reel::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border-radius: 0;
  background: linear-gradient(180deg,
    rgb(0 0 0 / .55) 0%, rgb(0 0 0 / 0) 22%,
    rgb(0 0 0 / 0) 78%, rgb(0 0 0 / .55) 100%);
}
.reels5 .reel .strip {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; will-change: transform;
}
.reels5 .reel .cell {
  flex: 0 0 33.3333%;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 800; color: var(--muted);
}
/* SIZED OFF THE ROW HEIGHT, not the column width, and square either way.
   A cell is wider than it is tall now; sizing a picture off the width would
   make it overflow its own row. The extra width is air either side, which
   is exactly what the originals do with it. */
.reels5 .reel .cell img { display: block; height: min(84%, 64px); width: auto;
                          margin-inline: auto; }

/* The blur travels exactly one repeat, so it loops without a seam. Three
   rows per window means the repeat is three cells, not six. */
@keyframes reel5-blur {
  from { transform: translateY(0); }
  to   { transform: translateY(-100%); }
}
.reels5 .reel.spinning .strip { animation: reel5-blur 260ms linear infinite; }
.reels5 .reel.landing .strip {
  transition: transform var(--land, 800ms) cubic-bezier(.16, .84, .34, 1.08);
}

/* THE HELD REEL, lit while everything waits on it. Fires when the four
   already on screen leave a line live for a fifth -- never on the stop being
   waited for. See play.js. */
.reels5 .reel.holding { border-color: var(--accent); }
.reels5 .reel.holding::after {
  content: ""; position: absolute; inset: 0; border-radius: 10px;
  box-shadow: inset 0 0 0 1px var(--accent);
  animation: reel-hold 620ms ease-in-out infinite alternate;
  pointer-events: none;
}

/* A CELL THAT PAID. Marked individually rather than lighting the whole reel:
   on a twenty-line machine most of the window had nothing to do with the win,
   and colouring all of it would claim otherwise. */
.reels5 .cell.paid {
  background: var(--warn-bg);
  box-shadow: inset 0 0 0 2px var(--accent);
  border-radius: 8px;
}

.reels5 .bet { display: flex; flex-direction: column; align-items: center; }
.reels5 .per-line {
  font-size: 11px; color: var(--muted); font-weight: 600;
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .reels5 .reel.spinning .strip { animation: none; }
  .reels5 .reel.landing .strip { transition: none; }
  .reels5 .reel.holding::after { animation: none; opacity: .5; }
}


/* The canvas runtime. Sized by the script from its parent, so the cabinet is
   whatever width the page gives it. */
.reels5 .window { position: relative; }
/* `display: block` on a class BEATS the browser's own `[hidden]` rule, which
   is a plain attribute selector -- so the canvas kept its grid cell while
   hidden, and the fifth reel wrapped onto a second row. That only happens on
   the path where the runtime did NOT start: no canvas support, script
   blocked, a failed load. The fallback was broken in exactly the case it
   exists for, and every browser that works never showed it. */
.reels5 .reel-canvas { display: block; width: 100%; }
.reels5 .reel-canvas[hidden] { display: none; }
/* When the runtime is up the DOM reels go, rather than sitting behind it
   costing layout. They are the fallback, not a backdrop. */
.reels5 .window.live .reel { display: none; }

/* ==========================================================================
   THE ARCADE, IN THE REFERENCE'S OWN LANGUAGE

   Everything above this line is a well-made dark room. Put it beside the
   platform we are rebuilding and the difference is not polish, it is
   VOLUME: that surface is saturated, outlined, oversized and lit, and ours
   was tasteful. Tasteful reads as a dashboard.

   The furniture under /static/chrome is the real thing, lifted from the
   build we own -- the category rail, the ribbons, the readout capsule. It
   is PLATFORM chrome and carries no game's identity, which is why it is
   these pieces and not the character art beside them in the same atlases.
   ========================================================================== */

body.arcade.loud {
  --loud-ink: #ffffff;
  --loud-gold: #ffd24a;
  --loud-edge: #134f1b;         /* the outline every label is cut with */
  background:
    radial-gradient(1100px 700px at 50% 8%, #43a450 0%, transparent 62%),
    radial-gradient(800px 500px at 12% 88%, #21862f 0%, transparent 66%),
    radial-gradient(900px 600px at 88% 92%, #277732 0%, transparent 66%),
    radial-gradient(150% 120% at 50% 40%, rgb(0 0 0 / 0) 38%, rgb(0 0 0 / .62) 100%),
    linear-gradient(180deg, #13441a 0%, #0b2a0f 55%, #071f0a 100%);
  background-attachment: fixed;
}
/* Starfield, drawn rather than fetched: three sizes of dot on three scales,
   which is enough parallax to stop the sky being a gradient. */
body.arcade.loud::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    radial-gradient(1.6px 1.6px at 18% 22%, rgb(255 255 255 / .9), transparent),
    radial-gradient(1.2px 1.2px at 62% 12%, rgb(255 255 255 / .7), transparent),
    radial-gradient(1.8px 1.8px at 82% 34%, rgb(255 235 180 / .8), transparent),
    radial-gradient(1.1px 1.1px at 33% 58%, rgb(255 255 255 / .6), transparent),
    radial-gradient(1.4px 1.4px at 72% 72%, rgb(200 230 255 / .7), transparent),
    radial-gradient(1px 1px at 8% 66%, rgb(255 255 255 / .5), transparent),
    radial-gradient(1.5px 1.5px at 46% 86%, rgb(255 245 210 / .6), transparent);
}
/* LIFT THE STRUCTURE ABOVE THE STARFIELD, and only the structure.
   
   This was `> *`, which is every direct child of body -- and at (0,2,1) it
   outranks a panel's own `.mail-panel { position: fixed }` at (0,1,0). So the
   mailbox and tasks panels were quietly forced back to `relative`, fell into
   normal flow at the end of the document, landed under the footer and were
   clipped off the bottom of the screen. The panels looked broken; the rule
   that broke them is thirty lines away and mentions neither.
   
   Naming the three elements that actually need lifting leaves anything
   positioned deliberately alone. */
body.arcade.loud > main,
body.arcade.loud > footer { position: relative; z-index: 1; }

/* AND THE HEADER OUTRANKS BOTH, by one.
   All three sat on z-index: 1, so header and main tied and DOM order decided
   -- main is later, so the wall painted over anything hanging off the top
   rail. Invisible until something did: the jackpot explainer opens downward
   out of the header and came out behind the machines.
   Two, not thirty. It has to clear `main` and nothing else: the wheel stage
   (40), the mail panels (20) and the loading screen (60) are all siblings of
   these three and must still cover the rail. */
body.arcade.loud > header { position: relative; z-index: 2; }

/* THE DISPLAY FACE. No webfont: the reference's is Square 721, a licensed
   Bitstream typeface, and owning a build is not owning a font licence. What
   carries the look is not the letterforms anyway -- it is the treatment:
   heavy weight, a hard dark outline, a drop shadow and a glow, so type reads
   as an object with edges rather than as text.

   THE OUTLINE IS BUILT FROM SHADOWS, NOT FROM -webkit-text-stroke. In HTML
   that stroke is drawn CENTRED ON THE GLYPH EDGE AND OVER THE FILL -- and
   `paint-order: stroke fill`, which would put it behind, is an SVG property
   that does nothing here. A 3px stroke on 20px letters eats most of the
   letter: the first version of this rendered the brand as a dark smear and
   the jackpot figure as mud. Eight offset copies of the glyph behind the
   fill is the technique that actually works in a browser.

   It also rules out a gradient fill, since background-clip:text makes the
   glyph transparent and the outline shows straight through it. Flat gold
   with a hard edge is the honest trade, and at display sizes with a glow
   behind it the difference is not visible. */
.loud-outline {
  text-shadow:
    -2px -2px 0 var(--loud-edge),  2px -2px 0 var(--loud-edge),
    -2px  2px 0 var(--loud-edge),  2px  2px 0 var(--loud-edge),
    -2px  0   0 var(--loud-edge),  2px  0   0 var(--loud-edge),
     0   -2px 0 var(--loud-edge),  0    2px 0 var(--loud-edge),
     0 4px 0 rgb(0 0 0 / .55), 0 0 24px rgb(255 210 90 / .4);
}

/* ------------------------------------------------------- the top marquee */
.loud .arcade-top {
  background: linear-gradient(180deg, #1f6b29 0%, #103815 100%);
  padding: 10px 18px;
  box-shadow: 0 10px 30px -14px rgb(120 60 200 / .8), 0 2px 12px rgb(0 0 0 / .7);
}
.loud .arcade-top .winners {
  font-size: 22px; font-weight: 900; letter-spacing: .06em; color: #ffd24a;
  text-shadow:
    -2px -2px 0 var(--loud-edge),  2px -2px 0 var(--loud-edge),
    -2px  2px 0 var(--loud-edge),  2px  2px 0 var(--loud-edge),
    -2px  0   0 var(--loud-edge),  2px  0   0 var(--loud-edge),
     0   -2px 0 var(--loud-edge),  0    2px 0 var(--loud-edge),
     0 4px 0 rgb(0 0 0 / .55), 0 0 26px rgb(255 210 90 / .5);
}

/* THE READOUT, IN THE CAPSULE IT IS DRAWN IN. A border-image so one 258x69
   sprite stretches to any width without its gold rim stretching with it --
   the corners stay the size they were painted and only the straight run
   repeats. */
.loud .pot { gap: 14px; }
.loud .pot-one {
  flex-direction: row; align-items: baseline; gap: 10px;
  background: none; box-shadow: none; overflow: visible;
  padding: 10px 26px 12px;
  border: 22px solid transparent;
  border-image: url("chrome/capsule.png") 30 fill / 22px / 0 stretch;
  /* SHRINK RATHER THAN WRAP. The reference keeps every pot on one line
     across the top; ours grew to its content and each capsule took a row of
     its own, so two pots ate a third of a phone screen before a single
     machine was visible. min-width: 0 is what lets a flex item go under its
     content width at all. */
  flex: 0 1 auto; min-width: 0;
}
.loud .pot-one::before { content: none; }
.loud .pot-name {
  font-size: 11px; letter-spacing: .14em; color: #ffe9a8;
  text-shadow: 0 1px 0 rgb(0 0 0 / .8);
}
.loud .pot-figure {
  font-size: 28px; font-weight: 900; letter-spacing: .01em; color: #ffe07a;
  font-variant-numeric: tabular-nums;
  text-shadow:
    -2px -2px 0 #37160a,  2px -2px 0 #37160a,
    -2px  2px 0 #37160a,  2px  2px 0 #37160a,
    -2px  0   0 #37160a,  2px  0   0 #37160a,
     0   -2px 0 #37160a,  0    2px 0 #37160a,
     0 3px 0 rgb(0 0 0 / .6), 0 0 26px rgb(255 190 60 / .65);
}

/* ------------------------------------------------------- the category rail */
.loud .rail { flex: 0 0 auto; gap: 14px; }
.loud .rail a {
  background: none; box-shadow: none; padding: 0;
  display: grid; place-items: center; gap: 0;
  height: 76px; position: relative; color: transparent; font-size: 0;
}
.loud .rail a::before {                       /* the glossy disc */
  content: ""; position: static; width: 66px; height: 66px;
  background: url("chrome/icon-all.png") center / contain no-repeat;
  opacity: 1; border-radius: 0; transition: transform .12s ease;
  filter: drop-shadow(0 4px 6px rgb(0 0 0 / .6));
}
.loud .rail a.on::before, .loud .rail a:hover::before { transform: scale(1.08); }
/* STILL MATCHED ON THE END OF THE HREF, which is why turning the tabs from
   anchors into queries cost nothing here: "#reels" and "?style=reels" both
   end in the same word. */
.loud .rail a[href$="reels"]::before  { background-image: url("chrome/icon-slot.png"); }
.loud .rail a[href$="keno"]::before   { background-image: url("chrome/icon-keno.png"); }
.loud .rail a[href$="fish"]::before   { background-image: url("chrome/icon-fish.png"); }
.loud .rail a.on::before              { background-image: url("chrome/icon-all-on.png"); }
.loud .rail a.on[href$="reels"]::before { background-image: url("chrome/icon-slot-on.png"); }
.loud .rail a.on[href$="keno"]::before  { background-image: url("chrome/icon-keno-on.png"); }
.loud .rail a.on[href$="fish"]::before  { background-image: url("chrome/icon-fish-on.png"); }

/* FAVORITE AND TABLE HAVE NO DISC, so they are DRAWN rather than borrowed.
   There are five faces in static/chrome and these are not among them; the
   default is icon-all.png, which would have left FAVORITE wearing ALL's face
   and two tabs looking like the same tab. Commissioning art is somebody
   else's lane, so these are a struck disc and a glyph -- the same heart the
   cabinets already use, and a suit for table games. They read correctly at a
   glance and they are honestly not the reference's illustration. */
.loud .rail a[href$="favourite"]::before,
.loud .rail a[href$="table"]::before {
  background-image: none;
  display: grid; place-items: center;
  border-radius: 50%; font-size: 30px; line-height: 1;
  background: radial-gradient(circle at 50% 30%, #4a3a72 0%, #241a44 70%);
  box-shadow: inset 0 2px 0 rgb(255 255 255 / .25),
              inset 0 0 0 2px rgb(255 200 90 / .35);
  color: #ffd98c;
}
.loud .rail a[href$="favourite"]::before { content: "\2665"; }
.loud .rail a[href$="table"]::before     { content: "\2660"; }
.loud .rail a.on[href$="favourite"]::before,
.loud .rail a.on[href$="table"]::before {
  background-image: none;
  background: radial-gradient(circle at 50% 30%, #ff6f9c 0%, #a01c4c 70%);
  box-shadow: inset 0 2px 0 rgb(255 255 255 / .45),
              inset 0 0 0 2px rgb(255 220 140 / .8),
              0 0 20px -4px rgb(255 120 160 / .9);
  color: #fff4d0;
}
/* The word under the disc, cut with the same outline as everything else. */
.loud .rail a::after {
  content: attr(data-label); font-size: 12px; font-weight: 900;
  letter-spacing: .06em; color: #fff;
  text-shadow:
    -1.5px -1.5px 0 var(--loud-edge),  1.5px -1.5px 0 var(--loud-edge),
    -1.5px  1.5px 0 var(--loud-edge),  1.5px  1.5px 0 var(--loud-edge),
    -1.5px  0    0 var(--loud-edge),   1.5px  0    0 var(--loud-edge),
     0    -1.5px 0 var(--loud-edge),   0     1.5px 0 var(--loud-edge),
     0 2px 0 rgb(0 0 0 / .5);
}

/* ----------------------------------------------------------- the cabinets */
.loud .cabinet {
  border-radius: 16px; padding: 5px 5px 0;
  background:
    linear-gradient(180deg, #ffeaa0 0%, #d8a63a 14%, #8a5f14 52%, #f0d283 96%);
  box-shadow: 0 10px 22px -8px rgb(0 0 0 / .85), 0 0 0 2px #5a3c0c,
              inset 0 1px 0 rgb(255 255 255 / .6);
}
.loud .cabinet:hover {
  box-shadow: 0 16px 28px -10px rgb(0 0 0 / .9), 0 0 0 2px #5a3c0c,
              0 0 34px -4px rgb(255 200 80 / .8),
              inset 0 1px 0 rgb(255 255 255 / .7);
}
.loud .cabinet::before { display: none; }
.loud .cabinet img, .loud .cabinet .noart { border-radius: 12px; }
/* The name plate is a struck brass plaque, not a caption. */
.loud .cabinet .plate {
  margin: 4px -5px 0; padding: 7px 10px 9px; border-radius: 0 0 16px 16px;
  background: linear-gradient(180deg, #4a2a08 0%, #241203 100%);
  box-shadow: inset 0 1px 0 rgb(255 220 140 / .45);
}
.loud .cabinet .plate strong {
  font-size: 14px; font-weight: 900; letter-spacing: .02em; color: #ffe9a8;
  text-shadow: 0 1px 0 rgb(0 0 0 / .9);
}
.loud .cabinet .kind, .loud .cabinet .plate .maths-tag { color: #c9a86a; }

/* The badges are the real ribbons, so they are images and carry no text. */
.loud .cabinet .badge {
  top: 6px; left: 6px; padding: 0; border-radius: 0; background: none;
  box-shadow: none; color: transparent; font-size: 0; overflow: hidden;
}
.loud .cabinet .badge.hot { width: 64px; height: 34px;
  background: url("chrome/badge-hot.png") center / contain no-repeat; }
.loud .cabinet .badge.new { width: 64px; height: 34px;
  background: url("chrome/badge-new.png") center / contain no-repeat; }
/* The house's pick hangs on the OTHER corner. Both ribbons are real sprites
   now and both are pinned to the left, so a hot featured game wore one on
   top of the other -- and the two mean different things, which is the whole
   reason they look different. */
.loud .cabinet .badge.featured {
  top: 6px; left: auto; right: 6px; width: 40px; height: 62px; border: 0;
  background: url("chrome/badge-event.png") center / contain no-repeat;
}
/* COMING SOON dims the picture, and dimming it to 35% over a gold surround
   turned the tile into a gold slab with a letter on it. The screen goes
   dark and stays opaque; it is the game that is not ready, not the cabinet. */
.loud .cabinet.soon img, .loud .cabinet.soon .noart { opacity: 1; filter: grayscale(.7) brightness(.45); }
.loud .cabinet.soon { filter: saturate(.55); }

/* ------------------------------------------------------------ the winners */
.loud .winners-panel {
  background: linear-gradient(180deg, rgb(38 18 74 / .92), rgb(18 8 38 / .92));
  box-shadow: inset 0 0 0 2px rgb(255 200 90 / .35),
              0 12px 30px -14px rgb(0 0 0 / .9);
}
.loud .winners-panel h2 { color: #ffd98c; }
.loud .winners-panel .won { color: #ffd24a;
  text-shadow: 0 1px 0 rgb(0 0 0 / .9), 0 0 16px rgb(255 190 60 / .4); }
/* A game with no art gets a dark screen behind its letter, not the gold of
   the surround shining through where the picture should be. */
.loud .cabinet .noart {
  background: linear-gradient(180deg, #13441a, #071f0a); color: #6ca674;
}

/* -------------------------------------------------------------- the purse */
.loud .arcade-bottom {
  background: linear-gradient(180deg, #164e1e 0%, #0b2a0f 100%);
}
.loud .wallet {
  background: linear-gradient(180deg, #08210b, #030f05);
  box-shadow: inset 0 0 0 2px rgb(255 200 90 / .3),
              inset 0 3px 8px rgb(0 0 0 / .9);
}
.loud .wallet .figure { color: #ffd24a; }
.loud .arcade-bottom .avatar {
  background: linear-gradient(180deg, #ffeaa0, #d8a63a 60%, #8a5f14);
  box-shadow: 0 0 0 3px #1f6b29, 0 0 14px rgb(255 200 90 / .5);
  color: #2a1400;
}

/* ==========================================================================
   THE PHONE, AND WHY THESE RULES ARE PREFIXED

   Two of these already existed, in the max-width block far above, and
   NEITHER OF THEM EVER APPLIED. They were written as
   `.cabinets .cabinet.featured`, which is exactly the specificity of the
   desktop rule they were meant to beat -- and the desktop rule is further
   down the file, so it won every time. I checked the result on a phone,
   saw a full-width tile, and called it fixed; it was full-width because a
   one-column grid has nowhere else to put a two-column tile, and the art
   was being cropped through its own headline the whole time.

   Measuring rather than looking is what found it: grid-column computed to
   `span 2` and aspect-ratio to `auto` with the media query matching. So
   these carry a `body.arcade` prefix and win on specificity instead of on
   position, which no future reordering can take away.
   ========================================================================== */
@media (max-width: 640px) {
  /* A tile cannot be wider than a grid one column across. */
  body.arcade .cabinets .slot.featured,
  body.arcade .cabinets .cabinet.featured { grid-column: auto; grid-row: auto; }
  /* THE ART IS NOT PINNED TO 3/4 ANY MORE. This held the featured tile's
     picture to a portrait aspect on a phone, which was right when the tile
     was two columns wide and its art would otherwise have been cropped
     through its own headline. The band is one row of one-column tiles now and
     fills the height it is given, so pinning the aspect left the art 207px
     tall in a 403px tile and a slab of empty cabinet under the name plate.

     Removed rather than restated: with this gone the general rule applies and
     the featured tile's art fills and crops exactly like every other tile's.
     A copy here saying the same thing would only be another rule to keep in
     step. */

  /* THE READOUT WAS PUSHING THE WHOLE PAGE SIDEWAYS. Two capsules at their
     desktop size are 564px of content in a 375px viewport, so the document
     scrolled to 582 and every full-width bar -- the marquee, the deck --
     stopped dead at 375 with the room showing past the end of it. They
     wrap and they shrink. */
  /* ONE ROW, still. `flex-wrap: wrap` here is what stacked them: two pots on
     a 375px screen became two full-width capsules and 300px of chrome above
     the machines. Nowrap plus a shrinkable capsule fits both across, which
     is the shape the reference has. */
  body.arcade.loud .pot { flex-wrap: nowrap; justify-content: center; gap: 6px; }
  body.arcade.loud .pot-one {
    flex: 0 1 auto; min-width: 0; margin-left: 0;
    padding: 2px 6px 4px; border-width: 12px;
    border-image: url("chrome/capsule.png") 30 fill / 12px / 0 stretch;
    gap: 5px;
  }
  body.arcade.loud .pot-name { font-size: 8px; letter-spacing: .06em; }
  body.arcade.loud .pot-figure {
    font-size: 15px;
    /* The eight-way outline is four extra pixels of ink on every glyph, and
       at this size it closed the counters up. Kept, at one pixel. */
    text-shadow:
      -1px -1px 0 #37160a,  1px -1px 0 #37160a,
      -1px  1px 0 #37160a,  1px  1px 0 #37160a,
       0 2px 0 rgb(0 0 0 / .6), 0 0 14px rgb(255 190 60 / .6);
  }
  body.arcade.loud .arcade-top .winners { font-size: 18px; }

  /* And a belt: nothing on this surface is meant to scroll sideways, so if
     something else ever overflows it clips rather than dragging the rails
     out of alignment with the room. */
  body.arcade { overflow-x: hidden; }
}

/* THE PAINTED BACKDROP, WHEN THERE IS ONE.

   The room above is built out of gradients and a drawn starfield, and that
   is a real answer rather than a placeholder -- but an illustrated scene is
   the one part of the reference's look that no amount of CSS reaches. This
   is the slot it drops into: a file appears under static/backdrop and the
   lobby hangs it; the file goes away and the CSS room is underneath,
   untouched. See backend/backdrop.py.

   The scene sits BEHIND the room's own vignette rather than replacing it,
   so a bright painting still darkens at the corners and the lit furniture
   still reads as the brightest thing on the screen. A backdrop that took
   over the whole background would flatten everything standing on it. */
body.arcade.painted::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: var(--backdrop) center / cover no-repeat;
}
body.arcade.painted {
  background:
    radial-gradient(150% 120% at 50% 38%, rgb(0 0 0 / 0) 40%, rgb(0 0 0 / .68) 100%),
    radial-gradient(900px 500px at 50% 118%, rgb(0 0 0 / .6) 0%, transparent 70%),
    linear-gradient(180deg, rgb(20 10 43 / .25) 0%, rgb(12 6 32 / .55) 100%);
  background-attachment: fixed;
}
/* And the starfield goes: a painted sky has its own stars, and ours drawn
   on top of somebody's artwork is two skies. */
body.arcade.loud.painted::before { content: none; }

/* The sound switch, beside the way out. Quiet until it is the thing you are
   looking for -- it is a control, not a feature to advertise. */
.arcade-top .sound {
  background: none; border: 0; padding: 0 4px; margin-left: auto;
  font-size: 18px; line-height: 1; cursor: pointer; opacity: .75;
}
.arcade-top .sound:hover { opacity: 1; }
.arcade-top .sound.off { opacity: .45; }
/* The exit keeps its own place; the pot no longer has to carry the margin
   that pushed it there. */
.arcade-top .exit { margin-left: 12px; }

/* THE FREE-ENTRY CONTROL. Deliberately not a wheel, not a chest, and not
   animated: this is the one part of the product with no gamble in it, and
   dressing it as a spin would make it look like the rest. It says what it
   is, what it gives, and when the next one is. */
.arcade-bottom .bonus {
  display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
  margin-left: 14px; padding: 6px 14px 7px; border: 0; border-radius: 10px;
  background: linear-gradient(180deg, #2f7d4a 0%, #17452a 100%);
  color: #d8ffe6; font: inherit; cursor: pointer;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .18), 0 2px 0 rgb(0 0 0 / .5);
}
.arcade-bottom .bonus:active { transform: translateY(2px);
                               box-shadow: inset 0 1px 0 rgb(255 255 255 / .12); }
.arcade-bottom .bonus[disabled] { cursor: default; filter: saturate(.4)
                                  brightness(.8); }
.bonus-word { font-size: 11px; font-weight: 800; letter-spacing: .14em; }
.bonus-when { font-size: 13px; font-weight: 700;
              font-variant-numeric: tabular-nums; }

/* INSTALL. The reference's is a green download disc and it is the loudest
   thing on its rail, which is the wrong weight for us: this button leaves the
   browser and adds an icon to somebody's phone, and it should ask rather than
   shout. Quieter than the bonus beside it, the same shape as the rest.

   It is hidden unless the shell says the browser will actually install --
   see play.js and play/shell.html -- so it is never a control that does
   nothing when pressed. */
/* MOVED UP TO THE TOP RAIL, beside sound and the way out. It keeps its own
   look rather than borrowing the exit's, because it is a button that does
   something and the exit is a link that leaves -- but it is sized for a row
   of controls now instead of a rail of tiles. */
.arcade-top .install {
  display: inline-flex; flex-direction: row; align-items: center; gap: 5px;
  margin-left: 12px; padding: 4px 10px 5px; border: 0; border-radius: 8px;
  background: linear-gradient(180deg, #2b442e 0%, #172719 100%);
  color: #cfe8d6; font: inherit; cursor: pointer;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .1), 0 2px 0 rgb(0 0 0 / .5);
}
.arcade-top .install:hover { color: var(--text); }
.arcade-top .install:active { transform: translateY(2px);
                                 box-shadow: inset 0 1px 0 rgb(255 255 255 / .08); }
.arcade-top .install[disabled] { cursor: default; filter: saturate(.4)
                                    brightness(.8); }
.install-icon { font-size: 13px; line-height: 1; }
.install-word { font-size: 10px; font-weight: 800; letter-spacing: .12em; }

/* THE MAILBOX. An envelope with a count, and no count when there is nothing
   -- a badge showing zero is a badge asking to be tapped for no reason. */
.arcade-bottom .mailbox {
  position: relative; margin-left: 14px; padding: 6px 12px;
  border: 0; border-radius: 10px; cursor: pointer; font: inherit;
  background: linear-gradient(180deg, #2b442e 0%, #172719 100%);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12), 0 2px 0 rgb(0 0 0 / .5);
}
.arcade-bottom .mailbox:active { transform: translateY(2px); }
.mail-icon { font-size: 17px; line-height: 1; }
.mail-count {
  position: absolute; top: -6px; right: -6px; min-width: 18px; height: 18px;
  padding: 0 4px; border-radius: 9px; background: var(--warn);
  color: var(--on-accent); font-size: 11px; font-weight: 800; line-height: 18px;
}
.mail-count:empty { display: none; }

/* THE DAILY WHEEL, with its countdown under the caption. Tabular figures so
   a ticking clock does not jiggle its own width once a second. */
.arcade-bottom .dailywheel {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  margin-left: 10px; padding: 5px 11px;
  border: 0; border-radius: 10px; cursor: pointer; font: inherit;
  background: linear-gradient(180deg, #4b2c5e 0%, #281433 100%);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12), 0 2px 0 rgb(0 0 0 / .5);
}
.arcade-bottom .dailywheel:active { transform: translateY(2px);
                                    box-shadow: inset 0 1px 0 rgb(255 255 255 / .12); }
.arcade-bottom .dailywheel[disabled] { cursor: default; }
.dailywheel-icon { font-size: 15px; line-height: 1; }
.dailywheel-word { font-size: 10px; font-weight: 800; letter-spacing: .12em; }
.dailywheel-when {
  font-size: 10px; font-variant-numeric: tabular-nums; color: var(--muted);
  letter-spacing: .04em;
}
.arcade-bottom .dailywheel:not([disabled]) .dailywheel-when {
  color: #9be89b; font-weight: 800;
}

/* SERVICES. The support button, dressed like the rest of the rail. */
.arcade-bottom .services {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  margin-left: 10px; padding: 5px 11px;
  border: 0; border-radius: 10px; cursor: pointer; font: inherit;
  background: linear-gradient(180deg, #4a3a24 0%, #291f12 100%);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12), 0 2px 0 rgb(0 0 0 / .5);
}
.arcade-bottom .services:active { transform: translateY(2px);
                                  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12); }
.services-icon { font-size: 15px; line-height: 1; }
.services-word { font-size: 10px; font-weight: 800; letter-spacing: .12em; }

/* HOW, THEN WHAT. The method is a small label over the number rather than
   beside it -- three rows of "Phone: ..." reads as a form. */
.services-list li { display: flex; flex-direction: column; gap: 2px; }
.services-list .subject { font-weight: 700; }
.services-list a.subject { color: #ffd98c; }

/* NEWS. The mailbox's neighbour and its sibling in style -- both are a button
   that opens a list of text. No count badge: nothing here is addressed to one
   player, so there is nothing to have read. */
.arcade-bottom .news {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  margin-left: 10px; padding: 5px 11px;
  border: 0; border-radius: 10px; cursor: pointer; font: inherit;
  background: linear-gradient(180deg, #2c3a52 0%, #16202f 100%);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12), 0 2px 0 rgb(0 0 0 / .5);
}
.arcade-bottom .news:active { transform: translateY(2px);
                              box-shadow: inset 0 1px 0 rgb(255 255 255 / .12); }
.news-icon { font-size: 14px; line-height: 1; }
.news-word { font-size: 10px; font-weight: 800; letter-spacing: .12em; }

/* A PINNED NOTICE IS MARKED, not reordered into a second list. It is already
   first -- news.for_players sorts on it -- and this says why it is. */
.news-list li.pinned .subject::before {
  content: "\1F4CC"; margin-right: 6px; font-size: 11px;
}

/* ------------------------------------------------- how the jackpots work */
/* An (i) beside the pots. A `details`, so it opens with nothing loaded --
   the same promise the winners list keeps. */
/* Inside the pot span, so it sits with the figures it explains and follows
   them when they wrap to their own line. */
.pot-info { position: relative; flex: 0 0 auto; align-self: center; }
.pot-info > summary {
  list-style: none; cursor: pointer; width: 22px; height: 22px;
  display: grid; place-items: center; border-radius: 50%;
  font-size: 12px; font-weight: 900; font-style: italic;
  color: #ffe9a8; background: rgb(255 255 255 / .1);
  box-shadow: inset 0 0 0 1px rgb(255 220 140 / .45);
}
.pot-info > summary::-webkit-details-marker { display: none; }
.pot-info > summary:hover { background: rgb(255 255 255 / .18); }
.pot-info-panel {
  /* HUNG OFF THE RAIL, not off the (i).
     Anchored to the icon it ran off whichever edge the icon happened to be
     near -- the left one before the control moved in with the pots, the right
     one after. The rail spans the screen, so a panel pinned inside it cannot
     overflow either way, at any width, wherever the icon ends up sitting. */
  position: absolute; top: calc(100% + 6px); right: 12px; z-index: 30;
  width: min(340px, calc(100vw - 32px)); max-height: 60vh; overflow-y: auto;
  padding: 12px 14px 14px; border-radius: 14px; text-align: left;
  background: linear-gradient(180deg, rgb(38 18 74 / .98), rgb(18 8 38 / .98));
  box-shadow: inset 0 0 0 2px rgb(255 200 90 / .35),
              0 24px 40px -18px rgb(0 0 0 / .95);
}
.pot-info-panel h2 { margin: 0 0 8px; font-size: 13px; letter-spacing: .08em;
                     color: #ffd98c; }
.pot-info-panel ul { list-style: none; margin: 0 0 10px; padding: 0; }
.pot-info-panel li { padding: 8px 0; font-size: 12px;
                     border-top: 1px solid rgb(255 255 255 / .08); }
.pot-info-panel li:first-child { border-top: 0; }
.pot-info-panel strong { display: block; color: var(--text); }
.pot-info-where { display: block; font-size: 10px; letter-spacing: .1em;
                  text-transform: uppercase; color: #c9a86a; }
.pot-info-fact { display: block; margin-top: 3px; color: var(--muted); }
.pot-info-note { margin: 8px 0 0; font-size: 11px; color: var(--muted); }

/* ------------------------------------------------ the rail, as one thing */
/* SEVEN BUTTONS THAT WERE SEVEN SHAPES.
 *
 * Each of these grew when the feature behind it landed, so each carried its
 * own padding, its own caption size and its own idea of how tall a rail
 * button is -- and with all seven visible the row read as a pile rather than
 * a rail. The reference's is uniform: a picture over a caption, every item
 * the same box.
 *
 * LAST, AND DELIBERATELY. Each button keeps its own colour from its own rule
 * above; this sets only the geometry they should have agreed on, and it wins
 * on those properties by coming after at equal specificity. Nothing here
 * touches a background, so the rail stays legible as seven different
 * controls rather than seven copies of one.
 */
.arcade-bottom .tasks,
.arcade-bottom .mailbox,
.arcade-bottom .cashback,
.arcade-bottom .services,
.arcade-bottom .news,
.arcade-bottom .dailywheel,
.arcade-bottom .bonus {
  display: inline-flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px;
  min-width: 64px; min-height: 48px;
  margin-left: 8px; padding: 5px 9px 6px;
  border-radius: 10px;
}
.tasks-icon, .bonus-icon { font-size: 15px; line-height: 1; }
.mail-word {
  font-size: 10px; font-weight: 800; letter-spacing: .12em;
}
/* One caption size across the rail. These were 10px and 11px depending on
   which week the button was written. */
.tasks-word, .bonus-word, .cashback-word, .dailywheel-word,
.services-word, .news-word, .install-word, .mail-word {
  font-size: 10px; font-weight: 800; letter-spacing: .12em;
}
/* The count sat on the corner of a button that was icon-only and is now
   icon-over-caption, so it needs pulling back onto the box. */
.arcade-bottom .mailbox { position: relative; }

/* AND THE ROW ITSELF. It wrapped into whatever order it happened to fit in;
   a gap and a consistent baseline make the wrap look intended. */
.arcade-bottom { gap: 6px 0; }

/* AUTO START. Quieter than SPIN on purpose -- the loud key is the one a
   player presses, and this is the one they set and leave. Lit while running,
   so "is it still going" is answered by looking. */
.controls .auto {
  padding: 9px 16px 10px; border: 0; border-radius: 10px; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 800; letter-spacing: .1em;
  background: linear-gradient(180deg, #2c3a52 0%, #16202f 100%);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12), 0 2px 0 rgb(0 0 0 / .5);
}
.controls .auto:active { transform: translateY(2px);
                         box-shadow: inset 0 1px 0 rgb(255 255 255 / .12); }
.controls .auto.on {
  background: linear-gradient(180deg, #b4472a 0%, #6d2413 100%);
  color: #fff2e0;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .3), 0 2px 0 rgb(0 0 0 / .5),
              0 0 18px -4px rgb(255 120 60 / .8);
}

/* ---------------------------------------------------------- the info book */
/* The reference's in-game book: tabs across the top, a page under them.
   TABS WITHOUT SCRIPT -- radio inputs and labels, so it opens, turns and
   closes with nothing loaded. The inputs are visually hidden rather than
   display:none, which would take them out of the tab order entirely. */
.infobook { margin-left: auto; position: relative; }
.infobook > summary {
  list-style: none; cursor: pointer; width: 26px; height: 26px;
  display: grid; place-items: center; border-radius: 50%;
  font-size: 14px; font-weight: 900; font-style: italic;
  color: #ffe9a8; background: rgb(255 255 255 / .1);
  box-shadow: inset 0 0 0 1px rgb(255 220 140 / .45);
}
.infobook > summary::-webkit-details-marker { display: none; }
.infobook > summary:hover { background: rgb(255 255 255 / .18); }

.infobook-panel {
  position: absolute; right: 0; top: 34px; z-index: 25;
  width: min(420px, calc(100vw - 28px)); max-height: 70vh; overflow-y: auto;
  padding: 12px; border-radius: 14px; text-align: left;
  background: linear-gradient(180deg, rgb(30 22 12 / .99), rgb(16 11 6 / .99));
  box-shadow: inset 0 0 0 2px rgb(255 200 90 / .35),
              0 24px 40px -18px rgb(0 0 0 / .95);
}
/* THE TAB RULES ARE GONE, and gone rather than left lying about.
   `.infobook-tabs`, `#infotab-pays:checked ~ ...` and the rest addressed
   markup that no longer exists once the book became a carousel -- see the
   carousel block at the foot of this file. Dead selectors that still parse
   are the exact shape of the bug this file has a test for: a rule scoped to a
   home it no longer has, which matches nothing and looks fine. */

.infobook-note { margin: 0 0 8px; font-size: 12px; color: var(--muted); }
/* AND NOT SHOUTED IN THE NOTES EITHER. Uppercasing a one-word key read as
   emphasis; uppercasing a sentence-long description reads as shouting. */
.infobook-note strong { color: #ffd98c;
                        font-size: 11px; letter-spacing: .08em; }
.infobook-page .paytable { width: 100%; border-collapse: collapse;
                           font-size: 12px; }
.infobook-page .paytable th, .infobook-page .paytable td {
  padding: 4px 6px; text-align: right;
  border-top: 1px solid rgb(255 255 255 / .07);
}
.infobook-page .paytable thead th { color: var(--muted); font-size: 10px;
                                    letter-spacing: .1em; }
/* NO LONGER CAPITALISED HERE. `capitalize` existed to turn the raw key
   `cherry` into `Cherry`; the row carries the vendor's own description now and
   it arrives correctly cased, so the same rule was retitling every word of it
   -- "Stepped Mayan Pyramid With WILD Lettering". Casing belongs where the
   name is made. See backend/symbol_names.py. */
.infobook-page .paytable tbody th {
  text-align: left; display: flex; align-items: center; gap: 7px;
  font-weight: 700;
  /* AND OVERRIDE THE GLOBAL `th`, which uppercases every table heading. That
     is right for the column headings above -- SYMBOL, 5, 4, 3 -- and wrong
     for this one, because a ROW heading here is the vendor's sentence about
     its own art and shouting it makes the whole ladder unreadable. */
  text-transform: none;
}
.infobook-page .paytable img { border-radius: 5px; }

/* THE LINE PATTERNS, drawn as the shape they are. A list of row indices is
   not something anybody reads; a five-by-three grid with three cells lit is
   the same information in the form the reference uses. */
.linegrid { list-style: none; margin: 0; padding: 0;
            display: grid; grid-template-columns: repeat(auto-fill, 76px);
            gap: 8px; }
.linegrid li { display: flex; align-items: center; gap: 6px; }
.linegrid-no { font-size: 10px; color: var(--muted); width: 14px;
               text-align: right; font-variant-numeric: tabular-nums; }
.linegrid-cells {
  display: grid; gap: 1px; flex: 1;
  grid-template-columns: repeat(var(--reels), 1fr);
  grid-auto-flow: column; grid-template-rows: repeat(var(--rows), 1fr);
}
.linegrid-cells i { aspect-ratio: 1; border-radius: 2px;
                    background: rgb(255 255 255 / .08); }
.linegrid-cells i.on { background: var(--brass); }

/* THE LINK'S OWN LADDER. Same table as the base paytable, minus the art --
   a coin carries a number and not a picture, and the row heading is either
   a tier's name or the word prize. */
.infobook-page .linkpays tbody th { display: table-cell; }
.linkpays-share { color: var(--muted); font-variant-numeric: tabular-nums; }
.infobook-provenance { margin-top: 10px; opacity: .9; }

.infobook-rules { margin: 10px 0 0; padding-left: 18px; font-size: 12px;
                  color: var(--muted); }
.infobook-rules li { margin-bottom: 5px; }
.infobook-legal { margin: 10px 0 0; font-size: 11px; color: var(--muted);
                  opacity: .85; }

/* ------------------------------------------------------------- the deck */
/* THE TWO STANDING READOUTS. The reference's deck carries GAME WIN and TOTAL
   PLAY as labelled figures that do not move; ours had an unlabelled number in
   the middle and a sentence that the next round wrote over. */
.deck-label {
  display: block; font-size: 9px; font-weight: 800; letter-spacing: .14em;
  color: var(--muted); line-height: 1;
}
.deck-read {
  display: inline-flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 5px 12px 6px; border-radius: 9px; background: var(--well);
  box-shadow: inset 0 3px 8px rgb(0 0 0 / .9),
              inset 0 0 0 1px rgb(255 255 255 / .05);
}
.deck-figure {
  font-size: 17px; font-weight: 800; line-height: 1.05;
  font-variant-numeric: tabular-nums; color: var(--text);
}
/* Lit only when there is something to light for -- a zero in gold reads as a
   win of nothing. */
.deck-figure:not(:empty) { transition: color .2s ease; }

/* ----------------------------------------------------------- the wait */
/* A LOADING SCREEN, not a caption over the old page. This is the first thing
   a new player sees after their PIN, and for a couple of seconds it IS the
   product -- so it is the brand's own room with the mark in the middle of it,
   at the size the sign-in panel uses. */
.loading {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  background:
    radial-gradient(120% 90% at 50% 40%, rgb(22 62 32 / .96) 0%,
                    rgb(4 10 6 / .99) 70%),
    #050d07;
}
.loading-box { display: flex; flex-direction: column; align-items: center;
               gap: 26px; padding: 0 24px; max-width: 520px; width: 100%; }
/* The macro's own class is `wordmark-mark`; `.mark` matched nothing and the
   logo sat at its natural 360px over a 375px screen. BOTH MARKS ARE NAMED
   because the veil now draws the hero -- the same file the front door does,
   so it is already in cache -- and a rule that knew only the small one would
   have repeated that exact bug the moment the markup changed. */
.loading-box .wordmark-mark,
.loading-box .wordmark-hero {
  max-width: min(420px, 82vw); height: auto;
  filter: drop-shadow(0 10px 30px rgb(0 0 0 / .7));
}
.loading-word { margin: 0; color: #cfe8d6; font-size: 13px;
                letter-spacing: .18em; text-transform: uppercase; }
.loading-bar {
  display: block; width: min(420px, 82vw); height: 14px; border-radius: 8px;
  background: rgb(0 0 0 / .55); overflow: hidden;
  box-shadow: inset 0 2px 6px rgb(0 0 0 / .8), 0 0 0 1px rgb(255 255 255 / .09);
}
.loading-bar i {
  display: block; height: 100%; width: 40%; border-radius: 8px;
  background: linear-gradient(90deg, #2f7d4a, var(--accent), #2f7d4a);
}
@media (prefers-reduced-motion: no-preference) {
  .loading-bar i { animation: loading-sweep 1.1s ease-in-out infinite; }
}
/* ASKED FOR NO MOTION, so the bar states the fact instead of travelling: a
   full rail, which reads as "in progress" without anything moving. */
@media (prefers-reduced-motion: reduce) {
  .loading-bar i { width: 100%; opacity: .75; }
}
@keyframes loading-sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

/* ------------------------------------------------------------- the wheel */
/* A STAGE, NOT A PANEL. Everything else that opens over the arcade is read in
   a corner; this is watched, and it is the one screen in the product that
   asks a player to stop and look at it. So it takes the middle and dims what
   is behind it. */
.wheel-stage {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center;
  background: rgb(0 0 0 / .78);
  padding: 16px;
}
.wheel-box {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 18px 20px 20px; border-radius: 18px;
  background: linear-gradient(180deg, #10381c 0%, #071309 100%);
  box-shadow: inset 0 0 0 2px rgb(255 200 90 / .35),
              0 30px 60px -20px rgb(0 0 0 / .95);
  max-width: min(420px, 100%);
}
.wheel-title { color: #ffd98c; letter-spacing: .1em; font-size: 13px; }

.wheel-dial { position: relative; width: min(300px, 68vw); aspect-ratio: 1; }
.wheel-canvas { width: 100%; height: 100%; display: block;
                border-radius: 50%;
                box-shadow: 0 0 0 4px rgb(255 200 90 / .5),
                            0 0 34px -6px rgb(255 200 90 / .45); }

/* THE RIM, cut from the reference's own wheel art -- the gold ring and its
   rivets, with the middle taken out so our slices show through it.
   
   ONLY THE RING. The original art is a whole disc with eight EQUAL coloured
   wedges baked in, and our slices are drawn in proportion to real odds, so
   laying that disc under them would put a picture on screen that disagrees
   with the maths behind it. The ring carries none of that claim.

   PURELY DECORATIVE, and the layout does not depend on it: it is positioned
   over a canvas that is already sized and already carries its own gold
   shadow, so if the image never loads the wheel is exactly what it was
   before. That is deliberate -- an unloaded image must not collapse a
   dialog. */
.wheel-rim {
  position: absolute; inset: -4.5%;
  background: url("/static/chrome/wheel-rim.png") center / contain no-repeat;
  pointer-events: none;
}
/* The pointer is fixed and the WHEEL turns under it, which is the way a real
   one works and the only way the landing angle is worth computing. */
.wheel-pointer {
  position: absolute; top: -6px; left: 50%; transform: translateX(-50%);
  width: 0; height: 0;
  border-left: 11px solid transparent; border-right: 11px solid transparent;
  border-top: 20px solid #ffd98c;
  filter: drop-shadow(0 2px 3px rgb(0 0 0 / .8));
}

.wheel-result { margin: 0; min-height: 22px; text-align: center;
                font-weight: 800; letter-spacing: .04em; color: #d8ffe6; }
.wheel-result.won { color: #ffd98c; font-size: 20px; }

/* SIZED FOR A THUMB. Most players are on a phone, so these are tap targets
   before they are buttons: the close control measured 25x24 and SPIN 43 high,
   both under the ~44px a fingertip actually needs. The glyph did not have to
   grow -- the hit area did. */
.wheel-go {
  border: 0; border-radius: 12px; padding: 13px 34px; cursor: pointer;
  min-height: 48px; min-width: 132px;
  font: inherit; font-weight: 800; letter-spacing: .14em; font-size: 15px;
  color: #10250f;
  background: linear-gradient(180deg, #ffd98c 0%, #d99b27 100%);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5), 0 3px 0 rgb(0 0 0 / .5);
}
.wheel-go:active { transform: translateY(2px);
                   box-shadow: inset 0 1px 0 rgb(255 255 255 / .4); }
.wheel-go[disabled] { cursor: default; filter: saturate(.35) brightness(.75); }
.wheel-close {
  position: absolute; top: 2px; right: 2px;
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px;
  background: none; border: 0; color: var(--muted);
  font-size: 24px; line-height: 1; cursor: pointer;
}
.wheel-close:hover { color: var(--text); }

.mail-panel {
  position: fixed; right: 16px; bottom: 74px; z-index: 20;
  width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow-y: auto;
  border-radius: 14px; padding: 12px 14px 14px;
  background: linear-gradient(180deg, rgb(38 18 74 / .98), rgb(18 8 38 / .98));
  box-shadow: inset 0 0 0 2px rgb(255 200 90 / .35),
              0 24px 40px -18px rgb(0 0 0 / .95);
}
.mail-head { display: flex; align-items: center; justify-content: space-between;
             margin-bottom: 8px; }
.mail-head strong { letter-spacing: .1em; font-size: 13px; color: #ffd98c; }
.mail-close { background: none; border: 0; color: var(--muted);
              font-size: 20px; line-height: 1; cursor: pointer; }
.mail-list { list-style: none; margin: 0; padding: 0; }
.mail-list li { padding: 10px 0; border-top: 1px solid rgb(255 255 255 / .08); }
.mail-list li:first-child { border-top: 0; }
.mail-list .subject { display: block; font-weight: 700; }
.mail-list li.unread .subject::before {
  content: ""; display: inline-block; width: 7px; height: 7px;
  margin-right: 7px; border-radius: 50%; background: var(--warn);
}
.mail-list .body { display: block; margin-top: 3px; font-size: 13px;
                   color: var(--muted); white-space: pre-wrap; }
.mail-list .when { display: block; margin-top: 4px; font-size: 11px;
                   color: var(--muted); opacity: .8; }
.mail-list .who { font-size: 10px; letter-spacing: .1em; color: #c9a86a; }

/* THE TIER BADGE. Metal, because that is what the names are, and small --
   it is a label on a person, not a score they are being shown. */
.arcade-bottom .tier {
  margin-left: 8px; padding: 2px 9px 3px; border-radius: 999px;
  font-size: 10px; font-weight: 800; letter-spacing: .12em;
  color: #241206; text-transform: uppercase;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5),
              0 1px 3px rgb(0 0 0 / .5);
}
.tier-bronze   { background: linear-gradient(180deg, #e0a878, #9a5f2c); }
.tier-silver   { background: linear-gradient(180deg, #eef2f7, #9aa6b5); }
.tier-gold     { background: linear-gradient(180deg, #ffeaa0, #c8971c); }
.tier-platinum { background: linear-gradient(180deg, #f4f7ff, #b9c6de);
                 box-shadow: inset 0 1px 0 rgb(255 255 255 / .7),
                             0 0 14px rgb(200 220 255 / .5); }

/* TASKS. The same furniture as the mailbox, because it is the same idea --
   a list of things behind a button on the rail. */
.arcade-bottom .tasks {
  display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
  margin-left: 14px; padding: 6px 12px 7px; border: 0; border-radius: 10px;
  background: linear-gradient(180deg, #326b39 0%, #18371c 100%);
  color: #d8fddd; font: inherit; cursor: pointer;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .16), 0 2px 0 rgb(0 0 0 / .5);
}
.arcade-bottom .tasks:active { transform: translateY(2px); }
.tasks-word { font-size: 11px; font-weight: 800; letter-spacing: .14em; }
.tasks-count { font-size: 13px; font-weight: 700;
               font-variant-numeric: tabular-nums; }
.tasks-panel { right: 16px; }

/* INVITE A FRIEND. A code meant to be read aloud off one phone and typed into
   another, so it is set large, spaced, and in the tabular face -- the same
   treatment a customer ID gets at the counter, for the same reason. */
.refer-body { padding: 4px 2px 2px; }
.refer-terms { margin: 0 0 12px; font-size: 13px; color: var(--muted); }
.refer-mine { margin: 0; font-size: 11px; letter-spacing: .08em;
              color: var(--muted); text-transform: uppercase; }
.refer-code {
  margin: 2px 0 8px; font-size: 26px; font-weight: 800;
  letter-spacing: .18em; font-variant-numeric: tabular-nums;
  color: #ffe8a3;
}
.refer-count { margin: 0 0 14px; font-size: 12px; color: var(--muted); }
.refer-label { display: block; font-size: 12px; color: var(--muted);
               margin-bottom: 6px; }
.refer-row { display: flex; gap: 8px; align-items: center; }
.refer-row input {
  flex: 1 1 auto; min-width: 0; padding: 8px 10px; border-radius: 8px;
  border: 1px solid rgb(255 255 255 / .18); background: rgb(0 0 0 / .25);
  color: inherit; font: inherit; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase;
}
.refer-row button {
  flex: 0 0 auto; padding: 8px 14px; border: 0; border-radius: 8px;
  background: linear-gradient(180deg, #2f7d4a, #17452a); color: #d8ffe6;
  font: inherit; font-weight: 700; cursor: pointer;
}
.refer-said { margin: 8px 0 0; font-size: 12px; color: var(--muted);
              min-height: 1em; }

/* THE HUB'S TABS -- PLATFORM / REELS / FISH / KENO / TABLE, drawn only when a
   shop has filed something under more than one of them. Scrolls sideways
   rather than wrapping: this panel is opened on a phone held landscape, and a
   second row of tabs would push the first task off the bottom. */
.task-tabs { display: flex; gap: 6px; margin: 2px 0 10px;
             overflow-x: auto; scrollbar-width: none; }
.task-tabs::-webkit-scrollbar { display: none; }
.task-tab {
  flex: 0 0 auto; padding: 5px 12px; border: 0; border-radius: 999px;
  background: rgb(255 255 255 / .08); color: var(--muted);
  font: inherit; font-size: 11px; font-weight: 700; letter-spacing: .06em;
  cursor: pointer;
}
.task-tab.on { background: linear-gradient(180deg, #2f7d4a, #17452a);
               color: #d8ffe6; }

/* A TASK IS A BAR AND A NUMBER, and the number is the truth of it -- the bar
   is drawn from the same two figures rather than from anything of its own. */
.task-list .task-name { display: block; font-weight: 700; }
.task-list .task-figures { display: block; margin-top: 2px; font-size: 12px;
                           color: var(--muted);
                           font-variant-numeric: tabular-nums; }
.task-bar { display: block; height: 6px; margin-top: 6px; border-radius: 3px;
            background: rgb(255 255 255 / .1); overflow: hidden; }
.task-bar i { display: block; height: 100%; border-radius: 3px;
              background: linear-gradient(90deg, #6fd47d, #b9a24a); }
.task-list button {
  margin-top: 8px; padding: 6px 14px; border: 0; border-radius: 8px;
  background: linear-gradient(180deg, #2f7d4a, #17452a); color: #d8ffe6;
  font: inherit; font-weight: 700; cursor: pointer;
}
.task-list button[disabled] { filter: saturate(.3) brightness(.75);
                              cursor: default; }

/* A SHOP THAT IS NOT TRADING. Stated once, at the top, in the colour this
   surface uses for things that are not a win. Not a modal: it does not need
   dismissing and there is nothing to decide -- the machines simply will not
   take a bet. */
.shop-closed {
  margin: 0 0 16px; padding: 11px 14px; border-radius: 10px;
  background: var(--warn-bg); color: var(--warn);
  font-weight: 700; text-align: center;
  box-shadow: inset 0 0 0 1px rgb(217 139 74 / .35);
}

/* UNDER MAINTENANCE. Dimmed like a coming-soon tile, because both are
   unopenable -- but its plate says which, and it keeps a warm edge so it
   reads as a machine that is off rather than one that was never installed. */
.loud .cabinet.down { filter: saturate(.5); }
.loud .cabinet.down .kind { color: var(--warn); }
.loud .cabinet.down .plate {
  box-shadow: inset 0 1px 0 rgb(217 139 74 / .35);
}

/* Signed out because of a newer sign-in. Not an error -- nothing went wrong
   and nothing was lost -- so it is not styled as one. */
.signin-note {
  margin: 0 0 16px; padding: 10px 12px; border-radius: 10px;
  background: var(--warn-bg); color: var(--text); font-size: 13px;
  text-align: left; box-shadow: inset 0 0 0 1px rgb(217 139 74 / .3);
}

/* ------------------------------------------------------- the cashback wheel */
/* Two states in one control, the way the reference draws it: a ring showing
   progress toward the next rank for most of the week, and a live button with
   the time remaining once a wheel is actually waiting.

   THE RING IS A CONIC GRADIENT over --pct, so the progress is one custom
   property the script sets and no geometry is computed in JavaScript. */
.arcade-bottom .cashback {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: none; border: 0; padding: 2px 6px; cursor: pointer;
  color: var(--muted); font: inherit;
}
.cashback-ring {
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  background:
    conic-gradient(var(--accent) calc(var(--pct, 0) * 1%),
                   rgb(255 255 255 / .10) 0);
  position: relative;
}
.cashback-ring::after {
  content: ""; position: absolute; inset: 3px;
  border-radius: 50%; background: var(--well);
}
.cashback-pct {
  position: relative; font-size: 10px; font-weight: 800;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.cashback-word {
  font-size: 9px; letter-spacing: .10em; font-weight: 700;
}

/* READY IS A DIFFERENT THING, not a brighter version of the same thing. A
   wheel waiting to be taken is money the player already owns, so it gets the
   money colour and the ring fills. */
.cashback[data-ready] { color: var(--money); }
.cashback[data-ready] .cashback-ring {
  background: conic-gradient(var(--money) 100%, var(--money) 0);
  box-shadow: 0 0 12px rgb(1 174 2 / .55);
}
.cashback[data-ready] .cashback-pct { color: var(--text); font-size: 13px; }

/* Nothing to chase and nothing to take: the control is still worth showing --
   it is how a player learns the promotion exists -- but it must not look
   pressable. */
.cashback:disabled { cursor: default; opacity: .85; }

/* --------------------------------------------------------- the account page */
/* The one page that publishes both ladders. Deliberately plain: it is a
   paytable, and a paytable that looks like an advert is a paytable nobody
   believes. */
.account { max-width: 760px; margin: 0 auto; }
.account h2 {
  margin: 26px 0 6px; font-size: 13px; letter-spacing: .10em;
  text-transform: uppercase; color: var(--accent);
}
.account .small { font-size: 12px; }

.account-figures {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; margin: 14px 0 4px;
}
.figure-card {
  background: var(--well); border: 1px solid var(--line);
  border-radius: 10px; padding: 10px 12px;
}
.figure-card .label {
  display: block; font-size: 10px; letter-spacing: .10em;
  text-transform: uppercase; color: var(--muted);
}
.figure-card .figure {
  display: block; margin-top: 2px; font-size: 20px; font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.ladder { width: 100%; border-collapse: collapse; margin-top: 6px; }
.ladder th {
  text-align: left; font-size: 10px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid var(--line); padding: 6px 8px;
}
.ladder td {
  padding: 8px; border-bottom: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}
.ladder td:not(:first-child) { text-align: right; }

/* WHERE THEY STAND, and nothing about where they do not. The row is marked
   because it is a fact about them; no row is marked as the one to aim at. */
.ladder tr.here td { color: var(--money); font-weight: 700; }
.ladder tr.here td:first-child::before { content: "\\25B8  "; }

.account-back { margin-top: 24px; }
.account-back a { color: var(--accent); }

/* ==========================================================================
   THE PHONE, TURNED SIDEWAYS

   EVERY BREAKPOINT ABOVE THIS LINE IS A WIDTH. The one that is not asks for
   a MINIMUM height -- a tall screen gets a second row of machines -- and
   there was nothing at all for a short one.

   A phone held in landscape is 852x393: WIDER than the desktop breakpoint
   and shorter than two rails, so every phone rule missed and the room was
   drawn at its full desktop height into 393 pixels of it. Measured on an
   iPhone-sized viewport, the marquee and the deck took 204 of those between
   them, the category strip took 76 more, and the wall of machines got what
   was left -- four gold slivers with no picture in them. It was not a
   squeeze; there was no room for a cabinet at all.

   This is not a fallback. The platforms this is a copy of are landscape-only
   and the manifest now asks Android for landscape, so this is the shape the
   product is meant to be held in and the block below is where it is drawn.
   What it does is give the wall the screen: both rails, the category strip
   and every readout on them compress until the machines have the height,
   because the machines are the product and the rest is furniture around them.

   GATED ON HEIGHT, AND THAT IS THE FIX RATHER THAN A DETAIL. `orientation:
   landscape` on its own is true of every desktop window ever opened, and
   these rules on a 27-inch monitor would be absurd. `max-height: 500px` is
   what says SHORT -- and a short viewport wants this whatever is holding it,
   which is why the pair is the gate and the width is not mentioned. Nothing
   taller than 500px changes, so the desktop layout is exactly what it was.

   PREFIXED WITH `body.arcade.loud`, for the reason the phone block above
   gives at length: most of what is overridden here is a `.loud` rule, which
   ties on specificity and would then be decided by whichever came last in the
   file. Winning on weight is what survives somebody moving a block.
   ========================================================================== */
@media (orientation: landscape) and (max-height: 500px) {

  /* --------------------------------------------------------- the two rails
     A marquee and a deck, at the height a 393px screen can actually spare.
     Everything on them keeps its shape -- the capsule is still the capsule,
     the purse is still a lit window -- because a rail that changes character
     between orientations is two rails. */
  body.arcade.loud .arcade-top,
  body.arcade.loud .arcade-bottom { min-height: 0; padding: 3px 10px; gap: 8px; }
  body.arcade.loud .arcade-top .wordmark-mark { height: 30px; }

  body.arcade.loud .pot { gap: 8px; }
  body.arcade.loud .pot-one {
    padding: 2px 10px 3px; gap: 6px;
    /* The border IS the capsule -- a border-image, so the rim has to shrink
       with the box or the corners keep their painted size and the readout
       ends up a gold ring with three digits inside it. */
    border-width: 12px;
    border-image: url("chrome/capsule.png") 30 fill / 12px / 0 stretch;
  }
  body.arcade.loud .pot-name { font-size: 8px; letter-spacing: .08em; }
  body.arcade.loud .pot-figure {
    font-size: 17px;
    /* The eight-way outline is four extra pixels of ink on every glyph and at
       this size it closes the counters. Kept, at one pixel -- the same trade
       the narrow-phone block makes. */
    text-shadow:
      -1px -1px 0 #37160a,  1px -1px 0 #37160a,
      -1px  1px 0 #37160a,  1px  1px 0 #37160a,
       0 2px 0 rgb(0 0 0 / .6), 0 0 14px rgb(255 190 60 / .6);
  }
  body.arcade.loud .pot-info > summary { width: 18px; height: 18px;
                                         font-size: 10px; }
  body.arcade.loud .arcade-top .sound { font-size: 15px; }
  body.arcade.loud .arcade-top .exit { font-size: 13px; margin-left: 8px; }

  /* --------------------------------------------------------- the room floor
     The body loses most of its padding. On a screen this short 20px top and
     bottom is a tenth of the wall. */
  body.arcade.loud .arcade-body { padding: 6px 10px; }
  body.arcade.loud .lobby { gap: 6px; }

  /* The customer number stays -- it is what a clerk asks for and the whole
     reason it was moved out of the bottom rail -- at the size a caption is. */
  body.arcade.loud .player-id { gap: 6px; }
  body.arcade.loud .player-id-label { font-size: 9px; }
  body.arcade.loud .player-id-number { font-size: 12px; }
  body.arcade.loud .player-id-copy { padding: 2px 8px 3px; font-size: 9px; }

  /* ------------------------------------------------------ the category rail
     Discs at two thirds, captions under them, same row. The reference's rail
     in landscape is a strip of small faces and that is what there is room
     for: at 76px tall this alone was a fifth of the screen. */
  body.arcade.loud .rail { gap: 10px; }
  body.arcade.loud .rail a { height: 44px; }
  body.arcade.loud .rail a::before { width: 34px; height: 34px; }
  body.arcade.loud .rail a[href$="favourite"]::before,
  body.arcade.loud .rail a[href$="table"]::before { font-size: 18px; }
  body.arcade.loud .rail a::after { font-size: 9px; letter-spacing: .04em; }

  /* ----------------------------------------------------------- the machines
     THE TILE IS SIZED OFF THE HEIGHT NOW, and it has to be. `--tile` is a
     width, and every other rule in this file derives it from the WIDTH of the
     band -- which is right in portrait and on a desktop, where the height is
     the plentiful axis. Turned sideways the scarce axis swaps over: 22vw of
     an 852px screen is a 190px column in a band about 200px tall, so the art
     would be cropped to a letterbox and the cabinet would stop being a
     cabinet. Read off vh instead, the tile stays the portrait machine it is
     everywhere else and simply fits fewer of itself across. */
  body.arcade .wall { --tile: clamp(88px, 31vh, 200px); }
  body.arcade.loud .cabinets { gap: 8px; padding-bottom: 6px; }

  /* The plate carries the same three facts at the size the tile can hold
     them. Nothing is dropped -- the maths name in particular is a fact a
     player is entitled to while they are still choosing, and hiding it on the
     orientation the product is meant to be held in would be hiding it. */
  body.arcade.loud .cabinet { padding: 4px 4px 0; border-radius: 12px; }
  body.arcade.loud .cabinet .plate { margin: 3px -4px 0; padding: 4px 7px 5px;
                                     border-radius: 0 0 12px 12px; }
  body.arcade.loud .cabinet .plate strong { font-size: 11px; }
  body.arcade.loud .cabinet .kind { font-size: 9px; }
  body.arcade.loud .cabinet .plate .maths-tag { font-size: 8px; }

  /* The heart shrinks with the plate it sits on, and no further: 30px is
     about the floor for something a thumb has to hit without opening the
     machine underneath it. */
  body.arcade.loud .fave { right: 5px; bottom: 6px; }
  body.arcade.loud .fave button { width: 30px; height: 30px; font-size: 15px; }

  /* The ribbons are sprites, so they scale rather than reflow. */
  body.arcade.loud .cabinet .badge.hot,
  body.arcade.loud .cabinet .badge.new { width: 46px; height: 24px; top: 4px;
                                         left: 4px; }
  body.arcade.loud .cabinet .badge.featured { width: 28px; height: 44px;
                                              top: 4px; right: 4px; }

  /* WINNERS keeps its corner and loses its bulk. */
  body.arcade.loud .winners-list > summary { min-height: 26px;
                                             padding: 3px 10px; font-size: 9px; }
  body.arcade.loud .winners-list { top: -2px; }

  /* ------------------------------------------------------------- the deck
     Seven buttons, one row, at the size a short screen leaves them. 34px is
     under the 44 a fingertip wants and is the one compromise in this block:
     the alternative is a deck that eats a third of the wall, and every one of
     these opens a panel rather than spending money. SPIN and the stake keys,
     which do, keep their size below. */
  body.arcade.loud .arcade-bottom .tasks,
  body.arcade.loud .arcade-bottom .mailbox,
  body.arcade.loud .arcade-bottom .cashback,
  body.arcade.loud .arcade-bottom .services,
  body.arcade.loud .arcade-bottom .news,
  body.arcade.loud .arcade-bottom .dailywheel,
  body.arcade.loud .arcade-bottom .bonus {
    min-width: 48px; min-height: 34px; margin-left: 5px; padding: 3px 7px 4px;
    border-radius: 8px;
  }
  body.arcade.loud .tasks-icon, body.arcade.loud .bonus-icon,
  body.arcade.loud .mail-icon, body.arcade.loud .install-icon,
  body.arcade.loud .dailywheel-icon, body.arcade.loud .services-icon,
  body.arcade.loud .news-icon { font-size: 12px; }
  body.arcade.loud .tasks-word, body.arcade.loud .bonus-word,
  body.arcade.loud .cashback-word, body.arcade.loud .dailywheel-word,
  body.arcade.loud .services-word, body.arcade.loud .news-word,
  body.arcade.loud .install-word, body.arcade.loud .mail-word {
    font-size: 8px; letter-spacing: .06em;
  }
  body.arcade.loud .bonus-when, body.arcade.loud .dailywheel-when,
  body.arcade.loud .tasks-count { font-size: 9px; }
  body.arcade.loud .cashback-ring { width: 24px; height: 24px; }

  body.arcade.loud .arcade-bottom .avatar { width: 24px; height: 24px;
                                            font-size: 12px; }
  body.arcade.loud .arcade-bottom .name { font-size: 12px; max-width: 12ch;
                                          display: inline-block;
                                          overflow: hidden; white-space: nowrap;
                                          text-overflow: ellipsis;
                                          vertical-align: bottom; }
  body.arcade.loud .arcade-bottom .tier { margin-left: 5px; padding: 1px 6px 2px;
                                          font-size: 8px; }
  body.arcade.loud .arcade-bottom .purse { gap: 8px; }
  body.arcade.loud .wallet { padding: 2px 10px 3px; }
  body.arcade.loud .wallet .label { font-size: 8px; letter-spacing: .1em; }
  body.arcade.loud .wallet .figure { font-size: 14px; }

  /* ==================================================== the machine itself

     THE REELS ARE CAPPED BY THE HEIGHT, for the same reason the tile is. A
     five-reel window is five columns of `aspect-ratio: 1 / 3`, so its height
     is its width times about 0.6 -- and at the full 852px of a landscape
     phone that is a window 480 pixels tall on a 393 pixel screen. The reels
     ran off the bottom and the deck was two screens down, on a page with no
     scroll bar because it is inside a frame.

     Capping the WIDTH is what fixes the height, since the ratio is what ties
     them: 66vh across leaves the window about 40vh tall, and that is the
     number the whole page is budgeted around -- two rails, the machine's
     head, the sentence and the deck come to about 60vh, so the reels get
     what is left and the deck lands ON the screen rather than below it.

     AND THE CABINET IS NO LONGER MUCH WIDER THAN ITS WINDOW. A reel was
     `aspect-ratio: 1 / 3` -- three square cells -- so five made a 5:3
     window, and a 5:3 window as wide as an 852px screen is 500px tall. The
     note here used to say that using the empty face either side "would mean
     rectangular symbols, which is a change to what the machine IS".

     It does not, and the originals settle it. They draw a cell about 1.2
     wide for every 1 tall and keep the PICTURE square inside it, sized off
     the row and centred across the column -- so the window is 2:1 and no
     art is stretched. Ours now does the same (`aspect-ratio: 1 / 2.5` above,
     CELL_ASPECT in reels-canvas.js), which both matches them and takes most
     of the pressure off this cap: a 2:1 window as wide as an 852px screen is
     426px tall rather than 500. The cap stays because the budget below it
     still has to add up. */
  body.arcade.loud .machine { padding: 6px 10px 8px; border-radius: 14px; }
  body.arcade.loud .machine-top { margin-bottom: 4px; gap: 10px; }
  body.arcade.loud .machine-top h1 { font-size: 16px; letter-spacing: .04em; }
  body.arcade.loud .machine-top .back { font-size: 12px; }

  body.arcade.loud .reels5 .window {
    max-width: min(100%, 68vh); margin: 4px auto 6px; padding: 4px; gap: 3px;
  }
  /* The three-reel cabinet is square windows in a row, so its height is a
     third of its width and it needs the same treatment one step smaller. */
  body.arcade.loud .reels { margin: 4px 0 8px; gap: 8px; }
  body.arcade.loud .reels .reel { max-width: min(150px, 30vh); }
  body.arcade.loud .reel .cell .face { font-size: 36px; }

  body.arcade.loud .outcome { min-height: 14px; margin: 0 0 6px;
                              font-size: 12px; }

  /* THE KEYS KEEP THEIR SIZE, deliberately. Everything else in this block
     gives up height; the two things a hand actually presses to spend money do
     not, because a stake key that misses is a bet nobody meant to make. What
     gives instead is the READOUT beside them -- the deck's height was set by
     the three stacked lines inside TOTAL PLAY, not by the buttons, so setting
     that type smaller takes the row down without taking a target with it. */
  body.arcade.loud .controls:not(.deck) { margin: 0 -10px -8px; padding: 6px 10px 7px;
                               gap: 10px; border-radius: 0 0 14px 14px; }
  body.arcade.loud .controls:not(.deck) .spin { padding: 11px 24px; font-size: 15px; }
  /* WIDE ENOUGH FOR ITS OWN THIRD LINE. Narrowed to 96px the per-line figure
     wrapped -- "0.05 x 20" over "lines" -- and a wrapped readout is 28px
     taller than an unwrapped one, which made the READOUT the tallest thing on
     the deck and set the height of the whole row. Squeezing it made the deck
     bigger. */
  body.arcade.loud .bet { min-width: 118px; font-size: 16px;
                          padding: 2px 9px 3px; }
  body.arcade.loud .reels5 .per-line { font-size: 9px; white-space: nowrap; }
  body.arcade.loud .controls:not(.deck) .deck-read { padding: 2px 9px 3px; }
  body.arcade.loud .controls:not(.deck) .deck-figure { font-size: 14px; }
  body.arcade.loud .controls:not(.deck) .deck-label { font-size: 8px; }
  body.arcade.loud .controls:not(.deck) .auto { padding: 7px 13px 8px; font-size: 11px; }

  /* --------------------------------------------------------- the keno board

     EIGHTY NUMBERS, TURNED THE SAME WAY THE PHONE TURNED. Ten columns is
     eight rows deep -- about 500px of board on a 393px screen, with DRAW two
     screens below the numbers it draws against, which on a ticket you choose
     by looking at the whole board at once is not a scrolling problem, it is a
     different game.

     Twenty across and four down is the SAME eighty numbers in the SAME order
     -- the grid is row-major and the markup does not move -- in the shape the
     screen actually is. It is the layout every landscape keno board uses, and
     for this reason.

     Full width rather than the 640px the upright board is centred in: at 852
     across that is a 37px spot, which is a number a thumb can hit. Held to
     640 it would be 28. */
  body.arcade.loud .keno { max-width: 100%; }
  body.arcade.loud .board { grid-template-columns: repeat(20, 1fr); gap: 4px;
                            margin: 2px 0 8px; }
  body.arcade.loud .spot { font-size: 12px; border-radius: 6px; }
  body.arcade.loud .keno .tally { margin-top: 6px; font-size: 11px; }

  /* The paperwork stays below the fold, which is where it was put on purpose
     -- see the comment in cabinet.html. It is one scroll away, in full. */
  body.arcade.loud .maths { margin-top: 8px; }
}

/* ==========================================================================
   TURN IT SIDEWAYS

   The manifest asks Android for landscape and the shell asks the Orientation
   API for it, and NEITHER OF THOSE REACHES AN iPHONE: Safari has never
   implemented manifest orientation, and screen.orientation.lock rejects
   there whether the app was installed or not. On the platform most of these
   players are on, the only thing that can ask for landscape is the screen.

   So this is the prompt every reference platform shows, and it is CSS with
   no script behind it: a media query knows the orientation, and a thing that
   works with nothing loaded cannot be broken by anything failing to load.

   WHO IT IS FOR, exactly. `pointer: coarse` and 500px of width together mean
   a phone: a tablet held upright is a fine way to read a lobby and a desktop
   window dragged narrow is not a phone at all. Getting that gate wrong is
   how a rotate prompt ends up in front of somebody who has nothing to
   rotate.
   ========================================================================== */
.rotate { display: none; }

@media (orientation: portrait) and (pointer: coarse) and (max-width: 500px) {
  .rotate {
    position: fixed; inset: 0; z-index: 100;
    display: flex; align-items: center; justify-content: center;
    /* The brand's own room, not a grey sheet over the app. This is a screen
       a player sees before they see anything else, so it is the loading
       screen's twin rather than a warning. */
    background:
      radial-gradient(120% 90% at 50% 40%, rgb(22 62 32 / .98) 0%,
                      rgb(4 10 6 / 1) 70%),
      #050d07;
  }
}

.rotate-box { display: flex; flex-direction: column; align-items: center;
              gap: 22px; padding: 0 28px; text-align: center; }
.rotate-box .wordmark-mark { max-width: min(300px, 72vw); height: auto; }

/* A PHONE, DRAWN. Two rounded rectangles and no request: a picture of the
   thing being asked for, which says it in every language the room is read
   in. */
.rotate-phone {
  display: block; width: 46px; height: 76px;
  border: 3px solid #ffd98c; border-radius: 10px;
  box-shadow: 0 0 22px -4px rgb(255 200 90 / .6);
  position: relative;
}
.rotate-phone::after {
  content: ""; position: absolute; left: 50%; bottom: 4px;
  width: 16px; height: 3px; margin-left: -8px; border-radius: 2px;
  background: #ffd98c;
}
/* It turns, because the instruction IS the turn. Ninety degrees, held at
   each end, so it reads as a gesture rather than as a spinner. */
@media (prefers-reduced-motion: no-preference) {
  .rotate-phone { animation: rotate-hint 2.4s ease-in-out infinite; }
}
@keyframes rotate-hint {
  0%, 30%   { transform: rotate(0deg); }
  55%, 85%  { transform: rotate(-90deg); }
  100%      { transform: rotate(0deg); }
}
/* ASKED FOR NO MOTION, so it states the position instead of travelling to it:
   already turned, which is the instruction. */
@media (prefers-reduced-motion: reduce) {
  .rotate-phone { transform: rotate(-90deg); }
}

.rotate-word { margin: 0; color: #ffe9a8; font-size: 15px; font-weight: 800;
               letter-spacing: .14em; text-transform: uppercase;
               line-height: 1.5; }
.rotate-note { margin: 0; color: #9ec4a6; font-size: 12px; letter-spacing: .04em; }

/* ==========================================================================
   ORCA FIDELITY

   The room was built against Golden Dragon and Troy's reference is Orca. They
   are cousins, not twins, and the differences are not taste -- they are
   measurable, and every one below was read off the screens in
   ~/Downloads/Game Screens/orca by cropping the element and putting it beside
   ours rather than by squinting at the two whole pages.

   WHAT IS COPIED IS SHAPE AND ARRANGEMENT, NEVER THE PALETTE. Orca is cyan
   and violet on a night-blue resort floor; this is TRIPLE G's, which is gold
   on green, and a lobby that borrows the competitor's colours is not a better
   copy, it is the wrong product. So: their proportions, their spacing, their
   pedestal under a live tab, their jackpot plate over a cabinet -- in our
   metal.

   PREFIXED `body.arcade.loud`, like the sideways block above it, so these win
   on weight rather than on being last in the file.
   ========================================================================== */

/* ------------------------------------------------------- the category rail

   MEASURED, NOT GUESSED. Orca's lobby renders at about 2053 CSS pixels wide
   in the reference capture; its six tabs sit on a 217px pitch with a ~99px
   icon, which is a pitch of 10.6% of the viewport and an icon of 4.8%. Ours
   was a 66px disc on an 81px pitch -- the icon was right and the pitch was
   half what it should be, so the rail read as four buttons huddled together
   rather than as a row of destinations.

   The other measured difference is the ACTIVE STATE, and it was backwards.
   Our lit tab is icon-*-on.png, which is the warm purple-and-gold face; the
   resting tabs are the ones with a cyan ring painted into them. So the tab
   you were NOT on glowed and the one you were on did not. Orca settles it
   with light: the live tab is bigger, sits on a lit pedestal, and everything
   else is turned down. */
/* A GROUND UNDER IT. Orca's rail sits on the dark navy of its deck, and that
   is not decoration -- it is what lets a lit pedestal read as lit. Ours sat
   straight on the bright green mosaic of the wall, so the glow under the live
   tab was washed out by the background it was glowing against, and the whole
   rail looked weaker than the machines above it. Cropped side by side that
   was the clearest difference of the six. */
body.arcade.loud .rail {
  gap: 44px; padding: 6px 26px 4px; border-radius: 14px;
  background: linear-gradient(180deg,
                rgb(4 14 7 / 0) 0%, rgb(4 14 7 / .55) 24%,
                rgb(4 14 7 / .62) 76%, rgb(4 14 7 / 0) 100%);
}

/* A LINE BETWEEN THEM, as the reference has. Drawn as a background rather
   than a border so it can fade out at both ends -- a hard 76px rule between
   two icons is a table, and Orca's divider is a short soft stroke. */
body.arcade.loud .rail a + a {
  background: linear-gradient(180deg,
                rgb(255 220 140 / 0) 0%, rgb(255 220 140 / .45) 28%,
                rgb(255 220 140 / .45) 72%, rgb(255 220 140 / 0) 100%)
              left center / 2px 64% no-repeat;
  margin-left: 0; padding-left: 22px;
}

/* THE PEDESTAL. Orca puts a lit ellipse under the live tab, between the icon
   and its caption, and it is the single thing that makes the rail read as a
   place you are standing rather than a row of pictures. Ours is the season's
   own gold, so it moves in December with everything else. */
body.arcade.loud .rail a.on {
  background-image:
    radial-gradient(64% 30% at 50% 68%,
                    var(--rail-glow) 0%, rgb(0 0 0 / 0) 72%);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
}
body.arcade.loud .rail a.on + a { background-position: left center, center; }

/* TURNED DOWN, so the live one is the bright one. This is the fix for the
   inversion described above and it costs no new art: the resting tabs lose a
   little colour and light, the live tab keeps all of both and grows. */
body.arcade.loud .rail a::before { filter: saturate(.7) brightness(.78); }
body.arcade.loud .rail a:hover::before { filter: saturate(.9) brightness(.92); }
body.arcade.loud .rail a.on::before {
  filter: none; transform: scale(1.12);
  /* The lit disc throws light of its own, which is what a pedestal is light
     FROM rather than a shape drawn under it. */
  filter: drop-shadow(0 4px 12px var(--rail-glow));
}
/* THE FAVOURITE FACE. There are five painted discs in static/chrome and
   none of them is a heart, so this tab has always been a drawn one -- and
   drawn flat, it was the one dark button in a row of lit ones. Orca's is a
   bright star that reads as lit as its neighbours. Same trick, our colours:
   a struck gold disc with the heart cut into it. */
body.arcade.loud .rail a[href$="favourite"]::before {
  /* The four painted discs are a glossy dome with a bright rim; a flat gold
     coin with a maroon heart on it -- the first thing tried here -- sat in
     the row looking like a different product. Same dome, same rim, and the
     heart is the lit thing on it. */
  background: radial-gradient(circle at 50% 26%, #7a4a86 0%, #3a1c56 62%,
                              #1d0f2e 100%);
  box-shadow: inset 0 2px 0 rgb(255 255 255 / .28),
              inset 0 0 0 2px rgb(150 240 220 / .5),
              0 3px 8px rgb(0 0 0 / .6);
  color: #ff6f9c;
  text-shadow: 0 0 12px rgb(255 110 160 / .9), 0 1px 0 rgb(0 0 0 / .6);
}
body.arcade.loud .rail a.on[href$="favourite"]::before {
  background: radial-gradient(circle at 50% 28%, #fff3cd 0%, #ff7fa4 40%,
                              #a01c4c 100%);
  color: #fff4d0;
  box-shadow: inset 0 2px 0 rgb(255 255 255 / .55),
              inset 0 0 0 2px rgb(255 220 140 / .8),
              0 0 22px -4px rgb(255 120 160 / .9);
}

body.arcade.loud .rail a::after { color: #bcd8c4; }
body.arcade.loud .rail a.on::after {
  color: #fff6d8;
  text-shadow:
    -1.5px -1.5px 0 var(--loud-edge),  1.5px -1.5px 0 var(--loud-edge),
    -1.5px  1.5px 0 var(--loud-edge),  1.5px  1.5px 0 var(--loud-edge),
    -1.5px  0    0 var(--loud-edge),   1.5px  0    0 var(--loud-edge),
     0    -1.5px 0 var(--loud-edge),   0     1.5px 0 var(--loud-edge),
     0 2px 0 rgb(0 0 0 / .5), 0 0 16px var(--rail-glow);
}

/* --------------------------------------------------------- the cabinets

   THE ONE THAT MATTERS ON A DESKTOP: our tiles were LANDSCAPE.

   Measured at 1440x900 the tile came out 309 wide by 249 tall -- 1:0.8 -- and
   the art inside it is a 3:4 portrait picture, so `cover` was throwing away
   the top and bottom of every character in the catalogue to fill a letterbox.
   Orca's ordinary tile is about 1:1.15 and its featured one about 1:1.7. It
   is a wall of upright machines; ours was a shelf of postcards.

   THE CAUSE is that `--tile` is a WIDTH -- 22vw -- while the band's height
   comes from the row. On a wide short screen 22vw is bigger than the row is
   tall and the tile has to give the difference back out of the picture. The
   sideways block above hit the same thing on a phone and solved it by reading
   the tile off vh; here the fix can be better than that, because in the
   two-row band the ROW HAS A DEFINITE HEIGHT and a grid item with an
   aspect-ratio can simply take its width from it. No viewport arithmetic, no
   number to keep in step with the rails above and below. See the block inside
   the tall-screen media query further down. */

/* THE LIT EDGE. Orca outlines every cabinet in a bright ring, which is what
   separates a machine from the floor behind it. Ours had a gold moulding and
   no light, so on a busy background the tiles ran together. Same idea, our
   metal: the season's gold, not their cyan. */
body.arcade.loud .cabinet {
  box-shadow:
    0 10px 22px -8px rgb(0 0 0 / .85),
    0 0 0 2px #5a3c0c,
    0 0 0 3px rgb(255 214 106 / .30),
    0 0 22px -6px var(--rail-glow),
    inset 0 1px 0 rgb(255 255 255 / .6);
}
body.arcade.loud .cabinet:hover {
  box-shadow:
    0 16px 28px -10px rgb(0 0 0 / .9),
    0 0 0 2px #5a3c0c,
    0 0 0 3px rgb(255 214 106 / .55),
    0 0 40px -4px var(--rail-glow),
    inset 0 1px 0 rgb(255 255 255 / .7);
}

/* THE PLATE, in the reference's proportions. Orca sets the machine's SHAPE --
   "50 LINES", "ALL WAYS" -- as the headline of the plate, outlined and
   centred, because that is the first question anybody asks about a machine.
   Ours had it as a 12px caption under the name.

   THE NAME STAYS, and Orca's does not have one. Two reasons, and neither is
   sentiment: a game with no art gets a letter and its name is the only thing
   identifying it, and the name is what a screen reader reads. So the plate
   carries both, with the shape given the weight the reference gives it. */
/* ROOM FOR THE FURNITURE ON EITHER SIDE. The badge is 24px at the left and
   the heart is 40px at the right, both sitting over the plate, so a centred
   name in a 170px tile had about ninety pixels to live in and every one of
   them was clipped mid-word. The padding is what gives the text its lane. */
/* SLIMMER, MEASURED. Cropped to the same rendered height beside Orca's, our
   plate was 28% of the tile against their 10%, and the moulding around the
   art was 14px against their 3. Their picture is the tile; ours was a picture
   in a frame with a caption under it.

   Three of those lines are ours and stay -- the name (a game with no art is a
   letter, and the name is what a reader announces), the shape and the maths.
   What comes off is the leading and the moulding. The remaining difference is
   that the reference FLOATS its bar on the artwork rather than putting it
   below; see the report -- that one is a change to how the cabinet is built
   rather than to its sizes, and it wants doing on purpose. */
body.arcade.loud .cabinet { padding: 3px 3px 0; }
body.arcade.loud .cabinet .plate {
  margin: 3px -3px 0; padding: 5px 42px 7px 34px; text-align: center;
  border-radius: 0 0 13px 13px;
}
body.arcade.loud .cabinet .plate strong {
  font-size: 12px; letter-spacing: .01em; line-height: 1.2;
  /* One line with an ellipsis rather than a second line: two cabinets whose
     names wrap differently are two cabinets of different heights, and the
     band aligns them by the top. */
  display: block; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
body.arcade.loud .cabinet .kind {
  display: block; margin-top: 0; line-height: 1.25;
  font-size: 15px; font-weight: 900; letter-spacing: .04em; color: #fff;
  text-shadow:
    -1.5px -1.5px 0 #3a2405,  1.5px -1.5px 0 #3a2405,
    -1.5px  1.5px 0 #3a2405,  1.5px  1.5px 0 #3a2405,
    -1.5px  0    0 #3a2405,   1.5px  0    0 #3a2405,
     0    -1.5px 0 #3a2405,   0     1.5px 0 #3a2405,
     0 2px 0 rgb(0 0 0 / .5);
}

/* WHAT KIND OF MACHINE IT IS, bottom-left of the plate, where the reference
   puts it. Orca draws a little slot machine on a reels game and a fish on a
   fish one; the same five faces are already in static/chrome driving the
   category rail, so this is the rail's own art at a badge's size and costs
   no request that is not already made. Drawn from data-kind on the slot. */
body.arcade.loud .slot { position: relative; }
body.arcade.loud .slot::after {
  content: ""; position: absolute; left: 8px; bottom: 10px; z-index: 2;
  width: 24px; height: 24px; pointer-events: none; opacity: .92;
  background: url("chrome/icon-slot.png") center / contain no-repeat;
  filter: drop-shadow(0 2px 3px rgb(0 0 0 / .8));
}
body.arcade.loud .slot[data-kind="keno"]::after {
  background-image: url("chrome/icon-keno.png");
}
body.arcade.loud .slot[data-kind="fish"]::after {
  background-image: url("chrome/icon-fish.png");
}
body.arcade.loud .slot[data-kind="fruit"]::after {
  background-image: url("chrome/icon-fruit.png");
}
/* The shelves are not machines anybody can open, so they get no kind badge --
   a coming-soon tile wearing a slot face reads as playable. */
body.arcade.loud .slot:has(.cabinet.soon)::after { content: none; }

/* THE HEART, as the reference draws it: on the plate at the right, an outline
   rather than a filled disc. The 40px hit area stays exactly as it was -- it
   is the one control on this tile that is not the tile, and a thumb has to be
   able to find it without opening the machine. */
/* OFF THE CORNER. The reference's NEW! hangs past the edge of the tile,
   which is what makes it read as a ribbon pinned to a machine rather than a
   sticker printed inside the picture. */
body.arcade.loud .cabinet .badge.hot,
body.arcade.loud .cabinet .badge.new { top: 4px; left: -5px; }

body.arcade.loud .fave { right: 6px; bottom: 8px; }
body.arcade.loud .fave button {
  background: rgb(0 0 0 / .18);
  color: #ffd0d8; font-size: 22px;
  text-shadow: 0 1px 2px rgb(0 0 0 / .9), 0 0 8px rgb(0 0 0 / .6);
}
body.arcade.loud .fave button:hover { background: rgb(0 0 0 / .42); }

/* ------------------------------------------------------- the jackpot plate

   Over the head of the machine, which is where Orca hangs it. The figure is
   the platform pot and it is the same on every tile, because that is what it
   actually is -- see the comment in lobby.html for why one number on sixteen
   cabinets is the honest drawing of our schema rather than a shortcut. */
body.arcade.loud .slot { overflow: visible; }

/* -------------------------------------------------------- the bottom rail

   Orca's deck is one row of illustrated buttons over gold captions, then a
   second row with the two purses on the left and the category rail on the
   right. Ours had the right ITEMS -- the labels already read MISSION, MAILBOX
   and FREEPLAY, and CASHBACK and NEWS were already built -- and the wrong
   WEIGHT: 10px captions in muted grey where the reference's are gold, and
   readouts a third of the size.

   TRANSFER is not here and is not an omission: it moves money, and money
   movement is deferred. SERVICES is here and stays -- ours is the shop's
   phone number, which is support rather than a transfer, and the two only
   look alike because Orca happens to draw them side by side. */
body.arcade.loud .tasks-word, body.arcade.loud .bonus-word,
body.arcade.loud .cashback-word, body.arcade.loud .dailywheel-word,
body.arcade.loud .services-word, body.arcade.loud .news-word,
body.arcade.loud .install-word, body.arcade.loud .mail-word {
  font-size: 11px; font-weight: 900; letter-spacing: .08em; color: #ffd98c;
  text-shadow:
    -1px -1px 0 var(--loud-edge),  1px -1px 0 var(--loud-edge),
    -1px  1px 0 var(--loud-edge),  1px  1px 0 var(--loud-edge),
     0 1px 0 rgb(0 0 0 / .6);
}
body.arcade.loud .tasks-icon, body.arcade.loud .bonus-icon,
body.arcade.loud .mail-icon, body.arcade.loud .install-icon,
body.arcade.loud .dailywheel-icon, body.arcade.loud .services-icon,
body.arcade.loud .news-icon { font-size: 19px; }

/* THE TWO PURSES, at the size the reference sets them. Orca's are wide pills
   with the label on the left and the figure on the right, not a caption over a
   number -- and they are the second-biggest thing on its deck after the
   jackpot rail. Ours were 10px labels over 18px figures in a 60px box. */
body.arcade.loud .arcade-bottom .purse { gap: 10px; }
body.arcade.loud .wallet {
  flex-direction: row; align-items: center; gap: 10px;
  /* No wider than the figures need. At 190 the pair came to 380px of a
     820px tablet deck and pushed the player's own name into wrapping. */
  min-width: 150px; padding: 7px 15px 8px; border-radius: 999px;
  background: linear-gradient(180deg, #08210b, #030f05);
  box-shadow: inset 0 0 0 2px rgb(255 214 106 / .38),
              inset 0 3px 8px rgb(0 0 0 / .9),
              0 2px 6px rgb(0 0 0 / .5);
}
body.arcade.loud .wallet .label {
  font-size: 11px; font-weight: 900; letter-spacing: .1em; color: #ffe9a8;
  text-shadow: 0 1px 0 rgb(0 0 0 / .8);
}
/* LABEL, THEN THE MARK, THEN THE FIGURE -- the reference's order, and it
   reads better: the gem belongs to the word ENTRIES rather than sitting out
   at the end of the pill like a bullet. Pseudo-elements take part in flex
   layout, so `order` is all it costs. */
body.arcade.loud .wallet .label { order: 1; }
body.arcade.loud .wallet::before { order: 2; }
body.arcade.loud .wallet .figure {
  order: 3; margin-left: auto; font-size: 20px; color: #fff;
  text-shadow: 0 1px 0 rgb(0 0 0 / .8);
}
/* A gem on the entries and a coin on the winnings, the way the reference
   marks which purse is which. Drawn rather than fetched: a rotated square and
   a disc, which is all either of them is at 14px. */
body.arcade.loud .wallet::before {
  content: ""; flex: 0 0 auto; width: 13px; height: 13px;
  background: linear-gradient(150deg, #bfefff 0%, #4bb8e8 45%, #1d6fa8 100%);
  transform: rotate(45deg); border-radius: 2px;
  box-shadow: 0 0 8px rgb(120 210 255 / .55);
}
body.arcade.loud .wallet:last-child::before {
  transform: none; border-radius: 50%;
  background: radial-gradient(circle at 38% 32%, #fff0b8 0%, #f0c343 45%,
                              #9a6a11 100%);
  box-shadow: 0 0 8px rgb(255 200 90 / .55);
}

/* --------------------------------------------------------- the top chrome

   ORCA'S RAIL SPANS THE SCREEN. Four capsules edge to edge, each with its
   tier's name in that tier's colour. Ours was two capsules huddled in the
   middle of a 1440px bar with most of the rail empty on both sides.

   WHAT IS COPIED IS THE SPAN, NOT THE TIERS. Orca shows GRAND / MAJOR /
   MINOR / MINI; our schema has one pool per store plus one for the platform,
   and there is no fourth, third or second tier to draw. Inventing three
   would be inventing three numbers on a gambling screen, which is the one
   thing this surface has refused since the first version of the rail. So the
   capsules stretch to fill the bar and there are as many of them as there
   are REAL pots. See the report: true tiers need the rake decision. */
/* ------------------------------------------------------- the room's floor

   Orca's lobby stands in a painted resort; ours is gradients and a drawn
   starfield, and that gap is art rather than CSS. What CSS can do is stop the
   floor being flat: a lit horizon behind the wall, and the arch we already
   own standing over it. Both are OURS -- static/chrome/arch.png ships with
   the build and has never been placed. */
body.arcade.loud.lobby-page .arcade-body::before {
  content: ""; position: absolute; inset: auto 0 0 0; height: 46%;
  z-index: -1; pointer-events: none; opacity: .5;
  background:
    radial-gradient(120% 100% at 50% 100%,
                    rgb(80 190 110 / .22) 0%, rgb(0 0 0 / 0) 70%);
}
body.arcade.loud.lobby-page .arcade-body { position: relative; }

/* ------------------------------------- the Orca furniture, held sideways

   EVERY SIZE IN THE BLOCK ABOVE IS A DESKTOP SIZE, and the sideways block is
   further up the file, so on a phone in landscape the new furniture won on
   order and wrecked the wall: a 150px jackpot plate across a 122px cabinet,
   "20 LINES" at 15px wrapping onto two lines inside a plate that then ate the
   picture, and two 190px purses filling the deck.

   Measured on an 852x393 viewport, not guessed -- the capture showed the
   plates spilling across their neighbours. This is the same furniture at the
   size that screen has for it, and it is LAST in the file so it cannot lose
   the same argument twice. */
@media (orientation: landscape) and (max-height: 500px) {
  body.arcade.loud .tile-jackpot {
    top: 24px; gap: 4px; padding: 1px 6px 2px; border-radius: 6px;
    max-width: calc(100% - 8px);
  }
  body.arcade.loud .tile-jackpot-word { font-size: 7px; letter-spacing: .06em; }
  body.arcade.loud .tile-jackpot-figure { font-size: 10px; }

  /* The plate goes back to a caption over a shape, at the sizes the sideways
     block set, with only the lane for the badge and the heart kept. */
  /* THE PLATE IS A THIRD OF THE TILE unless the leading is pulled in. It
     carries three lines here where the reference carries one -- the name, the
     shape and the maths -- and at ordinary leading that is 130px of plate on
     a 355px cabinet, which leaves the picture looking like a thumbnail glued
     to a caption. None of the three lines comes off: the name is what a
     reader announces, and the maths name is a disclosure. The leading does. */
  body.arcade.loud .cabinet .plate { padding: 4px 30px 5px 24px; }
  body.arcade.loud .cabinet .plate strong { font-size: 10px; line-height: 1.15; }
  body.arcade.loud .cabinet .kind { font-size: 11px; margin-top: 0;
                                    line-height: 1.2; }
  body.arcade.loud .cabinet .plate .maths-tag { font-size: 8px;
                                                line-height: 1.15; }

  body.arcade.loud .slot::after { width: 18px; height: 18px;
                                  left: 4px; bottom: 7px; }
  body.arcade.loud .fave { right: 4px; bottom: 6px; }
  body.arcade.loud .fave button { width: 30px; height: 30px; font-size: 15px; }

  /* The purses stay the reference's shape -- label left, figure right, one
     pill -- and lose the width that shape does not need. */
  body.arcade.loud .wallet { min-width: 118px; gap: 7px;
                             padding: 3px 11px 4px; }
  body.arcade.loud .wallet .label { font-size: 8px; letter-spacing: .06em; }
  body.arcade.loud .wallet .figure { font-size: 14px; }
  body.arcade.loud .wallet::before { width: 10px; height: 10px; }

  /* The rail keeps its dividers and its pedestal at the sideways pitch. */
  body.arcade.loud .rail { gap: 18px; }
  body.arcade.loud .rail a + a { padding-left: 9px;
                                 background-size: 2px 58%; }

  body.arcade.loud .arcade-top .pot { margin: 0 10px; gap: 10px; }
  body.arcade.loud .pot-one { max-width: 30vw; }
}

/* THE DECK BETWEEN A PHONE AND A DESKTOP. The purses took the reference's
   shape at the reference's size, and between 640px -- where the narrow-phone
   block starts tightening everything -- and about 900 there is a gap: a
   tablet held upright is 820 across, and a player card, three buttons and two
   150px pills come to more than that. It showed as the player's own name
   wrapping onto two lines and WINNING clipped off the right edge. */
@media (max-width: 900px) {
  body.arcade.loud .wallet { min-width: 0; gap: 7px; padding: 6px 12px 7px; }
  body.arcade.loud .wallet .label { font-size: 9px; letter-spacing: .06em; }
  body.arcade.loud .wallet .figure { font-size: 16px; }
  body.arcade.loud .arcade-bottom .purse { gap: 7px; }
  /* And the player's own name on one line. It is a name, and a name broken
     over two lines makes the whole deck a line taller for one person. */
  body.arcade.loud .arcade-bottom .name { white-space: nowrap; }
}

/* ==========================================================================
   THE CLONE

   The brief sharpened from "close the gaps" to "this lobby IS the reference's,
   in our green". What follows is the reference's STRUCTURE, measured off
   ~/Downloads/Game Screens/orca (and corroborated against gdc, which is the
   same build in another skin), rebuilt here: a four-slot jackpot rail across
   the top, a deck of two rows with the buttons above and the purses and the
   categories sharing the row below, and cabinets whose plate floats on the
   artwork rather than sitting under it.

   GREEN IS THE ONLY PALETTE CHANGE, and one judgement inside that: the four
   tier colours stay differentiated. They are not decoration, they are how a
   player tells GRAND from MINI at a glance -- the same reason a traffic light
   is not three greens. Everything around them is ours.

   WHAT CANNOT BE CLONED, and is flagged rather than faked: the reference's
   artwork. Its cabinets carry its own licensed games, its category discs and
   deck buttons are commissioned illustrations, and its floor is a painted
   resort. Geometry, spacing, weight and behaviour are copyable and are copied;
   the drawings are somebody else's and would have to be drawn.
   ========================================================================== */

/* ---------------------------------------------------- the four-slot rail

   MEASURED: the reference's capsules are a name PLATE on the left and the
   figure on the right, on one line, inside a pill with a rim in that tier's
   colour. Ours was a stacked caption over a number inside a painted
   border-image from the other build. The pill is drawn rather than fetched --
   it is a rounded rect with two rings, which CSS does exactly and a sprite
   only approximates at one size. */
body.arcade.loud .arcade-top {
  padding: 7px 14px;
  background: linear-gradient(180deg, #1f6b29 0%, #0d3213 55%, #09270f 100%);
  box-shadow: 0 8px 22px -12px rgb(0 0 0 / .9), 0 2px 10px rgb(0 0 0 / .6);
}
body.arcade.loud .arcade-top .pot {
  flex: 1 1 auto; display: flex; gap: 10px; margin: 0 12px;
  justify-content: center; align-items: center;
}
body.arcade.loud .pot-one {
  flex: 1 1 0; max-width: 300px; min-width: 0;
  display: flex; flex-direction: row; align-items: center; gap: 9px;
  padding: 4px 12px 5px 5px; border: 0; border-image: none;
  border-radius: 999px; overflow: visible;
  background: linear-gradient(180deg, #0d2412 0%, #061507 100%);
  box-shadow: inset 0 0 0 2px var(--tier, #ffd24a),
              inset 0 2px 6px rgb(0 0 0 / .8),
              0 0 14px -4px var(--tier, #ffd24a);
}
body.arcade.loud .pot-one::before { content: none; }
/* The name is a plate inside the pill, not a caption over the figure. */
body.arcade.loud .pot-name {
  flex: 0 0 auto; padding: 3px 11px 4px; border-radius: 999px;
  background: linear-gradient(180deg, rgb(255 255 255 / .1),
                                      rgb(0 0 0 / .35));
  box-shadow: inset 0 0 0 1.5px var(--tier, #ffd24a);
  font-size: 13px; font-weight: 900; letter-spacing: .06em; color: #fff;
  text-transform: uppercase; line-height: 1.15;
  text-shadow: -1px -1px 0 #06180a, 1px -1px 0 #06180a,
               -1px  1px 0 #06180a, 1px  1px 0 #06180a,
                0 1px 0 rgb(0 0 0 / .8);
  /* A long shop-given pot name cannot be allowed to push the figure out. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 52%;
}
body.arcade.loud .pot-figure {
  flex: 1 1 auto; text-align: right;
  font-size: 20px; font-weight: 900; letter-spacing: .01em;
  color: var(--tier, #ffd24a); font-variant-numeric: tabular-nums;
  text-shadow: -1.5px -1.5px 0 #06180a,  1.5px -1.5px 0 #06180a,
               -1.5px  1.5px 0 #06180a,  1.5px  1.5px 0 #06180a,
                0 2px 0 rgb(0 0 0 / .7), 0 0 16px var(--tier, #ffd24a);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* THE FOUR TIER COLOURS. Kept apart because they carry meaning -- see the
   note at the head of this block. Shifted toward our own palette: the gold is
   the brand's, and the other three are the reference's hues brought onto a
   green ground rather than lifted off a blue one. */
/* THE TIER'S COLOUR FOLLOWS ITS NAME, and it used to follow its position.
   nth-child is right only while the rail is in the reference's order, and it
   is not: a store's own pool is drawn first, so on the live rail GRAND sat in
   slot two and wore the slot-two colour. Sampled off the reference's own
   Ancient Maya rail rather than picked: #d83018, #c018f0, #48c030, #6090f0.
   A pool with an operator's name -- "House Pot" -- is none of the four and
   keeps the house gold, which is the honest answer rather than borrowing a
   tier colour it has no claim to. */
body.arcade.loud .pot-one                  { --tier: #ffd24a; }
body.arcade.loud .pot-one[data-tier=grand] { --tier: #d83018; }
body.arcade.loud .pot-one[data-tier=major] { --tier: #c018f0; }
body.arcade.loud .pot-one[data-tier=minor] { --tier: #48c030; }
body.arcade.loud .pot-one[data-tier=mini]  { --tier: #6090f0; }

/* A SLOT WITH NO POOL BEHIND IT. It has the reference's shape and no figure,
   because there is no figure -- see play_web._shell. Dimmed so it cannot be
   read as a pot standing at zero, which is a different and false claim. */
body.arcade.loud .pot-one[data-unbacked] { opacity: .5; }
body.arcade.loud .pot-figure-none {
  font-size: 20px; letter-spacing: .1em; text-shadow: none; opacity: .8;
}

/* The way out, as the reference's round button on the end of the rail. */
body.arcade.loud .arcade-top .exit {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; height: 34px; padding: 0 14px; margin-left: 8px;
  border-radius: 999px; font-size: 13px; font-weight: 900;
  letter-spacing: .08em; color: #d8ffe6;
  background: linear-gradient(180deg, #2f7d4a 0%, #17452a 100%);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .25), 0 2px 0 rgb(0 0 0 / .5);
}
body.arcade.loud .arcade-top .exit:hover { color: #fff; }

/* --------------------------------------------------------- the deck, in two

   The reference's is two rows: the buttons across the top, then the two
   purses on the left sharing the bottom row with the category rail. Ours had
   one row with the categories floating above it over the wall, which is
   Golden Dragon's arrangement -- the stylesheet said so in as many words.
   They are in different parents, so the rail had to move into the footer for
   this to be possible at all; see the deck_rail block in _layout.html. */
body.arcade.loud .arcade-bottom {
  flex-wrap: wrap; align-items: center; gap: 4px 0; padding: 6px 14px 8px;
}
/* Row one: who you are and the buttons. */
body.arcade.loud .arcade-bottom .who { order: 1; }
body.arcade.loud .arcade-bottom .tasks,
body.arcade.loud .arcade-bottom .mailbox,
body.arcade.loud .arcade-bottom .cashback,
body.arcade.loud .arcade-bottom .services,
body.arcade.loud .arcade-bottom .news,
body.arcade.loud .arcade-bottom .dailywheel,
body.arcade.loud body.arcade.loud .arcade-bottom .bonus { order: 2; }
/* Row two: the purses on the left, then the categories. */
body.arcade.loud .arcade-bottom .purse {
  order: 3; flex-basis: auto; margin-left: 0; margin-right: 0;
  flex-direction: column; gap: 4px;
}
body.arcade.loud .arcade-bottom .rail {
  order: 4; flex: 1 1 auto; margin-left: 18px;
}
/* THE BREAK. `flex-basis: 100%` on nothing -- a zero-height pseudo item -- is
   what forces the second row without wrapping being left to chance about how
   many buttons happen to fit. */
body.arcade.loud .arcade-bottom::after {
  content: ""; order: 2; flex-basis: 100%; height: 0;
}
/* The purses stack, as the reference stacks them. */
body.arcade.loud .arcade-bottom .purse .wallet { width: 100%; }

/* The rail inside the deck no longer needs a ground of its own -- it has the
   deck's. */
body.arcade.loud .arcade-bottom .rail {
  background: none; padding: 0; border-radius: 0;
  justify-content: flex-start; gap: 30px;
}

/* A CATEGORY WITH NOTHING BEHIND IT. The rail draws the reference's six and
   two of them have no games -- see play_web.lobby, where that reversal is
   recorded. Dimmed, because the tab is honest about being empty before it is
   pressed rather than after. */
body.arcade.loud .rail a[data-empty] { opacity: .55; }
body.arcade.loud .rail a[data-empty]::before { filter: saturate(.25)
                                               brightness(.7); }

/* ------------------------------------------------------------ the cabinet

   THE PLATE FLOATS ON THE ARTWORK. This is the difference the crops made
   plain last time and it was deferred then: the reference's picture IS the
   tile, and its shape bar, its heart, its kind badge and its jackpot figure
   all sit ON the picture rather than in a band under it. Ours was a photo in
   a frame with a caption below, which is why it read as a catalogue.

   The bar is absolutely positioned, so it takes no height in the column and
   the art can have all of it. */
body.arcade.loud .cabinet { padding: 0; overflow: visible; }
body.arcade.loud .cabinet img,
body.arcade.loud .cabinet .noart {
  border-radius: 13px; box-shadow: none;
}
/* AND THE SLOT TAKES ITS ROW RATHER THAN ITS CONTENT.
   `.slot` is `align-self: center`, so it was sized to what was inside it --
   the image -- which is the other half of the deadlock. Stretched, the row's
   own height reaches the tile and nothing in the layout asks the picture how
   tall it is. */
body.arcade.loud .cabinets .slot { align-self: stretch; }
body.arcade.loud .cabinets .cabinet img,
body.arcade.loud .cabinets .cabinet .noart {
  /* A RATIO, NOT `auto`. THIS WAS THE BLANK LOBBY.
     `auto` means take your height from the image; the image is lazy, so it
     only loads once it is in the viewport; a tile 0 tall is never in the
     viewport, so it never loaded and stayed 0. Every tile, forever. The
     ratio is a floor -- flex and height:100% still win wherever the row has
     a definite height, so the picture fills the tile exactly as before. */
  aspect-ratio: 3 / 4; flex: 1 1 auto; height: 100%; min-height: 0;
  object-fit: cover;
}
body.arcade.loud .cabinet .plate {
  position: absolute; left: 6px; right: 6px; bottom: 7px; z-index: 2;
  margin: 0; padding: 4px 34px 5px 30px; border-radius: 999px;
  background: linear-gradient(180deg, rgb(6 18 9 / .82),
                                      rgb(3 10 5 / .92));
  box-shadow: inset 0 0 0 1.5px rgb(255 214 106 / .35),
              0 2px 8px rgb(0 0 0 / .7);
  text-align: center;
}
/* THE SHAPE IS THE BAR. The reference prints "50 LINES" and nothing else on
   it; ours keeps the name and the maths because a game with no art is a
   letter and the maths line is a disclosure -- so the shape is the headline
   and the other two are set under it at a caption's size. */
body.arcade.loud .cabinet .kind {
  order: 1; font-size: 15px; line-height: 1.15;
}
body.arcade.loud .cabinet .plate strong {
  order: 2; font-size: 10px; font-weight: 700; line-height: 1.15;
  color: #cfe8d6; letter-spacing: .02em;
}
body.arcade.loud .cabinet .plate .maths-tag {
  order: 3; font-size: 8px; line-height: 1.1; opacity: .7;
}
body.arcade.loud .cabinet .plate {
  display: flex; flex-direction: column; align-items: center;
}

/* THE HEART STRADDLES THE EDGE, as the reference's does -- half on the bar,
   half off the corner of the cabinet. */
body.arcade.loud .fave { right: -2px; bottom: 2px; z-index: 3; }
body.arcade.loud .fave button {
  width: 38px; height: 38px; background: none; font-size: 24px;
  color: #ffd0d8;
  text-shadow: 0 0 3px rgb(0 0 0 / .95), 0 1px 2px rgb(0 0 0 / .9),
               0 0 10px rgb(0 0 0 / .8);
}
body.arcade.loud .fave button:hover { background: rgb(0 0 0 / .3); }

/* THE KIND BADGE STRADDLES IT TOO, on the other corner. */
body.arcade.loud .slot::after {
  left: -4px; bottom: 2px; width: 34px; height: 34px; opacity: 1;
}

/* --------------------------------------------------- the jackpot lockup

   Two parts, straddling the top edge exactly as the reference's does: the
   word ABOVE the cabinet, the figure in a bar INSIDE the top of it. */
.tile-jackpot-word {
  position: absolute; top: -14px; left: 50%; transform: translateX(-50%);
  z-index: 4; pointer-events: none; white-space: nowrap;
  font-size: 13px; font-weight: 900; letter-spacing: .08em; color: #ffe07a;
  text-shadow: -1.5px -1.5px 0 #06180a,  1.5px -1.5px 0 #06180a,
               -1.5px  1.5px 0 #06180a,  1.5px  1.5px 0 #06180a,
                0 2px 0 rgb(0 0 0 / .7), 0 0 14px rgb(255 190 60 / .8);
}
.tile-jackpot-figure {
  position: absolute; top: 5px; left: 6px; right: 6px; z-index: 3;
  pointer-events: none; text-align: center;
  padding: 2px 8px 3px; border-radius: 999px;
  background: linear-gradient(180deg, rgb(6 18 9 / .86), rgb(3 10 5 / .94));
  box-shadow: inset 0 0 0 1.5px rgb(255 214 106 / .3);
  font-size: 15px; font-weight: 900; color: #9be8a8;
  font-variant-numeric: tabular-nums; line-height: 1.15;
  text-shadow: 0 1px 0 rgb(0 0 0 / .9), 0 0 12px rgb(120 230 140 / .5);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The ribbon has to clear the figure's bar. */
body.arcade.loud .cabinet .badge.hot,
body.arcade.loud .cabinet .badge.new { top: 26px; left: -6px; z-index: 4; }

/* THE eZLOGIN RULES ARE GONE WITH THE MARKUP. eZFunds was briefly the
   submit button on the front door, which was wrong about what eZFunds is --
   a separate payment account a player LINKS, not an identity provider. The
   control moved to the lobby deck and these selectors matched nothing, which
   is the dead-rule trap this file already has a test for. */


/* ROOM ABOVE THE WALL FOR THE WORD. The band clips its children -- it is the
   box that scrolls -- so the JACKPOT word hung over the top of a first-row
   cabinet was sliced off at the band's edge. It showed on the second row and
   not the first, which is exactly the kind of half-working that looks like a
   z-index problem and is not one. */
body.arcade.loud .cabinets { padding-top: 18px; }

/* AND THE BAR'S LANES SHRINK, because the badge and the heart moved OUT of
   it. They straddle the cabinet's edge now, the way the reference has them,
   so the 30 and 34 pixels of padding that used to keep the text clear of them
   is holding the text away from nothing. */
body.arcade.loud .cabinet .plate { padding: 4px 20px 5px 18px; }

/* ------------------------------------------ the clone, held sideways

   Same trap as before and it has to be met again: every size in the clone
   block is a desktop size, the sideways block is further up the file, and on
   a phone in landscape the clone won on order. Measured at 852x393 the deck
   came out 290px of a 393px screen -- a two-row deck at desktop weight -- and
   the wall was a strip with the shape bars covering the artwork.

   THE TWO-ROW DECK STAYS. It is the reference's structure and it is what the
   brief asks for; what changes is its weight. Buttons, purses and category
   discs all come down until the deck is about a quarter of the screen rather
   than three quarters, which is roughly the share the reference's own deck
   takes of its (much taller) window. */
@media (orientation: landscape) and (max-height: 500px) {
  body.arcade.loud .arcade-bottom { padding: 3px 10px 4px; gap: 2px 0; }
  body.arcade.loud .arcade-bottom .purse { gap: 3px; }
  body.arcade.loud .wallet { min-width: 132px; padding: 2px 10px 3px; gap: 6px; }
  body.arcade.loud .wallet .label { font-size: 8px; }
  body.arcade.loud .wallet .figure { font-size: 13px; }
  body.arcade.loud .wallet::before { width: 9px; height: 9px; }
  body.arcade.loud .arcade-bottom .rail { margin-left: 12px; gap: 14px; }
  body.arcade.loud .arcade-bottom .avatar { width: 20px; height: 20px;
                                            font-size: 10px; }

  /* THE BUTTON CAPTIONS, restated. The clone block sets these to 11px for a
     desktop deck, and it is later in the file than the sideways block that
     had already sized them at 8 -- so on a phone the deck's first row came
     back at desktop weight and took 90px of a 393px screen on its own. This
     is the third time an ordering has cost a rule; the pattern is that
     anything the clone block touches has to be answered HERE. */
  body.arcade.loud .tasks-word, body.arcade.loud .bonus-word,
  body.arcade.loud .cashback-word, body.arcade.loud .dailywheel-word,
  body.arcade.loud .services-word, body.arcade.loud .news-word,
  body.arcade.loud .install-word, body.arcade.loud .mail-word {
    font-size: 8px; letter-spacing: .05em;
  }
  body.arcade.loud .tasks-icon, body.arcade.loud .bonus-icon,
  body.arcade.loud .mail-icon, body.arcade.loud .install-icon,
  body.arcade.loud .dailywheel-icon, body.arcade.loud .services-icon,
  body.arcade.loud .news-icon { font-size: 13px; }
  body.arcade.loud .arcade-bottom .tasks,
  body.arcade.loud .arcade-bottom .mailbox,
  body.arcade.loud .arcade-bottom .cashback,
  body.arcade.loud .arcade-bottom .services,
  body.arcade.loud .arcade-bottom .news,
  body.arcade.loud .arcade-bottom .dailywheel,
  body.arcade.loud   body.arcade.loud .arcade-bottom .bonus {
    min-height: 30px; padding: 2px 7px 3px; margin-left: 4px;
  }
  body.arcade.loud .arcade-bottom .name { font-size: 11px; }
  body.arcade.loud .arcade-bottom .tier { font-size: 7px; padding: 1px 5px 2px; }
  body.arcade.loud .bonus-when { font-size: 8px; }

  /* The capsules: the name plate was taking 52% of a 200px pill and the
     shop's own pot name came out "HOU...". Smaller type and a bigger share,
     because a truncated pot name on the rail is worse than a small one. */
  body.arcade.loud .arcade-top { padding: 3px 8px; }
  body.arcade.loud .arcade-top .pot { margin: 0 8px; gap: 6px; }
  body.arcade.loud .pot-one { padding: 2px 8px 3px 3px; gap: 5px; }
  body.arcade.loud .pot-name { font-size: 9px; padding: 2px 7px 3px;
                               max-width: 60%; letter-spacing: .03em; }
  body.arcade.loud .pot-figure { font-size: 13px; }
  body.arcade.loud .pot-figure-none { font-size: 13px; }
  body.arcade.loud .arcade-top .exit { min-width: 34px; height: 26px;
                                       padding: 0 10px; font-size: 11px; }

  /* The cabinet's furniture, at a 122px tile's scale. The bar was at desktop
     weight over a picture a third the size and simply covered it. */
  body.arcade.loud .cabinets { padding-top: 12px; }
  body.arcade.loud .cabinet .plate { left: 4px; right: 4px; bottom: 5px;
                                     padding: 3px 15px 4px 14px; }
  body.arcade.loud .cabinet .kind { font-size: 10px; line-height: 1.1; }
  body.arcade.loud .cabinet .plate strong { font-size: 8px; line-height: 1.1; }
  body.arcade.loud .cabinet .plate .maths-tag { font-size: 7px; }
  body.arcade.loud .fave button { width: 26px; height: 26px; font-size: 15px; }
  body.arcade.loud .slot::after { width: 22px; height: 22px;
                                  left: -3px; bottom: 1px; }
  .tile-jackpot-word { top: -10px; font-size: 8px; letter-spacing: .04em; }
  .tile-jackpot-figure { top: 3px; left: 4px; right: 4px; font-size: 10px;
                         padding: 1px 5px 2px; }
  body.arcade.loud .cabinet .badge.hot,
  body.arcade.loud .cabinet .badge.new { top: 18px; left: -4px; }
}

/* ==========================================================================
   THE OTHER THREE SCREENS, CLONED

   The lobby was the first pass; these are the loading screen, the front door
   and the machine. Same rule and same discipline: the reference's geometry,
   our palette, our data, and anything that would have to be DRAWN is flagged
   rather than traced.

   WHERE THE REFERENCE COMES FROM, per screen. Orca has 58 captures and not
   one of them is a login or a loading screen -- it is signed in throughout.
   Golden Dragon and Golden Dragon City have both, and they are the same build
   in another skin, so those two are the reference for the front door and the
   wait, and Orca is the reference for the machine. Said here because "match
   Orca" cannot be checked against a screenshot that does not exist.
   ========================================================================== */

/* -------------------------------------------------------------- the wait

   MEASURED off gdc's loading screen: the mark takes a little over half the
   width and sits above the middle; the bar is a wide, low, dark capsule about
   a seventh of the way up from the bottom. Ours was a small mark and a 14px
   bar stacked in the centre of the screen, which reads as a web spinner.

   WHAT IS NOT COPIED IS THE PERCENTAGE. The reference prints one -- 4.22% --
   and it is honest there: that is an asset loader and it knows what fraction
   of its bundle has arrived. Ours covers a NAVIGATION, and nothing on this
   side of the request knows any fraction at all, so a number here would be
   invented to look reassuring. The comment on the original bar said exactly
   that and it still holds. The bar keeps the reference's shape and says
   "working" rather than a figure. */
.loading-box {
  gap: 34px; max-width: none; width: 100%; height: 100%;
  justify-content: center; position: relative;
}
.loading-box .wordmark-mark,
.loading-box .wordmark-hero {
  max-width: min(760px, 56vw); width: 100%; height: auto;
  /* Above the middle, as the reference has it -- the bar owns the lower
     third and a mark centred between the two looks like it is falling. */
  margin-bottom: 4vh;
}
.loading-word {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: calc(14% + 34px); margin: 0;
}
.loading-bar {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 14%;
  width: min(52vw, 760px); height: 22px; border-radius: 11px;
  background: rgb(0 0 0 / .62);
  box-shadow: inset 0 2px 8px rgb(0 0 0 / .9),
              0 0 0 2px rgb(255 214 106 / .35);
}
.loading-bar i { border-radius: 11px; }
/* The room behind it, which the reference paints and we build: a glow behind
   the mark and the same speck field the lobby's floor uses. */
.loading {
  background:
    radial-gradient(58% 46% at 50% 42%, rgb(52 148 74 / .5) 0%,
                    rgb(0 0 0 / 0) 70%),
    radial-gradient(120% 90% at 50% 40%, rgb(18 56 28 / .97) 0%,
                    rgb(3 9 5 / 1) 72%),
    #040c06;
}
.loading::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image:
    radial-gradient(2px 2px at 16% 24%, rgb(190 255 200 / .55), transparent),
    radial-gradient(1.6px 1.6px at 63% 15%, rgb(255 255 255 / .4), transparent),
    radial-gradient(2.2px 2.2px at 81% 36%, rgb(255 235 180 / .45), transparent),
    radial-gradient(1.4px 1.4px at 34% 62%, rgb(190 255 200 / .35), transparent),
    radial-gradient(1.8px 1.8px at 72% 74%, rgb(200 255 220 / .4), transparent),
    radial-gradient(1.3px 1.3px at 9% 68%, rgb(255 255 255 / .3), transparent);
}

/* ---------------------------------------------------------- the front door

   The reference's panel is a wide, low bar across the lower half: the two
   labelled fields down its left, the way in on its right, the mark above it
   and the install badge in the corner. Ours was a 360px centred card.

   SCOPED TO THE ARCADE. `.signin` is shared furniture -- the till and the
   agent console use the same panel from console.css -- and those are working
   screens read at a desk, not front doors to a game. Everything here is under
   `body.arcade` so the other two are untouched. */
body.arcade .signin {
  max-width: min(1180px, 94vw); margin: 0 auto; padding: 4vh 0 0;
  min-height: 100dvh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 0;
  /* NO CARD. console.css gives every sign-in a panel to sit on, and the
     reason is contrast -- see "THE PANEL IS UNCONDITIONAL" there: labels on
     sunlit water shipped once. The reference has no card either; what it has
     is furniture under the FIELDS and nothing under the mark. That is what is
     built here -- .signin-panel below carries the dark fill and the rim, so
     every piece of text that needs a ground has one, and the mark, which is
     artwork, sits on the scene like the reference's does. */
  background: none; border: 0; box-shadow: none;
}
body.arcade .signin .wordmark-hero {
  max-width: min(520px, 40vw); max-height: 32vh; width: auto; height: auto;
  object-fit: contain; margin-bottom: 2.5vh;
}
/* The one line of body text with no furniture under it, so it carries its own
   -- the same dark halo the loud skin puts under every caption on the wall. */
body.arcade .signin > .tagline {
  margin: 0 0 2vh; font-size: 15px; color: #d8ffe6;
  text-shadow: 0 1px 0 rgb(0 0 0 / .9), 0 0 10px rgb(0 0 0 / .85),
               0 0 22px rgb(0 0 0 / .7);
}

body.arcade .signin-panel {
  display: flex; align-items: center; gap: 30px; width: 100%;
  padding: 20px 28px; border-radius: 18px;
  background: linear-gradient(180deg, rgb(8 24 12 / .92), rgb(3 12 6 / .95));
  box-shadow: inset 0 0 0 2px rgb(255 214 106 / .45),
              0 24px 50px -22px rgb(0 0 0 / .95);
}
body.arcade .signin-fields { flex: 1 1 auto; min-width: 0; }
/* LABEL BESIDE THE FIELD, not above it. That is the reference's line and it
   is what makes the panel low enough to be a bar rather than a card. */
body.arcade .signin-row {
  display: flex; align-items: center; gap: 18px; margin: 0 0 12px;
  max-width: none; text-align: left;
}
body.arcade .signin-label {
  flex: 0 0 auto; width: 150px;
  font-size: 17px; font-weight: 900; letter-spacing: .1em;
  text-transform: uppercase; color: #9be8a8; line-height: 1.1;
  text-shadow: 0 1px 0 rgb(0 0 0 / .8);
}
body.arcade .signin input {
  flex: 1 1 auto; width: auto; margin: 0; min-width: 0;
  font-size: 18px; letter-spacing: .12em; text-align: left;
  padding: 10px 14px; border-radius: 10px;
}
body.arcade .signin-reset {
  margin: 4px 0 0 168px; font-size: 13px; color: #cfe8d6;
  text-decoration: underline; text-underline-offset: 3px; text-align: left;
}
body.arcade .signin-go { flex: 0 0 auto; }
body.arcade .ezlogin-submit {
  border: 0; font: inherit; cursor: pointer; width: auto;
  padding: 14px 30px; font-size: 17px; font-weight: 900;
}

/* The install badge in the corner, as both references have it. */
body.arcade .signin-install {
  position: fixed; left: 3vw; bottom: 4vh; z-index: 5;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
body.arcade .signin-install-word {
  margin: 0; font-size: 13px; font-weight: 900; letter-spacing: .06em;
  line-height: 1.2; text-align: center; color: #ffe9a8;
  text-shadow: -1.5px -1.5px 0 var(--loud-edge),  1.5px -1.5px 0 var(--loud-edge),
               -1.5px  1.5px 0 var(--loud-edge),  1.5px  1.5px 0 var(--loud-edge),
                0 2px 0 rgb(0 0 0 / .6);
}
body.arcade .signin-install-go {
  width: 58px; height: 58px; border-radius: 50%; padding: 0;
  background: linear-gradient(180deg, #3fa05e 0%, #1c5732 100%);
  box-shadow: inset 0 2px 0 rgb(255 255 255 / .3), 0 4px 0 rgb(0 0 0 / .5),
              0 0 22px -6px rgb(120 230 140 / .8);
}
body.arcade .signin-install-go .install-icon { font-size: 24px; }
body.arcade .signin-install-go .install-word { font-size: 9px; }

/* Sideways, where the panel has a third of the height to live in. */
@media (orientation: landscape) and (max-height: 500px) {
  body.arcade .signin { padding: 2vh 0 0; }
  body.arcade .signin .wordmark-hero { max-width: min(360px, 38vw);
                                       margin-bottom: 1.5vh; }
  body.arcade .signin > .tagline { font-size: 12px; margin-bottom: 1vh; }
  body.arcade .signin-panel { padding: 10px 16px; gap: 16px;
                              border-radius: 14px; }
  body.arcade .signin-row { gap: 10px; margin-bottom: 7px; }
  body.arcade .signin-label { width: 104px; font-size: 12px; }
  body.arcade .signin input { font-size: 14px; padding: 6px 10px; }
  body.arcade .signin-reset { margin-left: 114px; font-size: 10px; }
  body.arcade .ezlogin-submit { padding: 10px 20px; font-size: 14px; }
  body.arcade .signin-install { left: 2vw; bottom: 2vh; }
  body.arcade .signin-install-word { font-size: 10px; }
  body.arcade .signin-install-go { width: 42px; height: 42px; }
  .loading-box .wordmark-mark,
  .loading-box .wordmark-hero { max-width: min(420px, 44vw); margin-bottom: 2vh; }
  .loading-bar { height: 16px; bottom: 12%; }
  .loading-word { bottom: calc(12% + 26px); font-size: 11px; }
}

/* ---------------------------------------------------------- the machine

   MEASURED against orca-05: the reference's machine fits ITS SCREEN. The
   jackpot rail is across the top, the reels fill the middle, and the deck --
   readouts, stake keys, the win panel, AUTO and a big round SPIN -- is one bar
   along the bottom, all of it visible at once without scrolling.

   Ours did not. At 1440x900 the window is `min(1180px, 100%)` wide, and five
   reels of three square cells make a 5:3 box, so the reels came out about
   700px tall and the deck was a screen and a half below the fold. The
   sideways block already solved this for a phone by capping the window off
   the viewport HEIGHT; the same cap was simply never applied to a desktop,
   because on a desktop nobody scrolled far enough to notice.

   68vh across leaves the window about 41vh tall, which is what fits under a
   rail and over a deck at any window shape. */
body.arcade.loud .reels5 .window {
  max-width: min(100%, 68vh); margin: 6px auto 10px;
}
body.arcade.loud .reels .reel { max-width: min(150px, 26vh); }

/* FULL BLEED. The reference's game has no cabinet panel around it -- the art
   is the room and the machine stands in it. Ours drew a rounded slab with a
   border and a shadow, which is the tile treatment at machine size and reads
   as a widget on a page. */
body.arcade.loud .machine {
  background: none; box-shadow: none; padding: 6px 14px 0;
  max-width: min(1320px, 100%);
}

/* The head: a round button either side of the title, as the reference has a
   home and a gear flanking its rail. */
body.arcade.loud .machine-top { gap: 14px; margin-bottom: 4px; }
body.arcade.loud .machine-top .back {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%; font-size: 0;
  background: linear-gradient(180deg, #2f7d4a 0%, #17452a 100%);
  box-shadow: inset 0 2px 0 rgb(255 255 255 / .25), 0 3px 0 rgb(0 0 0 / .5);
  color: #d8ffe6;
}
/* The glyph is drawn rather than the link's own text, which stays for a
   reader: "← Games" read aloud is better than "house". */
body.arcade.loud .machine-top .back::before {
  content: "\2302"; font-size: 26px; line-height: 1;
}
body.arcade.loud .machine-top h1 {
  font-size: 20px; letter-spacing: .06em;
  text-shadow: -2px -2px 0 var(--loud-edge),  2px -2px 0 var(--loud-edge),
               -2px  2px 0 var(--loud-edge),  2px  2px 0 var(--loud-edge),
                0 3px 0 rgb(0 0 0 / .6), 0 0 22px var(--rail-glow);
  color: #ffe07a;
}

/* ----------------------------------------------------------- the deck

   The reference's order, left to right: the readouts, the stake keys, the win
   panel, LINES, AUTO START, and a big round SPIN on the end. Ours ran the
   keys, the bet, the win, then SPIN and AUTO -- so the loudest control on the
   machine sat in the middle of the row with a quieter one after it.

   `order` rather than moving the markup: the DOM order is the order a
   keyboard walks the controls in, and stake-then-bet-then-spin is right
   there. This is the same argument the category rail's `order` makes. */
body.arcade.loud .controls:not(.deck) { gap: 14px; }
body.arcade.loud .controls:not(.deck) .stake { order: 1; }
body.arcade.loud .bet { order: 2; }
body.arcade.loud .controls:not(.deck) .stake + .bet + .stake { order: 3; }
body.arcade.loud .controls:not(.deck) .deck-read { order: 4; }
body.arcade.loud .controls:not(.deck) .auto { order: 5; }
body.arcade.loud .controls:not(.deck) .spin { order: 6; margin-left: auto; }

/* THE WIN PANEL. The reference joins TOTAL PLAY and GAME WIN into one lit
   block in the middle of the deck; ours were two separate wells with a gap.
   Joined by flattening the sides that meet, which is the whole trick. */
body.arcade.loud .bet {
  border-radius: 12px 0 0 12px; margin-right: -14px;
  padding-right: 18px;
}
body.arcade.loud .controls:not(.deck) .deck-read {
  border-radius: 0 12px 12px 0; padding-left: 18px;
  box-shadow: inset 0 3px 8px rgb(0 0 0 / .9),
              inset 0 0 0 1px rgb(255 255 255 / .05),
              inset 1px 0 0 rgb(255 214 106 / .25);
}

/* SPIN IS THE BIGGEST THING ON THE MACHINE, and round. The reference's is a
   green disc about twice the height of the rest of the deck, sitting proud of
   it on the right. Ours was a rectangle the same height as everything else. */
body.arcade.loud .controls:not(.deck) .spin {
  width: 92px; height: 92px; padding: 0; border-radius: 50%;
  font-size: 15px; letter-spacing: .06em;
  background: radial-gradient(circle at 50% 30%, #7fe08a 0%, #2f9c4a 55%,
                              #14602c 100%);
  color: #06240f;
  box-shadow: inset 0 3px 0 rgb(255 255 255 / .45),
              inset 0 -4px 8px rgb(0 0 0 / .35),
              0 5px 0 rgb(0 0 0 / .5),
              0 0 30px -6px rgb(120 230 140 / .9);
}
body.arcade.loud .controls:not(.deck) .spin:active {
  transform: translateY(4px);
  box-shadow: inset 0 3px 0 rgb(255 255 255 / .4),
              inset 0 -4px 8px rgb(0 0 0 / .35);
}
body.arcade.loud .controls:not(.deck) .spin[disabled] {
  transform: translateY(4px); opacity: .6;
  box-shadow: inset 0 4px 10px rgb(0 0 0 / .5);
}

/* The sentence sits between the glass and the deck, as the reference's win
   line does, and stops taking a row of its own when it is empty. */
body.arcade.loud .outcome { margin: 0 0 6px; font-size: 13px; }

@media (orientation: landscape) and (max-height: 500px) {
  body.arcade.loud .machine { padding: 4px 10px 0; }
  body.arcade.loud .machine-top { gap: 8px; margin-bottom: 2px; }
  body.arcade.loud .machine-top .back { width: 32px; height: 32px; }
  body.arcade.loud .machine-top .back::before { font-size: 19px; }
  body.arcade.loud .machine-top h1 { font-size: 14px; }
  body.arcade.loud .reels5 .window { max-width: min(100%, 60vh);
                                     margin: 3px auto 5px; }
  body.arcade.loud .controls:not(.deck) .spin { width: 62px; height: 62px;
                                     font-size: 11px; }
  body.arcade.loud .controls:not(.deck) { gap: 9px; }
  body.arcade.loud .bet { margin-right: -9px; padding-right: 12px; }
  body.arcade.loud .controls:not(.deck) .deck-read { padding-left: 12px; }
}

/* THE DECK IS A BAR. With the cabinet panel gone the controls were floating
   on the room -- the reference's deck is a lit band across the foot of the
   screen and it is what separates the machine from the floor. Full width
   rather than the machine's, because that is what the reference does and
   because the negative margins that used to tie this to the cabinet's padding
   have nothing to tie to now. */
body.arcade.loud .controls:not(.deck) {
  margin: 0; padding: 12px 20px 14px; border-radius: 16px 16px 0 0;
  background: linear-gradient(180deg, rgb(10 30 15 / .92), rgb(4 14 7 / .96));
  box-shadow: inset 0 2px 0 rgb(255 214 106 / .35),
              0 -8px 26px -14px rgb(0 0 0 / .9);
}

/* "$" AND THE FIGURE ON ONE LINE. The readout is a column flex, so the
   currency mark and the number were two separate rows -- "$" alone over
   "1.00", which is not how anybody writes money. A row that wraps, with the
   label and the per-line figure each taking a full basis, puts the pair back
   together and leaves the other two on their own lines. */
body.arcade.loud .bet {
  flex-direction: row; flex-wrap: wrap; align-items: baseline;
  justify-content: center; column-gap: 2px;
}
body.arcade.loud .bet .deck-label,
body.arcade.loud .reels5 .per-line { flex-basis: 100%; text-align: center; }

/* The sentence sits on the room now rather than on a cabinet, so it carries
   its own ground the way every other caption on this surface does. */
body.arcade.loud .outcome {
  color: #d8ffe6;
  text-shadow: 0 1px 0 rgb(0 0 0 / .9), 0 0 10px rgb(0 0 0 / .9),
               0 0 22px rgb(0 0 0 / .75);
}
body.arcade.loud .outcome.won { color: #ffe07a; }

/* THE PAPERWORK KEEPS ITS DISTANCE. It is deliberately on the page and not
   behind an (i) -- see cabinet.html -- but with the machine now fitting the
   screen it had crowded up under the deck. A screen's worth of gap puts it
   back where it was: one scroll down, in full. */
body.arcade.loud .maths { margin-top: 26px; }
body.arcade.loud .machine .paytable { margin-top: 26px; }

/* ====================================================== the info carousel

   Troy's call: the machine's info stops being a row of tabs and becomes the
   reference's paged book. Measured off its four info pages -- a framed panel
   over the dimmed game, one page at a time, a round arrow on each outer edge,
   a row of dots along the bottom, and BACK TO GAME on the corner.

   THE PAGING IS THE STYLESHEET'S. Radio inputs and labels, as the tabs were:
   the arrows are labels pointing at the neighbouring radio, and `:checked ~`
   decides which page and which pair of arrows is on screen. It pages, wraps
   and closes with nothing loaded.

   WHAT IS COPIED IS THE FURNITURE, NOT THE FRAME. The reference's border is a
   painted gold-and-jade rope from its own art; ours is drawn -- a rim, a
   bevel and a glow in the season's own metal. Flagged as art rather than
   traced. */
.infobook > summary {
  width: 40px; height: 40px; font-size: 19px;
}
.infobook-panel {
  /* OVER THE GAME, not hanging off the (i). The reference's book takes the
     screen and dims what is behind it, which is the difference between a
     tooltip and a page you read. */
  position: fixed; inset: 0; z-index: 45; width: auto; max-height: none;
  top: 0; right: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 3vh 6vw; border-radius: 0; overflow: visible;
  background: rgb(0 0 0 / .72); box-shadow: none;
}
.infobook-book {
  position: relative; width: 100%; max-width: 1080px; max-height: 94vh;
  display: flex; flex-direction: column;
  padding: 20px 22px 54px; border-radius: 18px;
  background: linear-gradient(180deg, rgb(14 38 20 / .98), rgb(5 16 8 / .99));
  box-shadow: inset 0 0 0 3px rgb(255 214 106 / .55),
              inset 0 0 0 7px rgb(10 30 15 / .9),
              inset 0 0 0 9px rgb(255 214 106 / .28),
              0 30px 70px -24px rgb(0 0 0 / .95);
}
/* The radios drive everything and are never seen. Not display:none -- that
   takes them out of the tab order, and the book has to be reachable from a
   keyboard. */
.infobook-radio {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  clip-path: inset(50%);
}

.infobook-stage { flex: 1 1 auto; min-height: 0; overflow-y: auto;
                  padding: 4px 6px; }
.infobook-page { display: none; }
/* ONE PAGE AT A TIME, chosen by which radio is checked. nth-of-type on the
   section, so the pages count themselves and the rule does not have to know
   how many there are. */
.infobook-radio:nth-of-type(1):checked ~ .infobook-stage .infobook-page:nth-of-type(1),
.infobook-radio:nth-of-type(2):checked ~ .infobook-stage .infobook-page:nth-of-type(2),
.infobook-radio:nth-of-type(3):checked ~ .infobook-stage .infobook-page:nth-of-type(3),
.infobook-radio:nth-of-type(4):checked ~ .infobook-stage .infobook-page:nth-of-type(4) {
  display: block;
}
.infobook-radio:focus-visible ~ .infobook-dots label { outline: 2px solid var(--accent); }

/* THE ARROWS, on the outer edges as the reference has them. Every pair is in
   the markup and only the checked page's pair is drawn. */
.infobook-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  display: none; align-items: center; justify-content: center;
  width: 54px; height: 54px; border-radius: 50%; cursor: pointer;
  font-size: 30px; line-height: 1; color: #06240f;
  background: radial-gradient(circle at 50% 30%, #7fe08a 0%, #2f9c4a 55%,
                              #14602c 100%);
  box-shadow: inset 0 3px 0 rgb(255 255 255 / .45), 0 4px 0 rgb(0 0 0 / .5),
              0 0 24px -6px rgb(120 230 140 / .8);
}
.infobook-prev { left: -27px; }
.infobook-next { right: -27px; }
.infobook-arrow:active { transform: translateY(calc(-50% + 3px)); }
.infobook-radio:nth-of-type(1):checked ~ .infobook-arrow[data-for="1"],
.infobook-radio:nth-of-type(2):checked ~ .infobook-arrow[data-for="2"],
.infobook-radio:nth-of-type(3):checked ~ .infobook-arrow[data-for="3"],
.infobook-radio:nth-of-type(4):checked ~ .infobook-arrow[data-for="4"] {
  display: flex;
}

/* THE DOTS. The reference's are unlabelled; ours carry the page's name, and
   at this size the name IS the dot -- four identical dots read aloud as four
   identical "page"s, and a reader has no other way to know where they are. */
.infobook-dots {
  position: absolute; left: 0; right: 0; bottom: 16px; z-index: 2;
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.infobook-dot {
  cursor: pointer; padding: 4px 12px 5px; border-radius: 999px;
  font-size: 10px; font-weight: 900; letter-spacing: .12em; color: #7fa88a;
  background: rgb(255 255 255 / .06);
  box-shadow: inset 0 0 0 1px rgb(255 214 106 / .18);
}
.infobook-radio:nth-of-type(1):checked ~ .infobook-dots .infobook-dot[data-for="1"],
.infobook-radio:nth-of-type(2):checked ~ .infobook-dots .infobook-dot[data-for="2"],
.infobook-radio:nth-of-type(3):checked ~ .infobook-dots .infobook-dot[data-for="3"],
.infobook-radio:nth-of-type(4):checked ~ .infobook-dots .infobook-dot[data-for="4"] {
  color: #06240f; background: var(--brass);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5), 0 0 16px -4px var(--rail-glow);
}

/* BACK TO GAME, on the corner where the reference puts it. */
.infobook-back {
  position: absolute; right: 18px; bottom: 12px; z-index: 3;
  padding: 9px 22px 10px; border: 0; border-radius: 999px; cursor: pointer;
  font: inherit; font-size: 13px; font-weight: 900; letter-spacing: .08em;
  color: #06240f;
  background: radial-gradient(circle at 50% 0%, #7fe08a 0%, #2f9c4a 60%,
                              #14602c 100%);
  box-shadow: inset 0 2px 0 rgb(255 255 255 / .45), 0 3px 0 rgb(0 0 0 / .5);
}
.infobook-back:active { transform: translateY(3px);
                        box-shadow: inset 0 2px 0 rgb(255 255 255 / .4); }

/* THE RETURN PAGE. The figure is the biggest thing on it and the qualifier is
   set on the same line at a size that is read rather than skipped -- the
   inline block set the note at 11px against a 13px figure, which was fine
   under a machine and is not fine as the headline of its own page. */
.infobook-page[data-page="return"] .maths { gap: 9px; font-size: 14px; }
.infobook-page[data-page="return"] .maths-return {
  align-items: baseline; gap: 10px;
  padding: 10px 14px; border-radius: 12px; background: var(--well);
  box-shadow: inset 0 3px 8px rgb(0 0 0 / .85),
              inset 0 0 0 1px rgb(255 214 106 / .3);
}
.infobook-page[data-page="return"] .maths-rtp { font-size: 30px; }
.infobook-page[data-page="return"] .maths-note {
  font-size: 13px; flex: 1 1 16ch; color: #cfe8d6;
}
.infobook-page[data-page="return"] .maths-win { font-size: 15px; }

@media (orientation: landscape) and (max-height: 500px) {
  .infobook-panel { padding: 2vh 7vw; }
  .infobook-book { padding: 12px 14px 40px; max-height: 96vh; }
  .infobook-arrow { width: 40px; height: 40px; font-size: 23px; }
  .infobook-prev { left: -20px; }
  .infobook-next { right: -20px; }
  .infobook-dots { bottom: 11px; gap: 7px; }
  .infobook-dot { font-size: 8px; padding: 3px 9px 4px; }
  .infobook-back { right: 12px; bottom: 8px; font-size: 11px;
                   padding: 6px 15px 7px; }
  .infobook-page[data-page="return"] .maths-rtp { font-size: 22px; }
  .infobook-page[data-page="return"] .maths { font-size: 12px; }
}

/* THE BOOK HAS TO ESCAPE ITS OWN STACKING CONTEXT.

   MEASURED, not guessed: the panel is `position: fixed; z-index: 45` and it
   still came out UNDER the top rail and behind the deck. The book itself fits
   -- 8px to 385px in a 393px viewport, dots and BACK TO GAME both inside it.
   Nothing was overflowing; it was being painted over.

   `main` is `position: relative; z-index: 1` -- set so the loud skin's
   structure lifts above the drawn starfield -- and a positioned element with
   a z-index makes a STACKING CONTEXT. So the panel's 45 is 45 among main's
   own children, and the whole of main is still the 1 that loses to the
   header's 2 and comes before the footer in paint order. A fixed overlay
   inside it can never cover the rails, whatever number it asks for.

   Lifting main outright is not the fix: the header outranks it deliberately,
   because the jackpot explainer opens downward out of the rail and has to
   come out over the machines. So main is lifted only WHILE THE BOOK IS OPEN,
   which is what `:has` is for. Where `:has` is not supported the book still
   opens and reads -- it simply sits under the rails, which is the old
   behaviour and not a broken one. */
body.arcade.loud:has(details.infobook[open]) > main { z-index: 55; }


/* THE DECK'S LEFT-HAND PANEL. The reference's purses sit on a raised block
   with a swept edge that runs into the rest of the deck -- it is what stops
   the two readouts reading as two loose pills. Drawn rather than fetched: a
   fill, a rim and a corner radius that is larger on the outer side than the
   inner, which is as much of a swoosh as CSS does honestly. */
body.arcade.loud .arcade-bottom .purse {
  padding: 5px 26px 6px 10px; border-radius: 12px 26px 26px 12px;
  background: linear-gradient(180deg, rgb(10 30 15 / .75), rgb(4 14 7 / .85));
  box-shadow: inset 0 0 0 1.5px rgb(255 214 106 / .28),
              inset 0 2px 6px rgb(0 0 0 / .5);
}

/* THE BUILD, bottom-right of the wait, where the reference prints its own.
   Ours is the asset version the templates already cache-bust on, so it is a
   real answer to "which build is this" rather than a number for decoration --
   which is the same test every other figure on this surface has to pass. */
.loading-version {
  position: absolute; right: 14px; bottom: 10px;
  font-size: 11px; letter-spacing: .06em; color: #6f8f78;
  font-variant-numeric: tabular-nums;
}

@media (orientation: landscape) and (max-height: 500px) {
  body.arcade.loud .arcade-bottom .purse {
    padding: 3px 18px 4px 7px; border-radius: 9px 20px 20px 9px;
  }
  .loading-version { font-size: 9px; right: 10px; bottom: 7px; }
}


/* ------------------------------------------------------------- eZFunds

   THE RAIL BUTTON IS GONE AND SO ARE ITS RULES. There were two controls for
   one placeholder; the eZFUNDS LINK pill is the one play.js actually binds,
   and it wears `signin-cta` because it stands where ACCOUNT LOGIN stands.
   What follows is the PANEL, which both of them opened and which the pill
   still opens. */

/* THE PANEL. The mailbox's cabinet, because it is the mailbox's kind of
   thing: a short piece of text behind a button on the rail. What is its own
   is the state -- everything in it is a placeholder and has to READ as one. */
.ezfunds-lead { margin: 0 0 8px; font-size: 13px; color: var(--text); }
.ezfunds-note { margin: 0 0 12px; font-size: 12px; color: var(--muted); }
.ezfunds-actions { display: flex; flex-direction: column; gap: 8px; }
.ezfunds-go {
  padding: 10px 14px; border: 0; border-radius: 10px; font: inherit;
  font-size: 13px; font-weight: 800; letter-spacing: .04em;
  background: linear-gradient(180deg, #2f7d4a, #17452a); color: #d8ffe6;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .16), 0 2px 0 rgb(0 0 0 / .5);
}
/* DISABLED AND LOOKING IT. A placeholder that looks pressable is a payment
   flow that lies -- see the template. The cursor says so too, because a
   pointer over a dead control is the last thing somebody checks. */
.ezfunds-go[disabled] {
  cursor: not-allowed; filter: saturate(.35) brightness(.72);
  box-shadow: inset 0 2px 6px rgb(0 0 0 / .5);
}
.ezfunds-soon {
  margin: 12px 0 0; padding: 9px 11px; border-radius: 9px;
  background: var(--warn-bg); color: var(--text); font-size: 12px;
  box-shadow: inset 0 0 0 1px rgb(217 139 74 / .35);
}

/* The front door's own submit, which is a plain sign-in button again. */
body.arcade .signin-submit {
  padding: 14px 30px; border: 0; border-radius: 999px; cursor: pointer;
  font: inherit; font-size: 17px; font-weight: 900; letter-spacing: .06em;
  background: var(--brass); color: var(--on-accent);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .5),
              inset 0 -2px 4px rgb(0 0 0 / .3),
              0 4px 0 rgb(0 0 0 / .55), 0 8px 20px -6px var(--rail-glow);
}
body.arcade .signin-submit:active {
  transform: translateY(3px);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .4);
}

@media (orientation: landscape) and (max-height: 500px) {
  body.arcade .signin-submit { padding: 10px 20px; font-size: 14px; }
}

/* --------------------------------------------- the machine's own room

   Every cabinet page stood on the lobby's green mosaic, so a player who
   opened five different games was looking at the same room five times. The
   reference paints a scene per title; that painting is art we do not have,
   and the honest stand-in is the game's OWN tile art -- the one picture we
   know exists for this game and know we may use, because it is already its
   face on the wall.

   THROWN LARGE AND SOFT, and every number here is doing a job. `cover` at
   115% so the portrait fills a landscape screen with nothing to letterbox;
   a heavy blur because this is a room rather than a poster, and a sharp
   character behind five reels competes with them; saturation up because blur
   flattens colour, and the point is the game's PALETTE reaching the walls;
   and a dark wash over the whole thing so the machine stays the brightest
   object on the screen -- which is the rule the lobby's floor already keeps.

   z-index: -1 puts it behind the structure without touching the stacking
   order the rest of this file depends on. */
body.arcade.loud.game-lit::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: var(--game-art) center / cover no-repeat;
  transform: scale(1.15);
  /* MEASURED BY LOOKING, and the first numbers were wrong: at brightness .42
     under the wash below, a light tile came out near-black and the room had
     no theme in it at all -- which is the whole point of the rule. .66 lets
     the game's palette reach the walls and still leaves the reels the
     brightest thing on the screen. */
  /* TUNED AGAINST ACTUAL ART THIS TIME. The earlier numbers were set while
     `--game-art` was empty -- the if/elif in the layout meant a shop with a
     backdrop never set it -- so what was being "tuned by looking" was a stack
     of gradients with no picture in it. The moment a real picture appeared
     behind them, .66 filled the screen with a rainbow wash that competed with
     the reels. A room is recessive: heavy blur, colour kept, brightness well
     down. */
  /* DARK AND ATMOSPHERIC, which is what the reference's rooms are: a painted
     scene the machine STANDS IN, not a lit wall behind it. Tuned down again
     after Troy saw .3 as still washed out on a handset -- a phone screen at
     full brightness in a lit room reads considerably lighter than this
     capture does. */
  filter: blur(58px) saturate(1.1) brightness(.2);
}
/* The wash and the vignette on top of it, so a light picture cannot wash the
   room out and the corners still fall away. */
body.arcade.loud.game-lit {
  background:
    radial-gradient(150% 120% at 50% 40%,
                    rgb(0 0 0 / .38) 26%, rgb(0 0 0 / .9) 100%),
    radial-gradient(900px 520px at 50% 118%, rgb(0 0 0 / .55) 0%, transparent 70%),
    linear-gradient(180deg, rgb(6 20 10 / .42) 0%, rgb(3 12 6 / .62) 100%);
  background-attachment: fixed;
}
/* The starfield is the LOBBY's weather. Over a game's own scene it is a
   second sky, which is the same call the painted-backdrop rule already makes
   a few blocks up. */
body.arcade.loud.game-lit::before { content: none; }

/* ============================================== the notch, and the corners

   THE THING HEADLESS CANNOT SHOW YOU.

   shell.html sets `viewport-fit=cover`, deliberately -- it is what lets the
   frame reach under a notch instead of leaving a black bar at the top of a
   phone. The price of that setting is that the viewport now includes area the
   hardware covers: in LANDSCAPE an iPhone puts the camera housing down one
   side and the home indicator along the bottom, and both sit ON TOP of
   whatever is drawn there.

   Nothing in this stylesheet accounted for it, so everything anchored to an
   edge -- the jackpot rail, the deck, the rail's panels -- was being drawn
   into a strip the phone was going to cover. Measured in a headless browser
   the layout does not overflow at all (852x393 and 667x375 both report
   scrollWidth == innerWidth and the eZFunds panel sixteen pixels INSIDE the
   right edge), which is exactly why this went unnoticed: a headless browser
   has no notch, so the clipping is invisible until it is on a real handset.

   `env(safe-area-inset-*)` is what the phone actually reports, and `max()`
   means a device without a notch is untouched -- the inset resolves to zero
   and the original padding wins. */
body.arcade.loud .arcade-top {
  padding-left: max(10px, env(safe-area-inset-left));
  padding-right: max(10px, env(safe-area-inset-right));
  padding-top: max(3px, env(safe-area-inset-top));
}
body.arcade.loud .arcade-bottom {
  padding-left: max(14px, env(safe-area-inset-left));
  padding-right: max(14px, env(safe-area-inset-right));
  padding-bottom: max(8px, env(safe-area-inset-bottom));
}
body.arcade.loud .arcade-body {
  padding-left: max(10px, env(safe-area-inset-left));
  padding-right: max(10px, env(safe-area-inset-right));
}
/* The rail's panels are fixed to the right-hand edge, which in landscape is
   the edge the camera housing is on half the time -- a phone rotated the
   other way puts it on the left, so both sides are answered. */
.mail-panel {
  right: max(16px, env(safe-area-inset-right));
  left: auto;
  bottom: max(74px, calc(74px + env(safe-area-inset-bottom)));
}
.infobook-panel {
  padding-left: max(6vw, env(safe-area-inset-left));
  padding-right: max(6vw, env(safe-area-inset-right));
  padding-bottom: max(3vh, env(safe-area-inset-bottom));
}
/* And the machine itself, which runs to the edges on a cabinet page. */
body.arcade.loud .machine {
  padding-left: max(14px, env(safe-area-inset-left));
  padding-right: max(14px, env(safe-area-inset-right));
}

@media (orientation: landscape) and (max-height: 500px) {
  body.arcade.loud .arcade-top {
    padding-left: max(8px, env(safe-area-inset-left));
    padding-right: max(8px, env(safe-area-inset-right));
  }
  body.arcade.loud .arcade-bottom {
    padding-left: max(10px, env(safe-area-inset-left));
    padding-right: max(10px, env(safe-area-inset-right));
    padding-bottom: max(4px, env(safe-area-inset-bottom));
  }
  body.arcade.loud .machine {
    padding-left: max(10px, env(safe-area-inset-left));
    padding-right: max(10px, env(safe-area-inset-right));
  }
}


/* ==========================================================================
   THE CABINET FACE

   Everything above draws a well-made recessed rectangle. The reference draws
   a piece of FURNITURE: a carved gold head over the window, a post between
   every reel, a stud at every joint, readouts hung off the right-hand side
   and a deck across the foot with a green key the size of a fist on the end
   of it. Ours was a dark box with four controls under it, and the difference
   is not polish -- it is that theirs reads as a machine and ours read as a
   form.

   ALL OF IT IS CSS. Gradients, one repeating fret and a radial per stud. That
   is deliberate and it is not a shortcut: this is PLATFORM chrome and it has
   to retint when a season moves --accent, stay sharp at any size on any
   screen, and cost nothing to ship. A PNG frame would do none of those.

   THE TWO THINGS IT DOES NOT DRAW ARE THE TWO THINGS THAT ARE ART: the scene
   behind the machine and the faces on the reels. Both have a named slot
   waiting -- `--cab-scene` below and the cell images the runtime already
   paints -- and both belong to the extraction lane. Nothing here invents a
   symbol, a background or a figure.
   ========================================================================== */

.reels5 {
  /* The frame's own dimensions, in one place, so the landscape block moves
     the whole cabinet by moving three numbers rather than twenty.

     IN vh, NOT vw, and that is not a stylistic choice. The window is capped
     off the viewport HEIGHT -- see the machine block above and the reason it
     gives -- so the frame around it is a height-budgeted object too. Sized in
     vw the band would have thickened on a wide short window while the glass
     inside it was shrinking, which is the one direction a frame must not
     move. */
  --cab-b: clamp(6px, 1.1vh, 15px);      /* the gold band's thickness */
  --cab-head: clamp(10px, 2.1vh, 26px);  /* the carved head above the glass */
  --cab-stud: clamp(10px, 2.3vh, 26px);  /* a joint stud */
  --cab-gold-lo: #7a5410;
  --cab-gold-mid: #d9a12c;
  --cab-gold-hi: #ffe9a8;
  /* THE JADE IN THE STUDS. Green because the brand is green -- the reference
     sets emeralds in its gold for the same reason its lobby is blue. */
  --cab-gem: #3fd07a;
  --cab-gem-deep: #0d5c31;
}

/* ------------------------------------------------------------ the head keys
   Two round keys on the shoulders of the cabinet, the way the reference hangs
   HOME on the left and the gear on the right. Dark glass in a gold ring: the
   same metal as the frame, so they read as part of the same object. */
.reels5 .machine-top {
  display: flex; align-items: center; gap: 10px; margin-bottom: 6px;
  flex-wrap: nowrap;
}
.reels5 .machine-top h1 {
  margin: 0 auto; font-size: clamp(15px, 2.2vw, 24px);
}
.cab-key, .reels5 .infobook > summary, .cabset > summary {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: clamp(34px, 4.6vw, 46px); height: clamp(34px, 4.6vw, 46px);
  border-radius: 50%; cursor: pointer; list-style: none;
  color: var(--cab-gold-hi); font-size: clamp(15px, 2vw, 21px);
  font-weight: 800; text-decoration: none;
  background:
    radial-gradient(120% 120% at 30% 22%, #3a3327 0%, #14120c 62%, #0a0906 100%);
  box-shadow:
    /* the ring: highlight, body, shadow -- a turned bezel, not a border */
    0 0 0 2px var(--cab-gold-lo),
    0 0 0 4px var(--cab-gold-mid),
    0 0 0 5px var(--cab-gold-hi),
    0 0 0 7px var(--cab-gold-lo),
    inset 0 2px 5px rgb(0 0 0 / .8),
    0 4px 10px -3px rgb(0 0 0 / .8);
}
.reels5 .infobook > summary::-webkit-details-marker,
.cabset > summary::-webkit-details-marker { display: none; }

/* The two glyphs are drawn, not typed: a house and a gear at any size, in the
   metal, with no font to fail to load. */
.cab-home > span {
  width: 46%; height: 46%;
  background: currentColor;
  -webkit-mask: var(--glyph-home) center / contain no-repeat;
          mask: var(--glyph-home) center / contain no-repeat;
}
.cabset > summary > span {
  width: 50%; height: 50%;
  background: currentColor;
  -webkit-mask: var(--glyph-gear) center / contain no-repeat;
          mask: var(--glyph-gear) center / contain no-repeat;
}
/* ON THE HEADER, NOT ON THE FIVE-REEL CABINET. These were scoped to
   `.reels5`, which was true while it was the only cabinet with a gear;
   the settings key is now on all three, and a mask with no URL behind it
   draws a blank disc. `.machine-top` is the one element the three
   cabinets genuinely share. */
.machine-top {
  --glyph-home: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 3 2.5 11.2l1.3 1.5L5 11.6V21h5v-6h4v6h5v-9.4l1.2 1.1 1.3-1.5Z'/%3E%3C/svg%3E");
  --glyph-gear: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M19.4 13a7.9 7.9 0 0 0 0-2l2-1.6-2-3.4-2.4 1a7.7 7.7 0 0 0-1.7-1L15 3.4h-4l-.3 2.6a7.7 7.7 0 0 0-1.7 1l-2.4-1-2 3.4L6.6 11a7.9 7.9 0 0 0 0 2l-2 1.6 2 3.4 2.4-1a7.7 7.7 0 0 0 1.7 1l.3 2.6h4l.3-2.6a7.7 7.7 0 0 0 1.7-1l2.4 1 2-3.4Zm-6.4 3.1A4.1 4.1 0 1 1 17.1 12 4.1 4.1 0 0 1 13 16.1Z'/%3E%3C/svg%3E");
}

/* THE SETTINGS PANEL. Two real controls and no third invented to fill it:
   the sound switch (the same hook as the rail's, so they agree) and the way
   back to the wall. */
.cabset { position: relative; }
.cabset[open] > summary { color: var(--accent); }
.cabset-panel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 40;
  width: min(230px, calc(100vw - 32px));
  padding: 12px; border-radius: 14px; text-align: left;
  background: linear-gradient(180deg, #1b2a1c, #0a1209);
  box-shadow: 0 0 0 2px var(--cab-gold-lo), 0 0 0 3px var(--cab-gold-mid),
              0 18px 34px -14px rgb(0 0 0 / .95);
}
.cabset-panel h2 {
  margin: 0 0 8px; font-size: 11px; letter-spacing: .16em;
  color: var(--cab-gold-hi);
}
.cabset-row {
  display: flex; width: 100%; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 6px; padding: 9px 11px; border: 0; border-radius: 9px;
  background: rgb(255 255 255 / .05); color: var(--text);
  font: inherit; font-size: 13px; text-decoration: none; cursor: pointer;
}
.cabset-row:hover { background: rgb(255 255 255 / .11); }

/* SKIP SPINS. The row says what it does under its own name, so the second line
   is smaller and quieter but still readable -- a control that changes what a
   gambling screen shows should not need to be pressed to find out. */
.cabset-row .cabset-word { display: flex; flex-direction: column; gap: 2px; }
.cabset-note {
  font-size: 10px; line-height: 1.25; letter-spacing: .02em;
  color: rgb(255 255 255 / .55);
}
/* THE STATE IS A WORD, not a colour alone: OFF and ON read the same to
   somebody who cannot tell the two greens apart. */
.cabset-state {
  flex: 0 0 auto; min-width: 34px; text-align: center;
  font-size: 11px; font-weight: 800; letter-spacing: .08em;
  padding: 3px 6px; border-radius: 6px;
  background: rgb(0 0 0 / .35); color: rgb(255 255 255 / .6);
}
.cabset-skip[aria-pressed="true"] { background: rgb(255 214 106 / .16); }
.cabset-skip[aria-pressed="true"] .cabset-state {
  background: var(--cab-gold-mid, #b98c2a); color: #17130a;
}

/* ------------------------------------------------------------- the face
   Post, frame, rail. The middle column is the one that shrinks; the two
   outside it are as wide as what is in them. */
/* A ROW, NOT A THREE-TRACK GRID, and the difference is where the post ends
   up. With `auto 1fr auto` the middle track eats every spare pixel and the two
   outside it are shoved against the edges of the screen -- so the unit plate,
   which the reference prints ON the frame's post, sat two hundred pixels away
   from the frame with nothing between them. A centred flex row puts all three
   back together whatever the window is doing. */
.cab {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(4px, 1vw, 14px);
  margin: 4px 0 6px;
}
.cab-frame { flex: 0 1 auto; }

/* The unit plate, printed down the post the way the reference prints it. */
.cab-post {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  font-size: clamp(9px, 1.15vw, 14px); font-weight: 800; letter-spacing: .1em;
  line-height: 1.05; color: var(--cab-gold-hi);
  text-shadow: 0 1px 0 #000, 0 0 12px rgb(255 200 80 / .45);
}
/* THE WORD ON ITS END, in both gutters: the count across, the letters stacked
   upright below it, as every original in the reference set prints it. The
   post sits hard against the frame on each side -- the reference hangs it ON
   the frame's edge -- so the gap on the posts' inner side is pulled in. */
.cab-post-stood {
  writing-mode: vertical-rl; text-orientation: upright;
  letter-spacing: 0; line-height: 1.05;
}
.cab-post-left { margin-right: calc(clamp(4px, 1vw, 14px) * -0.6); }
.cab-post-right { margin-left: calc(clamp(4px, 1vw, 14px) * -0.6); }

/* THE FRAME.

   Three layers of metal and one dark bevel, drawn with box-shadow rings so
   the whole band scales off one custom property. The `--cab-scene` slot is
   the machine's own painted room: EMPTY here on purpose, and the art lane
   fills it. Until it does the frame stands on the room's own floor, which is
   honest -- a placeholder scene invented to fill the gap would be exactly the
   thing this job was told not to draw. */
.cab-frame {
  position: relative; min-width: 0;
  padding: calc(var(--cab-b) + var(--cab-head)) var(--cab-b) var(--cab-b);
  border-radius: clamp(8px, 1.4vw, 16px);
  background:
    var(--cab-scene, none) center / cover no-repeat,
    /* the band itself: a turned edge, brightest just inside the rim */
    linear-gradient(160deg,
      var(--cab-gold-lo) 0%, var(--cab-gold-mid) 14%, var(--cab-gold-hi) 26%,
      var(--cab-gold-mid) 40%, var(--cab-gold-lo) 58%,
      var(--cab-gold-mid) 78%, var(--cab-gold-hi) 92%, var(--cab-gold-lo) 100%);
  box-shadow:
    inset 0 0 0 1px rgb(255 255 255 / .5),
    0 0 0 1px rgb(0 0 0 / .8),
    0 18px 34px -20px rgb(0 0 0 / .95),
    0 0 40px -10px rgb(255 190 60 / .22);
}

/* THE CARVED HEAD. A repeating fret across the top of the frame, which is the
   motif the reference carries and the one every cabinet of this kind carries.
   Geometry, tiled -- so it is one declaration whatever the cabinet's width. */
.cab-frame::before {
  content: ""; position: absolute; z-index: 1;
  left: var(--cab-b); right: var(--cab-b);
  top: calc(var(--cab-b) * .55); height: var(--cab-head);
  background:
    url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='24' viewBox='0 0 48 24'%3E%3Cg fill='none' stroke='%233a2607' stroke-width='2.4'%3E%3Cpath d='M2 20V8h10v8H6v-4'/%3E%3Cpath d='M46 20V8H36v8h6v-4'/%3E%3Cpath d='M18 20V4h12v16'/%3E%3C/g%3E%3C/svg%3E")
      center / auto 100% repeat-x;
  opacity: .55;
  filter: drop-shadow(0 1px 0 rgb(0 0 0 / .55));
  pointer-events: none;
}

/* THE STUDS. Laid across the top rim on the post lines, so a stud lands on
   every joint between two reels and one on each end -- the template emits
   one per joint from the reel count, and this only has to space them. */
.cab-studs {
  position: absolute; z-index: 2; pointer-events: none;
  left: 0; right: 0; top: calc(var(--cab-stud) / -2 + var(--cab-b) / 2);
  padding: 0 calc(var(--cab-b) / 2);
  display: flex; justify-content: space-between;
}
.cab-studs-foot {
  top: auto; bottom: calc(var(--cab-stud) / -2 + var(--cab-b) / 2);
}
.cab-stud {
  width: var(--cab-stud); height: var(--cab-stud); border-radius: 50%;
  background:
    /* the facet, then the stone, then the collet */
    radial-gradient(50% 50% at 34% 28%, rgb(255 255 255 / .95) 0%,
                    rgb(255 255 255 / 0) 55%),
    radial-gradient(75% 75% at 50% 50%, var(--cab-gem) 0%,
                    var(--cab-gem-deep) 72%, #04240f 100%);
  box-shadow:
    0 0 0 2px var(--cab-gold-lo),
    0 0 0 3px var(--cab-gold-hi),
    0 0 0 4px var(--cab-gold-lo),
    0 2px 5px rgb(0 0 0 / .8),
    0 0 12px -2px rgb(70 230 130 / .55);
}

/* THE CAP MOVES OUT ONTO THE FRAME.

   The window has been capped off the viewport height since the machine block
   above -- 68vh across, so five square cells come out about 41vh tall and the
   deck lands on the screen. With a frame around it that cap is in the wrong
   place: the glass would hold its size and the FRAME would grow past it, so
   the band ended up three hundred pixels of gold with a small window floating
   in the middle of it.

   So the frame takes the cap and the glass simply fills it. The number is
   smaller than 68 because the band, the head and the studs are height the
   window used to have. */
/* THE MACHINE TAKES ITS FULL WIDTH, and it has to be said rather than
   inherited. `.arcade-body` is a centred flex column, so a child that is not
   told otherwise is sized to its own CONTENT -- which used to be the window
   alone and is now a three-piece row. The head and the deck were then drawn
   at the width of that row instead of the width of the screen, so the deck
   stopped being a band across the foot and became a box under the reels. */
body.arcade.loud .machine.reels5 { width: 100%; }

/* THE FRAME IS SIZED OFF WHAT IS LEFT, not off a flat share of the height.

   The window has been capped in plain vh since the machine block above, and
   that is wrong for a reason nothing showed until the deck grew: the chrome
   around it -- two shell rails, the machine's head, the sentence, the deck --
   is a FIXED number of pixels, not a share. At 900 tall it is 24% of the
   screen and at 800 it is 27%, so a cap that takes the same 66% at both
   heights fits on one and runs off the other. It did: 1440x900 landed to the
   pixel while 1280x800 pushed the shell's own footer 77px below the fold.

   Subtract the chrome first, then divide. `--cab-chrome` is that fixed stack
   measured on this build, and the 5/3 is the machine's own geometry -- five
   square cells across, three down -- so the arithmetic reads as what it is:
   give the frame the height that is left, and let its width follow. */
body.arcade.loud .reels5 {
  /* the two rails, the head, the sentence, the deck and the band, in pixels */
  /* ...PLUS WHATEVER THE NOTCH TAKES. The rails pad themselves out of
     `env(safe-area-inset-*)` (see the safe-area block above), so on a phone
     the chrome is physically taller than the constant measured on a desktop,
     by an amount only the device knows. Adding the insets is exact where
     there are insets and free where there are none: env() resolves to zero on
     every screen without a cutout, so this is the same 545px everywhere else.
     Without it the budget was tuned on a browser that reports no notch and
     would have run the deck off the bottom of the one phone the product is
     actually held in. */
  --cab-chrome: calc(545px + env(safe-area-inset-top)
                          + env(safe-area-inset-bottom));
}
/* AND THE WIDTH THAT BUYS IS THE WINDOW'S OWN PROPORTION, not a remembered
   one. This said `* 5 / 3` -- five SQUARE cells three high -- and both halves
   of that have stopped being true. The cells are 1.2 wide per 1 tall now
   (CELL_ASPECT in reels-canvas.js, measured off three originals), so a five
   by three window is 2:1 and this was giving it width for a 5:3 one: on a
   720px screen, 292px where 350 would fit.

   And not every machine is five by three. Bite of London is SIX by FOUR and
   was drawn 292px across with room for 315 -- about a third of the cabinet it
   should have been, on every screen short enough for this cap to bite.

   `--win-aspect` comes from the template and is reels / rows * 1.2. The
   fallback is the five-by-three answer, so a page that somehow arrives
   without it behaves as the commonest machine rather than collapsing. */
body.arcade.loud .reels5 .cab-frame {
  flex: 0 1 min(100%, calc((100vh - var(--cab-chrome)) * var(--win-aspect, 2)));
  min-width: 0;
}
body.arcade.loud .reels5 .cab-frame > .window { max-width: none; }

/* AND THE RAIL MAY NOT BE THE TALLEST THING IN THE ROW. Ten faces stacked two
   wide came to more height than the frame beside them, so a side panel was
   deciding how tall the cabinet is. Capped to the glass; the faces scroll
   inside their own panel rather than being dropped, because which symbols are
   in play is a fact and a panel that silently shows eight of ten is worse
   than one you have to move. */
body.arcade.loud .reels5 .cab-rail {
  max-height: calc(100vh - var(--cab-chrome));
}
/* AND IT SHRINKS FROM THE GRID DOWN, not from the panel down. `min-height: 0`
   on the panel alone let it collapse to its caption -- ACTIVE SYMBOLS over an
   empty box, which says there are none. The grid is the part that scrolls, so
   the grid is the part that is allowed to be shorter than its content. */
body.arcade.loud .reels5 .actives { flex: 0 1 auto; min-height: 0; }
body.arcade.loud .reels5 .actives-grid {
  flex: 1 1 auto; overflow-y: auto; min-height: 34px; scrollbar-width: none;
}
body.arcade.loud .reels5 .actives-grid::-webkit-scrollbar { display: none; }

/* The glass sits INSIDE the frame now, so it drops the outer shadow it used
   to cast on the page and keeps only the recess. Everything about the reels
   themselves is unchanged: same grid, same aspect, same runtime. */
.reels5 .cab-frame > .window {
  position: relative; z-index: 0; width: 100%;
  margin: 0; border-radius: clamp(4px, .8vw, 9px);
  box-shadow: inset 0 4px 14px rgb(0 0 0 / .95),
              inset 0 0 0 1px rgb(0 0 0 / .85),
              0 0 0 1px rgb(255 255 255 / .12);
}

/* ------------------------------------------------------------- the rail
   The readouts down the right-hand side, where the reference keeps them. */
.cab-rail {
  display: flex; flex-direction: column; align-items: stretch;
  gap: clamp(4px, .9vw, 10px);
  width: clamp(56px, 8.5vw, 104px);
}

/* JP E'S. What this stake puts into the pots, to the cent, at the rates the
   engine actually rakes with -- see the template. The gauge is the share of
   the stake that is, drawn as a needle over an arc, because that is what the
   reference puts above the figure and it is a fact we hold. */
.jpe {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 5px 4px 6px; border-radius: 10px;
  background: linear-gradient(180deg, #1a1230, #0a0718);
  box-shadow: 0 0 0 2px #b03fd6, 0 0 14px -3px rgb(190 80 240 / .7),
              inset 0 1px 0 rgb(255 255 255 / .12);
}
.jpe-gauge {
  position: relative; width: 74%; aspect-ratio: 2 / 1; overflow: hidden;
  border-radius: 999px 999px 0 0;
  background:
    repeating-conic-gradient(from 180deg at 50% 100%,
      #5b7fd4 0deg 1.6deg, rgb(0 0 0 / 0) 1.6deg 12deg);
}
.jpe-needle {
  position: absolute; left: 50%; bottom: 0; width: 2px; height: 84%;
  background: var(--cab-gold-hi); transform-origin: 50% 100%;
  transform: translateX(-50%) rotate(calc(-90deg + var(--share, 0) * 180deg));
  box-shadow: 0 0 6px rgb(255 220 120 / .9);
}
.jpe-name {
  font-size: clamp(8px, 1vw, 11px); font-weight: 800; letter-spacing: .06em;
  color: #f78bff;
}
.jpe-figure {
  font-size: clamp(11px, 1.4vw, 16px); font-weight: 800; color: #fff;
  font-variant-numeric: tabular-nums;
}

/* ACTIVE SYMBOLS. The faces that are actually on these strips.
   THE SLOT IS THE CHROME AND THE FACE IS THE ART. Where the extraction lane
   has not reached a symbol yet the key is printed in the slot -- the same
   fallback the reels use, and the reason this panel needs no placeholder
   drawing of its own. */
.actives {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  /* The panel clips its own rounded box. Without this a scrolled grid draws
     its last row straight through the gold rim. */
  overflow: hidden;
  padding: 6px 4px 7px; border-radius: 10px;
  background: linear-gradient(180deg, rgb(255 255 255 / .07), rgb(0 0 0 / .45));
  box-shadow: 0 0 0 2px var(--cab-gold-lo), 0 0 0 3px var(--cab-gold-mid),
              inset 0 1px 0 rgb(255 255 255 / .1);
}
.actives-name {
  font-size: clamp(7px, .85vw, 9px); font-weight: 800; letter-spacing: .1em;
  line-height: 1.15; text-align: center; color: var(--cab-gold-hi);
}
.actives-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 3px; width: 100%;
  /* Rows start at the top and keep their size when the panel is short: a
     squeezed slot is an unreadable face, and scrolling to the rest is the
     honest trade. */
  align-content: start;
}
.actives-slot {
  display: grid; place-items: center; aspect-ratio: 1; overflow: hidden;
  border-radius: 5px; background: rgb(0 0 0 / .55);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .13);
}
.actives-slot img { display: block; width: 88%; height: auto; }
.actives-key {
  font-size: clamp(6px, .8vw, 9px); font-weight: 800; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; max-width: 100%;
  padding: 0 2px;
}

/* ------------------------------------------------------------- the deck
   Seven readings and controls in the reference's own order, on one bar with
   a lit gold lip along the top of it. A grid rather than a flex row: every
   piece has a job and a place, and none of them should be sliding about as
   the figures inside them change width. */
.reels5 .deck {
  display: grid; align-items: center;
  grid-template-columns:
    minmax(0, 1.25fr)   /* the purse */
    minmax(0, .95fr)    /* the unit and its keys */
    minmax(0, 1.5fr)    /* the plate */
    minmax(0, .7fr)     /* what the stake buys */
    auto                /* AUTO START */
    auto;               /* SPIN */
  gap: clamp(6px, 1.1vw, 16px);
  /* A BAND ACROSS THE FOOT OF THE SCREEN, which is what the reference's deck
     is and what the machine block above already made of the old row. Stated
     here because that rule now belongs to the cabinets that still run the old
     row, and the negative margins on the base `.controls` were tying this to
     a cabinet panel that is no longer drawn. */
  margin: 0; border-radius: 16px 16px 0 0;
  padding: clamp(8px, 1.1vw, 15px) clamp(10px, 1.4vw, 20px);
  background:
    linear-gradient(180deg, #241a0c 0%, #120d06 42%, #070502 100%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / .1),
    /* the lit lip the reference runs along the top of its deck */
    inset 0 3px 0 -1px var(--cab-gold-mid),
    0 -8px 22px -14px rgb(255 190 60 / .6);
}

.deck-purse { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
/* A readout: a lit window with its name over it, which is how every figure on
   this surface is drawn. */
.deck-read {
  display: flex; flex-direction: column; align-items: flex-start; min-width: 0;
  padding: 3px 10px 4px; border-radius: 999px;
  background: rgb(0 0 0 / .62);
  /* A RIM, because a lit window has one. Cropped side by side against the
     reference these were black lozenges that vanished into the bar; theirs
     sit in a thin bright outline and read as recessed glass. */
  box-shadow: inset 0 2px 6px rgb(0 0 0 / .9),
              inset 0 0 0 1px rgb(255 255 255 / .1),
              0 0 0 1px rgb(220 180 90 / .38);
}
.deck-read-wide { width: 100%; }
.deck-label {
  font-size: clamp(7px, .82vw, 10px); font-weight: 800; letter-spacing: .12em;
  color: var(--cab-gold-hi); white-space: nowrap;
}
.deck-figure {
  font-size: clamp(12px, 1.5vw, 19px); font-weight: 800; color: #fff;
  font-variant-numeric: tabular-nums; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.deck-figure .cur { color: var(--cab-gold-hi); font-size: .72em; margin-right: 2px; }

/* The unit, its figure and the rocker under them. */
.deck-stake {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  min-width: 0;
}
.deck-stake .deck-figure {
  padding: 2px 12px 3px; border-radius: 999px; background: rgb(0 0 0 / .62);
  box-shadow: inset 0 2px 6px rgb(0 0 0 / .9),
              inset 0 0 0 1px rgb(255 255 255 / .1),
              0 0 0 1px rgb(220 180 90 / .38);
}
/* ONE ROCKER, NOT TWO BUTTONS. The reference casts − and + as a single gold
   key split down the middle, and it is the better control as well as the
   matching one: two targets that touch cannot be missed between. */
.rocker {
  display: flex; border-radius: 999px; overflow: hidden;
  background: linear-gradient(180deg, var(--cab-gold-hi) 0%,
              var(--cab-gold-mid) 48%, var(--cab-gold-lo) 100%);
  box-shadow: 0 2px 0 rgb(0 0 0 / .6), 0 5px 10px -4px rgb(0 0 0 / .8),
              inset 0 1px 0 rgb(255 255 255 / .7);
}
.reels5 .deck .stake {
  flex: 0 0 auto; width: clamp(30px, 3.6vw, 46px);
  height: clamp(22px, 2.6vw, 32px);
  border: 0; background: none; color: #3a2a06;
  font: inherit; font-size: clamp(17px, 2.1vw, 26px); font-weight: 900;
  line-height: 1; cursor: pointer; border-radius: 0;
  box-shadow: none; transition: filter .07s ease;
}
.reels5 .deck .stake + .stake { box-shadow: inset 1px 0 0 rgb(0 0 0 / .35); }
.reels5 .deck .stake:hover { filter: brightness(1.12); }
.reels5 .deck .stake:active { filter: brightness(.86); transform: none; }

.deck-plate .deck-label { font-size: clamp(8px, .95vw, 12px); }

/* THE PLATE. Red, because the reference's is -- it is the one thing on the
   deck that is not gold, and that is why the eye lands on it. Two rows: what
   the last spin paid, and what this one costs. */
.deck-plate {
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
  padding: clamp(5px, .8vw, 10px) clamp(8px, 1.1vw, 15px);
  border-radius: clamp(6px, .9vw, 11px);
  background:
    repeating-linear-gradient(180deg,
      rgb(255 255 255 / .05) 0 1px, rgb(0 0 0 / 0) 1px 4px),
    linear-gradient(180deg, #5c1508 0%, #a32a0c 55%, #d8501c 100%);
  box-shadow:
    0 0 0 2px var(--cab-gold-lo), 0 0 0 3px var(--cab-gold-mid),
    inset 0 1px 0 rgb(255 255 255 / .25),
    inset 0 -6px 14px -6px rgb(0 0 0 / .6);
}
.deck-plate-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.deck-plate .deck-figure { font-size: clamp(12px, 1.6vw, 20px); }

/* THE FREE-GAME PLATE takes the LINES plate's place while a feature is
   running, rather than being squeezed in beside it: the deck is a fixed row
   and a fifth plate truncated GAME WIN and TOTAL PLAY to "0..." and "$..".
   Nothing is lost -- the count is printed down both gutters of the frame,
   which is where the reference puts it and where it stays all game. */
/* The free-spin row sits ON the win plate, so the deck keeps its shape. One
   line, never wrapped: "7 x $0.25" broken over three lines reads as three
   numbers rather than as one fact. */
.deck-free .deck-figure { color: #8ef08a; }
.deck-free-figure { white-space: nowrap; font-size: clamp(11px, 1.4vw, 17px); }

.deck-units { display: flex; flex-direction: column; align-items: center; gap: 2px;
              min-width: 0; }
.deck-units-figure {
  width: 100%; text-align: center; text-overflow: clip;
  /* ALL WAYS is two words and the plate is narrow. Wrapping is right and
     clipping is not: "ALL W..." tells a player nothing about what their stake
     bought, which is the one thing this readout is for. */
  white-space: normal; line-height: 1.05;
  padding: 4px 8px 5px; border-radius: 8px; background: rgb(0 0 0 / .6);
  font-size: clamp(10px, 1.25vw, 16px);
  box-shadow: inset 0 0 0 1px var(--cab-gold-lo),
              inset 0 2px 6px rgb(0 0 0 / .85);
}

/* AUTO START, cast in the same gold as the rocker. */
.reels5 .deck .auto {
  flex: 0 0 auto; padding: clamp(5px, .8vw, 10px) clamp(8px, 1.2vw, 16px);
  border: 0; border-radius: clamp(6px, .9vw, 11px);
  background: linear-gradient(180deg, var(--cab-gold-hi) 0%,
              var(--cab-gold-mid) 50%, #a06f13 100%);
  color: #2a1e04; font: inherit; font-weight: 800; line-height: 1.05;
  font-size: clamp(10px, 1.2vw, 15px); letter-spacing: .04em; cursor: pointer;
  /* AUTO over START, the way the reference stacks it -- and STOP on its own
     line when it says STOP. From the box, not from a <br>: autoplay.paint()
     writes this caption with textContent, so any element inside the button is
     gone the first time it repaints. */
  width: min-content; text-align: center; white-space: normal;
  box-shadow: 0 3px 0 rgb(0 0 0 / .55), 0 7px 14px -6px rgb(0 0 0 / .8),
              inset 0 1px 0 rgb(255 255 255 / .65);
  transition: transform .07s ease, box-shadow .07s ease;
}
.reels5 .deck .auto:active,
.reels5 .deck .auto[aria-pressed="true"] {
  transform: translateY(3px);
  box-shadow: 0 0 0 rgb(0 0 0 / .55), inset 0 2px 6px rgb(0 0 0 / .45);
}
.reels5 .deck .auto[aria-pressed="true"] { background: var(--accent); }

/* SPIN. Round, green, and the biggest thing on the deck -- it is the control
   that spends the money and the reference sizes it accordingly. It stands
   PROUD of the bar, which is the detail that stops it reading as one more
   button in a row. */
.reels5 .deck .spin {
  flex: 0 0 auto; justify-self: end;
  display: grid; place-items: center; gap: 0;
  position: relative;
  width: clamp(56px, 8vw, 104px); aspect-ratio: 1;
  margin: calc(-1 * clamp(6px, 1.4vw, 22px)) 0;
  padding: 0; border: 0; border-radius: 50%;
  background:
    radial-gradient(58% 58% at 34% 26%, rgb(255 255 255 / .78) 0%,
                    rgb(255 255 255 / 0) 62%),
    radial-gradient(120% 120% at 50% 32%, #7ef2a2 0%, #24b455 45%,
                    #0d6d31 82%, #06381a 100%);
  color: #06381a; font: inherit; font-weight: 800; letter-spacing: .06em;
  cursor: pointer;
  box-shadow:
    0 0 0 clamp(2px, .34vw, 5px) rgb(255 255 255 / .22),
    0 0 0 clamp(3px, .5vw, 7px) rgb(0 0 0 / .55),
    0 8px 16px -6px rgb(0 0 0 / .85),
    0 0 34px -6px rgb(60 230 120 / .55),
    inset 0 -6px 14px -4px rgb(0 0 0 / .45);
  transition: transform .07s ease, box-shadow .07s ease, filter .12s ease;
}
.reels5 .deck .spin .spin-glyph {
  width: 62%; height: 62%;
  background: #fff;
  -webkit-mask: var(--glyph-spin) center / contain no-repeat;
          mask: var(--glyph-spin) center / contain no-repeat;
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / .5));
}
/* The word is for a reader, not for the glass: the glyph carries it. */
.reels5 .deck .spin .spin-word {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
/* Two arcs chasing each other round a play triangle, which is the mark the
   reference strikes on its green key. Drawn at 48 rather than 24 so the
   arrowheads have whole numbers to sit on. */
.reels5 { --glyph-spin: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' fill='none'%3E%3Cg stroke='%23000' stroke-width='5.5'%3E%3Cpath d='M10.6 30.5A15 15 0 0 1 20.3 9.6'/%3E%3Cpath d='M37.4 17.5A15 15 0 0 1 27.7 38.4'/%3E%3C/g%3E%3Cpath fill='%23000' d='M20.6 3.2 26.6 11l-9.7 1.6z'/%3E%3Cpath fill='%23000' d='M27.4 44.8 21.4 37l9.7-1.6z'/%3E%3Cpath fill='%23000' d='M20.5 17 32 24l-11.5 7z'/%3E%3C/svg%3E"); }
.reels5 .deck .spin:active {
  transform: translateY(4px) scale(.97);
  filter: brightness(.92);
}
.reels5 .deck .spin[disabled] {
  opacity: 1; cursor: default; filter: saturate(.45) brightness(.72);
  transform: translateY(3px);
}

/* ------------------------------------------------- the sentence in between
   Kept, and kept where it was: it says how the machine paid, which the two
   figures on the plate cannot. */
.reels5 .outcome { margin: 2px 0 6px; }

/* --------------------------------------------------------------- narrow
   The rail folds away before the frame does. On a phone held upright there is
   no room beside the machine for a second column, and the two things in it --
   what the stake rakes and which faces are in play -- are facts, not
   controls: nothing is lost by their not being on this screen, and the deck
   would lose a target if the frame gave up width to keep them. */
@media (max-width: 620px) {
  .cab-rail { display: none; }
  .reels5 .deck {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
    row-gap: 8px;
  }
  .reels5 .deck .deck-plate { grid-column: 1 / -1; order: -1; }
  .reels5 .deck .deck-units { display: none; }
  .reels5 .deck .spin { grid-row: span 2; }
}

/* --------------------------------------------------------------- sideways
   The orientation the product is meant to be held in, and the one the whole
   page is budgeted around. Everything here gives up height so the frame and
   the deck both land ON the screen: the frame's band thins, the head shrinks,
   the studs shrink with it, and the window is capped off the height exactly
   as it was before the frame existed -- less what the frame itself now takes.

   THE ROCKER AND SPIN KEEP THEIR SIZE, for the reason the block above this
   one gives at length: the two things a hand presses to spend money do not
   shrink. What gives is the type in the readouts beside them. */
@media (orientation: landscape) and (max-height: 500px) {
  body.arcade.loud .reels5 {
    --cab-b: 7px; --cab-head: 11px; --cab-stud: 11px;
  }
  body.arcade.loud .reels5 .machine-top { margin-bottom: 2px; gap: 8px; }
  body.arcade.loud .reels5 .machine-top h1 { font-size: 14px; }
  body.arcade.loud .cab-key,
  body.arcade.loud .reels5 .infobook > summary,
  body.arcade.loud .cabset > summary { width: 26px; height: 26px; font-size: 12px; }

  body.arcade.loud .cab { gap: 5px; margin: 2px 0 3px; }
  body.arcade.loud .cab-post { font-size: 9px; }
  /* WIDER, NOT TALLER. Height is the scarce axis here and width is not -- the
     frame is a third of an 844px screen -- so the rail buys its rows back by
     spending the room it has. Four across puts ten faces in three rows.

     AND THE GAUGE GOES. It is the one thing on the plate that is a picture of
     a figure printed directly underneath it, so at this height it is the first
     thing that can be dropped without dropping a fact. */
  body.arcade.loud .cab-rail { width: 88px; gap: 4px; }
  body.arcade.loud .jpe-gauge { display: none; }
  body.arcade.loud .actives-grid { grid-template-columns: repeat(3, 1fr); }
  body.arcade.loud .actives-name { font-size: 7px; }

  /* 52vh across, not the 68 it was: the frame's band, head and studs are
     about 5vh of the height the window used to have, and the deck below is
     taller than the row of buttons it replaced. Capping the WIDTH is what
     caps the height, because a five-reel window of square cells is 5:3. */
  /* THE HEIGHT BUDGET, SPENT.
     A 390px screen carries a 36px rail overhead and a 104px one underfoot --
     36% of it is the shell -- so the machine has about 250px to be a machine
     in. The window takes what is left after the head, the sentence and the
     deck, and the frame's own band comes out of the window rather than out of
     the deck: a stake key that shrinks is a bet somebody misses, and the
     glass losing eight pixels is not. */
  /* SAME ARITHMETIC, SMALLER STACK. On a 390px screen the shell's two rails
     alone are 140px -- 36% of it -- and the machine's own head, sentence and
     deck are another 116. What is left is the frame, and it is about a third
     of the screen rather than the two thirds a flat vh cap would have asked
     for. */
  body.arcade.loud .reels5 {
    --cab-chrome: calc(290px + env(safe-area-inset-top)
                             + env(safe-area-inset-bottom));
  }
  body.arcade.loud .reels5 .cab-frame > .window { padding: 2px; gap: 2px; }

  body.arcade.loud .reels5 .machine-top { margin-bottom: 1px; }
  body.arcade.loud .reels5 .outcome {
    margin: 0 0 2px; font-size: 11px; min-height: 0; line-height: 1.2;
  }
  body.arcade.loud .reels5 .deck {
    gap: 7px; padding: 3px 8px 4px;
    grid-template-columns:
      minmax(0, 1fr) minmax(0, .8fr) minmax(0, 1.3fr) minmax(0, .6fr) auto auto;
  }
  body.arcade.loud .reels5 .deck-label { font-size: 7px; letter-spacing: .08em; }
  body.arcade.loud .reels5 .deck-figure { font-size: 13px; }
  body.arcade.loud .reels5 .deck-plate .deck-figure { font-size: 13px; }
  body.arcade.loud .reels5 .deck-read { padding: 1px 8px 2px; }
  body.arcade.loud .reels5 .deck-plate { padding: 4px 8px; }
  body.arcade.loud .reels5 .deck .auto { padding: 5px 9px; font-size: 10px; }
  body.arcade.loud .reels5 .deck .spin { width: 58px; margin: -9px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .reels5 .deck .spin { transition: none; }
}

/* ------------------------------------------------ the visitor's way in

   WHERE THE PURSE WOULD BE. The reference's signed-out lobby carries its whole
   deck -- the category rail, the app and services buttons -- and puts a green
   ACCOUNT LOGIN in the slot a signed-in player's balance occupies. Ours drew
   no rail at all when nobody was signed in, so a visitor got a wall of games
   and no way to filter it and no way in.

   Cut from the SPIN key's palette rather than a new green, because it is the
   same object doing the same job: the one lit control on a dark deck. */
.arcade-bottom .signin-cta {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 10px 26px; border-radius: 999px;
  font: 800 15px/1 inherit; letter-spacing: .08em; text-decoration: none;
  color: #06240c;
  background: linear-gradient(180deg, #7ede8a 0%, #3fb355 52%, #2b8f42 100%);
  border: 1px solid rgb(255 255 255 / .35);
  box-shadow: 0 2px 0 rgb(0 0 0 / .45), 0 8px 18px -8px rgb(63 179 85 / .8),
              inset 0 1px 0 rgb(255 255 255 / .5);
  white-space: nowrap;
}
.arcade-bottom .signin-cta:hover { filter: brightness(1.06); }
/* DOWN WHEN PRESSED, like every other key on this deck. */
.arcade-bottom .signin-cta:active {
  transform: translateY(1px);
  box-shadow: 0 1px 0 rgb(0 0 0 / .45), inset 0 1px 0 rgb(255 255 255 / .4);
}
@media (prefers-reduced-motion: reduce) {
  .arcade-bottom .signin-cta { transition: none; }
}

/* ------------------------- the machine gets the phone's screen back

   MEASURED, NOT GUESSED. On an 852x393 phone in landscape the reel window was
   getting 22% of the height and the shell's bottom rail -- avatar, SERVICES,
   NEWS, eZFUNDS, FREEPLAY, purse -- was getting 27%. The app furniture was
   bigger than the game. The reference has no such rail in a machine at all:
   its deck IS the bottom of the screen, and that is the whole of the screen
   the reels are not using.

   NOTHING IS LOST BY HIDING IT. The five-reel deck already carries ENTRIES and
   WINNING (see .deck-purse), so the two figures a player watches between spins
   are still on the screen -- they are simply on the machine rather than under
   it. That is also why this is scoped to `.game-page`, which only reels5.html
   sets: the three-reel and keno cabinets have no purse in their decks, and
   taking the rail from them would take a player's balance away with it.

   HEIGHT, NOT WIDTH, because the complaint is vertical. A tablet in landscape
   is 768 tall and keeps its rail; a phone in landscape is under 500 and does
   not. */
@media (max-height: 500px) and (orientation: landscape) {
  body.game-page .arcade-bottom { display: none; }

  /* AND THE BUDGET COMES DOWN WITH IT, which is the half that actually makes
     the reels bigger. `--cab-chrome` is the fixed stack the frame is sized
     out of -- `flex-basis: (100vh - chrome) * 5/3` -- and 290px was measured
     with the bottom rail in place. Hiding the rail without paying back its
     height just leaves 115px of dead screen under the machine: measured, the
     frame stayed 121px tall and the space went nowhere.

     104px is the rail, measured on an 852x393 phone. 200px rather than the
     186 that number alone suggests: at 186 the machine fitted 393 with ten
     pixels to spare and OVERFLOWED a 667x375 phone by fourteen, because the
     deck's own margins are not in the constant. 200 is the number that fits
     both -- checked at 393, 375 and 412 rather than derived and hoped for. */
  body.arcade.loud .game-page .reels5,
  body.game-page.arcade.loud .reels5 {
    --cab-chrome: calc(200px + env(safe-area-inset-top)
                             + env(safe-area-inset-bottom));
  }
}


/* ---------------------------------------------- one family of rail buttons
 *
 * They were drawn one at a time and it showed: SERVICES and NEWS in brown,
 * eZFUNDS in green, MAILBOX in its own colour, and ACCOUNT LOGIN a bright pill
 * twice their height on a different radius. Five controls sitting in a row,
 * none of them agreeing about what a button on this rail looks like.
 *
 * This is the last block in the file so it wins on order alone, and it sets
 * only the things that made them look unrelated -- size, radius, weight and
 * the green they share. Each keeps its own icon and its own word.
 *
 * eZFUNDS USED TO BE ONE OF THEM and is not on this rail any more -- there
 * were two controls for one placeholder and only the pill was ever wired.
 */
.arcade-bottom .services,
.arcade-bottom .news,
.arcade-bottom .mailbox,
.arcade-bottom .freeplay {
  display: inline-flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px;
  min-width: 78px; min-height: 52px;
  margin-left: 10px; padding: 6px 12px;
  border: 1px solid rgb(255 255 255 / .14);
  border-radius: 12px; cursor: pointer; font: inherit;
  background: linear-gradient(180deg, #2f7d4e 0%, #134d2f 100%);
  color: #e8ffee;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .16), 0 2px 0 rgb(0 0 0 / .5);
}
.arcade-bottom .services:active,
.arcade-bottom .news:active,
.arcade-bottom .mailbox:active,
.arcade-bottom .freeplay:active {
  transform: translateY(2px);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12);
}

/* AND THE ONE THAT STANDS WHERE THE PURSE WOULD. It is the widest control on
   the rail because it is the only one carrying a sentence rather than a word,
   but it now shares the family's height, radius and green instead of being a
   bright pill of its own. */
.arcade-bottom .signin-cta {
  min-height: 52px; padding: 0 24px; border-radius: 12px;
  font: 800 14px/1 inherit; letter-spacing: .10em;
  color: #04220f;
  background: linear-gradient(180deg, #35c07a 0%, #157a4a 100%);
  border: 1px solid rgb(255 255 255 / .3);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .35), 0 2px 0 rgb(0 0 0 / .45);
}

/* AND A BIG SCREEN GETS A BIG WALL.
 *
 * `--tile` is clamped at 360px, which is right up to about a laptop and wrong
 * past it: at 3840 the 22vw wants 845 and the cap pins it to 360, so a 4K
 * monitor drew the same small tiles it draws at 1440 with three times the
 * empty floor around them. Troy's word for it was "squared" -- islands of
 * cabinets in a green field -- and he is describing a cap, not a layout.
 *
 * So the ceiling rises with the room, and a third row comes in when there is
 * the height for one. Both are the same idea the clamp already had: size the
 * cabinet against what is visible. The reference shows about four cabinets on
 * any screen it runs on, and this keeps that true on a screen four times the
 * width instead of showing twelve stamps.
 */
@media (min-width: 2200px) {
  /* THE PLAYFIELD ITSELF WAS THE CAP. `.arcade-body` is max-width 1320px, so
     on a 3840 screen the whole lobby -- rail, wall and all -- was a 1320px
     column centred in it, with the background doing the rest. Every tile in
     Troy's screenshot was the size it is on a laptop because the room it sits
     in was the size it is on a laptop; `--tile` was a symptom.

     Not edge to edge: the reference keeps a margin and so does this. Wide
     enough that the wall is the page rather than an island in it. */
  .arcade-body { max-width: min(3000px, 94vw); padding: 28px; }
  .cabinets { --tile: clamp(360px, 19vw, 620px); gap: 24px; }
}
@media (min-width: 2200px) and (min-height: 1400px) {
  /* A THIRD ROW, AND A TILE THAT FITS IN IT. The wider ceiling above is for
     the two-row case; with three rows the height is what runs out first --
     the art is 3:4, so a 620px tile wants 830px of row and three of those do
     not fit in the wall. Sized against the row instead, the wall fills across
     as well as down: about eight cabinets rather than two clipped ones. */
  .cabinets { grid-template-rows: repeat(3, minmax(0, 1fr));
              --tile: clamp(300px, 12vw, 460px); }
}

/* ------------------------------------------------------- THE PROFILE PANEL

   The chip was a span with a link inside it and is now a button, so it needs
   the button furniture stripped back off: a rail control should not arrive
   wearing a browser's default face. It keeps the same geometry it had, so
   nothing on the rail moves. */
.arcade-bottom button.who {
  background: none; border: 0; padding: 4px 6px; margin: 0;
  color: inherit; font: inherit; cursor: pointer; border-radius: 10px;
}
.arcade-bottom button.who:hover { background: rgb(255 255 255 / .07); }
.arcade-bottom button.who:active { transform: translateY(1px); }
.arcade-bottom button.who .cid { text-decoration: none; opacity: .75; }

/* The panel borrows the mail panel's shell -- position, ground and shadow --
   and only lays its own contents out. Two columns where there is room, one
   where there is not, which is the reference's shape and also what a phone
   on its side can hold. */
.profile-panel { width: min(560px, 92vw); }
.profile-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
@media (min-width: 620px) {
  .profile-body { grid-template-columns: minmax(0, 200px) minmax(0, 1fr); }
}
.profile-who {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 12px 10px; border-radius: 12px;
  background: rgb(255 255 255 / .04);
  border: 1px solid rgb(255 255 255 / .10);
}
.profile-id { font-size: 11px; letter-spacing: .10em; color: var(--muted); }
.profile-avatar {
  display: grid; place-items: center; width: 56px; height: 56px;
  border-radius: 50%; font-size: 24px; font-weight: 800;
  background: linear-gradient(180deg, #3b5db8 0%, #1b2b57 100%);
  color: #eaf1ff; box-shadow: inset 0 1px 0 rgb(255 255 255 / .25);
}
.profile-name { font-size: 15px; font-weight: 800; }
.profile-purse { margin: 6px 0 0; width: 100%; display: grid; gap: 6px; }
.profile-purse > div, .profile-figures > div {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; padding: 5px 8px; border-radius: 8px;
  background: rgb(0 0 0 / .25);
}
.profile-purse dt, .profile-figures dt {
  font-size: 11px; letter-spacing: .08em; color: var(--muted);
}
.profile-purse dd, .profile-figures dd {
  margin: 0; font-size: 15px; font-weight: 800; color: var(--text);
}
.profile-record h3 {
  margin: 0 0 6px; font-size: 11px; letter-spacing: .12em;
  color: var(--muted); font-weight: 800;
}
.profile-faves { list-style: none; margin: 0 0 12px; padding: 0;
                 display: grid; gap: 4px; }
.profile-faves a { color: var(--text); text-decoration: none; font-size: 13px; }
.profile-faves a:hover { color: var(--brass); }
.profile-figures { margin: 0 0 12px; display: grid; gap: 6px; }
.profile-more { display: inline-block; margin-bottom: 8px;
                color: var(--brass); text-decoration: none; font-size: 13px; }
.profile-more:hover { text-decoration: underline; }
.profile-note { margin: 0; font-size: 11px; color: var(--muted); line-height: 1.45; }

/* A BLANK STOP IN THE DOM REELS. The canvas draws its own tile; this is the
   same thing for the fallback path -- an empty frame, never the word. */
.cell.cell-blank {
  background: rgb(255 255 255 / .035);
  border: 1px solid rgb(255 255 255 / .10);
  border-radius: 10px;
}

/* ======================================================================
   ONE RAIL, ONE BUTTON FAMILY.

   The deck had grown five different treatments: a wide flat pill for
   eZFUNDS LINK, a tall bordered box with an icon and two lines for
   FREEPLAY, a bare chip for the profile, and two more boxes for MISSION
   and MAILBOX -- different heights, radii, borders and paddings, sitting
   in a row. The earlier family block below covers four of them and left
   the profile chip, MISSION, the wheel and the bonus outside it.

   THE CLEANEST OF THE EXISTING TREATMENTS IS THE FAMILY BOX -- 52px tall,
   12px radius, a hairline border and the rail's green -- so everything is
   brought to that rather than to something new. Each control keeps its own
   icon and its own word; only the shell is shared.

   eZFUNDS LINK KEEPS ITS BRIGHTER FACE. It is the one control here that is
   not ours, and a player should be able to pick it out -- but it takes the
   family's geometry exactly, so it reads as a member and not an exception.

   Last block in the file, so it wins on order rather than on specificity.
   ====================================================================== */
.arcade-bottom .services,
.arcade-bottom .news,
.arcade-bottom .mailbox,
.arcade-bottom .freeplay,
.arcade-bottom .tasks,
.arcade-bottom .cashback,
.arcade-bottom .dailywheel,
.arcade-bottom .bonus,
.arcade-bottom button.who,
.arcade-bottom .signin-cta {
  box-sizing: border-box;
  min-height: 52px; height: 52px;
  margin-left: 10px; margin-right: 0;
  border: 1px solid rgb(255 255 255 / .14);
  border-radius: 12px;
  cursor: pointer; font: inherit;
  background: linear-gradient(180deg, #2f7d4e 0%, #134d2f 100%);
  color: #e8ffee;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .16), 0 2px 0 rgb(0 0 0 / .5);
}

/* THE ICON-OVER-WORD CONTROLS. One column, centred, same gap. */
.arcade-bottom .services,
.arcade-bottom .news,
.arcade-bottom .mailbox,
.arcade-bottom .freeplay,
.arcade-bottom .tasks,
.arcade-bottom .cashback,
.arcade-bottom .dailywheel,
.arcade-bottom .bonus {
  display: inline-flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 2px; min-width: 78px; padding: 6px 12px;
}

/* The two that carry a row rather than a stack: the profile chip has an
   avatar beside a name, and the link carries a sentence. */
.arcade-bottom button.who {
  display: inline-flex; flex-direction: row; align-items: center;
  gap: 8px; padding: 0 14px; min-width: 0;
}
.arcade-bottom .signin-cta {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 22px;
  font-weight: 800; letter-spacing: .10em; font-size: 13px;
  color: #04220f;
  background: linear-gradient(180deg, #35c07a 0%, #157a4a 100%);
  border-color: rgb(255 255 255 / .30);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .35), 0 2px 0 rgb(0 0 0 / .45);
}

/* One press, one movement, for all of them. */
.arcade-bottom .services:active,
.arcade-bottom .news:active,
.arcade-bottom .mailbox:active,
.arcade-bottom .freeplay:active,
.arcade-bottom .tasks:active,
.arcade-bottom .cashback:active,
.arcade-bottom .dailywheel:active,
.arcade-bottom .bonus:active,
.arcade-bottom button.who:active,
.arcade-bottom .signin-cta:active {
  transform: translateY(2px);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .12);
}
.arcade-bottom button.who:hover { background: linear-gradient(180deg, #37905a 0%, #175a37 100%); }

/* The icon and the word, the same size on every one of them. */
.arcade-bottom .tasks-icon, .arcade-bottom .mail-icon,
.arcade-bottom .free-icon, .arcade-bottom .news-icon,
.arcade-bottom .services-icon, .arcade-bottom .wheel-icon,
.arcade-bottom .bonus-icon, .arcade-bottom .cashback-icon {
  font-size: 15px; line-height: 1;
}
.arcade-bottom .tasks-word, .arcade-bottom .mail-word,
.arcade-bottom .free-word, .arcade-bottom .news-word,
.arcade-bottom .services-word, .arcade-bottom .wheel-word,
.arcade-bottom .bonus-word, .arcade-bottom .cashback-word {
  font-size: 10px; font-weight: 800; letter-spacing: .10em; line-height: 1;
}
/* The profile chip's own two pieces, sized to sit in the same 52px. */
.arcade-bottom button.who .avatar {
  width: 26px; height: 26px; font-size: 13px;
}
.arcade-bottom button.who .name { font-size: 12px; font-weight: 800; }
.arcade-bottom button.who .cid { font-size: 10px; opacity: .7; }

/* THE SHRINK, APPLIED TO THE WHOLE FAMILY.

   A phone on its side gets smaller controls, which is right -- but the
   landscape block only names the icon-and-word buttons, and it outranks the
   family block above on specificity (body.arcade.loud beats a bare class).
   So MISSION and MAILBOX shrank to 30px with an 8px radius while eZFUNDS
   LINK, FREEPLAY and the profile chip stayed at 52 and 12, and the row that
   had just been made a set came apart at exactly the size where the space
   matters most.

   Same members, same numbers, one radius. */
@media (orientation: landscape) and (max-height: 500px) {
  body.arcade.loud .arcade-bottom .services,
  body.arcade.loud .arcade-bottom .news,
  body.arcade.loud .arcade-bottom .mailbox,
  body.arcade.loud .arcade-bottom .freeplay,
  body.arcade.loud .arcade-bottom .tasks,
  body.arcade.loud .arcade-bottom .cashback,
  body.arcade.loud .arcade-bottom .dailywheel,
  body.arcade.loud .arcade-bottom .bonus,
  body.arcade.loud .arcade-bottom button.who,
  body.arcade.loud .arcade-bottom .signin-cta {
    min-height: 38px; height: 38px;
    border-radius: 9px; margin-left: 5px;
    padding-top: 2px; padding-bottom: 3px;
  }
  body.arcade.loud .arcade-bottom .signin-cta { padding-left: 14px; padding-right: 14px; font-size: 11px; }
  body.arcade.loud .arcade-bottom button.who { padding-left: 9px; padding-right: 9px; }
  body.arcade.loud .arcade-bottom button.who .avatar { width: 20px; height: 20px; font-size: 11px; }
}

/* ------------------------------------------------------------- THE CLOTH */
.tablegame { padding: 0 14px 24px; }
.table-felt {
  margin: 12px 0 16px; padding: 16px; border-radius: 14px;
  background: radial-gradient(120% 100% at 50% 0%, #1c5c39 0%, #0d3020 100%);
  border: 1px solid rgb(255 255 255 / .12);
}
.table-event { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.table-event-main { font-size: 30px; font-weight: 900; letter-spacing: .06em; color: #ffe9a8; }
.table-event-note { font-size: 12px; color: rgb(255 255 255 / .75); }
.table-controls {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; margin-top: 14px; flex-wrap: wrap;
}
.table-total { font-size: 12px; color: rgb(255 255 255 / .8); }
.table-total strong { font-size: 15px; color: #fff; margin-left: 4px; }
.table-clear, .table-roll {
  min-height: 40px; padding: 0 18px; border-radius: 10px; cursor: pointer;
  font: inherit; font-weight: 800; letter-spacing: .08em; border: 0;
}
.table-clear { background: rgb(255 255 255 / .12); color: #fff; }
.table-roll { background: linear-gradient(180deg, #35c07a 0%, #157a4a 100%); color: #04220f; }
.table-roll[disabled] { filter: saturate(.3) brightness(.75); cursor: default; }

.bet-list { list-style: none; margin: 0; padding: 0;
            display: grid; gap: 6px;
            grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.bet-pick {
  width: 100%; display: grid; align-items: center; gap: 8px;
  grid-template-columns: 1fr auto auto auto;
  min-height: 42px; padding: 6px 10px; border-radius: 9px;
  border: 1px solid rgb(255 255 255 / .12); cursor: pointer; font: inherit;
  background: rgb(255 255 255 / .04); color: var(--text); text-align: left;
}
.bet-pick:hover { background: rgb(255 255 255 / .09); }
.bet-pick.has-chip { border-color: #35c07a; background: rgb(53 192 122 / .14); }
.bet-name { font-size: 13px; }
.bet-pays { font-size: 11px; color: var(--muted); }
/* THE RETURN, BESIDE EVERY PLACE. Sic bo's own screen prints four totals in
   one row at one payout, and they do not share a return -- so each place
   here carries the figure it actually pays. */
.bet-return { font-size: 11px; font-weight: 800; color: #9fd7b4; }
.bet-chip { font-size: 11px; font-weight: 800; color: #ffe9a8; min-width: 34px;
            text-align: right; }
.table-note { margin: 12px 0 0; font-size: 12px; color: var(--muted);
              line-height: 1.5; }

/* ------------------------------------------------------------- a hand of cards

   The same felt as the table games, because it IS the same room -- but laid
   out top to bottom rather than as a list of places, because what a player
   watches here is two hands facing each other rather than a cloth full of
   chips. */
.cardgame { padding: 0 14px 24px; }
.card-felt {
  margin: 12px 0 16px; padding: 16px; border-radius: 14px;
  background: radial-gradient(120% 100% at 50% 0%, #1c5c39 0%, #0d3020 100%);
  border: 1px solid rgb(255 255 255 / .12);
  display: flex; flex-direction: column; gap: 14px;
}
.card-side { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.card-who {
  font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
  color: rgb(255 255 255 / .7);
}
.card-hand { color: #ffe9a8; letter-spacing: .04em; text-transform: none; }
.card-row { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center;
            min-height: 62px; }

/* A CARD. Drawn rather than an image: 52 of them at two sizes is a sprite
   sheet to keep in step with nothing, and the rank and pip are text the
   browser already has. */
.pcard {
  min-width: 42px; height: 60px; border-radius: 6px; background: #f7f5ef;
  color: #14181c; font-weight: 800; font-size: 17px; line-height: 60px;
  text-align: center; padding: 0 4px;
  box-shadow: 0 3px 8px rgb(0 0 0 / .45);
}
.pcard-red { color: #c62b32; }
.pcard-back {
  background: repeating-linear-gradient(45deg, #29406e 0 5px, #1b2b4b 5px 10px);
  box-shadow: inset 0 0 0 2px rgb(255 255 255 / .25), 0 3px 8px rgb(0 0 0 / .45);
}

/* AFTER A SPLIT there is more than one player hand, and the live one is the
   only one the buttons apply to -- so it is the only one lit. */
.card-hands { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.bj-hand {
  padding: 6px 8px; border-radius: 10px; border: 1px solid transparent;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.bj-live { border-color: #ffe9a8; background: rgb(255 233 168 / .08); }
.bj-gone { opacity: .45; }
.bj-total { font-size: 11px; color: rgb(255 255 255 / .85); letter-spacing: .04em; }

.card-event { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.card-event-main { font-size: 24px; font-weight: 900; letter-spacing: .04em; color: #ffe9a8; }
.card-event-note { font-size: 12px; color: rgb(255 255 255 / .75); text-align: center; }

.card-stakes { margin-bottom: 18px; }
.stake-list { list-style: none; margin: 0; padding: 0;
              display: flex; gap: 10px; flex-wrap: wrap; }
.stake-row { flex: 1 1 120px; }
.stake-pick {
  width: 100%; min-height: 54px; border-radius: 12px; cursor: pointer;
  font: inherit; color: var(--text); text-align: left; padding: 8px 12px;
  background: var(--panel); border: 1px solid rgb(255 255 255 / .12);
  display: flex; flex-direction: column; gap: 2px;
}
.stake-pick.has-chip { border-color: #ffe9a8; box-shadow: 0 0 0 1px #ffe9a8 inset; }
.stake-name { font-size: 12px; letter-spacing: .1em; text-transform: uppercase; }
.stake-name em { font-style: normal; color: var(--muted); font-size: 10px; }
.stake-chip { font-size: 16px; font-weight: 800; }

.card-controls {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; margin-top: 12px; flex-wrap: wrap;
}
.card-total { font-size: 12px; color: var(--muted); }
.card-total strong { font-size: 15px; color: var(--text); margin-left: 4px; }
.card-clear, .card-deal {
  min-height: 40px; padding: 0 18px; border-radius: 10px; cursor: pointer;
  font: inherit; font-weight: 800; letter-spacing: .08em; border: 0;
}
.card-clear { background: rgb(255 255 255 / .12); color: var(--text); }
.card-deal { background: linear-gradient(180deg, #35c07a 0%, #157a4a 100%); color: #04220f; }
.card-deal[disabled] { filter: saturate(.3) brightness(.75); cursor: default; }

/* THE DECISIONS. Only ever the ones the server offered. */
.card-actions { display: flex; gap: 8px; flex-wrap: wrap;
                justify-content: center; margin-top: 14px; }
.card-act {
  min-height: 44px; padding: 0 16px; border-radius: 10px; cursor: pointer;
  font: inherit; font-weight: 800; letter-spacing: .06em; border: 0;
  background: var(--brass); color: #1a1206;
}
.card-act[disabled] { filter: saturate(.3) brightness(.75); cursor: default; }
.card-act-fold, .card-act-surrender, .card-act-decline {
  background: rgb(255 255 255 / .14); color: var(--text);
}

.card-paytable h3 { font-size: 12px; letter-spacing: .14em;
                    text-transform: uppercase; color: var(--muted);
                    margin: 16px 0 6px; }
.pay-list { list-style: none; margin: 0; padding: 0; }
.pay-row { display: flex; justify-content: space-between; gap: 12px;
           padding: 5px 0; border-bottom: 1px solid rgb(255 255 255 / .07);
           font-size: 13px; }
.rule-list { margin: 0; padding-left: 18px; font-size: 13px;
             color: rgb(255 255 255 / .82); }
.rule-list li { margin: 4px 0; }
/* OURS, NOT THE GAME'S. Set apart on purpose -- a player reading a rule
   should be able to see at a glance whether it came off the original felt or
   was chosen here. */
.rule-ours { color: #ffd08a !important; }
.rule-list-ours { border-left: 2px solid #ffd08a; padding-left: 16px;
                  margin-left: 2px; }

/* ---------------------------------------------------------- the celebration

   The overlay a win big enough to deserve one takes the screen with. Below
   the first rung there is none: an overlay for a 2x win teaches a player that
   the big animation means nothing, and then it does.

   EVERYTHING HERE IS COMPOSITED -- transform and opacity only, no layout and
   no paint of anything under it. Forty-six falling coins over a canvas that
   is already drawing reels is exactly where a phone drops frames, and the
   difference between animating `top` and animating `translateY` is the
   difference between sixty frames and twelve. */
.celebrate {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(60% 50% at 50% 45%, rgb(20 12 0 / .72) 0%,
                              rgb(0 0 0 / .86) 100%);
  animation: celebrate-in .28s ease-out both;
  overflow: hidden; cursor: pointer;
  /* SAFE ON A NOTCHED PHONE IN LANDSCAPE, which is how this game is held. */
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
           env(safe-area-inset-bottom) env(safe-area-inset-left);
}
.celebrate.going { animation: celebrate-out .26s ease-in both; }
@keyframes celebrate-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes celebrate-out { from { opacity: 1; } to { opacity: 0; } }

/* The light behind it. One element, turning slowly. */
.celebrate-rays {
  position: absolute; width: 180vmax; height: 180vmax;
  background: conic-gradient(from 0deg,
    rgb(255 214 130 / .16) 0deg 8deg, transparent 8deg 24deg);
  animation: celebrate-turn 18s linear infinite;
  will-change: transform;
}
@keyframes celebrate-turn { to { transform: rotate(360deg); } }

.celebrate-card {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  animation: celebrate-pop .5s cubic-bezier(.2, 1.5, .4, 1) both;
}
@keyframes celebrate-pop {
  from { transform: scale(.55); opacity: 0; }
  to   { transform: scale(1);   opacity: 1; }
}
.celebrate-word {
  width: min(62vw, 420px); height: auto;
  filter: drop-shadow(0 8px 22px rgb(0 0 0 / .6));
  animation: celebrate-breathe 1.8s ease-in-out infinite;
}
@keyframes celebrate-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.045); }
}
.celebrate-amount {
  font-size: clamp(30px, 8vmin, 60px); font-weight: 900; letter-spacing: .04em;
  color: #ffe9a8; text-shadow: 0 0 26px rgb(255 190 60 / .55),
                               0 3px 0 rgb(0 0 0 / .55);
  font-variant-numeric: tabular-nums;   /* so the count-up does not jitter */
}
.celebrate-go {
  margin-top: 4px; min-height: 44px; padding: 0 26px; border: 0;
  border-radius: 999px; cursor: pointer; font: inherit; font-weight: 800;
  letter-spacing: .1em; color: #22180a;
  /* ITS OWN GOLD, with the shared token preferred where it exists.
     `--brass` is declared in console.css, not here, so a page that loads
     play.css alone resolved it to nothing -- and `background: <empty>` is a
     transparent button with dark text on a dark overlay. The only control on
     this screen, invisible. A fallback costs one line. */
  background: var(--brass, linear-gradient(180deg, #ffe9a8 0%, #e0a83c 55%,
                                                   #a5731d 100%));
  box-shadow: 0 6px 18px rgb(0 0 0 / .5);
}

/* THE COINS.

   ONE IMAGE, TURNED IN 3D, rather than a sprite sheet stepped frame by
   frame. The vendor ships its coin as nineteen frames of a spin and that is
   what this drew first -- but the decoder recovers only two of the nineteen
   intact, so seventeen of them were transparent and the shower was mostly
   invisible gaps. A single clean face rotated on its Y axis is the same
   effect, is 4KB instead of a sheet, and cannot come apart in the same way.

   COMPOSITED ONLY -- transform and opacity, no layout, no paint underneath.
   Forty-six of these over a canvas already drawing reels is exactly where a
   phone drops frames, and the difference between animating `top` and
   animating `translate3d` is the difference between sixty and twelve. */
.celebrate-coins { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.celebrate-coins-inline { position: absolute; inset: 0; z-index: 6; }
.celebrate-coin {
  position: absolute; top: -72px; width: 44px; height: 44px;
  background: url("/static/chrome/coin.webp") center/contain no-repeat;
  animation-name: celebrate-fall;
  animation-timing-function: cubic-bezier(.35, 0, .7, 1);
  animation-iteration-count: 1;
  animation-fill-mode: both;
  will-change: transform, opacity;
}
/* The fall and the spin in ONE keyframe set, because two animations on the
   same element both writing `transform` means the second silently wins. */
@keyframes celebrate-fall {
  0%   { transform: translate3d(0, 0, 0) rotateY(0deg) rotate(0deg);
         opacity: 0; }
  6%   { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translate3d(0, 116vh, 0) rotateY(1080deg) rotate(200deg);
         opacity: 0; }
}

/* SMALLER ON A PHONE HELD LANDSCAPE, where the whole screen is 360px tall and
   a 420px wordmark would not fit above the amount. */
@media (max-height: 460px) {
  .celebrate-word { width: min(46vw, 300px); }
  .celebrate-amount { font-size: clamp(22px, 9vmin, 38px); }
  .celebrate-coin { width: 30px; height: 30px; }
}

/* SOMEBODY WHO ASKED FOR LESS MOVEMENT GETS THE WIN AND NOT THE SHOW. The
   overlay still appears and still says what was won -- it is information --
   but nothing in it moves. */
@media (prefers-reduced-motion: reduce) {
  .celebrate, .celebrate-card, .celebrate-word, .celebrate-rays {
    animation: none !important;
  }
  .celebrate-coins { display: none; }
}

/* The jackpot leg of the return, under the figure it corrects. Same weight as
   the qualifier -- it is not a boast, it is the sentence that stops the
   number above it being an understatement. */
.maths-jackpot { display: block; }
.maths-jp { color: var(--muted); font-size: 12px; line-height: 1.45; }

/* ------------------------------------------------- the rail must not clip

   THE BAR WAS CUTTING ITS OWN NUMBERS. Five capsules share one row and each
   is allowed to shrink under its content so they fit across a phone, and the
   figure inside carried `text-overflow: ellipsis` -- so a signed-in player
   read "GRAND 500..." and "MAJOR 100...". The one figure this bar exists to
   show off, in half.

   A MONEY FIGURE IS NEVER ELLIPSED. It is shortened instead, by potmoney,
   which keeps cents while they mean anything -- and a short figure needs no
   clipping, so the clip comes off entirely. What is left is a capsule that
   sizes to its content and a rail that wraps to a second row rather than
   squeezing a fifth pot into a space for four. */
/* WRITTEN AT THE SAME SPECIFICITY AS THE RULE IT IS UNDOING.
   `body.arcade.loud .pot-figure` is three classes and an element; `.loud
   .pot-figure` is two classes and loses to it wherever they disagree. The
   first version of this fix was the short selector -- the figures stopped
   being cut only because potmoney had made them short enough to fit, which
   is luck rather than a fix, and the browser still reported ellipsis. */
body.arcade.loud .pot { flex-wrap: wrap; row-gap: 6px; }
body.arcade.loud .pot-one { flex: 0 0 auto; }
body.arcade.loud .pot-figure {
  /* AND IT MUST NOT SHRINK EITHER. `flex: 1 1 auto` let the figure give up
     width to its own capsule, which is what made an ellipsis reachable at
     all. */
  flex: 0 0 auto;
  overflow: visible; text-overflow: clip; white-space: nowrap;
}
body.arcade.loud .pot-name {
  overflow: visible; text-overflow: clip; max-width: none;
}

/* PHONE HELD LANDSCAPE. Still one row where it fits, and a smaller figure
   rather than a cut one; wrapping is the fallback when even that will not go,
   which beats five illegible stubs. */
@media (max-height: 460px) {
  body.arcade.loud .pot { flex-wrap: wrap; row-gap: 4px; }
  body.arcade.loud .pot-one { flex: 0 0 auto; }
  body.arcade.loud .pot-figure {
    font-size: 14px; flex: 0 0 auto;
    overflow: visible; text-overflow: clip;
  }
}

/* A VERY WIDE SCREEN has room for the full ladder on one line and should use
   it -- at 4K the capsules were sitting in the middle third of the bar. */
@media (min-width: 2200px) {
  /* body.arcade.loud AGAIN, and for the third time in this block: the base
     rules are written at that specificity, so anything shorter is a rule the
     browser reads and then ignores. At 4K this one lost and the whole ladder
     sat at 20px in the middle of a 3840-pixel bar. */
  body.arcade.loud .pot { gap: 22px; }
  body.arcade.loud .pot-figure { font-size: 34px; }
  body.arcade.loud .pot-name { font-size: 13px; }
  body.arcade.loud .pot-one { padding: 14px 34px 16px; }
}

/* --------------------------------------------------------------- the search

   A box, a button and a way out. It sits above the wall rather than in the
   deck: the deck is where a player's own things live (purse, favourites,
   settings) and this is a question about the shelf.

   IT MUST NOT CHANGE THE HEIGHT OF THE WALL. The band below is measured in
   viewport units and the tiles size to their row -- an element above it that
   grows on a phone takes that height off the machines. So it is one row,
   never wraps, and the label collapses to nothing on a short screen. */
.hunt {
  display: flex; align-items: center; gap: 8px;
  margin: 0 auto 8px; padding: 0 14px; max-width: 900px;
  /* Same reason as .promo: a flex item needs a width, not only a bound on
     one. The search happened to look right because its own children grow. */
  width: 100%;
}
.hunt-label {
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}
.hunt-box {
  flex: 1 1 auto; min-width: 0; min-height: 38px; padding: 0 12px;
  border-radius: 999px; font: inherit; color: var(--text);
  background: var(--well);
  border: 1px solid rgb(255 255 255 / .14);
}
.hunt-box::placeholder { color: var(--muted); }
.hunt-box:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.hunt-go, .hunt-clear {
  flex: 0 0 auto; min-height: 38px; padding: 0 16px; border: 0;
  border-radius: 999px; cursor: pointer; font: inherit; font-weight: 800;
  letter-spacing: .06em; text-decoration: none;
  display: inline-flex; align-items: center;
}
.hunt-go { background: var(--accent); color: var(--on-accent); }
.hunt-clear { background: rgb(255 255 255 / .12); color: var(--text); }

/* THE SEARCH IS PART OF THE ROOM in loud mode, not a form bolted over it. */
body.arcade.loud .hunt { max-width: 720px; margin-bottom: 6px; }
body.arcade.loud .hunt-box {
  background: rgb(4 12 7 / .72);
  border-color: rgb(255 214 106 / .28);
  box-shadow: inset 0 2px 6px rgb(0 0 0 / .6);
}
body.arcade.loud .hunt-go {
  background: var(--brass, linear-gradient(180deg, #ffe9a8, #a5731d));
  color: #22180a;
}

/* SHORT SCREENS GIVE THE HEIGHT BACK TO THE MACHINES. The label is the first
   thing to go -- the placeholder already says what the box is for. */
@media (max-height: 620px) {
  .hunt { margin-bottom: 4px; gap: 6px; }
  .hunt-label { display: none; }
  .hunt-box, .hunt-go, .hunt-clear { min-height: 32px; }
}

/* ------------------------------------------------------------- the banners

   A CAROUSEL THAT TURNS WITH NOTHING LOADED, the same way the info book
   pages: one radio per banner, the dots are labels pointing at them, and the
   checked radio decides which card is on top. Script may add the rotation;
   without it every banner is still reachable.

   ITS HEIGHT IS FIXED AND DOES NOT COME FROM ITS CONTENT. That is today's
   lesson written into the next thing built: a banner carrying a lazy image
   must not be able to collapse to nothing and take the shelf's layout with
   it. The frame is a fixed height, the art is positioned inside it, and an
   image that never arrives leaves a coloured panel rather than a hole. */
.promo {
  margin: 0 auto 8px; padding: 0 14px; max-width: 900px;
  /* WIDTH, NOT ONLY MAX-WIDTH. The lobby lays these out as flex items, and a
     flex item with only a max-width shrinks to its CONTENT -- the frame came
     out 90px wide with the words stacked one letter deep. `max-width` bounds
     a width; it does not create one. */
  width: 100%;
}
.promo-radio { position: absolute; opacity: 0; pointer-events: none; }
.promo-frame {
  position: relative; height: 86px; border-radius: 14px; overflow: hidden;
  background: linear-gradient(100deg, #12301d 0%, #0a1c11 60%, #071309 100%);
  box-shadow: inset 0 0 0 1px rgb(255 214 106 / .22),
              0 6px 18px -10px rgb(0 0 0 / .8);
}
.promo-card {
  position: absolute; inset: 0; display: flex; align-items: center;
  gap: 14px; padding: 0 18px; text-decoration: none; color: var(--text);
  opacity: 0; visibility: hidden; transition: opacity .45s ease;
}
/* WHICH CARD IS ON TOP is decided by which radio is checked, and the pairs
   have to be written out -- there is no "nth sibling" selector that counts
   only the checked one. Three, because MAX_BANNERS is three. */
.promo-radio:nth-of-type(1):checked ~ .promo-frame [data-promo="1"],
.promo-radio:nth-of-type(2):checked ~ .promo-frame [data-promo="2"],
.promo-radio:nth-of-type(3):checked ~ .promo-frame [data-promo="3"] {
  opacity: 1; visibility: visible;
}
.promo-art {
  width: 62px; height: 62px; flex: 0 0 auto; border-radius: 10px;
  object-fit: cover; box-shadow: 0 3px 10px rgb(0 0 0 / .6);
  /* A RESERVED BOX, so a picture that never loads leaves a panel rather than
     a hole that changes the layout around it. */
  background: rgb(255 255 255 / .06);
}
.promo-words { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.promo-eyebrow {
  font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
  color: #ffd98a;
}
.promo-headline {
  font-size: 18px; font-weight: 900; letter-spacing: .01em; color: #fff;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.promo-figure {
  font-size: 22px; font-weight: 900; color: #ffe07a;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 18px rgb(255 190 60 / .5);
}
.promo-note { font-size: 11px; color: var(--muted); }

.promo-dots {
  display: flex; justify-content: center; gap: 6px; margin-top: 6px;
}
.promo-dot {
  width: 26px; height: 5px; border-radius: 999px; cursor: pointer;
  background: rgb(255 255 255 / .2);
}
.promo-dot-word {
  /* The dot carries its banner's name for a reader; three identical "slide"
     labels are three identical announcements. */
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.promo-radio:nth-of-type(1):checked ~ .promo-dots [data-for="1"],
.promo-radio:nth-of-type(2):checked ~ .promo-dots [data-for="2"],
.promo-radio:nth-of-type(3):checked ~ .promo-dots [data-for="3"] {
  background: #ffd98a;
}
.promo-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

body.arcade.loud .promo { max-width: 720px; }

/* SHORT SCREENS GIVE THE HEIGHT BACK TO THE MACHINES, exactly as the search
   does -- the band below is measured in viewport units and every pixel spent
   above it comes off the tiles. */
@media (max-height: 620px) {
  .promo { margin-bottom: 4px; }
  .promo-frame { height: 58px; border-radius: 10px; }
  .promo-card { gap: 10px; padding: 0 12px; }
  .promo-art { width: 42px; height: 42px; }
  .promo-headline { font-size: 14px; }
  .promo-figure { font-size: 16px; }
  .promo-note { display: none; }
  .promo-dots { margin-top: 4px; }
}

/* A PHONE HELD LANDSCAPE DOES NOT GET A BANNER AT ALL.
   390 pixels of height is the whole room, the band below is measured in
   viewport units, and the banner plus the search were taking 90 of them --
   which came straight off the machines and left 109px tiles where there had
   been 186. The banner is the nice-to-have and the games are the product, so
   the banner is what goes. The search stays: it does something. */
@media (max-height: 430px) {
  .promo { display: none; }
}

/* Somebody who asked for less movement gets the banner and not the fade. */
@media (prefers-reduced-motion: reduce) {
  .promo-card { transition: none; }
}

/* ------------------------------------------------- the splash, and arriving

   THE GEOMETRY ABOVE IS THE REFERENCE'S and none of it changes here. What was
   missing is that the veil APPEARED -- one frame of nothing, then a full
   screen of logo -- and the front door it interrupts is the same mark at the
   same size, so the jump read as two screens rather than one continuing.

   ONE ANIMATION, ON THE BOX. Opacity and transform only, which the compositor
   handles without touching layout, so it cannot cost a frame on the phone
   this is mostly seen on. NOT on `.loading` itself: the wash has to be opaque
   from the first frame or the lobby shows through underneath it.

   NOTHING HERE CHANGES WHEN IT LIFTS. That is play.js, and the rule it keeps
   was bought by a six-second freeze: the wait is on the first few cabinet
   pictures, forced eager, under a hard cap -- never on anything below the
   fold, and never on this. A splash that waited on its own artwork would be
   the same bug with a better logo, so the mark is not in anything's count.
   It is also the file the sign-in screen just drew, which is the other half
   of why it can be this big and still cost nothing. */
@media (prefers-reduced-motion: no-preference) {
  .loading-box { animation: splash-in .34s cubic-bezier(.2, .9, .3, 1) both; }
}
@keyframes splash-in {
  from { opacity: 0; transform: scale(.985); }
  to   { opacity: 1; transform: none; }
}

/* ====================================================== a finger gets an answer

   AUDITED, NOT GUESSED. Forty-nine controls are rendered across the play
   templates; thirty-one of them had no `:active` at all and forty-eight had
   no focus ring. On a desktop that is a missing flourish. On a PHONE, which
   is nearly all of this platform, it is the whole feedback: there is no
   hover, so tapping a game tile did precisely nothing until the next page
   arrived, and on a slow connection that is a second or more of wondering
   whether the tap registered. The test beside this counts the same
   forty-nine and fails when one is added without a press state.

   THE PROPERTY IS `scale`, NOT `transform`, AND THAT IS THE POINT. A press
   written as `transform: scale(.96)` REPLACES whatever transform the element
   already had -- and this stylesheet centres things that way. See
   `.infobook-arrow:active`, which sits at `translateY(-50%)` and had to
   restate it inside its own press rule to keep from jumping half its height
   the moment it was touched. That is a trap laid for whoever adds the next
   one. `scale` is an independent transform property: it COMPOSES with any
   existing transform instead of overwriting it, so a rule that knows nothing
   about an element cannot break how it is positioned. Where support is
   missing the press simply does not happen; nothing moves that should not.

   OPACITY, FILTER AND SCALE ONLY -- all three are compositor properties, so
   a press costs no layout and no repaint. Nothing here runs on load, nothing
   waits on an asset, and nothing touches the veil. */
body.arcade {
  /* The ring is one colour on every skin. `--accent` is not: it is red on
     one, gold on another, pink on a third, and a focus ring that changes
     meaning per room is not a focus ring. Gold reads on all five. */
  --focus: #ffd98c;
}
/* NO BLANKET `transition` GOES WITH IT, and that is not an omission.
   `transition` is one declaration, not a list that accumulates: a rule
   setting it on `:is(a, button, summary)` has more specificity than
   `.cabinet` (0,1,0) or `.rail a` (0,1,1), so it would REPLACE what those
   already ease and silently take the easing off the tile lift and the rail
   press -- feel removed by the batch meant to add it. A press does not want
   easing anyway. A key that travels on a curve feels soft; the one on a real
   machine snaps down and snaps back, which is what no transition does. */
body.arcade :is(a, button, summary):active { scale: .96; }

/* THE TILE, which is the one that matters. Slightly less travel than a
   button -- a 172px card shrinking 4% reads as a lurch -- and a lift in
   brightness, because the thing being pressed is a picture and a picture
   answers in light. */
body.arcade .cabinet:active { scale: .975; filter: brightness(1.12); }
/* THE ONE PLACE A TRANSITION IS ADDED, and it restates what the tile already
   eased rather than replacing it. `.cabinet` eases `transform` and
   `box-shadow` for the hover lift; naming only the new properties here would
   have dropped both. Brightness alone is eased -- the scale still snaps. */
@media (prefers-reduced-motion: no-preference) {
  body.arcade .cabinet {
    transition: transform .14s ease, box-shadow .14s ease,
                filter .09s ease-out;
  }
}
/* A BANNER IS NOT A BUTTON. The same 4% across 720px is a slab of the page
   moving; across a 44px key it is a press. Scale by area, not by rule. */
body.arcade .promo-card:active { scale: .992; }

/* NO GREY FLASH ON TOP OF IT. Android paints its own translucent rectangle
   over any tapped link, which lands square on the press above and reads as a
   smudge. Removed only because there is now something in its place -- a tap
   highlight taken away and nothing given back is worse than the smudge. */
body.arcade { -webkit-tap-highlight-color: transparent; }

/* ------------------------------------------------------- and a keyboard too

   `:focus-visible`, not `:focus`: the ring appears for somebody tabbing and
   stays away from somebody who just clicked, which is the whole reason the
   pseudo-class exists. Three controls had one. Forty-eight did not, and the
   browser default -- which several resets on this page had already removed --
   was all that stood between a keyboard and an invisible cursor. */
body.arcade :is(a, button, summary, input, select, textarea):focus-visible {
  /* THE OUTLINE ALREADY FOLLOWS THE SHAPE. An outline traces the element's
     own border-radius, so the discs on the rail and the round hearts get a
     ring and not a box around a circle -- without this rule saying anything
     about radius. Saying it (`border-radius: inherit`) would have been worse
     than useless: it would take the PARENT's corners and restyle the control
     itself the instant somebody tabbed to it. */
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* ------------------------------------------------- the shelf, arriving

   CHOOSING A KIND AND SEARCHING ARE BOTH NAVIGATIONS on this platform -- the
   rail is links, the search box is a GET form -- so "smooth tab switching"
   is not a crossfade between two DOMs that exist at once. It is what the new
   shelf does when it lands. It rises.

   ONLY THE TILES THAT CAN BE SEEN. Fourteen, which covers the first screen
   on the widest layout; the eightieth tile does not need an entrance nobody
   is present for, and eighty simultaneous animations on a cheap phone is the
   jank this batch is supposed to avoid. The stagger is 22ms, so the whole
   thing is done in under half a second.

   IT DOES NOT TOUCH OPACITY, AND THAT IS THE WHOLE POINT OF IT.

   The first draft rose and faded in together, which is the ordinary way to
   write this and was measured doing the ordinary thing: with the animation
   pending, all three tiles read `opacity: 0`. That is what `backwards` fill
   means -- before the first frame the element holds the FROM state -- and it
   is correct right up until something stops the animation from starting.
   A tab that is never painted, a compositor that stalls, a stylesheet that
   arrives while the document is hidden: any of those leaves a shelf of
   nothing. A blank lobby has been a P0 on this platform TWICE, and neither
   time was it worth what caused it.

   So the entrance moves and does not fade. The worst case is now a shelf
   sitting ten pixels low, which nobody will ever see and which is a shelf.
   The other three guards stay: no plain rule makes a tile transparent, the
   fill is `backwards` rather than `forwards` so a finished animation hands
   the tile back to its own stylesheet, and the block sits inside
   `prefers-reduced-motion: no-preference`. Each is a test. */
@media (prefers-reduced-motion: no-preference) {
  .cabinets .slot:nth-child(-n+14) {
    animation: shelf-in .34s cubic-bezier(.2, .8, .3, 1) backwards;
  }
  .cabinets .slot:nth-child(2)  { animation-delay: .022s; }
  .cabinets .slot:nth-child(3)  { animation-delay: .044s; }
  .cabinets .slot:nth-child(4)  { animation-delay: .066s; }
  .cabinets .slot:nth-child(5)  { animation-delay: .088s; }
  .cabinets .slot:nth-child(6)  { animation-delay: .110s; }
  .cabinets .slot:nth-child(7)  { animation-delay: .132s; }
  .cabinets .slot:nth-child(8)  { animation-delay: .154s; }
  .cabinets .slot:nth-child(9)  { animation-delay: .176s; }
  .cabinets .slot:nth-child(10) { animation-delay: .198s; }
  .cabinets .slot:nth-child(11) { animation-delay: .220s; }
  .cabinets .slot:nth-child(12) { animation-delay: .242s; }
  .cabinets .slot:nth-child(13) { animation-delay: .264s; }
  .cabinets .slot:nth-child(14) { animation-delay: .286s; }
}
@keyframes shelf-in {
  from { transform: translateY(10px); }
  to   { transform: none; }
}
