@keyframes cell-fade-in {
    from { opacity: 0.45; }
}
@keyframes cell-shrink-out {
    to { transform: scale(0); }
}
/* Squares on the board fill in one at a time, as pieces land in them. The
   deck is left out of this: a piece there is not filled in a square at a
   time but dealt whole, with the two beside it, and comes up as part of
   that set instead (fadeInDeck in script.js). */
#game-board td.has-piece,
#game-board td.drag-shadow {
    animation: cell-fade-in 0.2s ease-out;
}

/* A manual move that completes a clear is previewed as a single event: both
   the piece in hand and every square that will leave the board brighten. */
.floating-piece {
    opacity: 0.8;
}
.floating-piece.clear-preview {
    opacity: 0.62;
}
#game-board tr:nth-of-type(n):nth-of-type(n) td.shrinking-piece:nth-of-type(n):nth-of-type(n) {
    background: rgb(54, 112, 232);
    border: 1px solid black;
    animation: cell-shrink-out 0.2s ease-in forwards;
}

/* A clear pays far more than the squares the piece put down, and the
   score line above the board is too far from the move to connect the
   two. The card says what the move was worth where it was earned: it
   pops up over the piece that landed, then drifts off the board.

   Fixed to the page rather than drawn in a cell, so it can float clear
   of the square it came from instead of being clipped by it, and under
   the piece being dragged so it can never hide what is in hand.

   A move that came from a combo or a streak stacks those under the
   points inside this one card, so the whole of what the move was worth
   rises off the board together. */
.score-card {
    position: fixed;
    z-index: 900;
    pointer-events: none;
    background: white;
    border-radius: 8px;
    box-shadow: 0 3px 12px rgba(0, 0, 0, 0.25);
    color: rgb(54, 112, 232);
    font-family: roboto;
    font-weight: bold;
    font-size: min(5vw, 3.3vh);
    line-height: 1;
    padding: min(2vw, 1.33vh) min(3.5vw, 2.33vh);
    white-space: nowrap;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35em;
    animation: score-card-float 1.25s ease-out forwards;
}

/* Smaller than the number they explain: the points are what the move
   paid, and a combo or a streak is why it paid that much. Kept to the
   card's own colour, which small text needs more of than the number
   above it does. */
.score-card-bonus {
    font-size: 0.8em;
}

/* Placed by its middle, so it sits over the piece whichever side of the
   board that is. It pops up, then sits still and full strength for a
   beat -- long enough to be read where it was earned, since a card that
   starts leaving the moment it has settled is read on its way out or not
   at all -- and only then rises about a block and a half: far enough to
   read as leaving, short enough to stay over the board it came from. */
@keyframes score-card-float {
    0% { transform: translate(-50%, -50%) scale(0.6); opacity: 0; }
    9% { transform: translate(-50%, -50%) scale(1.08); opacity: 1; }
    20% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
    48% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
    100% { transform: translate(-50%, calc(-50% - min(15vw, 10vh))) scale(1); opacity: 0; }
}

/* The column is sized off whichever axis is tighter, so on a page
   taller than it needs there is height left over. Left to itself that
   spare all collects under the deck, with the score pinned to the top
   of the screen -- worst on a phone, where the width binds long before
   the height does. Centring the column hands half of it back above the
   score.

   The padding is what the margin used to be, kept so the column still
   has room around it on a page with nothing to spare, and inside the
   height so it doesn't add a scrollbar to one that has. Small viewport
   units where they are understood: the toolbar is up when the page is
   opened, and a column centred against the taller viewport would sit
   low until it goes away. */
body {
    margin: 0;
    padding: 8px 0;
    box-sizing: border-box;
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    /* Safe where it is understood, so a column with nothing to spare
       overflows downwards, where it can be scrolled to, rather than
       splitting the overflow across a top edge that cannot be. */
    justify-content: center;
    justify-content: safe center;
}

/* Size the whole game from the same constrained column. In particular,
   the score and settings control should grow and shrink with the board,
   rather than looking fixed while the rest of the game changes size. */
