/* The only stylesheet. Everything readable in this game is DOM on top of the canvas —
   the canvas draws the panel, the gears and the sparks, and nothing else.

   Two media queries, and both are about the intro card, because its sizes are vw clamps and a
   vw clamp is wrong at both ends. A landscape phone is 844x390: wide and short, so every clamp
   runs at full desktop size exactly where the room is scarcest — the max-height tier re-clamps
   the tall things against vh. A desktop is the opposite: the clamps have all topped out by
   ~422px of width, so the card stops growing while the screen does not — the min-width tier
   hands it fixed, larger sizes. Both live at the bottom of this file. */

:root {
    /* Two faces, and the split is deliberate: monospace IS the machine — the HUD, the status
       line, the tray — and the rounded one is for the menu card, which is the only screen in
       this game that is not the machine. See the intro block. */
    --font-round: ui-rounded, 'SF Pro Rounded', 'Arial Rounded MT Bold', 'Quicksand', system-ui, -apple-system, sans-serif;
    --font: ui-monospace, Menlo, Consolas, monospace;

    --ink: #0b0e12;
    --panel: #1b232c;
    --frame: #37485a;
    --frame-dim: #2f3f4e;

    --text: #e8eef5;
    --dim: #8fa3b8;
    --faint: #5d7b93;

    --gold: #ffd166;
    --online: #4dffd0;
    --alarm: #ff5d73;

    /* The top inset reserves the row the home link and the mute sit in; the bottom one
       keeps the hint line clear of a gesture bar. game.js measures the real HUD and tray
       edges off the DOM, so the board is never laid out under either. */
    --ng-top: calc(env(safe-area-inset-top) + 56px);
    --ng-bottom: calc(env(safe-area-inset-bottom) + 12px);

    /* The chrome button size and where the top bar starts, as variables rather than as
       literals in two rules each. The landscape rail is centred on the viewport, so how far
       it may grow is decided by how far down the top bar reaches — and it has to read those
       two numbers rather than carry a copy of them that drifts when a tier moves one. */
    --mini: clamp(40px, 11vw, 52px);
    --topbar-top: calc(var(--ng-top) + 8px);
}

* {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    padding: 0;
    overflow: hidden;
    /* the board is a tap target from edge to edge; a browser scroll or a pinch is never
       something this game wants */
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    background: var(--ink);
    color: var(--text);
    font-family: var(--font);
}

canvas#game {
    position: fixed;
    inset: 0;
    z-index: 0;
    display: block;
}

/* ---------- chrome ---------- */

.mute {
    position: fixed;
    z-index: 40;
    top: calc(env(safe-area-inset-top) + 8px);
    right: 12px;
    width: 36px;
    height: 36px;
    padding: 0;
    border: 2px solid var(--gold);
    border-radius: 50%;
    background: rgba(8, 12, 16, .66);
    color: var(--gold);
    cursor: pointer;
}

.mute svg {
    width: 22px;
    height: 22px;
    display: block;
    margin: auto;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
}

.mute .note-head {
    fill: currentColor;
    stroke: none;
}

.mute .slash {
    opacity: 0;
    stroke: var(--alarm);
    stroke-width: 2.6;
}

.mute.off {
    border-color: rgba(232, 238, 245, .5);
    color: rgba(232, 238, 245, .5);
}

.mute.off .slash {
    opacity: 1;
}

/* ---------- HUD ---------- */

/* pointer-events:none, so nothing clickable may live in here — a nested button inherits
   it and becomes invisibly dead. The mute and the reset are siblings for that reason. */
#hud {
    position: fixed;
    z-index: 10;
    left: calc(env(safe-area-inset-left) + 14px);
    top: calc(var(--ng-top) + 8px);
    pointer-events: none;
    line-height: 1.35;
    text-shadow: 0 2px 0 #000;
}

#lvl {
    font-size: clamp(15px, 4.2vw, 20px);
    font-weight: 700;
    letter-spacing: .12em;
    color: var(--gold);
}

#task {
    font-size: clamp(11px, 3vw, 14px);
    letter-spacing: .06em;
    color: var(--dim);
}

#best {
    font-size: clamp(10px, 2.7vw, 13px);
    letter-spacing: .06em;
    color: var(--faint);
}

/* ---------- clock ---------- */

/* The other half of the top band: #hud reads down the left, this reads down the right, and
   game.js starts the board below whichever of the two is deeper.

   The hint button rides in here with the clock. It used to sit under the reset in the corner
   the thumb reaches, which is the right home for a button pressed often — this one is pressed
   a handful of times a board, and what it needs instead is to be where the eye already is. On
   a timer that is next to the clock.

   pointer-events:none for the same reason #hud has it, and `.mini` turns it back on for the
   button exactly the way `.slot` does inside #tray. */
