﻿/* The three zones every game page is laid out in: how it works, the game, and what other
   people are winning. See GameStage.razor for why.

   Layout only. Each game keeps its own stylesheet for the game itself - this decides
   where the three sit and nothing about what is in them. */

.gstage {
    display: grid;
    /* An explicit track, and min-width:0 on the children below. Without them the
       implicit track sizes to the widest child's minimum content, and a child that will
       not wrap - scratch has three white-space:nowrap rules - hands it a minimum wider
       than a phone. The stage then overflowed the screen and the page scrolled sideways,
       on the one game that happened to carry such a child. The same blowout .hl had to
       fix inside itself; fixed here it is fixed for every game at once. */
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
    width: 100%;
    max-width: 100%;
    color: var(--wx-ink, #fff);
}

.gstage-head,
.gstage-game,
.gstage-info,
.gstage-wins {
    min-width: 0;
}

/* Stacked, which is the phone and the app. Game first because it is what somebody came
   for; then one win, which is the hook; then the rules, which are reference by the time
   anybody scrolls this far. Order rather than markup order - see the note in GameStage
   about reading order on a wide screen. */
.gstage-head { order: 0; }
.gstage-game { order: 1; }
.gstage-wins { order: 2; }
.gstage-info { order: 3; }

/* The name of the game, across everything under it. Inset to the panel's own padding
   below, so the name lines up with the words inside the panel rather than with its
   border - flush with the edge it read as hanging off the page.

   A row: the game's own .gh bar, then the (i). The stage owns the button so every game
   has one; the bar is the game's, so this only has to sit them side by side.

   The grid's gap is for panels. Between the name and the first panel it is the same as
   the rail's padding above the name, so the name sits evenly between the two. */
.gstage-head {
    width: 100%;
    box-sizing: border-box;
    padding: 0 18px;
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: -6px;
}

.gstage-head > .gh {
    flex: 1 1 auto;
}

.gstage-infobtn {
    flex: none;
    padding: 4px 6px;
    border: none;
    background: none;
    color: var(--wx-muted, #9a9a9a);
    font-size: 1.05rem;
    line-height: 1;
    cursor: pointer;
}

@media (hover: hover) {
    .gstage-infobtn:hover { color: var(--wx-signal, #d4af37); }
}

/* Already beside the game - nothing to scroll to. */
.gstage.is-wide .gstage-infobtn { display: none; }

/* The bar every game puts inside that header: its name, and room for one short
   right-aligned note - spins left today, points just won, what a go costs.
   One class for all six games rather than six names for the same three rules.
   Higher or Lower and the three prize draws already agreed on
   this shape under four different class names (.hl-head/.ww-head/.tv-head/
   .pg-head, byte-for-byte identical); Slots and Scratch never joined that
   agreement at all - each drew its own large centred h1 instead, which is what
   put their titles a different size and position from everything else. */
.gh {
    width: 100%;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

.gh h2 {
    margin: 0;
    font-family: var(--wx-font-display, inherit);
    font-size: 1.1rem;
}

.gh-meta {
    color: var(--wx-muted, #9a9a9a);
    font-size: 0.82rem;
}

/* Slots' one addition to the bar: the moment it has something worth saying loudly. */
.gh-meta.is-win {
    font-weight: 700;
    color: var(--wx-good, #2e7d32);
}

/* The game itself gets the same card treatment its neighbours already had - a border,
   background and rounded corners, so all three read as one row of matching panels
   rather than two bordered cards flanking a widget floating loose between them. Applied
   to the neutral wrapper GameStage itself owns rather than to each game's own top-level
   class (.pg, .slots, .hl, ...), since that differs game to game and this has to look
   identical no matter which one is inside it. Not scoped to is-wide, for the same
   reason .gi/.gwf below aren't - stacked on a phone the game is still worth setting
   apart from whatever's below it the same way its neighbours are. */
.gstage-game {
    box-sizing: border-box;
    padding: 16px 18px;
    border: 1px solid var(--wx-line, #2c2c2c);
    border-radius: var(--wx-radius, 12px);
    background: var(--wx-surface, #1b1b1b);
}

/* ---------- three across ---------- */

/* Driven by the Wide parameter, not a media query. The admin's preview is a phone-shaped
   panel inside a desktop page, and a media query would hand it this layout inside a
   390px frame.

   Three EQUAL columns, always, on every game page - rules, game, wins, in that order,
   never fewer and never resized. This used to shrink to two columns (or hide the wins
   column outright) whenever nobody had won anything yet, on the idea that an empty
   column looks broken. It looked broken a different way instead: the game visibly
   shifted a whole column's width right of centre with nothing on screen to explain why,
   and the page resized itself the moment somebody's first win of the session arrived
   mid-play. A layout that changes shape depending on what's in it isn't fixed, no
   matter how good the reasoning for each shape was on its own. GameWinsFeed always
   draws something now - real wins, or a placeholder the same size a win would be - so
   this grid never has to decide whether to show the third column. It always does. */
.gstage.is-wide {
    grid-template-columns: repeat(3, minmax(280px, 1fr));
    grid-template-areas:
        "head head head"
        "info game wins";
    /* Stretch, not start - all three columns take the full height of the row (set by
       whichever is naturally tallest, usually the rules panel), rather than each
       sitting at its own short natural height with dead space below it. That dead
       space is what made the wins column - four lines of placeholder text in an
       otherwise-empty 600px cell - look like it was floating on top of the game
       instead of standing beside it as its own column. */
    align-items: stretch;
    gap: 20px;
}

.gstage.is-wide .gstage-head { grid-area: head; order: 0; }
.gstage.is-wide .gstage-info { grid-area: info; order: 0; }
.gstage.is-wide .gstage-wins { grid-area: wins; order: 0; }

/* The game's own widget (.slots, .hl, .pg, ...) is only ever as tall as it needs to
   be, which is rarely the full height stretch just gave this cell. Top rather than
   centred - every game now opens with its own GameStatusBar (see below), and centring
   the whole block vertically would float that bar a different distance down on every
   game depending on how tall the rest of it is, instead of it landing in the same
   place under the title every time. */
.gstage.is-wide .gstage-game {
    grid-area: game;
    order: 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
}

/* Wide asks for three columns; the window still has to have room for them. The website
   passes Wide on every screen, so on a phone browser three 280px columns made the page
   about 900px wide and it scrolled sideways. Below what the three need (3 x 280 + two
   gaps, plus the page's own margin) the stage stacks exactly as the app's does. The
   admin's preview never passes Wide, so this cannot reach it. */
@media (max-width: 939px) {
    .gstage.is-wide {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas: none;
        gap: 16px;
    }

    .gstage.is-wide .gstage-head,
    .gstage.is-wide .gstage-game,
    .gstage.is-wide .gstage-info,
    .gstage.is-wide .gstage-wins {
        grid-area: auto;
    }

    .gstage.is-wide .gstage-head { order: 0; }
    .gstage.is-wide .gstage-game { order: 1; }
    .gstage.is-wide .gstage-wins { order: 2; }
    .gstage.is-wide .gstage-info { order: 3; }

    /* Stacked, the rules are below the fold again, so the (i) that scrolls to them is
       back. */
    .gstage.is-wide .gstage-infobtn { display: inline-block; }
}

/* ==========================================================================
   The status bar every game opens with: plays today against the cap, and what
   the next one takes - see GameStatusBar.razor.
   ========================================================================== */

.gsb {
    display: flex;
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--wx-line, #2c2c2c);
    border-radius: var(--wx-radius, 12px);
    background: var(--wx-surface-2, #151515);
}

.gsb-cell {
    flex: 1;
    display: grid;
    gap: 2px;
    justify-items: center;
    padding: 9px 6px;
    text-align: center;
}

.gsb-cell + .gsb-cell { border-left: 1px solid var(--wx-line, #2c2c2c); }

.gsb-label {
    font-family: var(--wx-font-display, inherit);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--wx-muted, #9a9a9a);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

.gsb-value {
    font-family: var(--wx-font-data, ui-monospace, Consolas, monospace);
    font-size: 1.05rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--wx-signal, #d4af37);
}

/* "of 50" beside the count, quieter than the count. */
.gsb-of { font-size: 0.8rem; font-weight: 600; color: var(--wx-muted, #9a9a9a); }

/* What the next play takes. An ad is a real thing to say, not an alarm; tomorrow is
   just quiet. */
.gsb-value.is-ad { color: var(--wx-ink, #f2f2f2); font-size: 0.9rem; }
.gsb-value.is-quiet { color: var(--wx-muted, #9a9a9a); font-size: 0.9rem; }

/* ==========================================================================
   The ad slot - see AdSlotView.razor. The report sheet's scrim and card, laid out
   around a creative and a count rather than a form.
   ========================================================================== */

/* The whole screen, like the networks' own rewarded ads - Google's and ours read as one
   kind of thing. Self-contained: the games page does not load the streams stylesheet.

   Above 1000: on a phone-width website the sticky header and the fixed tab bar are both
   z-index 1000 (partner/base.css). 1101, the same as every sheet. */
.adslot {
    position: fixed;
    inset: 0;
    z-index: 1101;
    display: grid;
    grid-template-rows: auto 1fr auto;
    background: #000;
    color: #fff;
    padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
    font-family: var(--wx-font-body, system-ui, -apple-system, "Segoe UI", sans-serif);
}

/* The partner's theme stops at the ad's edge: an ad reads the same on every site, as
   the networks' do. Themes uppercase buttons and colour headings for their own ground;
   on a black screen that left the headline dark on black. */
.adslot button,
.adslot a {
    text-transform: none;
    letter-spacing: normal;
}

.adslot h2,
.adslot h3 {
    color: inherit;
    text-transform: none;
    letter-spacing: normal;
    font-family: inherit;
}

/* "Ad" left; the count, or the X, right. */
.adslot-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
}

.adslot-tag {
    padding: 2px 7px;
    border-radius: 4px;
    background: #fbbc04;
    color: #000;
    font-size: 0.72rem;
    font-weight: 800;
    letter-spacing: 0.02em;
}

.adslot-timer,
.adslot-close {
    margin-left: auto;
    appearance: none;
    border: 1px solid rgba(255, 255, 255, 0.35);
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    font: inherit;
    cursor: pointer;
}

.adslot-timer {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    border-radius: 999px;
    font-size: 0.85rem;
}

.adslot-timer b { font-variant-numeric: tabular-nums; min-width: 1.2em; text-align: center; }
.adslot-timer i { opacity: 0.6; font-size: 0.8rem; }

.adslot-close {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 1rem;
}

.adslot-close:disabled { cursor: default; opacity: 0.7; }

/* The reward, said the moment it is there - the X beside it collects. */
.adslot-earned {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border-radius: 999px;
    background: rgba(76, 175, 80, 0.18);
    color: #7ee08a;
    font-size: 0.82rem;
    font-weight: 700;
}

.adslot-earned + .adslot-close { margin-left: 0; }

/* The ad itself, as big as the screen allows. */
.adslot-stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 12px 20px;
    text-align: center;
    min-height: 0;
    overflow: auto;
}

.adslot-image {
    max-width: min(92vw, 760px);
    max-height: 58vh;
    max-height: 58dvh;
    object-fit: contain;
    border-radius: 12px;
}

.adslot-headline { margin: 0; font-size: clamp(1.3rem, 4vw, 2rem); font-weight: 800; }

.adslot-line { margin: 0; font-size: 1rem; color: rgba(255, 255, 255, 0.75); max-width: 40ch; }

.adslot-title { margin: 0; font-size: 1.3rem; }

.adslot-message { margin: 0; color: rgba(255, 255, 255, 0.7); }

.adslot-wait { margin: 0; color: rgba(255, 255, 255, 0.7); }

.adslot-error { margin: 0; font-size: 0.9rem; color: #ff8a80; }

.adslot-btn {
    appearance: none;
    font: inherit;
    font-weight: 700;
    padding: 10px 22px;
    border-radius: 999px;
    border: 0;
    background: #fff;
    color: #000;
    cursor: pointer;
}

/* The networks' call-to-action bar, along the foot. */
.adslot-cta {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 0 14px 14px;
    padding: 12px 14px;
    border-radius: 12px;
    background: #fff;
    color: #111;
}

.adslot-cta-name {
    flex: 1;
    min-width: 0;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.adslot-cta-btn {
    flex: none;
    padding: 9px 18px;
    border-radius: 999px;
    background: var(--wx-signal, #1a73e8);
    color: var(--wx-signal-ink, #fff);
    font-weight: 700;
    text-decoration: none;
}

/* "Close ad?" over the ad. */
.adslot-ask {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 20px;
    background: rgba(0, 0, 0, 0.6);
}

.adslot-ask-card {
    width: min(20rem, 100%);
    padding: 20px 20px 12px;
    border-radius: 14px;
    background: #fff;
    color: #111;
}

.adslot-ask-card h3 { margin: 0 0 6px; font-size: 1.1rem; color: #111; }
.adslot-ask-card p { margin: 0 0 12px; color: #555; }

.adslot-ask-acts { display: flex; justify-content: flex-end; gap: 4px; }

.adslot-ask-btn {
    appearance: none;
    border: 0;
    background: none;
    padding: 10px 12px;
    font: inherit;
    font-weight: 700;
    color: #555;
    cursor: pointer;
}

.adslot-ask-btn.is-primary { color: #1a73e8; }

/* Waiting - for an ad, or for the collect. */
.adslot-spin {
    width: 16px;
    height: 16px;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-top-color: #fff;
    border-radius: 50%;
    animation: adslot-spin 0.8s linear infinite;
}

.adslot-spin.is-big { width: 36px; height: 36px; border-width: 3px; }

@keyframes adslot-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
    .adslot-spin { animation-duration: 2.4s; }
}


/* ==========================================================================
   The button that buys a go - SPIN, DEAL, START, PLAY - see PlayButton.cs for
   what it says. One shape on every game.

   Five games had five of these: four gold pills of slightly different padding,
   one square orange block (Scratch), and Higher or Lower's outline pill in its
   own colour. The square one is the keeper - it fills the column's width the
   way the status bar above it does, and reads as part of the game rather than a
   pill floating under it. Disabled is a real grey, not the enabled colour at
   half opacity, which still looked pressable.
   ========================================================================== */

.gplay {
    width: 100%;
    min-height: 56px;
    box-sizing: border-box;
    padding: 12px 20px;
    border: 0;
    border-radius: 12px;
    background: var(--wx-signal, #d4af37);
    color: var(--wx-signal-ink, #111);
    font: inherit;
    font-size: 1.15rem;
    font-weight: 700;
    /* The games' main button is the one that shouts: "SPIN · NO ADS" (Frank, 2026-09-30).
       Every other game button is Title Case. */
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    transition: transform 120ms ease, filter 120ms ease;
}

.gplay:disabled {
    background: var(--wx-surface-2, #6a6a6a);
    color: var(--wx-muted, #cfcfcf);
    cursor: default;
}

.gplay:not(:disabled):active { transform: translateY(1px) scale(0.99); }

/* Higher or Lower's Bank the Win - the cautious choice in the slot the next go will take.
   The same shape, but an OUTLINE in the game's banking green rather than a solid block:
   solid is the button that spends, and this one is the one that stops. That was how it
   was drawn before the buttons were unified, and it came back solid by mistake. */
.gplay.is-bank {
    background: transparent;
    border: 1px solid var(--wx-hl-bank, #2ecc5a);
    color: var(--wx-hl-bank, #2ecc5a);
}

.gplay.is-bank:disabled {
    background: transparent;
    border-color: var(--wx-line, #2c2c2c);
    color: var(--wx-muted, #9a9a9a);
}

/* An outline button has nothing to brighten, so the hover is a wash of its own green
   behind it - enough to say it is live, not enough to make it the solid one. Mystery
   Box's TAKE wears this too. */
@media (hover: hover) {
    .gplay.is-bank:not(:disabled):hover {
        filter: none;
        background: color-mix(in srgb, var(--wx-hl-bank, #2ecc5a) 14%, transparent);
    }
}

@media (hover: hover) {
    .gplay:not(:disabled):hover { filter: brightness(1.06); }
}

/* ==========================================================================
   The wins themselves.
   ========================================================================== */

.gwf {
    padding: 14px 16px;
    border: 1px solid var(--wx-line, #2c2c2c);
    border-radius: var(--wx-radius, 12px);
    background: var(--wx-surface, #1b1b1b);
}

/* Full height on the desktop column - .gstage-wins is a stretched grid cell there (see
   gamestage.css's .gstage.is-wide), and without this the card inside it still sized to
   its own content and left the stretch invisible. Narrow/app mode never stretches
   .gstage-wins in the first place, so .gwf there keeps its plain natural height - this
   only fires where there's a tall cell for it to fill. */
.gstage.is-wide .gstage-wins .gwf {
    height: 100%;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
}

.gstage.is-wide .gstage-wins .gwf-list {
    flex: 1;
    align-content: start;
    overflow-y: auto;
}

/* Centred in whatever room is left under the title, rather than sitting right beneath
   it with the rest of a 600px card standing empty below. */
.gstage.is-wide .gstage-wins .gwf-empty {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.gwf-title {
    margin: 0 0 10px;
    display: flex;
    align-items: center;
    gap: 8px;
    font-family: var(--wx-font-display, inherit);
    font-size: 0.76rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--wx-signal, #d4af37);
}

.gwf-list {
    display: grid;
    gap: 8px;
}

/* Same footprint as a .gwf-item - the wins column is a fixed part of the page now (see
   GameStage), not something that appears once there's a win, so there has to be
   something here before there is one. Dashed rather than solid, so it still reads as
   "nothing yet" rather than as a win with no name in it. */
.gwf-empty {
    margin: 0;
    padding: 8px 10px;
    border: 1px dashed var(--wx-line, #2c2c2c);
    border-radius: 10px;
    background: var(--wx-surface-2, #151515);
    color: var(--wx-muted-dim, #6f6f6f);
    font-size: 0.86rem;
    text-align: center;
}

.gwf-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--wx-line, #2c2c2c);
    border-radius: 10px;
    background: var(--wx-surface-2, #151515);

    /* New arrivals slide in. The card is keyed on the post, so this plays when a win
       replaces the one before it and not on every poll - which is the point of the
       identity check in LoadAsync. */
    animation: gwf-in 380ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

@keyframes gwf-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* The game's own mark, ahead of the player's face: the same circle, the same size, one
   after the other - the game, then the person. */
.gwf-game {
    flex: none;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--wx-surface, #1b1b1b);
}

.gwf-face {
    flex: none;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--wx-surface, #1b1b1b);
}

/* No picture. Their initial, on the signal colour, so a face-less winner still reads as
   a person rather than as a missing image. */
/* A member's or a patron's face on the feed: the same ring the avatar wears. */
/* The winner's AvatarView, the same size as the game's mark beside it. */
.gwf-post .wx-avatar { flex: none; width: 38px; height: 38px; }

.gwf-face.is-member { box-shadow: 0 0 0 2px var(--wx-signal, #e5841e); }
.gwf-face.is-patron { box-shadow: 0 0 0 2px var(--wx-patron, #8b5cf6); }

/* The stand-in face. Contained rather than cropped, and inset a little, so the mark sits
   inside the circle instead of being filled to its edges the way a photograph is. */
.gwf-face.is-default {
    object-fit: contain;
    padding: 5px;
    background: var(--wx-surface-2, #151515);
}

/* The row is two links: the game's mark, and everything after it to the post. Both are
   anchors that look like what they wrap - the pointer is the tell. */
.gwf-game-link { flex: none; display: block; line-height: 0; cursor: pointer; }

.gwf-post {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    flex: 1;
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

.gwf-said {
    display: block;
    min-width: 0;
    flex: 1;
}

.gwf-line {
    display: block;
    margin: 0;
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--wx-ink, #fff);

    /* One line. A name and a number is the whole message, and a wrapped card in a column
       of cards makes the column look ragged. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.gwf-sub {
    display: block;
    margin: 1px 0 0;
    font-size: 0.78rem;
    color: var(--wx-muted, #9a9a9a);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.gwf-go {
    flex: none;
    padding: 6px 8px;
    color: var(--wx-muted, #9a9a9a);
}

@media (hover: hover) {
    .gwf-post:hover .gwf-go { color: var(--wx-signal, #d4af37); }
}

/* One card, on a phone. The heading goes: a section title over a single row is a label
   for something that is already obvious. */
.gwf.is-one .gwf-title {
    margin-bottom: 8px;
}

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

/* ==========================================================================
   The panel beside a game.

   One set of classes for every game's rules and paytable. Higher or Lower keeps its own
   .hl-info because its ladder is built differently, but everything here reads the same:
   a heading, a paragraph or two, and whatever table the game pays from.
   ========================================================================== */

.gi {
    width: 100%;
    padding: 16px 18px;
    border: 1px solid var(--wx-line, #2c2c2c);
    border-radius: var(--wx-radius, 12px);
    background: var(--wx-surface, #1b1b1b);
    text-align: left;
    color: var(--wx-ink, #fff);
    box-sizing: border-box;
}

/* Full height on the desktop column - .gstage-info is a stretched grid item there (see
   gamestage.css's .gstage.is-wide), but .gi is a plain child of it and, left alone,
   only grows to its own text's height. Whichever of the three panels has the least to
   say then had a border stopping short of the row the other two (and now the game
   panel, matching them) actually stretch to - the same gap .gwf below already had this
   fix for. */
.gstage.is-wide .gstage-info .gi {
    height: 100%;
}

.gi-title {
    margin: 0 0 6px;
    font-family: var(--wx-font-display, inherit);
    font-size: 0.76rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--wx-signal, #d4af37);
}

/* Air above every block but the first, without a trailing margin making the panel taller
   than what is in it. */
.gi-title:not(:first-child) {
    margin-top: 18px;
}

.gi-text {
    margin: 0 0 8px;
    font-size: 0.88rem;
    line-height: 1.5;
    color: var(--wx-muted, #9a9a9a);
}

.gi-text:last-child { margin-bottom: 0; }

/* ---------- the key ----------
   What each extra or go takes, one rung a row: "Peek 1 · No Ads", "Spin 6-50 · Watch
   Ad". One rule between rows rather than a border on each - a hairline grid without the
   doubled edges. The rung the player is standing on is lit, a bar down its leading
   edge; the ones already spent go quiet rather than disappearing, because what a look
   cost is worth still being able to see after paying it. Every game draws its key with
   this - see GameKey.razor. */
.gi-key {
    margin: 10px 0 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 1px;
    background: var(--wx-line, #2c2c2c);
    border: 1px solid var(--wx-line, #2c2c2c);
    border-radius: 8px;
    overflow: hidden;
}

.gi-key li {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 7px 10px;
    background: var(--wx-surface-2, #151515);
    font-size: 0.84rem;
    color: var(--wx-ink, #fff);
}

.gi-key-n { color: var(--wx-muted, #9a9a9a); }
/* The price in the data face, and No Ads in gold - the thing worth noticing. */
.gi-key-p {
    font-family: var(--wx-font-data, ui-monospace, Consolas, monospace);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.gi-key li:not(.is-spent) .gi-key-p:is([data-free="1"]) { color: var(--wx-signal, #d4af37); }

.gi-key li.is-note { justify-content: center; }
.gi-key li.is-note .gi-key-n { text-align: center; }

.gi-key li.is-now {
    background: rgba(212, 175, 55, 0.14);
    box-shadow: inset 3px 0 0 var(--wx-signal, #d4af37);
}

.gi-key li.is-now .gi-key-n {
    color: var(--wx-ink, #fff);
    font-weight: 700;
}

.gi-key li.is-spent { opacity: 0.45; }
.gi-key li.is-spent .gi-key-p { text-decoration: line-through; }

/* Room under a key before the next title. */
.gi-key + .gi-title { margin-top: 18px; }

/* ---------- the member link ----------
   The last block of every game's rules - see GameMemberLink.razor. */
.gi-member-btn { margin-top: 8px; }

/* ---------- the extra's button ----------
   The second button on a game - a peek, a bump, a change of box - as against .gplay,
   which starts a go. One shape on every game: a gold outline pill, uppercase, the
   label carrying what a press takes ("PEEK · WATCH AD"). is-on is pressed and waiting
   (a box is being chosen); is-locked is behind an ad. Games add their own class for
   what differs - the wheel's arrows, Higher or Lower's breathing. */
.gx-btn {
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 9px 12px;
    box-sizing: border-box;
    border: 1px solid var(--wx-signal, #d4af37);
    border-radius: 999px;
    background: none;
    color: var(--wx-signal, #d4af37);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: none;
    white-space: nowrap;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.gx-btn:disabled { opacity: 0.4; cursor: default; }

.gx-btn.is-on {
    background: var(--wx-signal, #d4af37);
    color: var(--wx-signal-ink, #111);
}

.gx-btn.is-locked { border-style: dashed; }

@media (hover: hover) {
    .gx-btn:not(:disabled):hover { filter: brightness(1.12); }
}

.gi-list {
    margin: 0;
    padding-left: 18px;
    display: grid;
    gap: 6px;
    font-size: 0.88rem;
    line-height: 1.45;
    color: var(--wx-muted, #9a9a9a);
}

/* ---------- the tables the games pay from ---------- */

/* Two shapes now, not three: Spin to Win/Drop Zone/Mystery Box and Scratch all show a
   label, a points payout and a chance, so they share one real table (.pg-info-odds) -
   Scratch used to draw the same data as its own div-row layout under a different name,
   in the opposite column order, with no header row. Slots pays by matching symbols
   rather than by a named prize, which a label/points/chance table can't express, so it
   keeps its own row shape (.slots-key) - styled identically, just built from what it
   actually has to show. */
.gi .pg-info-odds,
.gi .slots-key,
.gi .dz-key {
    width: 100%;
    margin: 0;
    border: 1px solid var(--wx-line, #2c2c2c);
    border-radius: 8px;
    overflow: hidden;
    font-size: 0.84rem;
    border-collapse: collapse;
    background: var(--wx-surface-2, #151515);
}

.gi .pg-info-odds th,
.gi .pg-info-odds td {
    padding: 7px 10px;
    border-top: 1px solid var(--wx-line, #2c2c2c);
    text-align: left;
}

.gi .pg-info-odds thead th {
    border-top: none;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--wx-muted, #9a9a9a);
    font-weight: 600;
}

.gi .pg-info-odds td {
    color: var(--wx-ink, #fff);
}

/* Numbers right, and lined up. A column of payouts that does not line up is a column
   nobody can compare down. */
.gi .pg-num {
    text-align: right;
    font-family: var(--wx-font-data, ui-monospace, Consolas, monospace);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.gi .pg-chance {
    color: var(--wx-muted, #9a9a9a);
}

/* Slots draws rows rather than table cells - a symbol combination isn't a table cell's
   worth of text. */
.gi .slots-key-row,
.gi .dz-key-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 7px 10px;
    border-top: 1px solid var(--wx-line, #2c2c2c);
}

.gi .slots-key-row:first-of-type,
.gi .dz-key-row:first-of-type {
    border-top: none;
}

.gi .slots-key-payout,
.gi .dz-key-payout {
    font-family: var(--wx-font-data, ui-monospace, Consolas, monospace);
    font-weight: 700;
    color: var(--wx-ink, #fff);
    white-space: nowrap;
}

/* The wild note is an explanation rather than a payout row, so it is not split left and
   right like one. */
.gi .slots-key-row-note {
    justify-content: center;
    color: var(--wx-muted, #9a9a9a);
}

/* ==========================================================================
   The message box every game talks through.

   One box, one size, on every game: what to do next, what just happened, what it paid.
   There were six copies of this - one per game, each pasted from the last and each a
   little different by the time anybody looked: 50px on scratch, 68px on the wheel, a
   different red for a loss on two of them. Same job on every screen, so one rule.

   Fixed height and a single line. A box that grew for a longer message pushed the game
   under it down the screen at exactly the moment the player was reaching for it.
   ========================================================================== */

.game-message {
    display: grid;
    justify-items: center;
    align-content: center;
    height: 54px;
    width: 100%;
    padding: 8px 18px;
    /* Outlined from the start, not only once something has happened. A box that grew
       a border on the first win looked like a different box; this one only changes
       colour. Same width in every state, so nothing under it moves.

       Not --wx-line. That token separates a surface from the page, and every theme sets
       it within a shade of --wx-surface-2 - which is this box's fill. The first version
       used it and was, measurably, outlined and, visibly, not. A line mixed down from
       the ink is a real edge on light themes and dark ones alike. */
    border: 1px solid color-mix(in srgb, var(--wx-ink, #fff) 24%, var(--wx-surface-2, #151515));
    border-radius: var(--wx-radius, 12px);
    background: var(--wx-surface-2, #151515);
    text-align: center;
    box-sizing: border-box;
    color: var(--wx-ink, #fff);
}

/* Two lines at most, inside the same 54px - a line that runs long wraps once before
   it is cut, rather than losing its end on a phone. The height never changes, so
   nothing under the box moves. The lines themselves are written to fit one line on
   a phone; the second is the net, not the plan. */
.game-message strong {
    max-width: 100%;
    font-size: 0.95rem;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
    text-align: center;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

.game-message.is-win {
    border-color: var(--wx-signal, #d4af37);
}

.game-message.is-win strong {
    color: var(--wx-signal, #d4af37);
    font-weight: 700;
}

/* A go that came to nothing. Lit the same weight a win is, in red, so the answer is not
   the quietest thing on the screen at the one moment it is being looked for. */
.game-message.is-lost {
    border-color: var(--wx-bad, #e2564d);
    background: color-mix(in srgb, var(--wx-bad, #e2564d) 10%, var(--wx-surface-2, #151515));
}

.game-message.is-lost strong {
    color: var(--wx-bad, #e2564d);
    font-weight: 700;
}

/* ---------- the big-win prompt ---------- */

.bw {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(92vw, 420px);
    padding: 22px 20px 18px;
    border-radius: var(--wx-radius, 14px);
    background: var(--wx-surface, #1b1b1d);
    border: 1px solid var(--wx-line, #2c2c2c);
    color: var(--wx-ink, #fff);
    font-family: var(--wx-font-body, system-ui, -apple-system, "Segoe UI", sans-serif);
    z-index: 1101;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    gap: 10px;
    animation: bw-in 220ms ease-out;
}

.bw-glow {
    position: absolute;
    inset: -40% -20% auto -20%;
    height: 60%;
    background: radial-gradient(ellipse at top, var(--wx-signal-soft, rgba(245, 165, 36, 0.18)), transparent 70%);
    pointer-events: none;
}

.bw-title {
    margin: 0;
    font-family: var(--wx-font-display, inherit);
    font-size: 1.6rem;
    position: relative;
}

.bw-lead { margin: 0; font-size: 0.95rem; color: var(--wx-muted, #cfcfcf); position: relative; }
.bw-lead b { color: var(--wx-ink, #fff); }
.bw-text { margin: 4px 0 0; font-size: 0.9rem; }

.bw-actions { display: flex; align-items: center; gap: 14px; margin-top: 4px; }

@keyframes bw-in {
    from { opacity: 0; transform: translate(-50%, -46%); }
    to { opacity: 1; transform: translate(-50%, -50%); }
}

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