/* ===========================================================================
 * Shared chrome for the games realm.
 *
 * All five games load this through extra_css. A game keeps a stylesheet of its
 * own for its BOARD and for nothing else: a 9x9 grid and two Legami tiles have
 * no geometry in common, everything around them does.
 *
 * It deliberately does NOT live in public.css: that bundle is a few hundred
 * bytes from the 17 KB gzip budget enforced by gate check 7, and these rules
 * only matter on five pages out of seventy.
 *
 * Breakpoints are 768 and 1024, the only two the realm allows (check 6). The
 * games used to be a registered exception with breakpoints of their own; the
 * frame rejoins the rule instead of widening it.
 * =========================================================================== */

/* --- Above-the-fold game switcher ----------------------------------------
 * One row, directly above the board. It scrolls sideways instead of wrapping:
 * wrapping would cost a second or third row on a phone and push the game out
 * of the first screen, which is the exact problem this component exists to fix.
 */

.tools-game-switcher {
    display: flex;
    gap: var(--s-2);
    margin: 0 0 var(--s-4);
    padding-bottom: var(--s-2);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    /* `width: 0` plus `min-width: 100%` instead of the plain `width: auto` the
     * row would get by default. The shell lays the page out in a grid whose
     * content track is `1fr`, so its minimum is the min-content width of what it
     * holds: six chips that never wrap come to ~684px, and the whole document
     * would pick up a horizontal scrollbar on a phone. Measured on a 390px
     * viewport: 716px of page width before, 390px after. Zeroing the preferred
     * width takes the row out of that calculation, and the min-width puts it
     * back to the width of the container it actually sits in, so the overflow
     * stays inside the strip, which is where it is wanted. */
    width: 0;
    min-width: 100%;
}