#topbar {
    position: fixed;
    z-index: 12;
    right: calc(env(safe-area-inset-right) + 14px);
    top: var(--topbar-top);
    display: flex;
    align-items: center;
    gap: 10px;
    pointer-events: none;
}

/* The gear bank. A readout, never a button — what it buys is bought from the tray, where the
   sizes and their prices already are — so it takes no pointer events back from #topbar. */
.pts {
    display: flex;
    align-items: center;
    gap: 4px;
    color: var(--online);
    text-shadow: 0 2px 0 #000;
}

.pts svg {
    width: clamp(15px, 4.2vw, 19px);
    height: clamp(15px, 4.2vw, 19px);
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linejoin: round;
}

.pts b {
    display: inline-block;
    font-family: var(--font);
    font-size: clamp(12px, 3.4vw, 15px);
    font-weight: 700;
    letter-spacing: .04em;
}

/* One payout token landing. It swells and goes white for a moment — the number is small and
   in a corner, and a value change alone is invisible to a player watching the tray. Restarted
   from script on every landing, so a run of them reads as a run. */
.pts.pop b {
    animation: gw-bank .32s cubic-bezier(.3, 0, .3, 1);
}

@keyframes gw-bank {
    0% {
        transform: scale(1);
    }

    36% {
        transform: scale(1.45);
        color: var(--text);
    }

    100% {
        transform: scale(1);
    }
}

#clock {
    width: clamp(70px, 20vw, 96px);
    text-align: right;
    text-shadow: 0 2px 0 #000;
}

#clock b {
    display: block;
    font-size: clamp(15px, 4.2vw, 20px);
    font-weight: 700;
    letter-spacing: .1em;
    color: var(--text);
}

#clock .bar {
    margin-top: 5px;
    height: 5px;
    border-radius: 3px;
    background: rgba(55, 72, 90, .55);
    overflow: hidden;
}

/* Drained with a transform, never a width. The loop offers this sixty times a second and a
   width would be a layout on every one it accepted. */
#clock .bar i {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 3px;
    background: var(--text);
    transform-origin: left center;
    transform: scaleX(1);
}

/* Steel, then gold at half, then alarm at a quarter. It stays out of the machine's own
   colour language until it is urgent — gold is the motor and the level, red is a jam — and
   by the time it is borrowing them the player has something to be alarmed about. */
#clock.warn b {
    color: var(--gold);
}

#clock.warn .bar i {
    background: var(--gold);
}

#clock.danger b {
    color: var(--alarm);
    animation: ct-tick .9s ease-in-out infinite;
}

#clock.danger .bar i {
    background: var(--alarm);
}

@keyframes ct-tick {

    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: .42;
    }
}

/* Sits directly above the panel, so `top` is written by game.js on every layout and the
   element lifts itself clear of whatever it was given. */
#status {
    position: fixed;
    z-index: 10;
    left: 0;
    right: 0;
    top: 0;
    transform: translateY(-100%);
    text-align: center;
    pointer-events: none;
    font-size: clamp(11px, 3.3vw, 14px);
    font-weight: 600;
    letter-spacing: .06em;
    color: var(--faint);
}

#status.jam {
    color: var(--alarm);
    font-weight: 700;
    letter-spacing: .18em;
}

#hint {
    position: fixed;
    z-index: 10;
    left: 0;
    right: 0;
    bottom: calc(var(--ng-bottom) + 30px);
    text-align: center;
    font-size: clamp(10px, 2.8vw, 13px);
    letter-spacing: .08em;
    color: #6f879e;
    pointer-events: none;
}

/* ---------- tray ---------- */

#tray {
    position: fixed;
    z-index: 12;
    left: 0;
    right: 0;
    bottom: calc(var(--ng-bottom) + 62px);
    display: flex;
    justify-content: center;
    gap: clamp(8px, 3vw, 16px);
    pointer-events: none;
}

