/* =============================================================================
   YachtSea

   The screen splits at a waterline. Above it is chart paper - the scorecard,
   where you record. Below it is open sea - the dice tray, where you roll. That
   split is not decoration: it is also the correct mobile layout, because the two
   things a thumb needs are the dice and the roll button, and they belong pinned
   to the bottom of the screen where the thumb already is.

   Palette is an Admiralty chart. Buff paper, engraving ink, brand teal for the
   water, and brass for the one warm accent - instrument bezels, the roll button,
   a bonus earned. Nothing else is warm, which is what makes the brass read as
   significant when it appears.
   ============================================================================= */

:root {
    /* Sea */
    --abyss:      #06232E;
    --deep:       #0A3A48;
    --sea:        #0E5C6B;
    --foam:       #8FD4D9;

    /* Chart */
    --chart:      #F2E8D5;
    --chart-edge: #E0D2B6;
    --shoal:      #CFE4E4;

    /* Ink */
    --ink:        #10333F;
    --ink-soft:   #5B7C87;
    --ink-faint:  #9DAFB6;

    /* Brass - the only warm value in the palette */
    --brass:      #C68A2E;
    --brass-lit:  #E8B457;

    --danger:     #B4472E;

    --display: "Bodoni Moda", "Didot", Georgia, serif;
    --ui:      "Barlow Semi Condensed", "Arial Narrow", system-ui, sans-serif;
    --mono:    "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;

    /* Dice geometry. Every measurement in the tray derives from these three, so
       scaling the dice up on a wide screen is a one-line change. */
    --die:  54px;
    --lift: calc(var(--die) * 0.6);
    --tray-pad-top: 26px;
    /* 34, not 14: the dice row sat close enough to the bottom edge that on a
       phone it fought the home indicator and the browser's own chrome. The
       extra 20px is clearance for a thumb, not styling. --tray-h derives from
       this, so the sea grows with it and the waterline does not move. */
    --tray-pad-bottom: 34px;

    /* A resting die sits in the slot's BOTTOM, so its top edge is one lift below
       the slot's top. The waterline is derived from that rather than typed in,
       because the whole hold affordance depends on two facts being true at once:
       a resting die is cut by the line, and a lifted die clears it completely.
       Hand-tuned numbers stop being true the moment --die changes. */
    --die-top: calc(var(--tray-pad-top) + var(--lift));

    /* A resting die is dipped, not sunk, and the depth is measured against the
       pips rather than chosen by eye.

       A face is 13% padding around a 3x3 grid, so on a 54px die the bottom row
       of pips spans roughly 35.5px to 45.1px below the die's top edge. At 0.74
       the line fell at 39.96px - straight through the middle of that row, which
       is why 4, 5 and 6 read worse than 1, 2 and 3: the faces with a bottom row
       were the only ones losing pips to the tint.

       0.88 puts the line at 47.5px, clearing the lowest pip by about 2px while
       still cutting 6.5px off the die, so it stays visibly in the water. The
       lift (0.6 x die = 32.4px) is far more than that, so a held die still
       clears the surface completely and the hold affordance is intact. Both
       hold at the 66px breakpoint. Re-derive these if --die changes. */
    --waterline: calc(var(--die-top) + var(--die) * 0.88);

    --tray-h: calc(var(--tray-pad-top) + var(--die) + var(--lift) + var(--tray-pad-bottom));
    --sea-h:  calc(var(--tray-h) + 68px);

    --radius: 10px;

    /* The status bar strip, and the only defense against the Dynamic Island.
       env() IS the dynamic check - it resolves to 0 in a Safari tab, where iOS
       draws its own chrome above the page, and to the real inset once the app
       is installed to the home screen. No user-agent sniffing, no JS, and no
       device list to keep current as Apple changes the cutout.

       Two things have to be true for it to report anything: the viewport meta
       carries viewport-fit=cover, and the head declares
       apple-mobile-web-app-status-bar-style: black-translucent. Both are set.
       That pair is what puts the page under the status bar in the first place -
       the immersive look is the whole point, and this token is the rent. Drop
       either one and this silently becomes 0 everywhere, which looks correct in
       every browser and wrong on exactly the device that matters.

       The 0px fallback is load-bearing: a bare env() with no fallback
       invalidates the whole declaration where it is unsupported, so calc()
       chains built on it would collapse rather than degrade. */
    --safe-top: env(safe-area-inset-top, 0px);
}

@media (min-width: 720px) {
    :root {
        --die: 66px;
        --tray-pad-top: 30px;
    }
}