.tools-game-switcher-item {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    gap: var(--s-2);
    padding: var(--s-2) var(--s-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-pill);
    background: var(--c-bg-card);
    color: var(--c-ink-soft);
    font-size: 0.8125rem;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;
    scroll-snap-align: start;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.tools-game-switcher-item svg {
    flex: 0 0 auto;
    opacity: 0.7;
}

a.tools-game-switcher-item:hover,
a.tools-game-switcher-item:focus-visible {
    border-color: var(--c-blue);
    color: var(--c-blue);
}

a.tools-game-switcher-item:hover svg,
a.tools-game-switcher-item:focus-visible svg {
    opacity: 1;
}

/* The page you are on: filled, and not a link. */
.tools-game-switcher-item[aria-current="page"] {
    border-color: var(--c-blue);
    background: var(--c-blue);
    color: #fff;
    cursor: default;
}

.tools-game-switcher-item[aria-current="page"] svg {
    opacity: 1;
}

.tools-game-switcher-hub {
    border-style: dashed;
}

/* --- End-of-game cross promo ---------------------------------------------
 * Rendered inside the stats modal, which is what opens when the daily puzzle
 * is over. Sits below the statistics, so it reads as "what now?" and not as an
 * advert competing with the result.
 */

.tools-cross-promo {
    margin-top: var(--s-5);
    padding-top: var(--s-4);
    border-top: 1px solid var(--c-line);
}

/* The block used to open on a single muted line, which read as a caption of the
 * statistics above rather than as a section of its own: players finished the
 * puzzle without registering that four other games existed. A real heading, in
 * ink and with the category icon, is what makes it a destination.
 */
.tools-cross-promo-heading {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    margin: 0 0 var(--s-1);
    font-size: 1rem;
    font-weight: 700;
    line-height: 1.3;
    color: var(--c-ink);
}

.tools-cross-promo-heading svg {
    flex: 0 0 auto;
    color: var(--c-blue);
}

.tools-cross-promo-title {
    margin: 0 0 var(--s-3);
    font-size: 0.875rem;
    line-height: 1.4;
    color: var(--c-ink-mute);
}

/* One row per game, full width: in a modal a wrapped row of pills hides the
 * last entries below the fold of the box, and these are navigation targets,
 * not tags.
 */
.tools-cross-promo-list {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
}

.tools-cross-promo-item {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    padding: var(--s-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    font-size: 0.875rem;
    line-height: 1.3;
    text-decoration: none;
    color: var(--c-ink);
    background: var(--c-bg-card);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.tools-cross-promo-item:hover,
.tools-cross-promo-item:focus-visible {
    border-color: var(--c-blue);
    box-shadow: var(--shadow-sm);
}

.tools-cross-promo-item svg {
    flex: 0 0 auto;
    color: var(--c-blue);
}

.tools-cross-promo-item svg:last-child {
    margin-left: auto;
    color: var(--c-ink-mute);
}

.tools-cross-promo-name {
    display: flex;
    min-width: 0;
    flex-direction: column;
    gap: 2px;
}

.tools-cross-promo-name strong {
    font-weight: 700;
}

.tools-cross-promo-name small {
    font-size: 0.75rem;
    line-height: 1.35;
    color: var(--c-ink-mute);
}

/* --- End-of-round "what now" -----------------------------------------------
 * Sits under the result, inside the review on legami and inside the modal that
 * opens by itself on the others. The order is deliberate: another day of the
 * same game first, the other games after, because the player was enjoying the
 * one they just finished.
 */

.tools-endgame-next {
    margin-top: var(--s-5);
    /* Sudoku centres the body of its win modal, and the promo rows inherited it:
     * a list of navigation targets reads as a column, not as centred copy. The
     * block states its own alignment so it looks the same in all five games. */
    text-align: left;
}

.tools-endgame-again {
    padding-top: var(--s-4);
    border-top: 1px solid var(--c-line);
}

.tools-endgame-again-text {
    margin: 0 0 var(--s-3);
    font-size: 0.875rem;
    line-height: 1.4;
    color: var(--c-ink-mute);
}

.tools-endgame-again-btn {
    width: 100%;
    justify-content: center;
}

/* The promo already draws its own rule; a second one right above it would read
 * as an empty band between the two halves.
 */
.tools-endgame-again + .tools-cross-promo {
    margin-top: var(--s-4);
}

/* Archive picker, inside its own modal. Deliberately not a page of its own: the
 * archive is there for whoever finished today's puzzle and wants to keep
 * playing, not to create indexable surface.
 */
.tools-archive-sub {
    margin: 0 0 var(--s-3);
    font-size: 0.875rem;
    line-height: 1.4;
    color: var(--c-ink-mute);
}

.tools-archive-row {
    display: flex;
    align-items: flex-end;
    gap: var(--s-3);
}

.tools-archive-row .tools-field {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
}

/* Shortcuts for the days a player is most likely to want. The date picker stays
 * the general answer; these cover the common case in one tap.
 */
.tools-archive-quick {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
    margin-top: var(--s-3);
}

@media (max-width: 768px) {
    .tools-archive-row {
        flex-direction: column;
        align-items: stretch;
    }
}

/* ===========================================================================
 * THE FRAME
 * ======================================================================== */

/* Heights come from the visual viewport, written by _games.js. `100vh` counts
 * the browser bars that are not there, so a board sized against it is taller
 * than the screen exactly on the devices where that hurts most. */
.tools-game {
    --tools-vvh: 100dvh;
    --tools-safe-b: env(safe-area-inset-bottom, 0px);
    --tools-safe-r: env(safe-area-inset-right, 0px);
    --tools-safe-l: env(safe-area-inset-left, 0px);
    --tools-dock-h: 0px;
}

/* Every tap target in a game: no double-tap zoom delay, no long-press text
 * selection callout in the middle of a round, no grey flash on Android. */
.tools-game button,
.tools-game [data-game-key],
.tools-game-board {
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

.tools-game-board,
.tools-game-kb {
    -webkit-user-select: none;
    user-select: none;
}

.tools-game-stage {
    display: flex;
    min-width: 0;
    align-items: flex-start;
    gap: var(--s-4);
}

/* --- The game zone: where the playing happens ----------------------------
 * Two very faint radials plus a barely blue base. It is the only surface on
 * the page that is not flat white, and that is the whole job: say where the
 * table is without anything shouting.
 */
.tools-game-zone {
    flex: 1 1 auto;
    min-width: 0;
    border: 1px solid #e6eef7;
    border-radius: var(--r-lg, 14px);
    overflow: hidden;
    background:
        radial-gradient(130% 95% at 50% -12%, rgb(0 101 192 / 0.075), rgb(0 101 192 / 0) 62%),
        radial-gradient(95% 85% at 108% 112%, rgb(194 90 0 / 0.055), rgb(194 90 0 / 0) 58%),
        linear-gradient(176deg, #fff 0%, #fcfdff 52%, #f7fafd 100%);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.9), var(--shadow-sm);
    transition: box-shadow 0.3s ease;
}

.tools-game.is-focus .tools-game-zone {
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.9), 0 8px 26px -18px rgb(0 101 192 / 0.45);
}

/* Concentration mode hides everything that is not the game. The selectors
 * reach outside the component on purpose: the hero and the switcher are what
 * push the board off a phone screen, and they are not ours to restyle. */
.tools-game.is-focus ~ .tools-section,
body:has(.tools-game.is-focus) .tools-hero-editorial,
body:has(.tools-game.is-focus) .tools-game-switcher {
    display: none;
}

/* --- Header --------------------------------------------------------------- */

.tools-game-head {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    padding: var(--s-2) var(--s-3);
    border-bottom: 1px solid #e9eff6;
}

.tools-game-head-icon {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: var(--r-md);
    background: var(--c-blue-50);
    color: var(--c-blue);
}

.tools-game-head-text {
    min-width: 0;
    flex: 1 1 auto;
}

.tools-game-head-line {
    display: flex;
    min-width: 0;
    align-items: baseline;
    gap: var(--s-2);
    margin: 0;
}

.tools-game-id {
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
    font-size: 0.875rem;
    font-weight: 700;
    line-height: 1.2;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tools-game-badge {
    flex: 0 0 auto;
    padding: 2px var(--s-2);
    border-radius: var(--r-pill);
    background: var(--c-blue-50);
    color: var(--c-blue);
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.tools-game-stat {
    display: block;
    overflow: hidden;
    color: var(--c-ink-mute);
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 0.6875rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tools-game-iconbtn {
    display: inline-grid;
    flex: 0 0 auto;
    place-items: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    background: #fff;
    color: var(--c-ink-soft);
    cursor: pointer;
    transition: border-color 0.15s ease, color 0.15s ease;
}

.tools-game-iconbtn:hover,
.tools-game-iconbtn:focus-visible {
    border-color: var(--c-blue);
    color: var(--c-blue);
}

.tools-game-iconbtn[aria-pressed="true"] {
    border-color: var(--c-blue);
    background: var(--c-blue-50);
    color: var(--c-blue);
}

/* An action with nothing to act on is disabled, and has to look it. There was
 * no disabled style at all: a locked "Conferma" was the same solid blue as a
 * live one and the hint bulb looked tappable with no hint banked, so every one
 * of them invited a tap that did nothing. */
.tools-game .tools-btn-primary:disabled,
.tools-game .tools-btn-primary:disabled:hover {
    background: #e2e8f0;
    color: #64748b;
    box-shadow: none;
    filter: none;
    cursor: not-allowed;
}

.tools-game .tools-game-iconbtn:disabled,
.tools-game .tools-game-secondary:disabled {
    border-color: var(--c-line);
    color: var(--c-ink-soft);
    opacity: 0.4;
    cursor: not-allowed;
}

/* The way into the panel. On a phone it is the ONLY way to statistics, rules
 * and archive, so it cannot look like the icon buttons next to it: filled,
 * labelled, and with a dot that pulses until it has been opened once. */
.tools-game-panelcta {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    gap: var(--s-2);
    height: 44px;
    padding: 0 var(--s-3);
    border: 0;
    border-radius: var(--r-md);
    background: var(--c-blue);
    color: #fff;
    font-size: 0.8125rem;
    font-weight: 700;
    cursor: pointer;
}

.tools-game-panelcta {
    min-width: 44px;
}

.tools-game-panelcta:hover,
.tools-game-panelcta:focus-visible {
    background: var(--c-blue-600);
}

.tools-game-panelcta.is-pulse::after {
    content: "";
    position: absolute;
    top: -3px;
    right: -3px;
    width: 11px;
    height: 11px;
    border: 2px solid #fff;
    border-radius: var(--r-pill);
    background: var(--c-orange, #c25a00);
    animation: tools-game-pip 1.6s ease-in-out infinite;
}

@keyframes tools-game-pip {
    0%,
    100% { transform: scale(1); opacity: 1; }
    50%  { transform: scale(1.25); opacity: 0.75; }
}

@media (prefers-reduced-motion: reduce) {
    .tools-game-panelcta.is-pulse::after { animation: none; }
}

/* --- Body, board, message ------------------------------------------------- */

.tools-game-body {
    padding: var(--s-3);
}

/* Height is reserved. The four message bars this replaces appeared and
 * disappeared from the flow, so every warning nudged the board down by a line
 * exactly while somebody was aiming at it. */
.tools-game-msg {
    display: flex;
    min-height: 24px;
    align-items: center;
    justify-content: center;
    margin: 0 0 var(--s-3);
    color: var(--c-ink-mute);
    font-size: 0.8125rem;
    text-align: center;
}

/* A line of coloured text above the board was easy to miss, and the one message
 * that matters most is the one that says why a word was refused. Toned states
 * get a pill: same height, so nothing moves, but it reads as a notice instead of
 * as a caption. */
.tools-game-msg.is-error,
.tools-game-msg.is-ok,
.tools-game-msg.is-warn {
    font-weight: 600;
    border-radius: var(--r-pill, 999px);
    padding: 2px 12px;
    width: fit-content;
    margin-inline: auto;
}

.tools-game-msg.is-error {
    color: var(--c-red, #c95151);
    background: color-mix(in srgb, var(--c-red, #c95151) 12%, #fff);
}

.tools-game-msg.is-ok {
    color: var(--game-correct);
    background: color-mix(in srgb, var(--game-correct) 14%, #fff);
}

.tools-game-msg.is-warn {
    color: var(--c-amber, #b45309);
    background: color-mix(in srgb, var(--c-amber, #b45309) 14%, #fff);
}

.tools-game-table {
    display: grid;
    gap: var(--s-3);
    grid-template-columns: 1fr;
}

.tools-game-board {
    display: flex;
    min-width: 0;
    align-items: center;
    justify-content: center;
}

.tools-game-board > * {
    width: 100%;
}

.tools-game-side { display: none; }

.tools-game-deck {
    margin-top: var(--s-3);
}

/* --- Short landscape: controls to the edges ------------------------------
 * A phone on its side has ~390px of height. Stacking keyboard under board
 * leaves the board about 150px tall. Splitting the keyboard to the sides keeps
 * the board as tall as the screen allows and puts both halves under a thumb.
 */
/* Three columns in short landscape: half the keyboard, the board, the other
 * half plus the controls. Both thumbs are already holding those edges.
 *
 * Two columns where there is no keyboard (connessioni, legami). With three
 * declared and only two children, the board took the first `auto` track and the
 * side column took the wide `1fr` in the middle, ending up ON TOP of the tiles:
 * the primary button covered the third tile and every tap on it went to the
 * button instead. */
.tools-game.is-landscape .tools-game-table {
    align-items: center;
    grid-template-columns: minmax(0, 1fr) auto;
}

.tools-game.is-landscape .tools-game-table:has(.tools-game-deck) {
    grid-template-columns: auto minmax(0, 1fr) auto;
}

.tools-game.is-landscape .tools-game-deck {
    width: clamp(150px, 25vw, 230px);
    margin: 0;
    padding-left: var(--tools-safe-l);
}

/* Left-handed: the controls swap edges with the bare half of the keyboard. */
.tools-game.is-landscape.is-hand-left .tools-game-deck {
    order: 3;
    padding-left: 0;
    padding-right: var(--tools-safe-r);
}

.tools-game.is-landscape.is-hand-left .tools-game-side {
    order: 1;
}

.tools-game.is-landscape .tools-game-side {
    display: flex;
    /* Wide enough that a row of five split keys stays above the floor the
     * suite enforces: at 160px they came out 21px across. */
    width: clamp(150px, 25vw, 230px);
    flex-direction: column;
    gap: var(--s-2);
    padding-right: var(--tools-safe-r);
}

.tools-game.is-landscape .tools-game-board > * {
    max-height: calc(var(--tools-vvh) - 150px);
}

/* --- On-screen keyboard --------------------------------------------------- */

.tools-game-kb {
    display: flex;
    max-width: 600px;
    flex-direction: column;
    gap: 6px;
    margin-inline: auto;
}

.tools-game-kb-row {
    display: flex;
    justify-content: center;
    gap: 5px;
}

/* 44px is the floor the whole realm holds itself to on touch, and a key is the
 * most tapped thing on the page. */
.tools-game-key {
    display: inline-flex;
    height: clamp(44px, 6.2vmin, 52px);
    min-width: 0;
    flex: 1 1 0;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--c-line);
    border-radius: 11px;
    background: rgb(255 255 255 / 0.92);
    color: var(--c-ink);
    font-size: clamp(0.75rem, 2vmin, 1rem);
    font-weight: 700;
    cursor: pointer;
    transition: transform 0.08s ease, background 0.12s ease;
}

.tools-game-key:active {
    transform: translateY(1px);
    background: #eef2f7;
}

.tools-game-key.is-mono { font-family: var(--font-mono, ui-monospace, monospace); }
.tools-game-key.is-wide { flex: 1.5 1 0; font-size: 0.6875rem; }

/* In a row that is already ten keys wide there is nothing to widen. */
.tools-game-kb-row:has(> :nth-child(10)) .tools-game-key.is-wide { flex: 1 1 0; }
.tools-game-key.is-correct { border-color: var(--game-correct); background: var(--game-correct); color: #fff; }
.tools-game-key.is-present { border-color: var(--game-present); background: var(--game-present); color: #fff; }
.tools-game-key.is-absent { border-color: var(--game-absent); background: var(--game-absent); color: #fff; }

.tools-game-kb.is-split .tools-game-key {
    height: clamp(38px, 5.4vmin, 46px);
    font-size: clamp(0.66rem, 1.7vmin, 0.85rem);
}

/* --- Action bar ----------------------------------------------------------- */

.tools-game-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-2);
    padding: var(--s-3);
    border-top: 1px solid #e9eff6;
}

.tools-game-primary {
    min-height: 48px;
}

.tools-game-secondary,
.tools-game-training {
    min-height: 44px;
}

/* On a phone the inline row is a duplicate of the dock, which is always in
 * reach. Two copies of the same button on one screen is one too many. */
.tools-game.is-phone .tools-game-actions--inline {
    display: none;
}

.tools-game.is-landscape .tools-game-side .tools-game-btn-label {
    display: none;
}

/* --- Rail (tablet) -------------------------------------------------------- */

.tools-game-rail {
    display: none;
    flex: 0 0 auto;
    flex-direction: column;
    gap: var(--s-2);
    padding-top: var(--s-1);
}

.tools-game.is-tablet .tools-game-rail {
    display: flex;
}

/* --- Aside (desktop) ------------------------------------------------------ */

.tools-game-aside {
    display: none;
}

.tools-game.is-desktop .tools-game-aside {
    position: sticky;
    top: var(--s-4);
    display: block;
    width: 340px;
    flex: 0 0 340px;
    align-self: flex-start;
}

/* ===========================================================================
 * THE PANEL
 * ======================================================================== */

.tools-game-panel {
    display: flex;
    min-height: 0;
    flex-direction: column;
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg, 14px);
    background: #fff;
    overflow: hidden;
}

/* Inside the sheet and the drawer the panel is the whole surface: its own
 * border would draw a box inside a box. */
.tools-game-sheet .tools-game-panel,
.tools-game-drawer .tools-game-panel {
    height: 100%;
    border: 0;
    border-radius: 0;
}

.tools-game-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-2);
    padding: var(--s-3) var(--s-3) var(--s-2);
}

.tools-game-panel-title {
    margin: 0;
    font-size: 0.875rem;
    font-weight: 700;
}

.tools-game.is-desktop .tools-game-panel [data-game-panel-close] {
    display: none;
}

/* On desktop the panel is already a column in plain sight: a button that opens
 * what is open says nothing. */
.tools-game.is-desktop .tools-game-panelcta {
    display: none;
}

.tools-game-panel-scroll {
    min-height: 0;
    flex: 1 1 auto;
    border-top: 1px solid var(--c-line);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

/* Containment belongs to the two hosts where the panel is an OVERLAY: reaching
 * the end of the sheet or the drawer must not start scrolling the page behind
 * it, which reads as the sheet fighting the finger.
 *
 * It must NOT apply to the desktop column. There the aside is sticky and sized
 * by its content, so the panel never overflows -- but `overflow-y: auto` still
 * makes it a scroll container, and a scroll container with `contain` swallows
 * the wheel even when it has nothing to scroll. The whole column became a dead
 * zone: the wheel did nothing until the pointer left the aside. */
.tools-game-sheet .tools-game-panel-scroll,
.tools-game-drawer .tools-game-panel-scroll {
    overscroll-behavior: contain;
}

/* --- Sections ------------------------------------------------------------- */

.tools-game-sec + .tools-game-sec {
    border-top: 1px solid #eef2f6;
}

.tools-game-sec-h {
    margin: 0;
    font-size: inherit;
    font-weight: inherit;
}

.tools-game-sec-btn {
    display: flex;
    width: 100%;
    min-height: 44px;
    align-items: center;
    gap: var(--s-2);
    padding: 13px var(--s-3);
    border: 0;
    background: none;
    color: var(--c-ink-mute);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.085em;
    text-align: left;
    text-transform: uppercase;
    cursor: pointer;
}

.tools-game-sec-btn:hover {
    background: #f8fafc;
    color: var(--c-ink-soft);
}

.tools-game-sec-label { flex: 1 1 auto; }
.tools-game-sec-ic { color: #94a3b8; }
.tools-game-sec-chev { transition: transform 0.2s ease; transform: rotate(-90deg); }

.tools-game-sec.is-open > .tools-game-sec-h .tools-game-sec-btn { color: var(--c-ink); }
.tools-game-sec.is-open > .tools-game-sec-h .tools-game-sec-ic { color: var(--c-blue); }
.tools-game-sec.is-open > .tools-game-sec-h .tools-game-sec-chev { transform: rotate(0deg); }

.tools-game-sec-body {
    padding: 0 var(--s-3) var(--s-3);
}

.tools-game-sec:not(.is-open) > .tools-game-sec-body {
    display: none;
}

.tools-game-sec--brand > .tools-game-sec-h .tools-game-sec-btn { color: var(--c-blue); }

/* --- Panel content -------------------------------------------------------- */

.tools-game-wide {
    width: 100%;
    min-height: 44px;
    justify-content: center;
}

.tools-game-wide + .tools-game-wide {
    margin-top: var(--s-2);
}

.tools-game-note {
    margin: var(--s-2) 0 0;
    color: var(--c-ink-mute);
    font-size: 0.75rem;
    line-height: 1.45;
}

.tools-game-progress-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 6px;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--c-ink-soft);
}

.tools-game-progress-head b { font-variant-numeric: tabular-nums; font-size: 0.875rem; }

.tools-game-progress-track {
    height: 8px;
    border-radius: var(--r-pill);
    background: #f1f5f9;
    overflow: hidden;
}

.tools-game-progress-track i {
    display: block;
    height: 100%;
    border-radius: var(--r-pill);
    background: var(--c-blue);
    transition: width 0.3s ease;
}

.tools-game-kv {
    margin: var(--s-3) 0 0;
}

.tools-game-kv-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-3);
    padding: 6px 0;
    border-bottom: 1px solid #f1f5f9;
    font-size: 0.8125rem;
}

.tools-game-kv-row:last-child { border-bottom: 0; }
.tools-game-kv-row dt { color: var(--c-ink-mute); margin: 0; }
.tools-game-kv-row dd { margin: 0; font-weight: 700; font-variant-numeric: tabular-nums; }

/* --- Statistics ----------------------------------------------------------- */

.tools-game-stat-grid {
    display: grid;
    gap: 6px;
    grid-template-columns: repeat(4, 1fr);
    text-align: center;
}

.tools-game-stat-cell b {
    display: block;
    font-size: 1.125rem;
    font-weight: 800;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}

.tools-game-stat-cell span {
    display: block;
    color: var(--c-ink-mute);
    font-size: 0.5625rem;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

/* The label is the point. The same index meant attempts in two games and
 * mistakes in two others, and no game said which. */
.tools-game-dist-label {
    margin: var(--s-3) 0 6px;
    color: var(--c-ink-soft);
    font-size: 0.6875rem;
    font-weight: 700;
}

.tools-game-dist {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.tools-game-dist-row {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.625rem;
}

.tools-game-dist-key {
    width: 72px;
    flex: 0 0 auto;
    color: var(--c-ink-mute);
    font-family: var(--font-mono, ui-monospace, monospace);
    text-align: right;
}

.tools-game-dist-bar {
    display: flex;
    height: 16px;
    align-items: center;
    justify-content: flex-end;
    padding: 0 6px;
    border-radius: 4px;
    background: var(--c-line-strong);
    color: #fff;
    font-weight: 700;
}

.tools-game-dist-bar.is-current { background: var(--game-correct); }

.tools-game-countdown {
    margin-top: var(--s-3);
    padding-top: var(--s-3);
    border-top: 1px solid var(--c-line);
    text-align: center;
    font-size: 0.75rem;
    color: var(--c-ink-mute);
}

.tools-game-countdown b {
    display: block;
    margin-top: 2px;
    color: var(--c-ink);
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 1.125rem;
    font-variant-numeric: tabular-nums;
}

/* --- Legend and preferences ---------------------------------------------- */

.tools-game-legend {
    margin: 0;
    padding: 0;
    list-style: none;
}

.tools-game-legend li {
    display: flex;
    align-items: flex-start;
    gap: var(--s-2);
    padding: 5px 0;
    font-size: 0.8125rem;
    line-height: 1.35;
}

.tools-game-legend-dot {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
    margin-top: 2px;
    border-radius: 4px;
}

.tools-game-pref {
    display: flex;
    min-height: 48px;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s-3);
    padding: 10px 0;
    cursor: pointer;
}

.tools-game-pref + .tools-game-pref {
    border-top: 1px solid var(--c-line);
}

.tools-game-pref-text { min-width: 0; }

.tools-game-pref-title {
    display: block;
    font-size: 0.8125rem;
    font-weight: 700;
}

.tools-game-pref-desc {
    display: block;
    color: var(--c-ink-mute);
    font-size: 0.6875rem;
    line-height: 1.35;
}

.tools-game-pref input {
    width: 20px;
    height: 20px;
    flex: 0 0 auto;
    margin-top: 2px;
    accent-color: var(--c-blue);
}

/* --- Wish form ------------------------------------------------------------ */

/* Off-screen rather than display:none. A field that is not rendered at all is
 * the first thing a scripted filler learns to skip. */
.tools-game-wish-trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.tools-game-wish-input {
    /* 16px is the floor that stops iOS from zooming the page on focus, and a
     * zoomed page mid-form is how a keyboard ends up covering the field. */
    min-height: 84px;
    font-size: 1rem;
    resize: vertical;
}

.tools-game-wish-examples {
    margin: var(--s-2) 0 var(--s-3);
    padding-left: 1.1em;
    color: var(--c-ink-mute);
    font-size: 0.75rem;
    line-height: 1.5;
}

.tools-game-wish-feedback {
    margin: var(--s-2) 0 0;
    font-size: 0.75rem;
    line-height: 1.4;
    min-height: 1.4em;
}

.tools-game-wish-feedback.is-ok { color: var(--game-correct); font-weight: 600; }
.tools-game-wish-feedback.is-error { color: var(--c-red, #c95151); font-weight: 600; }

.tools-game-wish.is-sent .tools-game-wish-input,
.tools-game-wish.is-sent .tools-game-wish-examples {
    display: none;
}

/* --- Author and CTA ------------------------------------------------------- */

.tools-game-author-head {
    display: flex;
    align-items: flex-start;
    gap: var(--s-3);
}

.tools-game-author-avatar {
    display: grid;
    width: 44px;
    height: 44px;
    flex: 0 0 auto;
    place-items: center;
    border-radius: var(--r-pill);
    background: var(--c-blue);
    color: #fff;
    font-size: 0.875rem;
    font-weight: 800;
}

.tools-game-author-name {
    margin: 0;
    font-size: 0.875rem;
    font-weight: 700;
}

.tools-game-cta {
    margin-top: var(--s-3);
    padding: var(--s-3);
    border: 1px solid #e3ebf5;
    border-radius: var(--r-lg, 14px);
    background: linear-gradient(170deg, #fbfdff, #fff);
}

.tools-game-cta-top {
    display: flex;
    align-items: flex-start;
    gap: var(--s-3);
}

.tools-game-cta-top p {
    margin: 0;
    color: var(--c-ink-soft);
    font-size: 0.8125rem;
    line-height: 1.5;
}

.tools-game-cta-icon {
    display: grid;
    width: 36px;
    height: 36px;
    flex: 0 0 auto;
    place-items: center;
    border-radius: var(--r-md);
    background: var(--c-blue-50);
    color: var(--c-blue);
}

.tools-game-cta-icon--warm {
    background: #fff4e6;
    color: var(--c-orange, #c25a00);
}

.tools-game-cta-links {
    display: grid;
    gap: var(--s-2);
    margin-top: var(--s-3);
}

.tools-game-cta-link {
    display: inline-flex;
    min-height: 44px;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 10px var(--s-3);
    border: 1px solid #cfe0f2;
    border-radius: var(--r-md);
    background: #fff;
    color: var(--c-blue);
    font-size: 0.8125rem;
    font-weight: 700;
    text-decoration: none;
    transition: border-color 0.15s ease, background 0.15s ease;
}

.tools-game-cta-link:hover,
.tools-game-cta-link:focus-visible {
    border-color: var(--c-blue);
    background: #f5faff;
}

.tools-game-cta-link--primary {
    border-color: var(--c-blue);
    background: var(--c-blue);
    color: #fff;
}

.tools-game-cta-link--primary:hover,
.tools-game-cta-link--primary:focus-visible {
    background: var(--c-blue-600);
    color: #fff;
}

/* ===========================================================================
 * MOBILE CONTAINERS: sheet, drawer, scrim, dock
 * ======================================================================== */

.tools-game-scrim {
    position: fixed;
    z-index: 55;
    inset: 0;
    background: rgb(15 23 42 / 0.42);
    opacity: 0;
    transition: opacity 0.24s ease;
    pointer-events: none;
}

.tools-game-scrim.is-on {
    opacity: 1;
    pointer-events: auto;
}

/* The sheet takes its height from the content, capped at 92% of the visual
 * viewport. A fixed height left a half-empty sheet floating mid-screen
 * whenever a single section was open. */
.tools-game-sheet {
    position: fixed;
    z-index: 60;
    right: 0;
    bottom: 0;
    left: 0;
    display: flex;
    max-height: 92dvh;
    flex-direction: column;
    border-radius: 22px 22px 0 0;
    background: #fff;
    box-shadow: 0 -14px 44px -14px rgb(15 23 42 / 0.32);
    transform: translateY(100%);
    transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
    --tools-peek: 48dvh;
}

.tools-game-sheet.is-peek { transform: translateY(calc(100% - var(--tools-peek))); }
.tools-game-sheet.is-full { transform: translateY(0); }
.tools-game-sheet.is-dragging { transition: none; }

/* Doubled class: `.tools-game button` (touch-action: manipulation) outranks a
 * single one, and with it the browser took the drag back after the first move. */
.tools-game-sheet-grip.tools-game-sheet-grip {
    display: flex;
    width: 100%;
    min-height: 44px;
    align-items: center;
    justify-content: center;
    padding: 11px 0 7px;
    border: 0;
    background: none;
    cursor: grab;
    /* The drag owns the gesture. Without this the page scrolls under the
     * finger and the sheet stutters. */
    touch-action: none;
}

.tools-game-sheet-grip i {
    display: block;
    width: 44px;
    height: 5px;
    border-radius: var(--r-pill);
    background: var(--c-line-strong);
}

/* The head drags the sheet too: a 44px grip was the only handle, and players
 * reach for the title row. */
.tools-game-sheet .tools-game-panel-head {
    touch-action: none;
    cursor: grab;
}

.tools-game-sheet-inner {
    display: flex;
    min-height: 0;
    flex: 1 1 auto;
    flex-direction: column;
    padding-bottom: var(--tools-safe-b);
}

.tools-game-drawer {
    position: fixed;
    z-index: 60;
    top: 0;
    right: 0;
    bottom: 0;
    display: flex;
    width: min(400px, 88vw);
    flex-direction: column;
    background: #fff;
    box-shadow: -14px 0 44px -14px rgb(15 23 42 / 0.28);
    transform: translateX(100%);
    transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1);
}

.tools-game-drawer.is-open { transform: translateX(0); }

.tools-game-dock {
    position: fixed;
    z-index: 50;
    right: 0;
    /* Above whatever else is fixed to the bottom, measured at runtime: the
     * cookie banner owns that band until it is dismissed, and it sits on a
     * higher layer, so the dock moves rather than trying to outrank it. */
    bottom: var(--tools-bottom-bar, 0px);
    left: 0;
    /* Clamped to the width the visitor can actually see: left:0/right:0 spans
     * the initial containing block, which on a viewport with a classic
     * scrollbar is wider than the visible area. */
    max-width: var(--tools-vw, 100%);
    padding: 10px 12px calc(10px + var(--tools-safe-b));
    border-top: 1px solid #e6eef7;
    background: rgb(255 255 255 / 0.97);
    backdrop-filter: blur(12px);
    /* Climbs above the software keyboard instead of being buried by it. The
     * value is measured from visualViewport, so it is 0 where the layout
     * viewport already shrinks (Android) and the keyboard height where it does
     * not (iOS). */
    transform: translateY(calc(-1 * var(--tools-kb-shift, 0px)));
    transition: transform 0.18s ease;
}

/* The safe-area inset belongs to the home indicator, which the keyboard covers
 * anyway: keeping it while raised would leave a white band above the keys. */
.tools-game.is-kbopen ~ .tools-game-dock,
.tools-game.is-kbopen .tools-game-dock {
    padding-bottom: 10px;
}

.tools-game-dock-row {
    display: flex;
    max-width: 520px;
    align-items: center;
    gap: var(--s-2);
    margin-inline: auto;
}

.tools-game-dock-row .tools-btn-primary {
    flex: 1 1 auto;
    min-height: 50px;
    justify-content: center;
}

.tools-game-dock-row .tools-game-panelcta { height: 50px; }

/* Room for the dock, so the last section of the page is reachable. */
.tools-game.is-phone {
    padding-bottom: calc(80px + var(--tools-safe-b) + var(--tools-bottom-bar, 0px));
}

/* ===========================================================================
 * MODAL: one component, two natures
 * ======================================================================== */

.tools-game-modal {
    width: 100vw;
    max-width: none;
    height: 100dvh;
    max-height: 100dvh;
    padding: 0;
    border: 0;
    background: transparent;
    overflow: visible;
}

.tools-game-modal::backdrop {
    background: rgb(15 23 42 / 0.5);
}

.tools-game-modal-wrap {
    display: flex;
    width: 100%;
    height: 100%;
    justify-content: center;
}

.tools-game-modal-box {
    display: flex;
    width: 100%;
    max-width: 560px;
    max-height: 92dvh;
    flex-direction: column;
    background: #fff;
    box-shadow: 0 24px 60px -18px rgb(15 23 42 / 0.4);
    transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
}

.tools-game-modal-grip { display: none; }

.tools-game-modal-head {
    position: sticky;
    z-index: 2;
    top: 0;
    display: flex;
    align-items: flex-start;
    gap: var(--s-3);
    padding: var(--s-4) var(--s-4) var(--s-3);
    border-bottom: 1px solid #eef2f6;
    background: #fff;
}

.tools-game-modal-titles { min-width: 0; flex: 1 1 auto; }
.tools-game-modal-titles h3 { margin: 0; font-size: 1.0625rem; font-weight: 800; line-height: 1.2; }
.tools-game-modal-titles p { margin: 4px 0 0; color: var(--c-ink-mute); font-size: 0.8125rem; line-height: 1.35; }

.tools-game-modal-icon {
    display: grid;
    width: 40px;
    height: 40px;
    flex: 0 0 auto;
    place-items: center;
    border-radius: var(--r-md);
    background: var(--c-blue-50);
    color: var(--c-blue);
}

.tools-game-modal-icon.is-win { background: #f2f9f2; color: #3f7a3a; }
.tools-game-modal-icon.is-lose { background: #fdf2f2; color: #a33e3e; }

.tools-game-modal-body {
    padding: var(--s-4);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}

.tools-game-modal-foot {
    display: flex;
    gap: var(--s-2);
    padding: var(--s-3) var(--s-4) calc(var(--s-4) + var(--tools-safe-b));
    border-top: 1px solid #eef2f6;
    background: #fff;
}

.tools-game-metrics {
    display: grid;
    gap: var(--s-2);
    grid-template-columns: repeat(2, 1fr);
}

.tools-game-metric {
    padding: 10px var(--s-3);
    border-radius: var(--r-md);
    background: #f8fafc;
    text-align: center;
}

.tools-game-metric b { display: block; font-size: 1.0625rem; font-weight: 800; line-height: 1.2; }
.tools-game-metric span { display: block; margin-top: 2px; color: var(--c-ink-mute); font-size: 0.59rem; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; }

.tools-game-sharebox {
    margin-top: var(--s-4);
    padding: var(--s-3);
    border: 1px solid var(--c-line);
    border-radius: var(--r-lg, 14px);
}

.tools-game-grid {
    margin: 0 0 var(--s-3);
    font-size: 1.0625rem;
    line-height: 1.2;
    text-align: center;
    white-space: pre-wrap;
}

.tools-game-endnext {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    margin-top: var(--s-4);
    padding: var(--s-3);
    border-radius: var(--r-lg, 14px);
    background: #f8fafc;
}

.tools-game-endnext-t { margin: 0; font-size: 0.8125rem; font-weight: 700; }
.tools-game-endnext-s { margin: 2px 0 0; color: var(--c-ink-mute); font-size: 0.75rem; }
.tools-game-endnext-s b { font-family: var(--font-mono, ui-monospace, monospace); color: var(--c-ink); }

.tools-game-endblock-h {
    margin: var(--s-4) 0 var(--s-2);
    color: var(--c-ink-mute);
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.tools-game-endauthor { border-top: 1px solid var(--c-line); margin-top: var(--s-4); }

/* Phone: the modal is a sheet from the bottom, with a grip and a drag to
 * dismiss. A centred box on a phone puts its close button where no thumb
 * reaches and its actions where a thumb is least accurate. */
.tools-game.is-phone ~ * .tools-game-modal-wrap,
.tools-game.is-phone .tools-game-modal-wrap {
    align-items: flex-end;
}

.tools-game.is-phone .tools-game-modal-box {
    max-width: none;
    border-radius: 24px 24px 0 0;
}

.tools-game.is-phone .tools-game-modal-grip {
    display: flex;
    justify-content: center;
    padding: 10px 0 2px;
    touch-action: none;
    cursor: grab;
}

.tools-game.is-phone .tools-game-modal-grip i {
    display: block;
    width: 44px;
    height: 5px;
    border-radius: var(--r-pill);
    background: var(--c-line-strong);
}

.tools-game.is-phone .tools-game-modal-foot > * { flex: 1 1 auto; justify-content: center; }

/* Tablet and desktop: a centred card. */
.tools-game.is-tablet .tools-game-modal-wrap,
.tools-game.is-desktop .tools-game-modal-wrap {
    align-items: center;
    padding: var(--s-4);
}

.tools-game.is-tablet .tools-game-modal-box,
.tools-game.is-desktop .tools-game-modal-box {
    border-radius: var(--r-lg, 14px);
}

@media (min-width: 768px) {
    .tools-game-body { padding: var(--s-4) var(--s-5); }
    .tools-game-head { padding: var(--s-3) var(--s-4); }
    .tools-game-actions { padding: var(--s-3) var(--s-4); }
    .tools-game-metrics { grid-template-columns: repeat(4, 1fr); }
    .tools-game-cta-links { grid-template-columns: repeat(2, 1fr); }
}

/* On a phone the header has to fit 393px alongside the panel pill, and the
 * mode badge is the least load-bearing thing in it: the same word is the first
 * row of the panel, one tap away. Hiding it here buys the round identifier the
 * room to be read instead of being cut mid-number. */
@media (max-width: 767px) {
    .tools-game-badge { display: none; }
    .tools-game-panelcta span { display: none; }
    .tools-game-panelcta { padding: 0 12px; }
    .tools-game-dock-row .tools-game-panelcta span { display: inline; }
}

/* --- Hero first, then the game ------------------------------------------
 * An earlier revision flipped the visual order on a phone, putting the board
 * above the editorial hero. It is gone: the hero is the first thing on the page
 * on every device, and it is drawn exactly like the hero of any other tool in
 * the realm -- same size, same lead, no per-game compaction. Operator decision,
 * 2026-09-22.
 *
 * What the board gets instead is an honest height budget: see sizeBoard() in
 * _games.js, which used to assume the board sat at the top of the page.
 */

/* The shell lays the page out in a grid whose content track is `1fr`, so its
 * floor is the min-content width of what it holds: Sudoku's row of difficulty
 * chips pushed the whole document to 408px on a 393px phone, which is a
 * horizontal scrollbar on every game. Zeroing the floor fixes it for every child
 * at once. */
.tools-main:has(.tools-game) {
    min-width: 0;
}

/* --- Docked keyboard, and a board that fits what is left -----------------
 * On a phone the keys live in the dock and the board takes the space above
 * them. The cap is measured at runtime (`--tools-board-max`) because the space
 * depends on the keyboard: three rows for Lemmia, one for Sudoku.
 */
.tools-game-dock-keys:empty { display: none; }

.tools-game-dock-keys {
    margin-bottom: var(--s-2);
}

.tools-game-dock-keys .tools-game-kb {
    /* The row has to be the width of the dock, not of the 600px the keyboard
     * allows itself elsewhere: a ten-key row was running past the right edge
     * and the backspace was half off screen. */
    width: 100%;
    max-width: 100%;
}

.tools-game.is-phone .tools-game-board > * {
    max-height: var(--tools-board-max, none);
}

/* A board capped by height must shrink in width too, or it stops being square.
 * `aspect-ratio` does that on its own once a max-height bites, provided the
 * element is allowed to be narrower than its max-width. */
.tools-game.is-phone .tools-game-board {
    justify-content: center;
}

/* Secondary actions mirrored into the dock, as icons: what a player uses while
 * playing (pause, notes, undo) rather than what they go looking for. */
.tools-game-dock-sec {
    display: flex;
    justify-content: center;
    gap: var(--s-2);
    margin-bottom: var(--s-2);
}

.tools-game-dock-sec .tools-game-iconbtn {
    width: 44px;
    height: 44px;
}

.tools-game-dock-sec .tools-game-iconbtn[aria-pressed="true"] {
    border-color: var(--c-blue);
    background: var(--c-blue-50);
    color: var(--c-blue);
}

/* --- Short landscape: the controls move to the edge -----------------------
 * A phone on its side leaves about 290px of height. A dock carrying keyboard,
 * secondaries and a full-width primary would take a third of it, so there the
 * primary and the secondaries move into the side column, beside the half of the
 * keyboard that already lives there, and the dock keeps only the panel button.
 */
.tools-game-side-controls {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    margin-bottom: var(--s-2);
}

.tools-game-side-controls .tools-btn-primary {
    width: 100%;
    min-height: 44px;
    justify-content: center;
    padding-inline: var(--s-2);
}

.tools-game-side-sec {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.tools-game.is-landscape .tools-game-side-controls .tools-game-btn-label {
    display: none;
}

/* The dock shrinks to the panel button alone. */
.tools-game.is-landscape ~ .tools-game-dock .tools-game-dock-row,
.tools-game.is-landscape .tools-game-dock-row {
    justify-content: flex-end;
}

/* Every pixel of height counts. The shell reserves 48px of padding above the
 * first section for the editorial rhythm, which on a 290px screen is a sixth of
 * everything there is; the header collapses to a single line and the message
 * channel gives back the row it reserves in portrait. */
.tools-main:has(.tools-game.is-landscape) {
    padding-top: var(--s-2);
}

.tools-main:has(.tools-game.is-landscape) > .tools-game {
    margin-top: 0;
}

.tools-game.is-landscape .tools-game-head {
    padding-block: 4px;
}

.tools-game.is-landscape .tools-game-head-text {
    display: flex;
    align-items: baseline;
    gap: var(--s-2);
}

.tools-game.is-landscape .tools-game-stat {
    flex: 1 1 auto;
}

.tools-game.is-landscape .tools-game-head-icon {
    display: none;
}

.tools-game.is-landscape .tools-game-msg {
    min-height: 0;
    margin-bottom: var(--s-2);
}

.tools-game.is-landscape .tools-game-body {
    padding: var(--s-2);
}