:root {
    --column: min(90vw, calc(90vh / (3/2)));
    --score-size: calc(var(--column) / 12);
    --score-line: calc(var(--score-size) * 1.2);
    --gear-size: calc(var(--column) / 15);
    --gear-gap: calc(var(--column) / 45);
    --board-outline: 4px;

    /* What the page has to spare under the deck. Everything else on it
       is the score (up to 5vh of type over a 2vh margin), the picker (6.5vh
       at its tallest), the board and the deck band (a column and a
       third of one), and the body's own padding with a little over, so
       the page keeps a few pixels rather than spending its last one.
       The picker is shorter than 6.5vh on a narrow page, so where this
       is wrong it is wrong towards leaving room. */
    --spare-height: max(0px, calc(85.75vh - 1.334 * var(--column) - 24px));

    /* A 5-tall piece fills its slot top to bottom, so without room
       around the band it sits on the board's outline above and the
       picker below. Half a block of clearance either side is enough to
       part it from both: the board runs the full width of the column,
       but the picker is only about a slot wide in the middle of the
       page, so the outer two pieces have next to nothing under them.

       Half either side comes to a cell's worth of clearance less the
       two margins, so with a slot W/3 square and a cell (W/3 - 2m)/5
       wide, the spare height caps the cell at (W/3 + spare)/6. Under
       that cap a cell is the size it always was and the margin works
       out at the 3px it always was; over it the pieces make up the
       difference, and where there is nothing spare at all the margin
       is exactly the half block on its own. */
    --deck-cell: min(calc((var(--column) / 3 - 6px) / 5),
                     calc((var(--column) / 3 + var(--spare-height)) / 6));
    --deck-margin: calc((var(--column) / 3 - 5 * var(--deck-cell)) / 2);
}

#page-description {
    text-align: center;
}

#score {
    text-align: center;
    font-family: roboto;
    font-size: var(--score-size);
    line-height: var(--score-line);
    font-weight: bold;
    margin-bottom: 2vh;
}

#for-aspect-ratio {
    aspect-ratio: 2/3;
    width: var(--column);
    /* Across only: an auto margin down the block axis would eat the
       spare height itself and take the centring off justify-content,
       losing the safety with it. */
    margin: 0 auto;
}

#game-table-container {
    aspect-ratio: 1 / 1;
    /* The game-over card is placed against this box, so it covers the
       board and stops short of the pieces below it. */
    position: relative;
}

#game-board {
    border-collapse: collapse;
    background: white;
    outline: var(--board-outline) solid rgb(180, 185, 190);
    margin: auto;
    width: 100%;
    height: 100%;
}

#game-board tr td {
    width: 10%;
    height: 10%;
    border: 1px solid rgb(180, 185, 190);
}

#game-board tr:nth-of-type(-n+3) td:nth-child(n+4):nth-child(-n+6),
#game-board tr:nth-child(n+4):nth-child(-n+6) td:nth-child(-n+3),
#game-board tr:nth-child(n+4):nth-child(-n+6) td:nth-child(n+7):nth-child(-n+9),
#game-board tr:nth-child(n+7):nth-child(-n+9) td:nth-child(n+4):nth-child(-n+6) {
    background: rgb(228, 233, 238)
}

#game-board tr:nth-of-type(n):nth-of-type(n) td.has-piece:nth-of-type(n):nth-of-type(n) {
    background: rgb(54, 112, 232);
    border: 2px solid black;
}

#game-board tr:nth-of-type(n):nth-of-type(n) td.clear-preview:nth-of-type(n):nth-of-type(n) {
    background: rgb(105, 148, 237);
    border: 2px solid rgba(0, 0, 0, 0.65);
}

#game-board tr:nth-of-type(n):nth-of-type(n) td.drag-shadow:nth-of-type(n):nth-of-type(n) {
    background: rgba(128, 128, 128, 0.45);
}

@media(hover: hover) and (pointer: fine) {
    table.pieces-on-deck tr td.has-piece {
        cursor: grab;
    }
}

#pieces-on-deck-container {
    display: flex;
    margin-left: auto;
    margin-right: auto;
    border-collapse: collapse;
    aspect-ratio: 3 / 1;
    width: 100%;
    /* Outside the ratio, which governs the content box, so the slots
       keep their size and the page carries the padding. */
    padding: calc(var(--deck-cell) / 2 - var(--deck-margin)) 0;
}

table.pieces-on-deck {
    border-collapse: collapse;
    border-spacing: 0;
    /* Whatever is left of the slot once its five cells are in it. The
       margin stays uniform, since equal margins take the same amount
       off both axes and keep the deck cells square. */
    margin: var(--deck-margin);
}

table.pieces-on-deck td,
table.pieces-on-deck th {
    padding: 0;
}

table.pieces-on-deck {
    width: 33%
}

table.pieces-on-deck tr td {
    width: 20%;
    height: 20%;
}

table.pieces-on-deck tr td.has-piece {
    background: rgb(54, 112, 232);
    border: 1px solid black;
}

/* Nothing on deck fits anywhere. The board stays readable under a wash
   so the position that ended it can still be studied, and the card is
   the only thing left to press. */