.slot {
    pointer-events: auto;
    position: relative;
    width: clamp(56px, 17vw, 84px);
    height: clamp(56px, 17vw, 84px);
    padding: 0;
    border-radius: 14px;
    border: 2px solid var(--frame);
    background: linear-gradient(#232c36, #151b22);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: transform .08s, border-color .1s;
}

.slot.sel {
    border-color: var(--gold);
    box-shadow: 0 0 16px rgba(255, 209, 102, .35);
    transform: translateY(-4px);
}

/* Out of stock, not disabled: the slot stays selectable so the ghost can keep explaining
   which size the player is holding. */
.slot.out {
    opacity: .32;
}

.slot canvas {
    width: 70%;
    height: 70%;
    display: block;
}

.cnt {
    position: absolute;
    bottom: 2px;
    right: 6px;
    font-size: clamp(10px, 2.8vw, 13px);
    font-weight: 700;
    color: #cfe3f5;
}

/* Slot plus the one line under it. The line is the SAME height in both its states — a price
   or a buy button — because game.js lays the board out against the top of #tray, and a line
   that grew when a slot ran dry would move the board under the player at the exact moment
   they were looking at the tray. */
.stack {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    pointer-events: none;
}

/* What the size costs, and it is deliberately quiet: it is a number to aim at, not an offer.
   The offer is the button below, and it is the only thing on this line that ever lights up. */
.price {
    display: block;
    width: 100%;
    height: clamp(20px, 5.5vw, 26px);
    line-height: clamp(20px, 5.5vw, 26px);
    text-align: center;
    font-family: var(--font);
    font-size: clamp(9px, 2.6vw, 12px);
    font-weight: 700;
    letter-spacing: .06em;
    color: var(--faint);
}

/* Teal because teal already means GO in this game — a satisfied output, a system online — and
   because it can never be read as the violet the hint button and its marker share. The two
   offers on screen have to be told apart at a glance, and they are the only two. */
.buy {
    pointer-events: auto;
    display: block;
    width: 100%;
    height: clamp(20px, 5.5vw, 26px);
    padding: 0;
    border-radius: 9px;
    border: 1px solid rgba(77, 255, 208, .55);
    background: rgba(77, 255, 208, .12);
    color: var(--online);
    font-family: var(--font);
    font-size: clamp(9px, 2.6vw, 12px);
    font-weight: 700;
    letter-spacing: .06em;
    cursor: pointer;
    transition: transform .08s, background .1s;
}

.buy:hover {
    background: rgba(77, 255, 208, .22);
    border-color: var(--online);
}

.buy:active {
    transform: translateY(1px);
}

#btns {
    position: fixed;
    z-index: 12;
    right: 14px;
    bottom: calc(var(--ng-bottom) + 62px);
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.mini {
    pointer-events: auto;
    width: var(--mini);
    height: var(--mini);
    padding: 0;
    border-radius: 12px;
    border: 2px solid var(--frame);
    background: #1b232c;
    color: #9fb6cc;
    font-family: var(--font);
    font-size: clamp(16px, 4.5vw, 20px);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.mini:hover {
    color: var(--gold);
    border-color: var(--gold);
}

/* The hint button carries the same violet the marker is drawn in, so the button and the
   thing it puts on the board read as one feature. */
.mini.tip {
    position: relative;
    color: #c9a8ff;
    border-color: #4a3d63;
}

.mini.tip:hover {
    color: #efe4ff;
    border-color: #c9a8ff;
}

.mini.tip svg {
    width: clamp(18px, 5vw, 23px);
    height: clamp(18px, 5vw, 23px);
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* The count is inside the button so the badge is part of the tap target. */
.mini.tip i {
    position: absolute;
    right: -3px;
    top: -4px;
    min-width: 17px;
    padding: 0 3px;
    border-radius: 9px;
    background: #c9a8ff;
    color: #191024;
    font-family: var(--font);
    font-style: normal;
    font-weight: 700;
    font-size: 11px;
    line-height: 17px;
    text-align: center;
}

/* Half the clock gone on a board past the turntable, with a point in the bank. The button
   gets louder in the violet it already wears rather than putting up a new signal to learn,
   and it never does this while `broke` — the two cannot be on at once, because `urge` is
   gated on having something to spend.

   It PINGS rather than glowing: a ring that leaves the button and a body that swells with it.
   A breathing box-shadow was the first version and it was invisible — peripheral vision reads
   movement and edges, not brightness, and the player is looking at the board. */
.mini.tip.urge {
    color: #efe4ff;
    border-color: #c9a8ff;
    background: #241b33;
    animation: ct-urge .95s cubic-bezier(.35, 0, .3, 1) infinite;
}

.mini.tip.urge i {
    animation: ct-urge-dot .95s cubic-bezier(.35, 0, .3, 1) infinite;
}

@keyframes ct-urge {
    0% {
        transform: scale(1);
        box-shadow: 0 0 0 0 rgba(201, 168, 255, .75), 0 0 10px rgba(201, 168, 255, .3);
    }

    45% {
        transform: scale(1.14);
        box-shadow: 0 0 0 10px rgba(201, 168, 255, .12), 0 0 26px rgba(201, 168, 255, .85);
    }

    100% {
        transform: scale(1);
        box-shadow: 0 0 0 20px rgba(201, 168, 255, 0), 0 0 10px rgba(201, 168, 255, .3);
    }
}

@keyframes ct-urge-dot {

    0%,
    100% {
        transform: scale(1);
    }

    45% {
        transform: scale(1.3);
    }
}

/* ---------- the lead ---------- */

/* Three dots that run from the middle of the board to the hint button, once, the moment half
   the clock is gone. The button pulsing forever is the standing offer; this is the one moment
   it arrives, and a pulse with no beginning has no moment.

   It starts at the board because that is where the player is looking — the chrome is not
   somewhere anyone watches while they are solving. game.js hands it both ends in viewport
   pixels; the canvas is fixed at inset:0, so a board coordinate already IS one. */
#lead {
    position: fixed;
    z-index: 22;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}

#lead i {
    position: absolute;
    left: var(--lx, 0);
    top: var(--ly, 0);
    width: 13px;
    height: 13px;
    margin: -6.5px 0 0 -6.5px;
    border-radius: 50%;
    background: #efe4ff;
    box-shadow: 0 0 16px 5px rgba(201, 168, 255, .85);
    opacity: 0;
}

#lead.run i {
    animation: ct-lead .78s cubic-bezier(.45, 0, .35, 1) forwards;
}

