/* Yatzy solver.

   Layout: the page is one full-height grid. The score sheet is the only
   flexible row, so anything that appears or disappears below it (the EV panel)
   is absorbed by the sheet instead of pushing the page past the viewport.
   The sheet then sizes its own font from the height it was given via cqh
   units, which is what keeps 24 rows fitting without any vh guesswork. */

:root {
    color-scheme: light dark;

    --gap: clamp(4px, 1.1vh, 12px);
    --die: min(9vh, 12vw, 76px);

    /* Every colour in this file is a variable so the dark scheme at the bottom
       can restate it. Anything added here needs a dark counterpart, or it will
       be a light-mode value sitting on a near-black page. */

    /* Text and surfaces */
    --ink: #202124;
    --muted: #5f6368;
    --faint: #80868b;
    --hint: #bdc1c6;
    --bg: #fff;
    --shade: #f8f9fa;
    --line: #e8eaed;
    --cell: #f1f3f4;
    --rule: #dadce0;
    --shadow: #0000000d;

    /* Accents. The green is solid enough to carry white text on a button and
       still clears 7:1 as text on the dark background, so it needs no dark
       variant; only the things around it do. */
    --green: #27bb31;
    --green-hover: #219e29;
    --on-green: #fff;
    --blue: #1a73e8;
    --best-cell: #27bb3114;
    --best-glow: #27bb31b3;
    --btn-shadow: #27bb314d;
    --barred: #0000000a;
    --disabled-bg: #dadce0;

    /* Dice and roll chips. These depict objects rather than interface, so the
       two schemes show different objects rather than restyled ones: red dice
       with white pips and black chips on a light page, white dice with black
       pips and white chips on a dark one. Every value below has a dark
       counterpart -- including the rims, which have to swap from a light inset
       to a dark one once the surface underneath them turns white. */
    --die-a: #e8483c;
    --die-b: #bf2419;
    --die-core: #96170f;
    --face-rim: #ffffff2e;
    --face-shade: #00000040;
    --pip-a: #fff;
    --pip-b: #dcd6d2;
    --pip-shade: #00000038;
    --chip-a: #5c5c5c;
    --chip-b: #0d0d0d;
    --chip-rim: #ffffff33;
    --chip-shadow: #00000066;
    --chip-em: #666;

    /* Odds and ends */
    --tag-ink: #444;
    --tag-bg: #eee;
    --kbd-edge: #c8ccd0;
    --toast-bg: #202124e6;
    --toast-ink: #fff;
    --warn-edge: #f0c36d;
    --warn-bg: #fdf6e3;
    --warn-ink: #7a5b00;

    /* Read by analysis.js for the chart series, so the charts follow the
       scheme instead of being two hardcoded values in JavaScript. */
    --chart-human: #1a73e8;
    --chart-robot: #d93025;

    /* Same red as the robot series today, but a separate variable on purpose:
       one means "this cost you points", the other means "this is the robot".
       Recolouring the chart should not silently recolour the blunder table. */
    --danger: #d93025;
}

* {
    box-sizing: border-box;
}

/* The link home and the game's name, sharing the score sheet's own header row
   rather than taking a strip of the page for themselves. On a phone every row
   the sheet does not get is a row someone has to scroll for.

   Two things keep this free: it stays on ONE line, and its font is smaller than
   the Player/Robot headings, so this cell can never be what decides the header
   row's height. `white-space: nowrap` and the ellipsis are inherited from
   `#sheet th`, so an unexpectedly long name is clipped rather than wrapped --
   losing a few characters costs nothing, gaining a second line costs a row. */
#sheet th#brand {
    text-align: left;
    font-weight: 400;
    font-size: .85em;
    line-height: 1.2;
    /* Long-pressing here reveals the EV display, so suppress the text callout
       that iOS would otherwise offer instead. */
    user-select: none;
    -webkit-touch-callout: none;
}

#brand a {
    color: var(--blue);
    text-decoration: none;
}

#brand a:hover {
    text-decoration: underline;
}

#variant-name {
    color: var(--muted);
}

#variant-name::before {
    content: ' · ';
    color: var(--hint);
}

/* Confirms a reveal gesture landed, since long-pressing gives no other
   feedback. Fixed so it can never disturb the grid the page is built on. */