#game-over {
    position: absolute;
    /* Square, pinned to the top of the container: that is exactly the
       board. The container itself is taller than its 1/1 ratio, since
       the pieces on deck hang below the board inside it, and a wash
       drawn over those would fight the grey they are already given. */
    top: 0;
    left: 0;
    right: 0;
    aspect-ratio: 1 / 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.72);
}

/* Beats the display above, which would otherwise keep it on screen. */
#game-over[hidden] {
    display: none;
}

#game-over-card {
    background: white;
    border: 2px solid rgb(54, 112, 232);
    border-radius: 10px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
    padding: min(4vw, 2.7vh) min(6vw, 4vh);
    text-align: center;
    font-family: roboto;
}

#game-over-card h2 {
    margin: 0 0 0.2em;
    font-size: min(7vw, 4.6vh);
    color: #222;
}

#game-over-summary {
    margin: 0 0 0.7em;
    font-size: min(4vw, 2.7vh);
    color: #555;
}

#game-over-summary b {
    color: #222;
    font-size: min(5.5vw, 3.7vh);
}

#new-game {
    background: rgb(54, 112, 232);
    border: none;
    border-radius: 6px;
    color: white;
    cursor: pointer;
    font-family: roboto;
    font-size: min(4.5vw, 3vh);
    font-weight: bold;
    padding: min(2.4vw, 1.6vh) min(6vw, 4vh);
}

/* The pieces are still on deck, and that is the point: draining them
   shows they are out of moves rather than waiting to be dragged. */
body.game-over #pieces-on-deck-container {
    filter: grayscale(1);
    opacity: 0.45;
}

/* Sparkles is a colour emoji, drawn gold everywhere, and gold washes out
   on the tint the picker wears while the assist is playing. This is the
   same glyph from Noto Emoji -- the monochrome family -- subset to that
   one codepoint and inlined so it costs no request. Being an outline
   font it takes the element's own colour, so it tracks both states.
   unicode-range keeps it off everything else, the gamepad on Manual
   included, and if it ever fails to load the colour emoji is still
   underneath it.
   Noto Emoji, SIL Open Font License 1.1:
   https://github.com/googlefonts/noto-emoji */
@font-face {
    font-family: 'sparkle';
    font-style: normal;
    font-weight: 700;
    src: url(data:font/woff2;base64,d09GMgABAAAAAAPAAA8AAAAABqQAAANpAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHB4GYD9TVEFUKgBpEQgKhAiEOQsMAAE2AiQDFAQgBYMSByAXJBgMG8EFyMSHr9Pen8wEPzWmTlNQU0q7ewN97B5hnajk4FjqP1UDZThW3EcEBUUUPj54FL32KsFGOqBNBzgF81rK1wQdqkAAUFiIAyQ2XsiBFCyYQ612bwFLHZklcADnw61XXMRK3iO6hzpGT0ObY4iIMKR2AhAxUa8PWXTQAx9MSE9PAA9epS1pUWkAkhbyDZhJAbKkBSkeMhAO0hIwcPMtwPTXN/Q0MTEtk4RlljqYRTYyCLh2IKr1B0FLgOXIRlXkMoSRIHBSrMTgKdNSSMFMmHRSqLQ3pgwRWiXqF/cfk2+LUvXIELUaNAbOVcIgFfpTcJOUFsC6Zib1LjHL2luYWJjw8mHdwTZtmdZg0V2DkZdH8u8x8ovlhXcYubnIuZ2fU6CXd/fuzdr6a/fy8+W5d+5cqclcv52Tk9cBSH+gTQE9p4bzRlS3146mgvCtewnk7N2bvNdvx+LMnRszg/fujaSVWqmQ/+EGcAMm4AZOp9Y8rkF1VJnC2HiHz72U2R7hiOeoyRarNVusRk2OePYIKbM+94x3KIzX1ZLvGu/8/Dv7PYZVNnWfonQPemw46BGlp7apM6yy37OljK6O0sPrfvr1SWe0S50GI4V69wxdmqFb7x4pOA1ql1LxVVz3D7MZrVQ+M1Ep6JQYrCgxEHQTnfksLcFsZo3ZjLZgnZXknKKryqToJCmtM7VTf3TcFnmSVbky005FVZl2SqtyeRLN/f0hK//pjSmXghbTK9pi13hXGdEqozXebbHpFUGLyiW9MVqjfTwPAiKS+Pv7hwvl2qHfpXJpUeLJHb96/+Trt61z4FRZ1E7i+PItTA/1V9JAncGBo3boNDZxovdooIpMkipQNNN+TnYUVWQB+k9Y/UCV4wYoxr4SOyujYW+8jNkEBrgqR1iKs2CL4I1VbBZW6GNzMEMJWwwLRLK04AsNO4EwA8COdyoftWhEPRrQY2f3oAPB8BCHulGNLjSiAz3U4K7K8dYWuKMdXaiHB9IRDwFpaEcP/HFolR9pQiPcEI12tKAGeahFV5+X2tEGHj5whyc84ScmeaD4jjbfPQQJSEc6EhAyi9bvO6YhBsEODO5Dvce7eHjDE17wAY8EtNPT9WhBLXgIEBDz/1KwuexjTMPwEJ1XsJsC3T/dZGM3KkcuoQqmQHLddOAP) format('woff2');
    unicode-range: U+2728;
}

#assist-container {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1.5vw;
    min-height: 4vh;
}