/* -----------------------------------------------------------------------------
   Reset
   -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

/* The `hidden` attribute is only `display: none` in the user-agent stylesheet,
   so ANY author rule that sets display beats it. Several things here are hidden
   by attribute and laid out with grid or flex when shown - without this the
   finish overlay is on screen from the first paint. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    min-height: 100dvh;
    font-family: var(--ui);
    font-size: 16px;
    line-height: 1.45;
    color: var(--chart);

    /* The whole page is under water; the chart floats on it. */
    background:
        radial-gradient(120% 80% at 50% 0%, #12586A 0%, transparent 60%),
        linear-gradient(180deg, var(--deep) 0%, var(--abyss) 60%);
    background-color: var(--abyss);
    background-attachment: fixed;
}

h1, h2, h3, p, dl, dd, figure { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; }
img { max-width: 100%; height: auto; display: block; }

.skip-link {
    position: absolute;
    left: -9999px;
    top: var(--safe-top);
    background: var(--chart);
    color: var(--ink);
    padding: .6rem 1rem;
    z-index: 100;
}
.skip-link:focus { left: 0; }

/* Frosted bar across the status-bar strip.

   Without it the safe-area padding only fixes the resting position: the page
   still scrolls underneath, so the scorecard slides behind the clock and the
   camera cutout and stays legible through them. Verified against a simulated
   59px inset - "Total of all 2s" was readable through the Dynamic Island.

   Frosted rather than a flat fill because the page background is a radial
   gradient, so any single color matches at the center of the screen and banks
   against it at the edges. Blur takes whatever is behind and diffuses it, which
   is also how .tally already treats the same problem.

   Height collapses to 0 off-device, where --safe-top is 0, and a 0-height
   backdrop-filter costs nothing. z-index 95 puts it over the finish overlay
   (60) and under the skip link (100), which has to stay reachable. */
body::before {
    content: "";
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--safe-top);
    z-index: 95;
    background: rgba(6, 35, 46, .55);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    pointer-events: none;
}

:where(button, a, input):focus-visible {
    outline: 2px solid var(--brass-lit);
    outline-offset: 2px;
}

/* =============================================================================
   Shell
   ============================================================================= */

/* Pushes every page below the status bar. Sits on .main rather than on .helm
   and .page separately, so a new page template cannot forget it. */
.main {
    min-height: 100dvh;
    padding-top: var(--safe-top);
}

.helm {
    max-width: 560px;
    margin: 0 auto;
    padding: 0 12px;
    /* Clear the fixed sea, plus the iOS home indicator. */
    padding-bottom: calc(var(--sea-h) + env(safe-area-inset-bottom) + 20px);
}

/* -----------------------------------------------------------------------------
   Masthead
   -------------------------------------------------------------------------- */

.masthead {
    display: flex;
    /* The mark is an image, so its baseline is its bottom edge. flex-end lines
       the round counter and the log link up with the waterline in the lockup
       rather than with the bottom of the transparent plate. */
    align-items: flex-end;
    gap: 12px;
    padding: 18px 4px 10px;
}

.masthead__mark {
    margin: 0 auto 0 0;
    line-height: 0;
}

.masthead__logo {
    display: block;
    /* Height-driven, not width-driven: the round counter beside it is set in a
       fixed size, and matching the lockup to that keeps the two in proportion
       from a narrow phone up. No max-width - with an explicit height, capping
       the width squashes the artwork instead of scaling it. */
    height: clamp(38px, 11vw, 54px);
    width: auto;
}

.masthead__round {
    font-size: .78rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--foam);
    white-space: nowrap;
}
.masthead__round-n { font-family: var(--mono); font-weight: 600; color: var(--chart); }
.masthead__round-of { color: var(--ink-faint); }

.masthead__log {
    font-size: .78rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--brass-lit);
    text-decoration: none;
    border-bottom: 1px solid rgba(232, 180, 87, .35);
    padding-bottom: 1px;
}
.masthead__log:hover { border-bottom-color: var(--brass-lit); }

/* -----------------------------------------------------------------------------
   Tally - the running total, sticky at the top

   Sticky because the number you are playing for should never be something you
   have to scroll to find.
   -------------------------------------------------------------------------- */

.tally {
    position: sticky;
    /* Not 0. Sticky pins to the scrollport, not to the padded .main above it,
       so top: 0 parks the running total directly under the clock and the
       camera cutout the moment the page scrolls. */
    top: var(--safe-top);
    z-index: 20;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    margin-bottom: 14px;
    border-radius: var(--radius);
    background: linear-gradient(180deg, rgba(6, 35, 46, .96), rgba(6, 35, 46, .88));
    backdrop-filter: blur(8px);
    border: 1px solid rgba(143, 212, 217, .18);
    border-top: 2px solid var(--brass);
}

.tally__main { display: flex; align-items: baseline; gap: 8px; }