#lead.run i:nth-child(2) {
    animation-delay: .1s;
}

#lead.run i:nth-child(3) {
    animation-delay: .2s;
}

@keyframes ct-lead {
    0% {
        transform: translate(0, 0) scale(.3);
        opacity: 0;
    }

    16% {
        transform: translate(calc(var(--dx) * .16), calc(var(--dy) * .16)) scale(1);
        opacity: 1;
    }

    100% {
        transform: translate(var(--dx), var(--dy)) scale(.3);
        opacity: 0;
    }
}

/* ---------- the payout flight ---------- */

/* The gears left in the tray, flying to the points badge at the win. Above the banner at 21
   and below the home link at 40, because `canvas#game` is at 0 and a token drawn there would
   pass under the tray it came out of.

   Every number the animation needs is a custom property written by payout.js: there is one
   set of durations and it lives in that file. The container has no size — the tokens are
   placed at absolute viewport coordinates measured off the slot and the badge. */
#fly {
    position: fixed;
    z-index: 21;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
    pointer-events: none;
}

#fly i {
    position: absolute;
    left: var(--x);
    top: var(--y);
    display: block;
    width: var(--w);
    height: var(--w);
    margin: calc(var(--w) / -2) 0 0 calc(var(--w) / -2);
    opacity: 0;
    filter: drop-shadow(0 0 10px rgba(77, 255, 208, .45));
    animation: gw-fly var(--t) cubic-bezier(.42, 0, .3, 1) var(--d) forwards;
}

#fly i canvas {
    display: block;
    width: 100%;
    height: 100%;
}

/* The lift at the midpoint is what stops this reading as a gear being dragged along a ruler:
   it leaves the slot, arcs over the board and drops into the badge. The rotation is the same
   idea — it is a gear, and a gear that travels without turning reads as a sticker. */
@keyframes gw-fly {
    0% {
        transform: translate(0, 0) scale(.45) rotate(0deg);
        opacity: 0;
    }

    12% {
        transform: translate(calc(var(--dx) * .12), calc(var(--dy) * .12 - 12px)) scale(1) rotate(26deg);
        opacity: 1;
    }

    55% {
        transform: translate(calc(var(--dx) * .55), calc(var(--dy) * .55 - 44px)) scale(.86) rotate(124deg);
        opacity: 1;
    }

    100% {
        transform: translate(var(--dx), var(--dy)) scale(.3) rotate(228deg);
        opacity: 0;
    }
}

/* Still pressable at zero — pressing it is how a player finds out what the points are. */
.mini.tip.broke {
    color: #5d6b7d;
    border-color: var(--frame);
}

.mini.tip.broke i {
    background: #33404f;
    color: #8fa6b8;
}

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

#ban {
    position: fixed;
    z-index: 20;
    left: 0;
    right: 0;
    top: 36%;
    text-align: center;
    pointer-events: none;
    opacity: 0;
    transition: opacity .2s;
}