#ai-assist {
    background: #f0f0f0;
    border: 1px solid #ddd;
    border-radius: 6px;
    color: #444;
    /* A board cell is min(10vw, 6.67vh); size off the same min() so the
       control stays about a block tall whichever axis is binding. */
    padding: min(2vw, 1.33vh) min(3vw, 2vh);
    cursor: pointer;
    font-family: 'sparkle', roboto;
    font-size: min(4.5vw, 3vh);
    font-weight: bold;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

/* Native popups ignore this on some platforms; harmless where they do. */
#ai-assist option {
    font-weight: bold;
}

/* Tinted while the assist is playing, plain while you are. */
#ai-assist.assist-on {
    background: #e8eefc;
    border-color: rgb(54, 112, 232);
    color: #222;
}

/* Settings belong to the screen rather than to the game column. Keeping
   this fixed in the viewport's top-right corner breaks its former visual
   alignment with the score and leaves it in a predictable place at every
   aspect ratio. The button padding provides the inset and touch target;
   safe-area insets keep both usable around a phone's display cutouts. */
#settings {
    position: fixed;
    top: env(safe-area-inset-top, 0px);
    right: env(safe-area-inset-right, 0px);
    font-family: roboto;
    /* Above the page, below the piece being dragged. */
    z-index: 10;
}

/* No chrome of its own: the gear is the whole control. */
#settings-button {
    display: block;
    background: none;
    border: none;
    color: #444;
    cursor: pointer;
    padding: var(--gear-gap);
    transition: color 0.15s;
}

#settings-button svg {
    display: block;
    width: var(--gear-size);
    height: var(--gear-size);
    fill: currentColor;
}

#settings-menu {
    position: absolute;
    /* Clear of the button's padding on both axes, so it hangs off the
       gear itself: below it, and opening onto the board rather than
       off the side of the page. */
    top: 100%;
    right: var(--gear-gap);
    display: flex;
    flex-direction: column;
    gap: calc(var(--gear-gap) / 4);
    background: white;
    border: 1px solid #ddd;
    border-radius: 6px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
    padding: calc(var(--gear-gap) / 2);
}

/* display: flex above would otherwise show the menu while it is shut. */
#settings-menu[hidden] {
    display: none;
}

#settings-menu button,
#settings-menu a {
    display: flex;
    align-items: center;
    gap: var(--gear-gap);
    background: none;
    border: none;
    border-radius: 4px;
    color: #444;
    cursor: pointer;
    font-family: inherit;
    font-size: min(3.5vw, 2.33vh);
    padding: calc(var(--gear-gap) * 0.75) var(--gear-gap);
    text-align: left;
    text-decoration: none;
    white-space: nowrap;
}

/* A column of its own, so the labels line up whatever the icons are. */
#settings-menu .menu-icon {
    width: var(--gear-size);
    text-align: center;
}

/* The tint the picker wears while the assist plays, for the same
   reason: it marks the setting that is currently on. */
#sound.sound-on {
    background: #e8eefc;
    color: #222;
}

/* Hover left on a touch screen sticks to whatever was tapped last. */
@media(hover: hover) and (pointer: fine) {
    #settings-button:hover {
        color: #222;
    }

    #settings-menu button:hover,
    #settings-menu a:hover {
        background: #f0f0f0;
    }

    #sound.sound-on:hover {
        background: #dbe5fa;
    }
}

/* The label swaps to a question and back. The question is carried here
   too, laid out but never drawn, so the widest of the two is what sets
   the width and the menu doesn't breathe under the pointer. */
#restart-label::after {
    content: 'Start over?';
    display: block;
    height: 0;
    overflow: hidden;
    visibility: hidden;
}