.tally__score {
    font-family: var(--display);
    font-weight: 700;
    font-size: 2.1rem;
    line-height: 1;
    color: var(--chart);
    font-variant-numeric: tabular-nums;
}

.tally__unit {
    font-size: .7rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

.tally__meta {
    font-size: .82rem;
    color: var(--foam);
    text-align: right;
    text-wrap: balance;
}
.tally__meta.is-hot { color: var(--brass-lit); font-weight: 600; }

/* =============================================================================
   The chart - scorecard on paper
   ============================================================================= */

.chart { display: grid; gap: 14px; }

@media (min-width: 860px) {
    .helm { max-width: 900px; }
    .chart { grid-template-columns: 1fr 1fr; align-items: start; }
}

.chart__section {
    background: var(--chart);
    color: var(--ink);
    border-radius: var(--radius);
    padding: 12px 12px 14px;

    /* Paper, not a card: a warm edge and a soft drop rather than a hard border,
       so it reads as something resting on the water. */
    box-shadow:
        0 1px 0 rgba(255, 255, 255, .5) inset,
        0 10px 30px rgba(3, 18, 24, .45);
}

.chart__title {
    font-family: var(--display);
    font-size: .95rem;
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink-soft);
    padding: 0 4px 8px;
    border-bottom: 1px solid var(--chart-edge);
    margin-bottom: 4px;
}

/* -----------------------------------------------------------------------------
   Score boxes
   -------------------------------------------------------------------------- */

.boxes__item + .boxes__item .box { border-top: 1px solid var(--chart-edge); }

.box {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    min-height: 46px;            /* comfortable thumb target */
    padding: 6px 6px 6px 8px;
    background: none;
    border: 0;
    border-radius: 6px;
    text-align: left;
    cursor: pointer;
    transition: background-color .14s ease, transform .1s ease;
}

.box__label { display: flex; flex-direction: column; gap: 1px; min-width: 0; }

.box__name {
    font-size: 1rem;
    font-weight: 500;
    color: var(--ink);
    line-height: 1.15;
}

.box__hint {
    font-size: .74rem;
    color: var(--ink-soft);
    letter-spacing: .01em;
}

.box__value {
    flex: 0 0 auto;
    min-width: 3.1ch;
    font-family: var(--mono);
    font-size: 1.05rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: right;
    color: var(--ink);
}

/* An open box is a sounding dot on a chart, not an empty cell. */
.box__open { color: var(--ink-faint); font-weight: 400; }

/* Available: this box would take the current roll. */
.box.is-available { background: rgba(14, 92, 107, .07); }
.box.is-available:hover { background: rgba(14, 92, 107, .13); }
.box.is-available:active { transform: scale(.985); }
.box.is-available .box__value { color: var(--sea); }

/* Worth nothing, but still legal - the sacrifice box. Shown, never hidden: part
   of playing well is knowing what a zero costs you.

   Muted rather than red. On a mediocre roll six boxes at once would pay nothing,
   and six red zeros reads as six errors when it is really just the ordinary
   state of a scorecard mid-game. The digit already says what it says. */
.box.is-available.is-zero .box__value { color: var(--ink-faint); }
.box.is-available.is-zero { background: none; }
.box.is-available.is-zero:hover { background: rgba(14, 92, 107, .08); }

/* Already scored. */
.box.is-taken { cursor: default; }
.box.is-taken .box__name,
.box.is-taken .box__hint { color: var(--ink-faint); }
.box.is-taken .box__value { color: var(--ink); }
.box.is-taken .box__hint { text-decoration: line-through; text-decoration-color: var(--chart-edge); }

.box:disabled { cursor: default; }

/* The commit. A short, sharp print rather than a long flourish - it fires
   thirteen times a game and anything slower would start to cost time. */
@keyframes ink-set {
    0%   { transform: scale(1.5); opacity: 0; }
    55%  { transform: scale(.94); opacity: 1; }
    100% { transform: scale(1); opacity: 1; }
}
.box.just-scored .box__value { animation: ink-set .34s cubic-bezier(.2, .9, .3, 1); }

/* A joker forcing the placement. Brass, because it is a windfall. */
.box.is-forced {
    background: rgba(198, 138, 46, .14);
    box-shadow: inset 0 0 0 1px rgba(198, 138, 46, .5);
}

/* -----------------------------------------------------------------------------
   The sounding gauge

   The 63-point upper bonus is the one piece of strategy a new player never
   discovers on their own, because a paper scorecard gives no sense of
   approaching it. Drawn as a depth scale with the threshold marked, it teaches
   the rule without a tutorial.
   -------------------------------------------------------------------------- */