#toast {
    position: fixed;
    left: 50%;
    bottom: 12%;
    transform: translateX(-50%);
    padding: .5em 1em;
    border-radius: 999px;
    background: var(--toast-bg);
    color: var(--toast-ink);
    font-size: 14px;
    pointer-events: none;
    z-index: 10;
    animation: toast-fade 1.6s forwards;
}

@keyframes toast-fade {
    0% { opacity: 0; }
    15% { opacity: 1; }
    70% { opacity: 1; }
    100% { opacity: 0; }
}

body {
    height: 100dvh;
    margin: 0;
    padding: var(--gap);
    display: grid;
    /* Only the sheet's track is declared, so a hidden EV panel leaves no gap. */
    grid-template-rows: minmax(0, 1fr);
    grid-auto-rows: auto;
    gap: var(--gap);
    justify-items: center;
    overflow: hidden;
    font-family: system-ui, sans-serif;
    color: var(--ink);
    background: var(--bg);
}

body>* {
    width: 100%;
    max-width: 820px;
}

/* Held for one frame while a click skips the running animations. */
body.skip * {
    transition: none !important;
}

/* --- score sheet --------------------------------------------------------- */

#sheet-wrap {
    min-height: 0;
    container-type: size;
    border: 1px solid var(--line);
    border-radius: 12px;
    box-shadow: 0 4px 12px var(--shadow);
    overflow: auto;
}

#sheet {
    width: 100%;
    height: 100%;
    table-layout: fixed;
    border-collapse: collapse;
    /* 24 rows at ~4cqh each; the vw term keeps numbers from crowding on phones. */
    font-size: min(2.15vh, 3.4vw);     /* if container units are unsupported */
    font-size: min(2.3cqh, 3.4vw);
}

#sheet th,
#sheet td {
    padding: .15em .55em;
    border: 1px solid var(--cell);
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#sheet th {
    background: var(--shade);
    border-bottom: 2px solid var(--rule);
    font-size: 1.05em;
}

#sheet th:first-child,
#sheet td:first-child {
    width: 52%;
    text-align: left;
}

/* Category name left, dice glyphs right. The flex box has to be a child: a
   <td> set to display:flex stops being a table cell, and the browser wraps it
   in an anonymous one, which shows up as a phantom extra column. */
td.cat>div {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .4em;
}

/* text-overflow needs a block box, so it goes on the name rather than the cell. */
td.cat span {
    overflow: hidden;
    text-overflow: ellipsis;
}

td.cat b {
    flex: none;
    font-size: 1.25em;
    line-height: 1;
    letter-spacing: -.07em;
}

/* Section divider. Sits above a row, and is independent of .tally so the row
   that opens a section stays an ordinary score row. */
.rule td {
    border-top: 2px solid var(--ink);
}

/* Rows that add something up rather than being scored into. */
.tally td {
    background: var(--shade);
    font-weight: 700;
}

th.turn {
    color: var(--green);
    box-shadow: inset 0 -3px 0 var(--green);
}

/* The EV toggle is reached by gesture rather than by clicking -- see revealEV()
   in game.js. It stays in the DOM because every EV code path keys off its
   checked state, but it must never be visible. Being explicit rather than
   relying on the `hidden` attribute is deliberate: any author rule that sets
   `display` on this element would silently beat the browser's own
   [hidden] { display: none }, which is exactly how it leaked out before. */
#sheet #brand label {
    display: none;
}

td.pick {
    cursor: pointer;
    user-select: none;
    touch-action: manipulation;
}

td.pick.preview {
    color: var(--blue);
    font-style: italic;
    font-weight: 700;
}

td.pick.best {
    color: var(--green);
    background: var(--best-cell);
    font-style: normal;
    font-weight: 900;
}

/* Yahtzee's joker rule can force a roll into one particular box and close off
   the rest. Only that variant ever sets this. */
td.pick.barred {
    cursor: default;
    background: var(--barred);
}

td.bot.latest {
    color: var(--green);
    font-weight: 700;
}

/* --- solver outage ------------------------------------------------------- */

/* Only ever in the layout while the solver is unreachable; the score sheet
   gives up the height for it and takes it back on recovery. */
#offline {
    padding: .5em .8em;
    border: 1px solid var(--warn-edge);
    border-radius: 10px;
    background: var(--warn-bg);
    color: var(--warn-ink);
    font-size: clamp(.68rem, 1.55vh, .95rem);
    font-weight: 600;
    text-align: left;
}