#ban .card {
    display: inline-block;
    max-width: 86vw;
    padding: 16px 26px;
    border-radius: 18px;
    background: rgba(8, 12, 16, .88);
    border: 2px solid var(--frame-dim);
    box-shadow: 0 12px 44px rgba(0, 0, 0, .75);
}

#ban b {
    display: inline-block;
    font-size: clamp(22px, 7vw, 34px);
    font-weight: 800;
    letter-spacing: .18em;
    color: var(--online);
    text-shadow: 0 0 24px rgba(77, 255, 208, .6), 0 3px 0 #000;
}

#ban span {
    display: block;
    margin-top: 6px;
    font-size: clamp(13px, 3.6vw, 17px);
    letter-spacing: .1em;
    color: var(--gold);
}

/* What the clear PAID, under what it WAS. Its own row because the two are different readings
   and the row above already carries a star line, a gear count and a par. `[hidden]` collapses
   it, so a clear that paid nothing reserves no blank row. */
#ban em {
    display: block;
    margin-top: 5px;
    font-family: var(--font);
    font-style: normal;
    font-size: clamp(11px, 3vw, 14px);
    font-weight: 700;
    letter-spacing: .1em;
    color: var(--online);
}

#ban em[hidden] {
    display: none;
}

/* The failure card. Same card, same place, same shape — the only thing that changed between
   finishing a board and running out of clock is the news. */
#ban.bad b {
    color: var(--alarm);
    text-shadow: 0 0 24px rgba(255, 93, 115, .5), 0 3px 0 #000;
}

#ban.bad span {
    color: var(--dim);
}

/* The one-time "saved for offline" note. Borrows the #ban card, but sits at the bottom and
   above the intro card, because the first launch is exactly when the picker is up. */
#offline {
    position: fixed;
    z-index: 45;
    left: 0;
    right: 0;
    bottom: calc(env(safe-area-inset-bottom) + 18px);
    text-align: center;
    pointer-events: none;
    opacity: 0;
    transition: opacity .3s;
}

#offline.on {
    opacity: 1;
}

#offline .card {
    display: inline-block;
    max-width: 86vw;
    padding: 10px 20px;
    border-radius: 14px;
    background: rgba(8, 12, 16, .92);
    border: 2px solid var(--frame-dim);
    box-shadow: 0 12px 44px rgba(0, 0, 0, .75);
}

#offline b {
    display: block;
    font: 800 clamp(15px, 4.4vw, 19px) var(--font);
    letter-spacing: .18em;
    color: var(--online);
}

#offline span {
    display: block;
    margin-top: 3px;
    font: 700 clamp(11px, 3vw, 13px) var(--font);
    letter-spacing: .1em;
    color: var(--gold);
}

/* ---------- intro ---------- */

/* The one screen in this game that is not the machine, and it is styled as the odd one out
   on purpose: soft, warm and rounded where everything else is monospace and technical. The
   HUD is an instrument panel and reads like one; this is a menu, and a menu that reads like
   an instrument panel is what made the first pass feel like a settings dialog.

   Everything about the card is borrowed from Crown Rush, down to the hard-edged bottom
   shadow on the buttons that compresses when they are pressed — the two games are on the
   same shelf and the player meets this card before they meet either machine. */

#intro {
    position: fixed;
    inset: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 18px;
    background: radial-gradient(circle at 50% 40%, rgba(19, 25, 32, .86), rgba(8, 11, 15, .94) 78%);
    backdrop-filter: blur(2px);
}

#intro .card {
    width: min(90%, 380px);
    padding: 22px 20px 18px;
    text-align: center;
    border-radius: 22px;
    border: 2px solid rgba(255, 220, 140, .26);
    background: linear-gradient(180deg, rgba(31, 40, 50, .97), rgba(19, 25, 32, .97));
    box-shadow: 0 12px 40px rgba(4, 8, 12, .65);
    font-family: var(--font-round);
}

#intro .ico {
    font-size: clamp(28px, 8vw, 36px);
    line-height: 1;
}

#intro h1 {
    margin: 4px 0 0;
    font-size: clamp(23px, 7vw, 30px);
    font-weight: 900;
    letter-spacing: 1.4px;
    color: var(--gold);
    text-shadow: 0 3px 0 rgba(70, 45, 0, .5);
}

/* One sentence. The rest of what this game is, it teaches by being played — the four-line
   version of this paragraph was read by nobody and made the card feel like homework. */
#intro p {
    margin: 10px 0 0;
    font-size: clamp(12px, 3.5vw, 13.5px);
    font-weight: 700;
    line-height: 1.5;
    color: #c3d2e0;
}