.sounding {
    margin: 10px 4px 0;
    padding-top: 10px;
    border-top: 1px solid var(--chart-edge);
}

.sounding__head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 6px;
}

.sounding__label {
    font-size: .74rem;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.sounding__read {
    font-family: var(--mono);
    font-size: .8rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

.sounding__track {
    position: relative;
    height: 12px;
    border-radius: 3px;
    background: var(--shoal);
    overflow: hidden;

    /* Depth ticks, every 1/12 of the run to 63. Drawn in the background rather
       than as elements: twelve empty divs would be twelve lies about the
       document's structure. */
    background-image: repeating-linear-gradient(
        90deg,
        rgba(16, 51, 63, .18) 0 1px,
        transparent 1px calc(100% / 12)
    );
}

.sounding__fill {
    height: 100%;
    background: linear-gradient(90deg, var(--sea), #1B7F91);
    transition: width .45s cubic-bezier(.2, .8, .3, 1);
}

.sounding__mark {
    position: absolute;
    inset: 0 0 0 auto;
    width: 2px;
    background: var(--ink);
}

.sounding__note {
    margin-top: 5px;
    font-size: .74rem;
    color: var(--ink-soft);
}

/* Crossed. The whole instrument goes brass at once. */
.sounding.is-earned .sounding__fill { background: linear-gradient(90deg, var(--brass), var(--brass-lit)); }
.sounding.is-earned .sounding__note { color: var(--brass); font-weight: 600; }
.sounding.is-earned .sounding__read { color: var(--brass); font-weight: 600; }

.extras { margin: 8px 4px 0; }

.extras__row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding-top: 9px;
    border-top: 1px solid var(--chart-edge);
    font-size: .84rem;
    color: var(--brass);
    font-weight: 600;
}

.extras__value { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* =============================================================================
   The sea
   ============================================================================= */

.sea {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 30;
    padding-bottom: env(safe-area-inset-bottom);

    /* Four stops doing three jobs. The first pair dissolves the chart scrolling
       underneath into the panel instead of cutting it off with a hard edge. The
       pair either side of --waterline is a hard color change with no transition
       between them, which is what makes the surface a surface: night air above,
       lit water below. Anything softer there and the wave graphic looks like it
       is floating on a gradient rather than sitting on top of the water. */
    background: linear-gradient(
        180deg,
        rgba(6, 35, 46, 0)   0px,
        rgba(6, 35, 46, .94) 22px,
        #05202A              48px,
        #05202A              var(--waterline),
        #0C4E5E              var(--waterline),
        var(--abyss)         100%
    );
}

.tray {
    position: relative;
    padding: var(--tray-pad-top) 12px var(--tray-pad-bottom);
}

.tray__dice {
    max-width: 560px;
    margin: 0 auto;
    display: flex;
    justify-content: center;
    gap: clamp(6px, 2.4vw, 14px);
}

/* --- the waterline ---------------------------------------------------------

   One line, drawn once. Above it the dice are in the air; below it they are in
   the water. Held dice are raised past it, which is the whole hold affordance.
*/

.tray__surface {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--waterline);
    height: 20px;
    margin-top: -10px;
    z-index: 2;
    pointer-events: none;
    overflow: hidden;

    /* A hairline of lit foam along the crest. Without it the wave is a shape;
       with it, it is a surface catching the light. */
    filter: drop-shadow(0 -1px 0 rgba(143, 212, 217, .45));
}

.tray__wave {
    position: absolute;
    inset: 0;
    width: 200%;
    height: 100%;
    fill: rgba(29, 133, 150, .9);
    animation: drift 9s linear infinite;
}

.tray__wave--back {
    fill: rgba(10, 58, 72, .6);
    animation-duration: 14s;
    animation-direction: reverse;
    opacity: .9;
}

@keyframes drift {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}

/* The body of water in front of the resting dice. */
.tray__veil {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--waterline);
    bottom: 0;
    z-index: 2;
    pointer-events: none;
    /* A tint, not a curtain. No backdrop blur: blurring the pips is precisely
       the thing this interface cannot afford to do. */
    background: linear-gradient(180deg, rgba(12, 78, 94, .42) 0%, rgba(6, 35, 46, .26) 70%, rgba(6, 35, 46, 0) 100%);
}

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

.slot {
    position: relative;
    z-index: 1;
    width: var(--die);
    height: calc(var(--die) + var(--lift));
    perspective: 700px;
}

.die {
    position: absolute;
    left: 0;
    bottom: 0;
    width: var(--die);
    height: var(--die);
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    transform: translateY(0);
    transition: transform .34s cubic-bezier(.22, 1.1, .36, 1);
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
}

.die:disabled { cursor: default; }

/* Held: lifted clear of the water, and raised above the veil so it is no longer
   seen through it. */
.slot:has(.die[aria-pressed="true"]) { z-index: 3; }

.die[aria-pressed="true"] {
    transform: translateY(calc(var(--lift) * -1));
    filter: drop-shadow(0 12px 10px rgba(3, 18, 24, .55));
}

.die__cube {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    transform: rotateX(var(--rx, -18deg)) rotateY(var(--ry, 22deg));
    transition: transform var(--roll-ms, 0ms) cubic-bezier(.16, .72, .24, 1);
}

.die__face {
    position: absolute;
    inset: 0;
    display: grid;
    grid-template: repeat(3, 1fr) / repeat(3, 1fr);
    padding: 13%;
    border-radius: 20%;
    backface-visibility: hidden;

    /* Bone, not white. A white die under a teal sea reads blue; a warm bone
       stays ivory and keeps its edge against the water. */
    background: linear-gradient(152deg, #FFFCF3 0%, #F4EADA 48%, #E2D3BC 100%);
    box-shadow:
        inset 0 0 0 1px rgba(16, 51, 63, .12),
        inset 0 -3px 6px rgba(16, 51, 63, .10);
}

/* Opposite faces sum to seven, as on a real die: 1-6, 2-5, 3-4. */
.die__face--1 { transform: translateZ(calc(var(--die) / 2)); }
.die__face--6 { transform: rotateY(180deg) translateZ(calc(var(--die) / 2)); }
.die__face--2 { transform: rotateY(90deg)  translateZ(calc(var(--die) / 2)); }
.die__face--5 { transform: rotateY(-90deg) translateZ(calc(var(--die) / 2)); }
.die__face--3 { transform: rotateX(90deg)  translateZ(calc(var(--die) / 2)); }
.die__face--4 { transform: rotateX(-90deg) translateZ(calc(var(--die) / 2)); }

.pip {
    width: 72%;
    aspect-ratio: 1;
    place-self: center;
    border-radius: 50%;
    background: radial-gradient(circle at 34% 30%, #37697A 0%, var(--ink) 68%);
    box-shadow: inset 0 1px 1.5px rgba(0, 0, 0, .45);
}

.die__face--1 .pip:nth-child(1) { grid-area: 2 / 2; }

.die__face--2 .pip:nth-child(1) { grid-area: 1 / 1; }
.die__face--2 .pip:nth-child(2) { grid-area: 3 / 3; }

.die__face--3 .pip:nth-child(1) { grid-area: 1 / 1; }
.die__face--3 .pip:nth-child(2) { grid-area: 2 / 2; }
.die__face--3 .pip:nth-child(3) { grid-area: 3 / 3; }

.die__face--4 .pip:nth-child(1) { grid-area: 1 / 1; }
.die__face--4 .pip:nth-child(2) { grid-area: 1 / 3; }
.die__face--4 .pip:nth-child(3) { grid-area: 3 / 1; }
.die__face--4 .pip:nth-child(4) { grid-area: 3 / 3; }

.die__face--5 .pip:nth-child(1) { grid-area: 1 / 1; }
.die__face--5 .pip:nth-child(2) { grid-area: 1 / 3; }
.die__face--5 .pip:nth-child(3) { grid-area: 2 / 2; }
.die__face--5 .pip:nth-child(4) { grid-area: 3 / 1; }
.die__face--5 .pip:nth-child(5) { grid-area: 3 / 3; }

.die__face--6 .pip:nth-child(1) { grid-area: 1 / 1; }
.die__face--6 .pip:nth-child(2) { grid-area: 1 / 3; }
.die__face--6 .pip:nth-child(3) { grid-area: 2 / 1; }
.die__face--6 .pip:nth-child(4) { grid-area: 2 / 3; }
.die__face--6 .pip:nth-child(5) { grid-area: 3 / 1; }
.die__face--6 .pip:nth-child(6) { grid-area: 3 / 3; }

/* Dice that have never been rolled. */
.tray.is-empty .die { opacity: .55; }

/* --- the helm bar ---------------------------------------------------------- */

.helmbar {
    max-width: 560px;
    margin: 0 auto;
    padding: 0 12px 14px;
    display: flex;
    align-items: center;
    gap: 12px;
}

.helmbar__hint {
    flex: 1;
    min-width: 0;
    /* Right-aligned, hard against the button. It describes what the button will
       do, so it belongs next to it - and it leaves the bottom-left corner free
       for the environment badge, which otherwise lands on top of this text. */
    text-align: right;
    font-size: .84rem;
    line-height: 1.25;
    color: var(--foam);
    text-wrap: balance;
}
.helmbar__hint.is-warn { color: var(--brass-lit); }

.roll {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 9px;
    min-height: 52px;
    padding: 0 22px;
    border: 0;
    border-radius: 26px;
    cursor: pointer;

    font-family: var(--display);
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: .03em;
    color: #241503;

    /* Brass, lit from above. The one warm object on the screen and the one
       control that matters. */
    background: linear-gradient(180deg, var(--brass-lit) 0%, var(--brass) 55%, #A06E1F 100%);
    box-shadow:
        0 1px 0 rgba(255, 255, 255, .45) inset,
        0 6px 18px rgba(3, 18, 24, .5);
    transition: transform .1s ease, filter .15s ease;
}

.roll:active:not(:disabled) { transform: translateY(1px) scale(.99); }

.roll:disabled {
    cursor: default;
    filter: grayscale(.75) brightness(.62);
    box-shadow: none;
}

.roll.is-busy { pointer-events: none; }

/* Rolls remaining, as pips rather than a number: it is a count of three, and
   three dots are read faster than a digit plus a noun. */
.roll__pips { display: inline-flex; gap: 4px; }

.roll__pips i {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgba(36, 21, 3, .30);
}
.roll__pips i.is-on { background: #241503; }

.roll__sr {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* =============================================================================
   Finish
   ============================================================================= */

.finish {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: grid;
    place-items: center;
    /* inset: 0 means this overlay owns the notch too, and the card below can
       grow tall enough to reach the padding edge. */
    padding: calc(16px + var(--safe-top)) 16px calc(16px + env(safe-area-inset-bottom));
    background: rgba(3, 18, 24, .72);
    backdrop-filter: blur(6px);
    animation: fade-in .28s ease;
}

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.finish__card {
    width: min(420px, 100%);
    max-height: calc(100dvh - 32px - var(--safe-top) - env(safe-area-inset-bottom));
    overflow-y: auto;
    padding: 24px 22px 20px;
    border-radius: 14px;
    background: var(--chart);
    color: var(--ink);
    border-top: 3px solid var(--brass);
    box-shadow: 0 24px 60px rgba(3, 18, 24, .6);
    text-align: center;
}

.finish__eyebrow {
    font-size: .74rem;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.finish__score {
    font-family: var(--display);
    font-size: clamp(3.4rem, 18vw, 4.6rem);
    font-weight: 700;
    line-height: 1;
    margin: 4px 0 2px;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

.finish__rank {
    font-size: .92rem;
    color: var(--brass);
    font-weight: 600;
    min-height: 1.3em;
}

.finish__breakdown {
    margin: 16px 0 4px;
    padding: 12px 0;
    border-top: 1px solid var(--chart-edge);
    border-bottom: 1px solid var(--chart-edge);
    text-align: left;
    font-size: .88rem;
}

.finish__breakdown > div {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 3px 0;
}

.finish__breakdown dt { color: var(--ink-soft); }
.finish__breakdown dd {
    margin: 0;
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.signlog { margin: 16px 0 0; text-align: left; }

.signlog__label {
    display: block;
    font-size: .74rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin-bottom: 6px;
}

.signlog__row { display: flex; gap: 8px; }

.signlog__input {
    flex: 1;
    min-width: 0;
    font: inherit;
    font-size: 1rem;
    padding: 11px 12px;
    color: var(--ink);
    background: #FBF6EA;
    border: 1px solid var(--chart-edge);
    border-radius: 8px;
}
.signlog__input::placeholder { color: var(--ink-faint); }
.signlog__input:focus { border-color: var(--sea); outline-offset: 1px; }

.signlog__error { margin-top: 8px; font-size: .84rem; color: var(--danger); }

.signlog__done {
    margin: 16px 0 0;
    font-size: .92rem;
    color: var(--sea);
    font-weight: 600;
}

/* The honeypot. Off-screen rather than display:none - a bot that respects
   display:none would skip it, and skipping it is what a person does. */
.gate {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.finish__actions {
    display: flex;
    gap: 8px;
    margin-top: 18px;
}

.finish__logline { margin-top: 12px; font-size: .84rem; }
.finish__logline a { color: var(--sea); }

/* Looking at the final card behind the overlay. */
.reviewbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    max-width: 560px;
    margin: 0 auto;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
    background: rgba(6, 35, 46, .96);
    border-top: 1px solid rgba(143, 212, 217, .2);
    font-size: .84rem;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--foam);
}

/* =============================================================================
   Buttons
   ============================================================================= */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    padding: 0 18px;
    border: 0;
    border-radius: 8px;
    font-size: .95rem;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    transition: filter .15s ease, transform .1s ease;
}

.btn:active { transform: translateY(1px); }
.btn--sm { min-height: 34px; padding: 0 12px; font-size: .82rem; }

.btn--primary { background: var(--sea); color: #F4FBFB; flex: 1; }
.btn--primary:hover { filter: brightness(1.1); }

.btn--brass { background: linear-gradient(180deg, var(--brass-lit), var(--brass)); color: #241503; }
.btn--brass:hover { filter: brightness(1.06); }

.btn--ghost {
    background: none;
    color: var(--ink-soft);
    box-shadow: inset 0 0 0 1px var(--chart-edge);
    flex: 1;
}
.btn--ghost:hover { color: var(--ink); }

/* =============================================================================
   The log
   ============================================================================= */

.page {
    max-width: 620px;
    margin: 0 auto;
    padding: 22px 14px 40px;
}

.page__head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 6px; }

.page__title {
    font-family: var(--display);
    font-size: clamp(1.8rem, 7vw, 2.4rem);
    font-weight: 700;
    color: var(--chart);
    margin-right: auto;
    line-height: 1.1;
}

.page__back {
    font-size: .78rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--brass-lit);
    text-decoration: none;
    white-space: nowrap;
}

.page__lede { color: var(--foam); font-size: .92rem; margin-bottom: 18px; }

.statbar {
    display: flex;
    gap: 10px;
    margin-bottom: 18px;
}

.statbar__item {
    flex: 1;
    padding: 10px 12px;
    border-radius: var(--radius);
    background: rgba(10, 58, 72, .6);
    border: 1px solid rgba(143, 212, 217, .16);
}

.statbar__value {
    display: block;
    font-family: var(--mono);
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--chart);
    font-variant-numeric: tabular-nums;
}

.statbar__label {
    font-size: .68rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-faint);
}

.logtable {
    background: var(--chart);
    color: var(--ink);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: 0 10px 30px rgba(3, 18, 24, .45);
}

.logrow {
    display: grid;
    grid-template-columns: 2.6rem 1fr auto;
    align-items: center;
    gap: 12px;
    padding: 11px 14px;
}

.logrow + .logrow { border-top: 1px solid var(--chart-edge); }

.logrow__rank {
    font-family: var(--mono);
    font-size: .9rem;
    color: var(--ink-faint);
    font-variant-numeric: tabular-nums;
}

.logrow__name { font-size: 1rem; font-weight: 500; overflow-wrap: anywhere; }

.logrow__meta { display: block; font-size: .74rem; color: var(--ink-soft); }

.logrow__score {
    font-family: var(--mono);
    font-size: 1.1rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* The top three get the brass. Fourth place does not, which is what makes the
   first three mean something. */
.logrow--medal .logrow__rank { color: var(--brass); font-weight: 600; }
.logrow--medal .logrow__score { color: var(--brass); }

.logempty {
    padding: 34px 20px;
    text-align: center;
    color: var(--ink-soft);
}
.logempty p { margin-bottom: 14px; }

/* =============================================================================
   Toast
   ============================================================================= */

.toast {
    position: fixed;
    left: 50%;
    bottom: calc(var(--sea-h) + 16px + env(safe-area-inset-bottom));
    transform: translateX(-50%);
    z-index: 70;
    max-width: min(420px, calc(100vw - 32px));
    padding: 11px 16px;
    border-radius: 8px;
    background: var(--ink);
    color: var(--chart);
    font-size: .88rem;
    text-align: center;
    box-shadow: 0 8px 24px rgba(3, 18, 24, .55);
    animation: toast-in .22s cubic-bezier(.2, .9, .3, 1);
}

.toast.is-error { background: var(--danger); }

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

/* =============================================================================
   Footer
   ============================================================================= */

.footer {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 22px 16px 26px;
    text-align: center;
    color: var(--ink-faint);
    font-size: .8rem;
}

/* On the game screen the sea is fixed over the bottom of the viewport, so the
   footer is only ever reached by scrolling past the chart. */
.layout--game .footer { padding-bottom: calc(var(--sea-h) + 20px); }

.footer__brand {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--foam);
    text-decoration: none;
}
.footer__brand:hover { color: var(--chart); }
.footer__brand strong { font-weight: 600; }

.footer__tree {
    width: 22px;
    height: 22px;
    padding: 3px;
    box-sizing: content-box;
    border-radius: 50%;

    /* The Kirkor mark is navy and teal on transparent, drawn for white paper. On
       the abyss it all but vanishes. A pale roundel gives it the light ground it
       was designed for, which is better than filtering it brighter - that would
       shift the brand colors to make them visible, and they are the point. */
    background: var(--chart);
}

.footer__copy { font-size: .74rem; }

/* =============================================================================
   Environment badge
   ============================================================================= */

.env-badge {
    position: fixed;
    left: 10px;
    bottom: calc(10px + env(safe-area-inset-bottom));
    z-index: 90;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 9px;
    border-radius: 99px;
    background: rgba(3, 18, 24, .85);
    color: var(--chart);
    font-size: .66rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    pointer-events: none;
}

.env-badge__dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--env-badge-color, #2f9e51);
}

/* =============================================================================
   Error pages
   ============================================================================= */

.card {
    max-width: 460px;
    margin: 10vh auto;
    padding: 28px 24px;
    background: var(--chart);
    color: var(--ink);
    border-radius: 14px;
    text-align: center;
    box-shadow: 0 18px 44px rgba(3, 18, 24, .5);
}

.card h1 { font-family: var(--display); font-size: 1.6rem; margin-bottom: 8px; }
.card p { margin-bottom: 14px; }

.error-code {
    font-family: var(--display);
    font-size: 3.4rem;
    font-weight: 700;
    line-height: 1;
    color: var(--brass);
}

.text-muted { color: var(--ink-soft); }

/* =============================================================================
   Reduced motion

   The waves stop, the dice stop tumbling, and the ink stops printing. Everything
   still changes state - it just arrives instead of travelling.
   ============================================================================= */

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

    .die__cube { transition-duration: 120ms !important; }
    .die { transition-duration: 120ms; }

    .box.just-scored .box__value { animation: none; }

    .finish, .toast { animation: none; }
    .sounding__fill { transition: none; }
}

/* =============================================================================
   Share

   A QR and a link. Built on the same overlay shape as .finish, including the
   safe-area padding - a full-screen dialog owns the notch exactly the way the
   page does, and forgetting that is how the close button ends up under the
   clock.

   z-index 80 puts it over the finish overlay (60), because Share is reachable
   from inside that card, and over the toast (70), which would otherwise print
   through the backdrop.
   ============================================================================= */

.share {
    position: fixed;
    inset: 0;
    z-index: 80;
    display: grid;
    place-items: center;
    padding: calc(16px + var(--safe-top)) 16px calc(16px + env(safe-area-inset-bottom));
    background: rgba(3, 18, 24, .72);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    animation: fade-in .28s ease;
}

.share__card {
    position: relative;
    width: min(360px, 100%);
    max-height: calc(100dvh - 32px - var(--safe-top) - env(safe-area-inset-bottom));
    overflow-y: auto;
    padding: 24px 22px 20px;
    border-radius: 14px;
    background: var(--chart);
    color: var(--ink);
    text-align: center;
}

.share__close {
    position: absolute;
    top: 6px;
    right: 10px;
    /* 44px is the Apple minimum for a touch target, and this one sits in the
       corner where a thumb is least accurate. */
    min-width: 44px;
    min-height: 44px;
    border: 0;
    background: none;
    color: var(--ink-soft);
    font-size: 1.6rem;
    line-height: 1;
    cursor: pointer;
}
.share__close:hover { color: var(--ink); }

.share__title {
    font-family: var(--display);
    font-weight: 700;
    font-size: 1.5rem;
    line-height: 1.1;
}

.share__lede {
    margin-top: 4px;
    font-size: .88rem;
    color: var(--ink-soft);
}

.share__qr {
    /* White tile under a white-background code: the card is cream, and letting
       the QR's own quiet zone sit directly on it puts a faint square edge right
       where a scanner is looking for clean paper. */
    width: 200px;
    height: 200px;
    margin: 16px auto;
    padding: 8px;
    border-radius: 8px;
    background: #fff;
}

.share__label {
    text-align: left;
    font-size: .72rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin-bottom: 4px;
}

.share__row { display: flex; align-items: stretch; gap: 8px; }

/* Deliberately not styled as a field. No border and no white fill, because
   both of those read as "type here" and this is text you copy, not text you
   edit. The tint is enough to group it with the button beside it. */
.share__url {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    padding: 0 10px;
    min-height: 40px;
    font-family: var(--mono);
    font-size: .82rem;
    color: var(--ink);
    background: rgba(16, 51, 63, .06);
    border-radius: 8px;
    /* One tap takes the whole address, not the word under the finger. */
    user-select: all;
    -webkit-user-select: all;
    /* The address is short enough to fit, but a longer one must scroll inside
       this box rather than widening the dialog. */
    overflow-x: auto;
    white-space: nowrap;
}

.share__status {
    min-height: 1.2em;
    margin-top: 8px;
    font-size: .82rem;
    color: var(--sea);
}

.share__native { width: 100%; margin-top: 4px; }

.footer__share {
    border: 0;
    background: none;
    padding: 4px 2px;
    color: var(--foam);
    font-size: .82rem;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}
.footer__share:hover { color: var(--chart); }