#offline[hidden] {
    display: none;
}

/* --- roll counters ------------------------------------------------------- */

#rolls {
    display: flex;
    justify-content: space-between;
    font-size: clamp(.7rem, 1.7vh, 1.05rem);
    font-weight: 700;
}

#rolls b {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
}

/* One chip per banked roll, overlapping into a stack. Everything scales off the
   font size and the rim is an inset ring rather than a hairline border, so it
   stays smooth instead of crunching at small sizes. */
#rolls i {
    width: 1.2em;
    height: 1.2em;
    margin-left: -.42em;
    border-radius: 50%;
    background: radial-gradient(circle at 36% 30%, var(--chip-a), var(--chip-b) 72%);
    box-shadow: inset 0 0 0 .09em var(--chip-rim), 0 .05em .12em var(--chip-shadow);
}

#rolls i:first-child {
    margin-left: 0;
}

#rolls em {
    margin-left: .4em;
    font-style: normal;
    font-weight: 400;
    color: var(--chip-em);
}

/* --- dice ---------------------------------------------------------------- */

#dice {
    display: flex;
    /* The gap is the floor when space is tight; any surplus is shared out, so
       the dice spread across the row instead of huddling in the middle. */
    justify-content: space-evenly;
    gap: clamp(4px, 2.2vw, 18px);
    padding: calc(var(--die) / 3.2) 0;   /* room for the Keep / Best tags */
}

.die {
    position: relative;
    width: var(--die);
    height: var(--die);
    perspective: calc(var(--die) * 6);
    touch-action: manipulation;
    user-select: none;
    border-radius: 18%;
}

.cube {
    width: 100%;
    height: 100%;
    position: relative;
    transform-style: preserve-3d;
    /* Ease-out so the tumble decelerates onto the final face. */
    transition: transform .7s cubic-bezier(.18, .74, .3, 1);
}

.face {
    position: absolute;
    inset: 0;
    display: grid;
    grid-template: repeat(3, 1fr) / repeat(3, 1fr);
    padding: 11%;
    border-radius: 15%;
    background: linear-gradient(150deg, var(--die-a), var(--die-b));
    /* A lit top edge and a shaded bottom one, so each face reads as a surface. */
    box-shadow: inset 0 0 0 1px var(--face-rim), inset 0 -20% 24% -12% var(--face-shade);
}

/* Rounding the faces opens gaps along the cube's edges. These three planes
   through the middle sit behind them, so the gaps read as a shaded interior
   instead of a hole through the die. Never visible otherwise. */
.core {
    position: absolute;
    inset: 0;
    border-radius: 8%;
    background: var(--die-core);
}

.core.x { transform: rotateX(90deg) }
.core.y { transform: rotateY(90deg) }

.face i {
    width: 68%;
    aspect-ratio: 1;
    align-self: center;
    justify-self: center;
    border-radius: 50%;
    background: radial-gradient(circle at 38% 32%, var(--pip-a), var(--pip-b));
    box-shadow: inset 0 -1px 2px var(--pip-shade);
}

/* Pip slots, named row-then-column on the face's 3x3 grid. */
.p11 { grid-area: 1/1 }
.p13 { grid-area: 1/3 }
.p21 { grid-area: 2/1 }
.p22 { grid-area: 2/2 }
.p23 { grid-area: 2/3 }
.p31 { grid-area: 3/1 }
.p33 { grid-area: 3/3 }

/* Half the cube depth, so the visible face sits in the element's own plane. */
.f1 { transform: translateZ(calc(var(--die) / 2)) }
.f2 { transform: rotateX(-90deg) translateZ(calc(var(--die) / 2)) }
.f3 { transform: rotateY(-90deg) translateZ(calc(var(--die) / 2)) }
.f4 { transform: rotateY(90deg) translateZ(calc(var(--die) / 2)) }
.f5 { transform: rotateX(90deg) translateZ(calc(var(--die) / 2)) }
.f6 { transform: rotateY(180deg) translateZ(calc(var(--die) / 2)) }

.tag {
    position: absolute;
    left: 50%;
    translate: -50% 0;
    display: none;
    padding: 1px 5px;
    border-radius: 4px;
    font-size: max(9px, calc(var(--die) / 4.5));
    font-weight: 800;
    text-transform: uppercase;
    white-space: nowrap;
}