#intro p b {
    color: #ffe9a8;
}

#picker {
    margin-top: 18px;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 9px;
}

/* All three wear the same gold, because all three are the primary action and none of them
   is a cancel. An arm picked out in its own colour reads as the recommended one and the
   other two read as warnings, which is three decisions where there is one.

   Each says only what it takes AWAY from the middle arm. That is the whole difference and it
   is the only thing the card has to carry: two switches spelled out per button was correct
   and unreadable. */
.pick {
    cursor: pointer;
    /* The compress on :active is the only press feedback this button has, and a blue system
       highlight sitting on top of it is louder than the thing it is drowning out. */
    -webkit-tap-highlight-color: transparent;
    padding: 11px 4px 9px;
    border: none;
    border-radius: 16px;
    font-family: var(--font-round);
    color: #3a2a08;
    background: linear-gradient(180deg, #ffe184, #ffc43d 60%, #e89c1e);
    box-shadow: 0 4px 0 #a86a10, 0 8px 18px rgba(6, 10, 14, .5);
}

/* The button has a thickness and losing it is the press. Nothing else in this game moves
   like this; nothing else in this game is a button you push rather than a machine you
   operate. */
.pick:active {
    transform: translateY(3px);
    box-shadow: 0 1px 0 #a86a10, 0 4px 10px rgba(6, 10, 14, .5);
}

.pick b {
    display: block;
    font-size: clamp(13px, 3.7vw, 15px);
    font-weight: 900;
    letter-spacing: .4px;
}

.pick span {
    display: block;
    margin-top: 2px;
    font-size: clamp(9.5px, 2.7vw, 11px);
    font-weight: 800;
    color: #7a5a12;
}

/* The arm already in force. It is not a selection — this card has no confirm step — it is
   telling a player who opened it mid-run which button hands their board back instead of
   restarting them at level 1. A ring, and no words: a badge saying CURRENT is a fourth thing
   to read on a card whose whole problem was how much there was to read. */
.pick.on {
    box-shadow: 0 0 0 3px rgba(255, 236, 180, .55), 0 4px 0 #a86a10, 0 8px 18px rgba(6, 10, 14, .5);
}

#intro small {
    display: block;
    margin-top: 13px;
    font-size: clamp(10px, 2.8vw, 11.5px);
    font-weight: 700;
    color: #7d8fa2;
}

/* The difficulty pill stays in the machine's own language, because it lives in the machine:
   it shares a column with the reset and has to read as the same kind of object. A letter
   rather than a word so the square does not have to grow, and the gold is the one thing it
   borrows from the card it opens. */
.mini.diff {
    color: var(--gold);
    border-color: #6b5726;
    font-weight: 800;
    letter-spacing: 0;
}

.mini.diff:hover {
    color: #fff0c2;
    border-color: var(--gold);
}

/* ---------- short-axis tier ---------- */

/* 560px sits above the 1042x586 QA frame on purpose: that height fits untouched, and
   anything shorter needs help. Everything below re-clamps against vh, so it keeps
   degrading as the viewport shortens rather than stepping once — and the touch target
   floor relaxes to 40px, because two 56px slots plus the hint line are a fifth of a
   390px-tall viewport and the board cannot pay that and still be readable. */
@media (max-height: 560px) {
    :root {
        --ng-top: calc(env(safe-area-inset-top) + 40px);
        --ng-bottom: calc(env(safe-area-inset-bottom) + 6px);
        --mini: clamp(36px, 11vh, 46px);
        --topbar-top: calc(var(--ng-top) + 4px);
    }

    .mute {
        top: calc(env(safe-area-inset-top) + 5px);
        width: 30px;
        height: 30px;
    }

    .mute svg {
        width: 18px;
        height: 18px;
    }

    #hud {
        top: calc(var(--ng-top) + 4px);
        line-height: 1.25;
    }

    #lvl {
        font-size: clamp(13px, 4vh, 17px);
    }

    #task {
        font-size: clamp(10px, 3vh, 12px);
    }

    #best {
        font-size: clamp(9px, 2.6vh, 11px);
    }

    #topbar {
        gap: 8px;
    }

    #clock {
        width: clamp(56px, 18vh, 80px);
    }

    #clock b {
        font-size: clamp(13px, 4vh, 17px);
    }

    #clock .bar {
        margin-top: 3px;
        height: 4px;
    }

    #status {
        font-size: clamp(10px, 2.8vh, 12px);
    }

    #hint {
        bottom: calc(var(--ng-bottom) + 18px);
        font-size: clamp(9px, 2.4vh, 11px);
    }

    #tray {
        bottom: calc(var(--ng-bottom) + 38px);
        gap: 10px;
    }

    .slot {
        width: clamp(40px, 13vh, 60px);
        height: clamp(40px, 13vh, 60px);
        border-radius: 10px;
    }

    /* 844x390: every vw clamp above is running at full desktop size exactly where the room
       is scarcest, and this line is directly between the board and the bottom edge. */
    .stack {
        gap: 3px;
    }

    .price,
    .buy {
        height: clamp(16px, 4.4vh, 21px);
        font-size: clamp(8px, 2.2vh, 11px);
    }

    .price {
        line-height: clamp(16px, 4.4vh, 21px);
    }

    .pts svg {
        width: clamp(13px, 4vh, 17px);
        height: clamp(13px, 4vh, 17px);
    }

    .pts b {
        font-size: clamp(11px, 3.2vh, 14px);
    }

    #ban em {
        font-size: clamp(10px, 2.9vh, 13px);
    }

    /* 844x390: the whole flight is only about 300px of height here, so the arc has to flatten
       or the tokens leave the top of the screen. */
    @keyframes gw-fly {
        0% {
            transform: translate(0, 0) scale(.45) rotate(0deg);
            opacity: 0;
        }

        12% {
            transform: translate(calc(var(--dx) * .12), calc(var(--dy) * .12 - 6px)) scale(1) rotate(26deg);
            opacity: 1;
        }

        55% {
            transform: translate(calc(var(--dx) * .55), calc(var(--dy) * .55 - 20px)) scale(.86) rotate(124deg);
            opacity: 1;
        }

        100% {
            transform: translate(var(--dx), var(--dy)) scale(.3) rotate(228deg);
            opacity: 0;
        }
    }

    #btns {
        bottom: calc(var(--ng-bottom) + 38px);
    }

    .mini.tip svg {
        width: clamp(16px, 5vh, 20px);
        height: clamp(16px, 5vh, 20px);
    }

    .mini.tip i {
        min-width: 15px;
        font-size: 10px;
        line-height: 15px;
    }

    #ban {
        top: 30%;
    }

    #ban .card {
        padding: 12px 22px;
    }

    #ban b {
        font-size: clamp(19px, 6.5vh, 28px);
    }

    #ban span {
        font-size: clamp(11px, 3.4vh, 14px);
    }

    /* The intro is the tallest thing in the game and this viewport is the shortest. The card
       loses its icon and tightens every step; the arms were already one row and stay one. */
    #intro .card {
        width: min(92%, 460px);
        padding: 14px 18px 12px;
    }

    #intro .ico {
        display: none;
    }

    #intro h1 {
        font-size: clamp(17px, 6vh, 22px);
    }

    #intro p {
        margin-top: 6px;
        font-size: clamp(10px, 3vh, 11.5px);
        line-height: 1.4;
    }

    #picker {
        margin-top: 11px;
        gap: 7px;
    }

    .pick {
        padding: 8px 4px 7px;
        border-radius: 13px;
    }

    .pick b {
        font-size: clamp(11px, 3.2vh, 12.5px);
    }

    .pick span {
        font-size: clamp(8.5px, 2.4vh, 9.5px);
    }

    #intro small {
        margin-top: 9px;
        font-size: clamp(8.5px, 2.4vh, 10px);
    }
}

/* ---------- landscape: the tray becomes a side rail ---------- */

/* Portrait stacks the chrome above and below the panel, which is right when the short axis is
   the horizontal one. In landscape it is exactly backwards: a tray band across the bottom and
   a level block across the top both spend the axis that is already scarce, and the panel —
   square, and therefore sized by the SMALLER of the two — collapses to a fifth of the width
   while a third of the screen sits empty on either side of it.

   So in landscape the TRAY, and only the tray, runs down the right, and the panel takes the
   column between the level block and that rail over the full height. The level block itself
   does not move: it stays in the corner it occupies in portrait, and the panel simply starts
   to the right of it instead of below it. game.js measures both edges off the DOM; nothing
   here is a breakpoint it has to be told about — `Tray.rail()` asks the computed
   flex-direction, so the stylesheet stays the only place that decides.

   7/5 rather than 1/1: a squarish tablet still has room under the board, and there the tray
   reads better where the thumbs already are. The 600px floor is the other guard: under it the
   two rails plus the clock leave a column narrower than `boardMin`, and the panel would be
   clamped back OVER the chrome it was supposed to clear. Below that the portrait stack — which
   fits a 320px-tall phone today — is simply the better layout. */