.keep-tag {
    bottom: -1.5em;
    color: var(--tag-ink);
    background: var(--tag-bg);
}

.best-tag {
    top: -1.5em;
    color: var(--green);
    background: var(--bg);
    border: 1.5px solid var(--green);
}

.die.keep .keep-tag,
.die.best .best-tag {
    display: block;
}

.die.best {
    box-shadow: 0 0 14px var(--best-glow);
}

/* --- EV panel ------------------------------------------------------------ */

#ev-panel {
    display: flex;
    /* Stretch, not center: the best-hold card carries an extra line for the
       dice, and centring each card independently knocks the two labels and the
       two values off each other's baselines. */
    align-items: stretch;
    gap: clamp(6px, 2vw, 16px);
    padding: .5em .9em;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--shade);
    font-size: clamp(.7rem, 1.7vh, 1rem);
}

#ev-panel[hidden] {
    display: none;
}

#ev-panel div {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
}

#ev-panel small {
    font-size: .75em;
    font-weight: 700;
    letter-spacing: .5px;
    text-transform: uppercase;
    color: var(--chip-em);
}

#ev-panel strong {
    font-size: 1.4em;
}

#cur-ev {
    color: var(--blue);
}

#best-ev,
#best-dice {
    color: var(--green);
}

/* Held open even when empty, so the panel keeps a constant height and the
   score sheet above it doesn't resize as the advice changes. */
#best-dice {
    min-height: 1.3em;
    font-size: 1.2em;
    font-weight: 700;
    line-height: 1.1;
}

#ev-panel button {
    align-self: center;
}

/* --- buttons ------------------------------------------------------------- */

button {
    border: none;
    border-radius: 10px;
    background: var(--green);
    color: var(--on-green);
    font-family: inherit;
    font-weight: 700;
    cursor: pointer;
    user-select: none;
    touch-action: manipulation;
    box-shadow: 0 3px 8px var(--btn-shadow);
    transition: background .15s, box-shadow .15s, translate .15s;
}

button:disabled {
    background: var(--disabled-bg);
    color: var(--faint);
    box-shadow: none;
    translate: none;
}

button:not(:disabled):hover {
    background: var(--green-hover);
}

button:not(:disabled):active {
    translate: 0 1px;
    box-shadow: 0 1px 3px var(--btn-shadow);
}

#best-btn {
    padding: .5em .8em;
    border-radius: 8px;
    font-size: 1em;
}

#roll {
    padding: .6em;
    font-size: clamp(1rem, 2.6vh, 1.5rem);
}

/* --- post-game analysis -------------------------------------------------- */

/* Sits over the game rather than inside its grid, and scrolls normally: unlike
   the game, this screen has no reason to fit a single viewport. */
#analysis {
    position: fixed;
    inset: 0;
    /* Overrides the 820px cap every body child gets: without this the overlay
       is only as wide as the game column and the board shows beside it. */
    max-width: none;
    z-index: 10;
    overflow-y: auto;
    background: var(--bg);
    padding: clamp(12px, 3vw, 32px);
}

#analysis[hidden] {
    display: none;
}

#analysis .sheet {
    max-width: 760px;
    margin: 0 auto;
    text-align: left;
}

#analysis h2 {
    margin: 0 0 .6em;
    font-size: clamp(1.3rem, 4.5vw, 2rem);
}

#analysis h3 {
    margin: 1.6em 0 .3em;
    font-size: clamp(.95rem, 3vw, 1.15rem);
}

#analysis .hint,
#analysis .warn,
#analysis .clean,
#analysis .reveal,
#analysis .loading {
    margin: 0 0 .6em;
    font-size: clamp(.72rem, 2.4vw, .85rem);
    line-height: 1.45;
    color: var(--muted);
}

/* The one place the hidden EV toggle is advertised. */
#analysis .reveal {
    margin-top: 1.4em;
    padding: .6em .8em;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--shade);
}

#analysis kbd {
    display: inline-block;
    min-width: 1.6em;
    padding: .1em .4em;
    border: 1px solid var(--kbd-edge);
    border-bottom-width: 2px;
    border-radius: 4px;
    background: var(--bg);
    font: inherit;
    font-weight: 700;
    text-align: center;
    color: var(--ink);
}

#analysis .warn {
    padding: .5em .7em;
    border: 1px solid var(--warn-edge);
    border-radius: 8px;
    background: var(--warn-bg);
    color: var(--warn-ink);
}