@media (orientation: landscape) and (min-aspect-ratio: 7/5) and (min-width: 600px) {
    /* The rail is centred on the viewport and the top bar is not, so the room it has is
       whatever is left after clearing that bar TWICE — once above the centre line and once
       below it, or the column stops being centred. Three stacks do not fit that on a short
       landscape phone, and they cannot be shrunk into it either: at 360px of height they
       overrun even with every clamp on its 40px touch-target floor. So the rail wraps into a
       second column instead, which costs width the board is not using — a board here is
       square and sized by the HEIGHT between the status line and the hint line, so the
       column beside it already has room to spare. It stays one column wherever one fits. */
    #tray {
        left: auto;
        right: calc(env(safe-area-inset-right) + 14px);
        bottom: auto;
        top: 50%;
        transform: translateY(-50%);
        flex-direction: column;
        flex-wrap: wrap;
        align-content: center;
        max-height: calc(100vh - 2 * (var(--topbar-top) + var(--mini) + 10px));
        gap: clamp(6px, 1.8vh, 14px);
    }

    /* Every size off vh from here down. Three stacks now have to fit the SHORT axis, and on a
       390px-tall phone the vw clamps above are all running at full desktop size. */
    .slot {
        width: clamp(42px, 11vh, 78px);
        height: clamp(42px, 11vh, 78px);
    }

    .price,
    .buy {
        height: clamp(17px, 4.4vh, 24px);
        font-size: clamp(9px, 2.4vh, 12px);
    }

    .price {
        line-height: clamp(17px, 4.4vh, 24px);
    }

    .cnt {
        font-size: clamp(10px, 2.6vh, 13px);
    }

    /* The block STAYS in the top-left corner it lives in everywhere else — only the tray
       moves. All this tier does is cap it: game.js starts the board at this block's right
       edge, so an unbounded task line would decide the layout. */
    #hud {
        max-width: min(26vw, 260px);
    }

    /* Out of the rail's way. The tray owns the middle of the right edge now, so these two go
       to the corner, and the hint line drops to the floor the panel no longer needs. */
    #btns {
        right: calc(env(safe-area-inset-right) + 14px);
        bottom: calc(var(--ng-bottom) + 6px);
        flex-direction: row;
    }

    #hint {
        bottom: calc(var(--ng-bottom) + 8px);
    }
}

/* The other end of the same problem. Every size on the intro card is a vw clamp, and all of
   them top out by about 422px of viewport width — past that the card is a flat 380x279 on a
   laptop and on a 27-inch monitor alike, while the board behind it keeps growing. It reads as
   a dialog nobody scaled.

   Fixed sizes, not clamps: this tier only runs when there is provably room for them, so a
   clamp here would have no ceiling left to find. Both bounds are required — width alone would
   catch a landscape phone on a wide short screen and undo the tier above. The two queries are
   mutually exclusive, so their order in this file does not matter. */
@media (min-width: 1000px) and (min-height: 700px) {
    #intro .card {
        width: 500px;
        padding: 30px 28px 24px;
        border-radius: 26px;
    }

    #intro .ico {
        font-size: 42px;
    }

    #intro h1 {
        font-size: 38px;
        letter-spacing: 1.8px;
    }

    #intro p {
        margin-top: 13px;
        font-size: 15.5px;
    }

    #picker {
        margin-top: 23px;
        gap: 11px;
    }

    .pick {
        padding: 14px 6px 12px;
        border-radius: 18px;
    }

    .pick b {
        font-size: 17.5px;
    }

    .pick span {
        font-size: 12.5px;
    }

    #intro small {
        margin-top: 16px;
        font-size: 12.5px;
    }
}

@media (prefers-reduced-motion: reduce) {

    .slot,
    #ban {
        transition: none;
    }

    /* All three say what they say with colour as well as with motion, so dropping the
       animation loses only the timing: the clock is already alarm-red at a quarter and the
       hint button is already lit violet against a violet ground. The lead has nothing left
       once it cannot travel, so it does not run at all. */
    #clock.danger b,
    .mini.tip.urge,
    .mini.tip.urge i,
    #lead.run i,
    .pts.pop b,
    #fly i {
        animation: none;
    }

    /* payout.js already pays the whole thing out at once rather than building a token when
       this query matches; this is the second lock, so a token can never be left parked at the
       tray with no animation to carry it off. */
    #fly {
        display: none;
    }

    .mini.tip.urge {
        box-shadow: 0 0 0 5px rgba(201, 168, 255, .2), 0 0 16px rgba(201, 168, 255, .65);
    }
}