/* Player and robot summaries, side by side until there is no room. */
#analysis .cards {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

#analysis .card {
    flex: 1 1 240px;
    padding: .8em 1em;
    border: 1px solid var(--line);
    border-top: 3px solid var(--who);
    border-radius: 10px;
    background: var(--shade);
}

#analysis .card h3 {
    margin: 0;
    color: var(--who);
}

#analysis .final {
    margin: .1em 0 .5em;
    font-size: 2.2rem;
    font-weight: 800;
    line-height: 1;
}

#analysis dl {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: .18em .8em;
    margin: 0;
    font-size: clamp(.72rem, 2.4vw, .85rem);
}

#analysis dt { color: var(--muted) }
#analysis dd { margin: 0; font-weight: 700; font-variant-numeric: tabular-nums }

#analysis .identity {
    margin: .7em 0 0;
    padding-top: .5em;
    border-top: 1px solid var(--line);
    font-size: clamp(.7rem, 2.3vw, .8rem);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.chart {
    display: block;
    width: 100%;
    height: auto;
}

.chart .grid {
    stroke: var(--line);
    stroke-width: 1;
}

.chart .tick {
    fill: var(--faint);
    font: 11px system-ui, sans-serif;
}

.legend {
    display: flex;
    flex-wrap: wrap;
    gap: .3em 1.1em;
    font-size: clamp(.68rem, 2.2vw, .78rem);
    color: var(--muted);
}

.legend i {
    display: inline-block;
    width: 1.1em;
    height: .28em;
    margin-right: .4em;
    border-radius: 2px;
    vertical-align: middle;
}

.blunders {
    width: 100%;
    border-collapse: collapse;
    font-size: clamp(.72rem, 2.4vw, .85rem);
}

.blunders th,
.blunders td {
    padding: .4em .5em;
    border-bottom: 1px solid var(--line);
    text-align: left;
}

.blunders th {
    color: var(--muted);
    font-size: .9em;
    text-transform: uppercase;
    letter-spacing: .4px;
}

.blunders .cost {
    color: var(--danger);
    font-weight: 700;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

#again {
    margin: 1.8em 0 1em;
    padding: .7em 1.4em;
    font-size: 1rem;
}


/* --- dark scheme ---------------------------------------------------------- */

/* Follows the operating system; there is no in-page toggle. Only colours are
   restated -- no layout, no sizes -- so the two schemes cannot drift apart.

   The toast inverts rather than darkening: a dark pill on a dark page does not
   read as an overlay. The dice and roll chips invert too -- white dice with
   black pips, and white chips -- which is why their rim and highlight colours
   flip as well: a white inset edge is invisible on a white die. */
@media (prefers-color-scheme: dark) {
    :root {
        --ink: #e8eaed;
        --muted: #9aa0a6;
        --faint: #80868b;
        --hint: #5f6368;
        --bg: #17181a;
        --shade: #26282b;
        --line: #3c4043;
        --cell: #2b2d30;
        --rule: #5f6368;
        --shadow: #00000059;

        --best-cell: #27bb3126;
        --barred: #ffffff0a;
        --disabled-bg: #3c4043;
        --blue: #8ab4f8;

        --chip-em: #9aa0a6;
        --tag-ink: #e8eaed;
        --tag-bg: #3c4043;
        --kbd-edge: #5f6368;

        --toast-bg: #e8eaedf2;
        --toast-ink: #202124;

        --warn-edge: #8a6d1f;
        --warn-bg: #2e2716;
        --warn-ink: #f2d98c;

        --chart-human: #8ab4f8;
        --chart-robot: #f28b82;
        --danger: #f28b82;

        /* White dice, black pips. The face gradient runs bright to slightly
           grey so each face still reads as a lit surface, and the rim flips
           from a light inset to a dark hairline so the edges stay defined. */
        --die-a: #fdfdfd;
        --die-b: #d9d9d9;
        --die-core: #a8a8a8;
        --face-rim: #00000017;
        --face-shade: #00000026;
        --pip-a: #4a4a4a;
        --pip-b: #101010;
        --pip-shade: #ffffff1a;

        /* Banked-roll chips, likewise inverted. */
        --chip-a: #ffffff;
        --chip-b: #cccccc;
        --chip-rim: #00000026;
        --chip-shadow: #00000080;
    }
}
