/* Only body had a background-color before - fine as long as body's own
   box always exactly covers the viewport, but that's not guaranteed in
   every circumstance (window-resize timing, OS display scaling, etc.).
   Without this, any gap between the painted canvas and body's box shows
   the browser's default white instead of the page background - matches
   a user report of "body doesn't reach the bottom of the window" that
   also showed up once in a local screenshot (didn't reproduce on retry,
   consistent with a timing-dependent paint gap rather than a layout
   bug). Setting the same color here is the standard defensive fix
   regardless of the exact trigger. */
html {
    background-color: #0f172a;
}
body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    background-color: #0f172a;
    color: #f8fafc;
    margin: 0;
    /* Trimmed from 24px/10px - the sticky nav bar's initial (unscrolled)
       position follows normal flow, so this top padding was pure dead
       space above it, adding directly to the game board page's total
       height (see the footer's own recent trim for the same underlying
       768px-viewport-overflow problem). */
    padding: 8px 10px 6px;
    box-sizing: border-box;
    /* display:flex/min-height:100vh + .main-content-center's flex:1 below
       is what lets the picker/board vertically center in the space below
       the sticky nav (see that rule's own comment) - a standard
       "sticky footer" style layout, not JS-measured. The nav
       (.site-account-bar) stays in normal flow as the first child, sized
       to its own content as always. */
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

/* Fills whatever vertical space is left below the nav - flex:1 alone is
   safe at every width (it only claims genuinely free space; without it
   this is a no-op, identical to a plain block). Every modal overlay
   (win/settings/feedback/...) lives outside this wrapper as a sibling -
   see board.html's own comment - so none of them are affected. */
.main-content-center {
    flex: 1;
    display: flex;
    flex-direction: column;
    width: 100%;
}

/* The board's own height should govern the page instead of stretching to
   fill the viewport - flex:1 above pins the footer to the bottom of the
   screen (sticky-footer pattern), which is what the pre-game picker still
   wants (centers nicely on a tall viewport) but looked broken for the
   game itself: on an ordinary 900px-tall window it left a large gap of
   dead space between the Possibilities bar and the footer's border-top
   line, which read as disconnected/wrong rather than intentional.
   body.board-page-active is toggled in showStartPicker/hideStartPicker
   (game_engine.js) - the single choke point every game-start path
   already funnels through. Added 2026-07-28 per user report. Higher
   specificity than the base rule above (two classes vs one), so this
   safely wins regardless of source order - see the CSS override-ordering
   gotcha in the desktop 3-column layout history for why that matters. */
body.board-page-active .main-content-center {
    flex: none;
}

/* Outer row pairing the existing 540px dashboard column with the desktop
   side column (see .side-pad-outer below). Below the 1024px breakpoint
   this stays a plain block - .game-dashboard-container's own
   width:100%/max-width:540px/margin:0 auto do all the centering exactly
   as before this redesign, untouched. */
.board-page-layout {
    width: 100%;
    position: relative;
}

/* See SudokuEngine.alignSidePadToBoard, which sets `top` to the tallest
   of the three columns' own natural heights - the side columns no longer
   get force-stretched to match the grid+Possibilities span (that's what
   caused the Values/Marks card overflow bug), so this line stands in for
   the visual "everything ends here" cue exact height-matching used to
   give for free. Absolutely positioned against .board-page-layout
   (position:relative above) so it doesn't participate in the flex row
   itself. Desktop-only - the columns are single-stacked below 1024px, so
   there's nothing to visually close between them there. */
.board-page-floor {
    display: none;
    position: absolute;
    left: 0;
    right: 0;
    border-top: 1px solid #334155;
    pointer-events: none;
}
/* Lowered from 1040px to 1024px 2026-07-28 per user request - keep the
   3-column desktop layout on longer instead of falling back to the
   mobile/tablet single-column view, since 1024px-wide windows/screens
   (split-screen snapping, smaller laptops, older 4:3 monitors, tablets
   in landscape) are common and the mobile fallback looked cramped/wrong
   for something that's still clearly a computer, not a phone. The
   3-column layout's own real width need (~940px: 190+10+540+10+190,
   see the desktop-3col-layout build history) still leaves ~84px of
   margin at this new floor, confirmed no horizontal overflow/scroll at
   1024px down through the new breakpoint. Every occurrence of this
   number across style.css/game_engine.js/board.html/_account_nav.html/
   _site_footer.html must move together - grep for 1024 (or, before
   this change, 1040) to find every copy. */
@media (min-width: 1024px) {
    .board-page-floor {
        display: block;
    }
}

.game-dashboard-container {
    position: relative; /* Context anchor for the level-1 keypad positioning */
    text-align: center;
    width: 100%;
    max-width: 540px;
    margin: 0 auto;
}

/* Desktop-only side columns (left + right, see SudokuEngine.
   usesSidePanelInput) - hidden below the breakpoint; today's exact
   click-cycle/dblclick model is the fallback there, completely unchanged.
   .side-pad-outer is the shared base both #side-pad-outer-left and
   #side-pad-outer-right carry; -left/-right below add only what differs
   between them. Deliberately NOT recolored to the dark-navy keypad/modal
   chrome (#1e293b/#334155) used elsewhere on this page for the Values/
   Marks side-pad-group - it exists to visually preview real board
   contents ("blue like on board", matching an actual candidate color), so
   its buttons stay white/light like a real cell instead. Every other new
   card on both columns follows the site's usual dark/accent chrome. */
.side-pad-outer {
    display: none;
    width: 190px;
    flex: none;
}

.side-pad-outer-left, .side-pad-outer-right {
    /* 6px (2026-07-31) - the left column now stacks 4 separate cards
       (New Game/#input-mode-card/Values/Marks) rather than 2, so even a
       modest per-gap trim adds up to real room for the button grids
       below without the column running noticeably tighter. */
    gap: 6px;
}

.side-pad-inner {
    flex: 1;
    display: flex;
    flex-direction: column;
}

/* Values and Marks are two separate cards again as of 2026-07-31 (see
   board.html's own comment on #normal-pad-left) - this just stacks them
   with the same rhythm as everything else in the column. */
#normal-pad-left {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

@media (min-width: 1024px) {
    .board-page-layout {
        display: flex;
        justify-content: center;
        align-items: flex-start;
        gap: 10px;
    }

    /* .game-dashboard-container's own margin:0 auto (needed for the
       non-flex single-column layout below this breakpoint) becomes a real
       problem as a flex item: auto margins absorb ALL the flex container's
       free space before justify-content ever gets a say, so the board was
       silently grabbing ~200px on each side and shoving the side columns far
       off to the edges instead of sitting close to it. Flatten it to a
       plain 0 here so .board-page-layout's own justify-content:center +
       gap do the centering instead. */
    .game-dashboard-container {
        margin: 0;
        /* Shrinks the grid (and, since both are width:100% of this
           container with their own looser 540px caps, the Possibilities
           bar and hint banner along with it) continuously as viewport
           height gets tight, instead of a single hard breakpoint -
           min() means this is a no-op above ~695px tall (540+155) and
           never affects width-driven sizing. 155px is the real measured
           cost of everything else in the vertical stack at this
           breakpoint once the hint banner has already swapped to
           #hint-card-right (nav 25.6 + digit-legend-wrap's 10px margin +
           58px bar + footer's 16px margin + ~28px content + ~14px body
           padding) - see the max-height:715px hint-swap query below,
           still comfortably above (not below) this 695px crossover so
           the swap stays active before the grid-shrink calc below
           needs it to be, same ordering guarantee as the old 780px
           had, just with less margin. The 380px floor is a
           deliberate tradeoff at extreme heights: a page that needs a
           little scrolling beats a grid too cramped to play. */
        max-width: min(540px, max(380px, calc(100vh - 155px)));
    }

    /* .difficulty-card and #hint-banner-desktop's own >=1024px overrides
       live right after each one's base rule further down (same reasoning
       as .pencil-controls' own dedicated media query there) - not here,
       where they'd sit *before* that base rule in source order and lose
       the equal-specificity tiebreak to it (the exact bug this comment
       replaces: both silently did the opposite of what they said). */

    .side-pad-outer {
        display: flex;
        flex-direction: column;
    }
}

/* Dev-only rollback (see FORCE_LEGACY_DESKTOP_INPUT in game_engine.js,
   which stamps this class onto <html> at parse time) - usesSidePanelInput()
   already makes the flag functionally inert (all clicks fall back to the
   legacy model regardless of width), but without this the panels would
   still visibly render, just silently doing nothing - a "rollback" should
   look like the pre-redesign page, not a broken one. Higher specificity
   than the plain .side-pad-outer/.board-page-layout rules inside the
   media query above (two classes + a type selector beats one class), so
   this wins regardless of viewport width without needing !important.
   Combined with the force-mobile-layout selectors below for these same
   two properties, since both flags want the identical override here. */

/* Touch-tablet-at-wide-width fallback (see prefersMobileLayoutOnWideScreen/
   applyForceMobileLayoutClass in game_engine.js, which stamps this class
   onto <html> before first paint - no flash of the wrong layout - and
   re-checks it live once a real keypress proves the device has a
   keyboard). Reverts every piece of the >=1024px desktop layout back to
   its own <1024px default, restoring the exact single-column mobile view
   (.difficulty-card/.pencil-controls/.hint-toast) regardless of how wide
   the screen actually is. Every selector here targets the SAME element a
   `@media (min-width: 1024px)` rule elsewhere in this file also targets -
   grep for `force-mobile-layout` before adding a new one, or removing an
   existing >=1024px rule, to keep this list complete. Combined with the
   force-legacy-desktop-input selectors above for .side-pad-outer/
   .board-page-layout since both flags want the identical override there. */
html.force-legacy-desktop-input .side-pad-outer,
html.force-mobile-layout .side-pad-outer {
    display: none;
}
html.force-legacy-desktop-input .board-page-layout,
html.force-mobile-layout .board-page-layout {
    display: block;
}
html.force-mobile-layout .board-page-floor {
    display: none;
}
html.force-mobile-layout .difficulty-card {
    display: flex;
}
html.force-mobile-layout .pencil-controls {
    display: flex;
}
html.force-mobile-layout #hint-banner-desktop,
html.force-mobile-layout #hint-card-right {
    display: none;
}
/* The >=1024px media query flattens .game-dashboard-container's own
   margin:0 auto to a plain 0 (needed when .board-page-layout is a real
   flex container - see that rule's own comment) - but that rule has no
   force-mobile-layout exception, so a wide touch-tablet's mobile-fallback
   content was sitting flush left instead of centered. Restore the base
   rule's own margin value. User-reported 2026-07-28 against iPad Pro
   portrait (1024x1366). */
html.force-mobile-layout .game-dashboard-container {
    margin: 0 auto;
}

.side-pad-group {
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 8px;
    padding: 5px;
    box-sizing: border-box;
}

.side-pad-label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #94a3b8;
    text-align: center;
    margin-bottom: 3px;
}

/* justify-items:center matters most for the 2-column candidate-color-
   grid: its two 1fr tracks are ~86px wide each in this ~172px-wide card,
   but the 52px swatches default to sitting flush-left within their own
   track (grid's default justify-items:start) rather than centering in
   it - measured a real 9px/40px left/right asymmetry from this before
   adding the rule. (justify-content, tried first, does nothing here -
   it distributes space between tracks, but 1fr tracks already consume
   the full container width, so there's none left to distribute; the
   gap is inside each track, at the item level, which is justify-items'
   job instead.) The 3-column value-number-grid was already close to
   symmetric (its tracks are narrower, ~57px, closer to the button's own
   52px), so this is a no-op there, not a regression. */
.side-pad-grid {
    display: grid;
    gap: 2px;
    justify-items: center;
}

/* Value Number Pad - big centered digits in the app's own "correct entry"
   blue (matches .cell-value-layer.cell-user-correct), so arming a number
   here previews exactly what it'll look like once placed. */
.value-number-grid {
    grid-template-columns: repeat(3, 1fr);
}

.value-number-btn {
    width: 52px;
    height: 52px;
    background-color: #ffffff;
    border: 1px solid #ced4da;
    border-radius: 4px;
    color: #0d6efd;
    font-size: 24px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
}
.value-number-btn:hover { background-color: #eef2ff; }

/* Candidate Color Pad (desktop mouse copy, #marks-desktop-pad) -
   full-square button footprint (2 columns x 3 rows), but the color fill
   inside stays small and pinned to a corner rather than filling the
   button (see .candidate-color-fill below) - a candidate mark is
   inherently a small note inside one of a cell's 9 mini-slots, not a
   whole-cell wash, so its own picker reads as visually smaller/off-center
   within the button. Colors are painted inline by
   SudokuEngine.bindSidePanelEvents from the same CANDIDATE_STATE_STYLES map
   the keypad color keys already use - one source of truth for the six hues
   everywhere. */
.candidate-color-grid {
    grid-template-columns: repeat(3, 1fr);
}

/* Docked touch marks pad (2026-07-28, reworked same day into its own
   purpose-built layout - see SudokuEngine.usesDockedMarksPad/
   toggleLoadedMarkNumber) vs the desktop mouse copy just above/below -
   exactly one of #marks-touch-pad/#marks-desktop-pad is ever visible,
   toggled by syncDockedMarksPadUI. Hidden by default (display:none, not
   just visibility, so it takes no layout space) - a real mouse/keyboard
   user's Marks section looks exactly as it always has. */
#marks-touch-pad {
    display: none;
}
#marks-touch-pad.marks-pad-active {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
/* Desktop mouse copy - visible by default (matches its pre-2026-07-28
   behavior exactly, plain block stacking, same as before this pad had
   any touch-only alternative), hidden only when the touch pad above is
   active. */
#marks-desktop-pad.marks-pad-inactive {
    display: none;
}
/* Number grid: exact duplicate of the popup keypad's own 3x3 number
   layout, just positioned here instead of floating near a cell - same
   52x52px button footprint as the Values pad above and the color
   swatches below it, for one consistent touch-target size throughout
   this whole column. */
.candidate-number-grid {
    grid-template-columns: repeat(3, 1fr);
}
/* #marks-desktop-pad's own numbers grid (2026-07-29 desktop marks-pad
   reuse, see SudokuEngine.usesDesktopMouseMarksPad) - reuses
   .candidate-number-grid/.candidate-number-btn verbatim from
   #marks-touch-pad's copy above (identical 52x52 buttons, hover, armed
   fill, and the >=1024px height-shrink query further down - nothing
   desktop-specific needed for any of that), just conditionally hidden:
   off by default, shown only via the marks-desktop-pad-numbers class
   syncDockedMarksPadUI toggles for a genuine desktop-mouse session
   specifically. #marks-desktop-pad being visible at all is NOT enough on
   its own - a touch-primary PREFER_NEAR_CELL_KEYPAD session also lands
   here for its colors only (see board.html's own comment on this pad) and
   must not gain a redundant second numbers grid. ID-scoped on both sides
   so this pair wins regardless of source order relative to .side-pad-grid
   (which also sets display:grid) without needing !important. */
#marks-desktop-pad .marks-desktop-number-grid {
    display: none;
}
#marks-desktop-pad.marks-desktop-pad-numbers .marks-desktop-number-grid {
    display: grid;
    /* #marks-desktop-pad is plain block stacking (no flex/gap the way
       #marks-touch-pad.marks-pad-active gets, see that rule above) - this
       makes up the same 6px breathing room below the number grid before
       the color grid starts, matching every other gap value on this
       panel, instead of the two grids sitting flush together. */
    margin-bottom: 6px;
}
.candidate-number-btn {
    width: 52px;
    height: 52px;
    background-color: #ffffff;
    border: 1px solid #ced4da;
    border-radius: 4px;
    color: #495057;
    font-size: 22px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
}
.candidate-number-btn:hover { background-color: #eef2ff; }
/* Same "staged" blue fill the Values pad's own armed number gets (see
   .side-pad-btn.side-pad-btn-armed.value-number-btn below) - one
   consistent "this is what's armed" language across both pads. */
.side-pad-btn.side-pad-btn-armed.candidate-number-btn {
    background-color: #0d6efd;
    border-color: #0d6efd;
    color: #ffffff;
}

/* Color grid: same 6 swatch buttons as #marks-desktop-pad's copy (same
   data-pad-kind="candidate-color", painted by the same
   renderLoadedMarkColorUI/bindSidePanelEvents class-based selectors, kept
   in sync automatically) but in the popup keypad's own 3-column x 2-row
   arrangement and solid edge-to-edge fill (see the .candidate-color-fill
   override below) instead of the desktop copy's 2x3 grid with a small
   inset dot - matching "look like the popup menu" exactly, per its own
   .keypad-btn.color-btn styling further down this file. */
.candidate-color-grid-touch {
    grid-template-columns: repeat(3, 1fr);
}
.candidate-color-grid-touch .candidate-color-swatch {
    width: 52px;
    height: 52px;
    border-radius: 4px;
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.35);
}
.candidate-color-grid-touch .candidate-color-fill {
    width: 100%;
    height: 100%;
    margin: 0;
    border-radius: inherit;
}
/* Same white double-ring "staged" indicator the popup keypad's own
   .keypad-btn.color-btn.staged uses (see that rule further down) - one
   consistent "this is armed" language between the two, unlike the
   desktop copy's blue outer ring (already established there, left
   alone). */
.candidate-color-grid-touch .side-pad-btn.side-pad-btn-armed.side-pad-swatch {
    box-shadow: 0 0 0 3px #1e293b, 0 0 0 5px #f8fafc;
}

.candidate-color-swatch {
    width: 52px;
    height: 52px;
    border: none;
    border-radius: 4px;
    background-color: #ffffff;
    cursor: pointer;
    padding: 0;
    display: flex;
    align-items: flex-start;
    justify-content: flex-start;
    position: relative;
}
/* The small inset fill (painted by bindSidePanelEvents) - deliberately
   smaller than the button itself and pinned to the top-left rather than
   centered/filling it, see the comment above .candidate-color-swatch.
   Overridden to fill the whole button in the touch copy above instead. */
.candidate-color-fill {
    width: 16px;
    height: 16px;
    margin: 4px;
    border-radius: 2px;
    display: block;
    pointer-events: none;
}

/* Keyboard-shortcut hint (Z/X/C/V/B/N - see SudokuEngine.COLOR_KEY_MAP),
   pinned to the opposite corner from the color fill so neither competes
   for the same space. Muted against the swatch's default white so it
   doesn't outweigh the color itself; switches to white when armed (see
   renderLoadedMarkColorUI), since the button's own background becomes a
   solid, fairly dark hue at that point and the muted grey would vanish. */
.side-pad-swatch-key {
    position: absolute;
    bottom: 3px;
    right: 5px;
    font-size: 11px;
    font-weight: 700;
    color: #adb5bd;
    line-height: 1;
    pointer-events: none;
}
.side-pad-btn.side-pad-btn-armed.side-pad-swatch .side-pad-swatch-key {
    color: rgba(255, 255, 255, 0.9);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
}

/* #27: shrink the Values/Marks/docked-touch-pad buttons together with the
   grid at short viewport heights, instead of staying pinned at their base
   size while .game-dashboard-container's own calc(100vh - 155px) rule (see
   that comment above) shrinks the grid out from under them - same
   crossover height (~695px: 540+155) and the same calc() term, just scaled
   down from the grid's 540px span to each button's own base size, so both
   shrink in lockstep by construction instead of via two breakpoints that
   could drift apart. Floors at ~70% of the base size (matching the grid's
   own 380/540 floor ratio) rather than shrinking to nothing - still
   comfortably tappable at the shortest heights this site supports.
   Font-size floors follow the same ratio so digits/text don't overflow the
   smaller button. Placed in its own >=1024px query after every base rule
   it overrides (value-number-btn/candidate-number-btn/candidate-color-swatch
   above), not the shared one near the top of this file - see that query's
   own comment on the source-order/specificity tiebreak this avoids.

   Touch's own copies (.candidate-number-btn inside #marks-touch-pad,
   .candidate-color-grid-touch .candidate-color-swatch) keep this same
   52px cap/36px floor - the left column's own vertical budget doesn't
   apply to them at all (they render in the popup keypad/docked touch pad
   instead). Values/desktop-marks (.value-number-btn, .candidate-color-swatch,
   #marks-desktop-pad .candidate-number-btn) get their own smaller 48px
   cap/33px floor instead (2026-07-31, up from a 44/30 low on 2026-07-29
   while the left column was overflowing the board) - #input-mode-card's
   own footprint (board.html) leaves less headroom in that same budget
   than the old floating mouse-direct button + hidden-by-default Direct
   Click slider did, so the full 52px shared cap doesn't quite fit
   alongside Values and Marks both being full separate cards; 48px is the
   largest that does. */
@media (min-width: 1024px) {
    .candidate-number-btn,
    .candidate-color-grid-touch .candidate-color-swatch {
        width: min(52px, max(36px, calc((100vh - 155px) * 52 / 540)));
        height: min(52px, max(36px, calc((100vh - 155px) * 52 / 540)));
    }
    .candidate-number-btn {
        font-size: min(22px, max(15px, calc((100vh - 155px) * 22 / 540)));
    }

    .value-number-btn,
    .candidate-color-swatch,
    #marks-desktop-pad .candidate-number-btn {
        width: min(48px, max(33px, calc((100vh - 155px) * 48 / 540)));
        height: min(48px, max(33px, calc((100vh - 155px) * 48 / 540)));
    }
    .value-number-btn {
        font-size: min(22px, max(16px, calc((100vh - 155px) * 22 / 540)));
    }
    #marks-desktop-pad .candidate-number-btn {
        font-size: min(20px, max(14px, calc((100vh - 155px) * 20 / 540)));
    }
}

/* Unarms the sticky loaded color (see board.html's comment). Minimal
   functional styling for now - a small, unobtrusive text button under the
   color pad, same divider treatment the row it replaced used. Visual
   polish deferred until the desktop marks+color pad's broader rework
   (2026-07-29 Number-Entry redesign). */
.side-pad-no-color-btn {
    display: block;
    width: 100%;
    margin-top: 3px;
    padding-top: 4px;
    padding-bottom: 3px;
    border: none;
    border-top: 1px solid #e2e8f0;
    background: none;
    color: #64748b;
    font-size: 0.8rem;
    font-family: inherit;
    cursor: pointer;
}
.side-pad-no-color-btn:hover {
    color: #334155;
}

/* Armed state - the pad button currently staged in
   SudokuEngine.sidePanelArmedInput (see renderSidePanelArmState). The number
   pad gets the app's own accent-blue "staged" fill (matches the mobile
   keypad's own .staged look); the swatch gets a blue ring instead, so the
   swatch's own color stays visible while still reading as "picked". */
.side-pad-btn.side-pad-btn-armed.value-number-btn {
    background-color: #0d6efd;
    border-color: #0d6efd;
    color: #ffffff;
}
.side-pad-btn.side-pad-btn-armed.side-pad-swatch {
    box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #0d6efd;
}

.game-dashboard-container h2 {
    margin: 0 0 14px;
    font-size: 1.5rem;
    font-weight: 700;
    color: #f8fafc;
}

/* Fixed-height card (see SudokuEngine.renderDifficultyCard) so swapping
   between the stats line, an active hint's explanation, and the inline
   New Game picker never shifts the grid below it. min-height is sized to
   the common case at desktop/wide widths - a typical tier-2 hint
   explanation (a single/hidden-single sentence, by far the most common
   hint text; measured ~82px including this card's padding) and the
   inline-picker (~90px) - not a rare multi-cell-elimination explanation
   (naked/hidden subset, fish, wing), which can run longer and, on the
   rare puzzle state where one actually appears, is allowed to grow the
   card past this floor rather than paying for it with dead centered
   space around every ordinary hint. Narrow/mobile widths need their own
   pass later - the picker's button rows wrap further there and can
   already exceed this floor. */
.difficulty-card {
    /* min-height deliberately untouched (2026-07-31 short-viewport
       request) - it's calibrated to the hint-text/inline-picker states'
       own floor (see this rule's own comment just above), not the
       stats display, which is well under it regardless since the
       2-row/1-line-per-stat redesign; shrinking it would reintroduce the
       exact grid-shifts-on-every-hint jump this floor exists to prevent.
       margin-bottom trimmed instead (15px->8px) - pure breathing room
       between this card and New Game below it, not tied to that
       calibration, so it's free to give back on short viewports like
       iPhone SE where the Possibilities bar was getting cut off at the
       very bottom. */
    min-height: 92px;
    margin-bottom: 8px;
    padding: 10px 14px;
    box-sizing: border-box;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 10px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/* Mobile-only fallback - #hint-banner-desktop and the two side columns
   take over the same jobs (stats, hint, New Game/level picker) at
   >=1024px. Must come after the base rule above, or this loses the
   equal-specificity tiebreak to its own display:flex. */
@media (min-width: 1024px) {
    .difficulty-card {
        display: none;
    }
}

.stats-panel {
    font-weight: 500;
    color: #94a3b8;
}

/* 2-row stat layout (2026-07-28, replaces a single flowing text line;
   turned from 2 side-by-side columns into 2 stacked rows 2026-07-31,
   see board.html's own comment on why) - grouped by meaning (difficulty
   stats together, session stats together), same grouping as before, just
   transposed: each group is now one row of side-by-side stats instead of
   one column of stacked ones. Deliberately borderless/background-less
   per cell (unlike desktop's individual .fact-line cards) -
   .difficulty-card already has its own background/border, and nesting
   another bordered card inside at this width read as boxes-within-boxes;
   the gap plus .fact-label/.fact-value's own typography (reused as-is
   from the desktop cards) does the separating instead. */
.stats-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    text-align: left;
}
.stats-row {
    display: flex;
    gap: 16px;
}

/* Each stat is one row (label left, value right) instead of two stacked
   lines (2026-07-31) - the stacked version was taking up enough height on
   short/narrow phones to push the grid below the fold. Overrides
   .fact-label/.fact-value's own display:block (shared with the desktop
   .fact-line/.combo-row cards, left alone there) just for this mobile
   panel. align-items:baseline instead of center - label (10px) and value
   (16px) sit on the same text baseline that way, not vertically centered
   against each other's different line-heights. Label is deliberately NOT
   nowrap - "Hardest Skill"/"Overall Difficulty" are long enough that on
   the narrowest phones they may still wrap to a second line rather than
   overlap the value; every other label here is short enough to never
   need it. The value gets flex-shrink:0 so it always stays intact on one
   line instead of being squeezed by a wrapping label fighting it for
   space. flex:1/min-width:0 (moved here from .stats-col, 2026-07-31) so
   the 2-3 stats sharing a .stats-row divide its width evenly instead of
   each sizing to its own content and leaving the row unbalanced. */
.stat-cell {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}
/* Deliberately NOT display:flex here (unlike most of this file's
   label+icon pairings) - a flex row centers the "?" button vertically
   against the label's own full height, which for a wrapping 2-line label
   ("Hardest Skill"/"Overall Difficulty" on the narrowest phones) floats
   it in the gap between the two lines - right next to the value beside
   it, not visibly attached to either line of the label (2026-07-31
   report). Plain inline flow instead: the button is an ordinary
   inline-block sibling of the label text, so it wraps together with the
   text's own last word instead of floating independently. */
.stat-cell .fact-label .info-btn {
    margin-left: 4px;
    vertical-align: middle;
}
/* Even with plain inline flow, the browser can still treat the button as
   its own separately-wrappable inline element - on the very narrowest
   phones "Skill"/"Difficulty" fit at the end of the label's line but the
   button alone doesn't, so it wraps down to a bare third line by itself
   (2026-07-31 follow-up report). Wrapping the last word + button together
   in board.html (.fact-label-nowrap) and forbidding a break inside that
   span forces them to wrap as one unit instead - either both fit on the
   current line, or both move to the next one together. */
.fact-label-nowrap {
    white-space: nowrap;
}
.stat-cell .fact-value {
    display: block;
    flex-shrink: 0;
    text-align: right;
}

/* Fixed-width digits - without this, most fonts render "1" narrower than
   the other digits, so every value that ticks through one (the timer
   most noticeably, updating every second) shifts the rest of the stats
   line slightly each time it does. */
.label-time, .label-score, .label-mistakes, .label-mistake-limit, .label-hints-used, .label-hint-limit {
    font-variant-numeric: tabular-nums;
}

/* "Unlimited" (see SudokuEngine.renderLimitLabel) instead of the old "∞"
   glyph for an unlimited mistake/hint budget - a bit of right margin so
   the longer word has some breathing room instead of sitting flush
   against the card's edge. Experimental per direct feedback ("let's see
   how it would look") - not a settled design yet. */
.limit-unlimited {
    margin-right: 4px;
}

/* Brief badge shown next to the clock when the tab regains focus (see
   SudokuEngine.showTimerResumeToast). Collapsed to zero width/margin/padding
   while hidden so it doesn't leave a gap in the stats line, then expands in
   alongside the fade so the rest of the line doesn't jump when it appears. */
.timer-resume-toast {
    display: inline-block;
    overflow: hidden;
    white-space: nowrap;
    vertical-align: middle;
    max-width: 0;
    margin-left: 0;
    padding: 2px 0;
    border-radius: 10px;
    background-color: rgba(13, 110, 253, 0.12);
    color: rgba(13, 110, 253, 1);
    font-size: 12px;
    font-weight: 600;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease, max-width 0.25s ease, margin-left 0.25s ease, padding 0.25s ease;
}
.timer-resume-toast.timer-resume-toast-visible {
    opacity: 1;
    max-width: 160px;
    margin-left: 6px;
    padding: 2px 8px;
}

/* 9x9 Layout Frame */
.sudoku-grid-container {
    display: grid;
    grid-template-columns: repeat(9, 1fr);
    grid-template-rows: repeat(9, 1fr);
    width: 100%;
    max-width: 540px;
    aspect-ratio: 1 / 1;
    gap: 1px;
    background-color: #343a40;
    border: 3px solid #212529;
    margin: 0 auto;
    box-sizing: border-box;
    overflow: hidden;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

/* Wraps each row of 9 cells for ARIA's role="row" grouping (see
   _board_cells.html's comment) without taking part in the CSS Grid
   layout above - display:contents removes the wrapper's own box
   entirely, so its .sudoku-cell children still become direct grid items
   exactly as if this element weren't there at all. The wrapper still
   exists in the DOM/accessibility tree (that's the whole point), just
   not in the render tree's box model. */
.sudoku-grid-row {
    display: contents;
}

/* Individual Cell Matrix Wrappers */
.sudoku-cell {
    position: relative;
    aspect-ratio: 1 / 1;
    background-color: #ffffff;
    overflow: visible; /* Allows the larger keypad layer to spill over borders cleanly */
    user-select: none;
    -webkit-touch-callout: none; /* Suppresses iOS's long-press copy/select bubble */
    -webkit-user-select: none;
    touch-action: manipulation; /* Blocks double-tap-zoom/other gestures racing our own long-press timer */
}

/* Thick Border Grid Enforcements for 3x3 Blocks */
.sudoku-cell[data-col="2"], .sudoku-cell[data-col="5"] { border-right: 2px solid #212529; }
.sudoku-cell[data-row="2"], .sudoku-cell[data-row="5"] { border-bottom: 2px solid #212529; }

/* Press-and-hold charge-up glow, ramping toward the long-press threshold
   (see SudokuEngine.startPressGlow/resetPressGlow, LAYER 3 above value +
   candidates so it reads clearly while held). */
.sudoku-cell::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-color: rgba(13, 110, 253, 0);
    box-shadow: inset 0 0 0 0 rgba(13, 110, 253, 0.85);
    transition: background-color var(--press-transition-duration, 150ms) linear,
                box-shadow var(--press-transition-duration, 150ms) linear;
    z-index: 4;
}

.sudoku-cell.cell-press-charging::after {
    background-color: rgba(13, 110, 253, 0.25);
    box-shadow: inset 0 0 0 4px rgba(13, 110, 253, 0.85);
}

/* Desktop side-panel hover preview (see SudokuEngine.handleSidePanelCellHover)
   - whole-cell wash while a value-* tool is armed. Reuses the same ::after
   layer as the press-charge glow above rather than adding a third pseudo-
   element: the two features are mutually exclusive by input mode (the
   charge-up glow only ever runs under usesKeypadOnlyInput(), the side panel
   only under its own opposite condition, see usesSidePanelInput), so they
   never need to render at once. */
.sudoku-cell.side-hover-cell-preview::after {
    background-color: rgba(13, 110, 253, 0.18);
}
/* .candidate-button's own cursor:pointer (below) otherwise wins over the
   digit-badge cursor SudokuEngine.handleSidePanelCellHover sets on the cell
   wrapper - the 9 mini-slots fill nearly the whole cell, so without this
   the custom cursor would only ever show in the thin gaps between them. */
.sudoku-cell.side-hover-cell-preview .candidate-button {
    cursor: inherit;
}

/* Candidate mini-slot hover (see SudokuEngine.handleSidePanelSlotHover) -
   scoped to just the one .candidate-button under the pointer, live on every
   hover now (no arming required - see the mark-cycle redesign). A ring
   rather than a background-color override: the cursor icon already shows
   what a click will do, so this only needs to mark which exact slot is
   "live" without hiding a colored mark's own background - an !important
   background here (the old design, from when hover only ever meant "a
   candidate-color tool is armed") would blot out the mark's real color for
   as long as the pointer sat still over it. */
.candidate-button.side-hover-slot-preview {
    box-shadow: inset 0 0 0 2px rgba(13, 110, 253, 0.7);
}

/* Persistent selection marker for click/keyboard cell selection (see
   SudokuEngine.selectCell/handleKeyDown) - independent of the transient
   press-charge glow above, which is mousedown-only and fades on release.
   Bolder ring (was 2px/75% opacity) is the primary indicator - keyboard/
   WASD navigation made it more important to always be able to spot the
   selected cell at a glance.
   The tint is a separate ::before layer painted on top of the cell,
   not a background-color on .sudoku-cell itself: .sudoku-cell's own
   background is opaque white, and .candidate-button (the 9 mini-slots
   in an empty cell) has none of its own, so a translucent
   background-color set directly on .sudoku-cell would *replace* that
   opaque white rather than tint it - letting whatever sits behind the
   board bleed through the gaps between mini-slots instead of a subtle
   wash over them. A dedicated ::before (not the ::after already used
   for the press-charge glow/side-hover-preview above - a selected cell
   can be simultaneously mid-press or hovered, and both of those already
   drive that same layer) keeps the true white base intact underneath. */
.sudoku-cell.cell-selected {
    box-shadow: inset 0 0 0 3px rgba(13, 110, 253, 1);
}
.sudoku-cell.cell-selected::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-color: rgba(13, 110, 253, 0.06);
    z-index: 4;
}

/* Row/column/box companions of the selected cell (see
   SudokuEngine.updatePeerHighlights) - matches the NYT Sudoku palette (user
   preference, compared against several sites 2026-07-14). Flat opaque color
   rather than a translucent wash, so the same value covers both the plain
   cell and the .cell-given override below - no blending needed. */
.sudoku-cell.cell-peer,
.sudoku-cell.cell-peer .cell-value-layer.cell-given {
    background-color: #f9eac2;
    transition: background-color 90ms ease;
}

/* Every other cell sharing the selected cell's digit - only applied when
   the selected cell has a value. */
.sudoku-cell.cell-same-value,
.sudoku-cell.cell-same-value .cell-value-layer.cell-given {
    background-color: #d48200;
    transition: background-color 90ms ease;
}

/* Blink-then-release for a digit that just got fully placed while its
   legend view was active (see SudokuEngine.blinkAndReleaseBlockedHighlight)
   - the legend button goes inert once a digit is complete, so there's no
   button left to click to release the marking; this toggles the digit's
   own 9 cells off a few times instead of leaving everything stuck lit
   forever. Only .cell-same-value flashes - the .cell-peer wash covering
   the rest of the board stays steady throughout and disappears together
   with everything else at the final release, not mid-flash. Board-level
   class rather than per-cell so a single JS toggle covers all 9 at once.
   Skipped entirely under reduced motion (see the JS) - flashing content is
   an accessibility concern, not just a nicety to trim. */
#main-sudoku-board.legend-blink-off .sudoku-cell.cell-same-value,
#main-sudoku-board.legend-blink-off .sudoku-cell.cell-same-value .cell-value-layer.cell-given {
    background-color: transparent;
}

/* Hint highlighting (see SudokuEngine.renderHintTier) - pulsing amber ring
   on the hint's target cell(s), a color not already in use by selection
   (blue) or peer/same-value (cream/orange) so it reads as its own thing.
   .cell-hint-support marks cells that back up the explanation from tier 2
   on (e.g. an X-Wing's other corners) - previously a plain red ring, but
   real feedback: a red-green colorblind user can't distinguish red from
   the amber .cell-hint ring well enough. Color alone was never going to
   be reliable here (WCAG's own guidance: don't encode meaning in color
   alone) - solid vs dashed is the primary distinction now, teal is the
   secondary one, chosen specifically because it's far from both amber
   and red on a red-green-blind viewer's confusion axis (unlike another
   red/amber/orange, or blue which is already .cell-selected's color).
   The dashed ring itself is a real appended element (.hint-support-ring,
   see SudokuEngine.renderHintTier/clearHintHighlight) rather than
   box-shadow, which has no dashed option. */
.sudoku-cell.cell-hint {
    animation: hintPulse 1.4s ease-in-out infinite;
}
@keyframes hintPulse {
    0%, 100% { box-shadow: inset 0 0 0 2px rgba(255, 193, 7, 0.9); }
    50% { box-shadow: inset 0 0 0 4px rgba(255, 193, 7, 0.5); }
}
.hint-support-ring {
    position: absolute;
    inset: 2px;
    pointer-events: none;
    z-index: 3;
    border: 3px dashed #0891b2;
    box-sizing: border-box;
}

/* Persistent handwritten-style X over the specific candidate a tier-2+
   hint explanation says to eliminate (see SudokuEngine.
   renderHintEliminationMarks/clearHintHighlight) - .cell-hint-support
   above says "this cell is involved," this says exactly which digit and
   why. Two independently-curved strokes (not straight lines) for a
   sketchy, drawn-by-hand feel rather than a computed X; the two paths'
   endpoints don't quite mirror each other on purpose, like a real quick
   pen cross-out would. Same red as the Always-On flash's strike-through
   (.cell-hint-flash-eliminate) for a consistent "this gets eliminated"
   color language, just persistent instead of a 1.6s fade. */
.hint-elimination-x {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 3;
}
.hint-elimination-x svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
}
.hint-elimination-x path {
    fill: none;
    stroke: #dc2626;
    stroke-width: 2.4;
    stroke-linecap: round;
}

/* Persistent handwritten-style circle around the digit that defines a
   fish/locked-candidates pattern (see SudokuEngine.
   renderHintPatternMarks) - e.g. the X-Wing's own "2" in each of its 4
   corner cells. Only drawn for techniques where SudokuEngine.
   renderHintTier's own check finds a single digit governs the whole
   pattern (every elimination targets the same digit as data.digit) -
   Naked/Hidden Pair-and-up involve more than one digit per cell, where
   circling just data.digit would show only part of the real pattern, so
   those are deliberately skipped rather than shown misleadingly. Same
   sketchy-bezier construction as the X above (an imperfect loop whose
   ends don't quite meet, not a perfect <circle>), amber instead of red -
   this digit isn't being removed, it's what defines the pattern. */
.hint-pattern-circle {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 3;
}
.hint-pattern-circle svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
}
.hint-pattern-circle path {
    fill: none;
    stroke: #ffc107;
    stroke-width: 2.2;
    stroke-linecap: round;
}

/* Always-On Hint level 3's brief answer preview (see
   SudokuEngine.renderAlwaysOnFlash) - overlays the cell without touching
   its real content, fades in then out over ~1.6s (matches the JS timeout
   that removes the element), never left permanently visible. */
.cell-hint-flash-digit {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: clamp(18px, 5vw, 28px);
    font-weight: 700;
    color: rgba(232, 89, 12, 1);
    pointer-events: none;
    z-index: 5;
    animation: hintFlash 1.6s ease-in-out both;
}
@keyframes hintFlash {
    0% { opacity: 0; }
    20% { opacity: 1; }
    70% { opacity: 1; }
    100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .cell-hint-flash-digit { animation: none; opacity: 0.9; }
}

/* Always-On Hint level 3's elimination-technique counterpart to
   .cell-hint-flash-digit above (see SudokuEngine.renderAlwaysOnEliminationFlash)
   - locked-candidates/fish/wing-style hints have no single answer digit to
   flash, so level 3 previously looked byte-for-byte identical to level 2
   for every hint type except plain Singles. Same brief fade language, but a
   strike-through red flash directly on the specific candidate button(s)
   being eliminated, since that's the actual "answer" for this hint shape. */
.candidate-button.cell-hint-flash-eliminate {
    animation: hintFlashEliminate 1.6s ease-in-out both;
}
@keyframes hintFlashEliminate {
    0%, 100% { background-color: transparent; text-decoration: none; }
    20%, 70% { background-color: rgba(220, 38, 38, 0.35); text-decoration: line-through; }
}
@media (prefers-reduced-motion: reduce) {
    .candidate-button.cell-hint-flash-eliminate { animation: none; background-color: rgba(220, 38, 38, 0.25); text-decoration: line-through; }
}

/* LAYER 1: Core Value Layer */
.cell-value-layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: clamp(18px, 6vw, 26px);
    font-weight: bold;
    background: transparent;
    user-select: none;
}

/* Original preset numbers explicitly render black */
.cell-value-layer.cell-given {
    background-color: #e9ecef;
    color: #000000 !important;
}

/* Correct user entries dynamically render blue */
.cell-value-layer.cell-user-correct {
    color: #0d6efd !important;
}

/* LAYER 2: Candidate Overlay Grid */
.candidate-grid-layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    background: transparent;
}

.candidate-button {
    position: relative; /* anchor for .hint-elimination-x, see its own comment */
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    font-size: clamp(8px, 2.2vw, 11px);
    font-weight: bold;
    text-align: center;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none; /* Native <button> elements don't reliably inherit this from ancestors on iOS */
    touch-action: manipulation;

    /* Default hidden state */
    color: rgba(0, 0, 0, 0);
    transition: color 0.2s ease, transform 0.12s ease;
}

/* Darkened legible baseline for valid candidates */
.candidate-button.candidate-possible {
    color: rgba(33, 37, 41, 0.70); /* Bold, crisp gray watermark */
}

/* Turned-off candidate, normally kept faintly visible as a reminder it can be
   tapped back on. Default here is fully invisible; game_engine.js sets an
   inline color that overrides this in every mode except Manual-mode-on-a-
   mouse, where it deliberately leaves the inline color unset so the
   hover-reveal rule below can take over (inline styles beat CSS classes, so
   JS opting out is what lets this rule matter at all). */
.candidate-button.candidate-ghost {
    color: rgba(0, 0, 0, 0);
}

/* Cell-First mode's dot-click preview (see pendingDotToggle/
   handleCellFirstDotClick in game_engine.js) - a brief flash confirming
   the click registered, before either committing (a same-dot repeat
   click) or fading back out uncommitted (timeout, or a Values-pad press
   redirecting the gesture to a value instead). Uses the app's own accent
   blue rather than a real mark's neutral gray, so "still just a preview"
   reads differently from "already a placed mark" - relies on
   .candidate-button's own existing `transition: color` to animate both
   the flash-in and the fade-out via the same class toggle. !important is
   required to beat setCandidateState's inline color - a class toggle
   can't otherwise win against an inline style. */
.candidate-button.candidate-preview {
    color: rgba(13, 110, 253, 0.55) !important;
}

/* AUTO mode's illegal-placement digits (recalculateAllCellCandidates' else
   branch) - invisible by default, same as .candidate-ghost above. */
.candidate-button.candidate-blank {
    color: rgba(0, 0, 0, 0);
}

@media (hover: hover) and (pointer: fine) {
    .sudoku-cell:hover .candidate-button.candidate-ghost {
        color: rgba(33, 37, 41, 0.22);
    }

    /* Hovering a cell in AUTO mode fades its illegal digits in at a ghost
       level, filling out the full 1-9 layout so it's easier to see exactly
       where to click - the legal candidates themselves never change
       appearance. game_engine.js leaves color unset for "blank" in AUTO mode
       (see setCandidateState) so this can take over. Touch/keypad-only input
       has no hover, so it's untouched and those digits stay invisible there,
       as before. Excludes .cell-filled (assignFinalCellValue/
       renderVisualBoard's clue branch) - once a cell holds a value there's
       nothing left to place, and without this exclusion the candidate grid
       underneath (still z-index-stacked below the value layer, which is
       otherwise transparent) would show through on hover. */
    #main-sudoku-board.mode-auto .sudoku-cell:not(.cell-filled):hover .candidate-button.candidate-blank {
        color: rgba(33, 37, 41, 0.22);
    }

    /* Peer/same-value highlighting's blue wash (see .cell-peer/.cell-same-value
       below) sits behind the candidate grid, and AUTO's illegal-digit hover
       reveal is already at the faint end (0.22) - boost it here so it's
       still legible over the tinted background, not just over plain white. */
    #main-sudoku-board.mode-auto .sudoku-cell.cell-peer:not(.cell-filled):hover .candidate-button.candidate-blank {
        color: rgba(33, 37, 41, 0.4);
    }

    /* Same boost for MANUAL/NONE's hover-reveal ghosts (the base rule
       above) - no !important needed here, since neither mode sets an inline
       color for a real mouse (see getGhostColor), so plain specificity
       (this selector has an extra class) is enough to win. */
    .sudoku-cell.cell-peer:hover .candidate-button.candidate-ghost,
    .sudoku-cell.cell-same-value:hover .candidate-button.candidate-ghost {
        color: rgba(33, 37, 41, 0.4);
    }
}

/* AUTO mode's ghost candidates are inline-visible at all times, not just on
   hover (see getGhostColor), at a faint 0.22 that becomes unreadable once a
   cell also carries a peer/same-value wash underneath. Scoped to AUTO via
   #main-sudoku-board.mode-auto: !important is required to beat AUTO's inline
   color, but applying that unconditionally would also force MANUAL/NONE's
   hover-only ghosts (handled separately above) to stay visible without
   hovering, which isn't what we want there. */
#main-sudoku-board.mode-auto .sudoku-cell.cell-peer .candidate-button.candidate-ghost,
#main-sudoku-board.mode-auto .sudoku-cell.cell-same-value .candidate-button.candidate-ghost {
    color: rgba(33, 37, 41, 0.4) !important;
}

/* Player-chosen colored marks — a full-square background fill behind the
   digit, sized to the candidate button's own 1/3x1/3 grid slot. Colors and
   text are set inline by game_engine.js (CANDIDATE_STATE_STYLES) so they
   always match. Previously a small circle (border-radius:50%), which the
   digit's own text mostly covered - a square fill stays visible around the
   digit instead of hiding behind it. Six colors are enough to tell apart by
   hue alone, so all six share one shape. */
.candidate-button.candidate-c1,
.candidate-button.candidate-c2,
.candidate-button.candidate-c3,
.candidate-button.candidate-c4,
.candidate-button.candidate-c5,
.candidate-button.candidate-c6 {
    font-weight: 700;
    border-radius: 2px;
}

/* GLOBAL LEVEL-1 SINGLE KEYPAD STRUCTURE */
.global-keypad-overlay {
    position: absolute;
    box-sizing: border-box;
    left: 0;
    width: 100%;
    max-width: 540px;
    height: clamp(200px, 52vw, 246px);
    display: none;
    grid-template-columns: repeat(6, 1fr);
    grid-template-rows: repeat(3, 1fr);
    background-color: #1e293b;
    border: 2px solid #334155;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
    padding: 4px;
    gap: 4px;
    z-index: 4;
    /* Grows in from whichever edge sits nearest the cell that opened it -
       see SudokuEngine.openGlobalKeypad, which sets --keypad-anim-origin
       and --keypad-anim-offset before toggling .keypad-anim-start off. */
    transform-origin: var(--keypad-anim-origin, center);
    opacity: 1;
    transform: scale(1) translateY(0);
    transition: opacity 160ms ease, transform 160ms ease;
}
.global-keypad-overlay.keypad-anim-start {
    opacity: 0;
    transform: scale(0.85) translateY(var(--keypad-anim-offset, 0px));
}
@media (prefers-reduced-motion: reduce) {
    .global-keypad-overlay {
        transition: none;
    }
}

.keypad-btn {
    position: relative;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 5px;
    font-size: clamp(18px, 5vw, 22px);
    font-weight: bold;
    color: #f8fafc;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    touch-action: manipulation;
    min-width: 44px;
    min-height: 44px;
}
.keypad-btn:hover { background-color: #334155; }

/* Close (X) keeps the app's existing cancel/destructive red. */
.keypad-btn.close-btn { background-color: #dc3545; color: #ffffff; }

/* # reads as a distinct, secondary key from the digit/close keys, using the
   app's existing blue accent. */
.keypad-btn.hash-btn {
    color: #0d6efd;
    border-color: #0d6efd;
}

/* "Staged" = this key is armed, waiting on its combo partner (a number tapped
   first waits for # or a color; # or a color tapped first waits for a
   number). A solid fill plus glow ring signals "the next digit tap finishes
   this." */
.keypad-btn.num-btn.staged,
.keypad-btn.hash-btn.staged {
    background-color: #0d6efd;
    color: #ffffff;
    border-color: #0d6efd;
    box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.35), 0 0 10px 2px rgba(13, 110, 253, 0.55);
}

/* Colored pencil-mark keys (the 6 hues plus white/c0, see
   CANDIDATE_STATE_STYLES): solid swatch fill (set inline by game_engine.js's
   paintKeypadColorSwatches, the same map that colors the mini-grid
   candidates), no border of their own so the color reads edge-to-edge - a
   consistently-white pencil icon on top of each (see the shared svg rule
   below) is what identifies these as pencil-mark keys rather than the fill
   alone. */
.keypad-btn.color-btn {
    border: none;
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.35);
}
.keypad-btn.color-btn.staged {
    box-shadow: 0 0 0 3px #1e293b, 0 0 0 5px #f8fafc;
}
.keypad-btn.color-btn svg {
    width: 20px;
    height: 20px;
    fill: #ffffff;
    stroke: #1e293b;
    stroke-width: 0.75;
}
/* White/c0's own fill is white too, so its icon would vanish without the
   stroke above - it also gets a dark inset ring (replacing the white one
   every other swatch uses) so the key's own edge stays legible against the
   keypad's dark background. */
.keypad-btn.color-btn[data-color="c0"] {
    box-shadow: inset 0 0 0 2px rgba(30, 41, 59, 0.6);
}
.keypad-btn.color-btn[data-color="c0"].staged {
    box-shadow: 0 0 0 3px #1e293b, 0 0 0 5px #f8fafc, inset 0 0 0 2px rgba(30, 41, 59, 0.6);
}

/* Live hint badge on each number key: a small corner circle previewing what
   the next tap will do while # or a color is staged (see
   SudokuEngine.renderKeypadHints). */
.keypad-btn.num-btn .badge {
    position: absolute;
    top: 3px;
    right: 3px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    box-shadow: 0 0 0 1.5px #1e293b;
    display: flex;
    align-items: center;
    justify-content: center;
}
.keypad-btn.num-btn .badge.mark-badge svg {
    width: 9px;
    height: 9px;
    fill: #ffffff;
    stroke: #1e293b;
    stroke-width: 1;
}

/* With # or a color staged in AUTO, a number key that isn't a legal candidate
   here is left in place (unlike num-btn-hidden below) but shows neither its
   digit nor a mark-badge preview - there's genuinely nothing this key can do
   for that number right now (see SudokuEngine.renderKeypadHints). */
.keypad-btn.num-btn .digit.digit-blank-for-color {
    visibility: hidden;
}

/* With # staged in AUTO, a number key that isn't a legal candidate here is
   hidden rather than removed from the grid (see SudokuEngine.renderKeypadHints)
   - visibility, not display, so the other 8 keys don't reflow into its slot. */
.keypad-btn.num-btn.num-btn-hidden {
    visibility: hidden;
}

/* Persistent 1-9 legend under the board (see SudokuEngine.toggleBlockedHighlight)
   - unlike the global keypad, this is always visible and isn't tied to a
   selected cell, so it's the entry point for picking a digit with nothing
   selected. Each button's count badge shows how many of that digit remain
   to be placed. */
.digit-legend-wrap {
    width: 100%;
    max-width: 540px;
    margin: 10px auto 0;
    box-sizing: border-box;
}
.digit-legend-label {
    font-size: 12px;
    color: #94a3b8;
    text-align: center;
    margin-bottom: 4px;
}
.digit-legend {
    display: grid;
    grid-template-columns: repeat(9, 1fr);
    gap: 4px;
    width: 100%;
    box-sizing: border-box;
}
.digit-legend-btn {
    position: relative;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 5px;
    font-size: clamp(14px, 3.6vw, 18px);
    font-weight: bold;
    color: #f8fafc;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 38px;
    padding: 3px 0;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    touch-action: manipulation;
}
.digit-legend-btn:hover { background-color: #334155; }

.digit-legend-count {
    font-size: 9px;
    font-weight: normal;
    color: #94a3b8;
    margin-top: 1px;
}

/* Fully placed - just dimmed now, not inert (used to also set
   pointer-events:none/cursor:default, disabling the button entirely -
   per feedback, a fully-placed digit is still worth being able to tap:
   "let one see where that number is used if they like." The click
   handler itself never checked this class either way - the highlight
   math in applyBlockedHighlight already works identically regardless of
   how many instances are placed, this was purely a CSS-level block. */
.digit-legend-btn.digit-legend-complete {
    opacity: 0.35;
}

/* Armed - this digit's blocked cells are the ones currently shown (see
   applyBlockedHighlight); matches the keypad's own staged/armed blue. */
.digit-legend-btn.digit-legend-active {
    background-color: #0d6efd;
    color: #ffffff;
    border-color: #0d6efd;
}
.digit-legend-btn.digit-legend-active .digit-legend-count {
    color: rgba(255, 255, 255, 0.85);
}

/* Error Animation Keyframes */
@keyframes errorWiggle {
    0% { transform: translateX(0); }
    25% { transform: translateX(-3px); }
    50% { transform: translateX(3px); }
    75% { transform: translateX(-3px); }
    100% { transform: translateX(0); }
}
.candidate-error-wiggle { animation: errorWiggle 0.12s ease-in-out infinite; }

/* Diagonal wave pop across the board on completion (see
   SudokuEngine.celebrateWin) - staggered per-cell via --cell-pop-delay. */
@keyframes cellSolvedPop {
    0% { transform: scale(1); box-shadow: inset 0 0 0 0 rgba(25, 135, 84, 0); }
    40% { transform: scale(1.12); box-shadow: inset 0 0 0 3px rgba(25, 135, 84, 0.85); }
    100% { transform: scale(1); box-shadow: inset 0 0 0 0 rgba(25, 135, 84, 0); }
}
.sudoku-cell.cell-solved-pop {
    animation: cellSolvedPop 0.5s ease var(--cell-pop-delay, 0ms) both;
}

/* Win celebration modal (see SudokuEngine.celebrateWin/hideWinOverlay) */
.win-overlay {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(33, 37, 41, 0);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease, background-color 0.25s ease;
    z-index: 10;
}
.win-overlay.win-overlay-visible {
    opacity: 1;
    background-color: rgba(33, 37, 41, 0.45);
    pointer-events: auto;
}
.win-card {
    background-color: #1e293b;
    border-radius: 14px;
    padding: 32px 28px;
    max-width: 300px;
    text-align: center;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
    transform: scale(0.85) translateY(10px);
    transition: transform 0.25s ease;
}
.win-overlay-visible .win-card {
    transform: scale(1) translateY(0);
}
.win-emoji {
    font-size: 42px;
    line-height: 1;
    margin-bottom: 8px;
}
.win-title {
    margin: 0 0 6px;
    font-size: 22px;
    color: #f8fafc;
}
.win-subtitle {
    margin: 0 0 20px;
    font-size: 14px;
    color: #94a3b8;
}

/* Daily Challenge streak badge (see SudokuEngine.renderWinStreak) - empty
   and collapsed by default (regular-puzzle wins and anonymous players
   never populate it), fades/pops in once report_completion's response
   lands, which can arrive slightly after the overlay itself is already
   showing. */
.win-streak {
    margin: -8px 0 20px;
    font-size: 15px;
    font-weight: 700;
    color: #b45309;
    background-color: #fef3c7;
    border-radius: 999px;
    padding: 0;
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    transition: opacity 0.25s ease, padding 0.25s ease, max-height 0.25s ease, margin 0.25s ease;
}
.win-streak.win-streak-visible {
    padding: 6px 14px;
    max-height: 40px;
    opacity: 1;
}

/* Shown instead of a streak badge when Always-On Hint tainted this solve
   (see SudokuEngine.celebrateWin) - display:none/"" toggled inline by JS,
   this just styles it once visible. */
.win-assisted-note {
    margin: -8px 0 20px;
    font-size: 12px;
    color: #94a3b8;
}

.win-new-btn {
    background-color: #0d6efd;
    border: none;
    border-radius: 8px;
    padding: 10px 22px;
    font-size: 15px;
    font-weight: 600;
    color: #ffffff;
    cursor: pointer;
}
.win-new-btn:hover { background-color: #0b5ed7; }

/* Mistake-limit lockout modal (see SudokuEngine.lockBoardOnMistakeLimit/hideMistakeLimitOverlay) */
.mistake-limit-overlay {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(33, 37, 41, 0);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease, background-color 0.25s ease;
    z-index: 10;
}
.mistake-limit-overlay.mistake-limit-overlay-visible {
    opacity: 1;
    background-color: rgba(33, 37, 41, 0.45);
    pointer-events: auto;
}
.mistake-limit-card {
    background-color: #1e293b;
    border-radius: 14px;
    padding: 32px 28px;
    max-width: 300px;
    text-align: center;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
    transform: scale(0.85) translateY(10px);
    transition: transform 0.25s ease;
}
.mistake-limit-overlay-visible .mistake-limit-card {
    transform: scale(1) translateY(0);
}
.mistake-limit-emoji {
    font-size: 42px;
    line-height: 1;
    margin-bottom: 8px;
    color: rgba(220, 53, 69, 1);
}
.mistake-limit-title {
    margin: 0 0 6px;
    font-size: 22px;
    color: #f8fafc;
}
.mistake-limit-subtitle {
    margin: 0 0 20px;
    font-size: 14px;
    color: #94a3b8;
}
.mistake-limit-new-btn {
    background-color: rgba(220, 53, 69, 1);
    border: none;
    border-radius: 8px;
    padding: 10px 22px;
    font-size: 15px;
    font-weight: 600;
    color: #ffffff;
    cursor: pointer;
}
.mistake-limit-new-btn:hover { background-color: rgba(189, 45, 59, 1); }

@media (prefers-reduced-motion: reduce) {
    .sudoku-cell.cell-solved-pop { animation: none; }
    .win-overlay, .win-card, .win-streak, .mistake-limit-overlay, .mistake-limit-card, .settings-overlay, .settings-card { transition: none; }
    .sudoku-cell.cell-peer, .sudoku-cell.cell-same-value { transition: none; }
    .sudoku-cell.cell-hint { animation: none; box-shadow: inset 0 0 0 3px rgba(255, 193, 7, 0.9); }
}

/* Explanation text for the active hint (see SudokuEngine.renderHintTier) -
   same collapsed-until-populated shape as .win-streak, so it doesn't
   reserve layout space while no hint is active. */
.hint-toast {
    max-width: 480px;
    margin: 0 auto;
    padding: 0;
    font-size: 13px;
    line-height: 1.4;
    color: #fcd34d;
    background-color: rgba(251, 191, 36, 0.14);
    border-radius: 8px;
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    transition: opacity 0.25s ease, max-height 0.25s ease, padding 0.25s ease, margin 0.25s ease;
}
.hint-toast.hint-toast-visible {
    padding: 8px 14px;
    margin: 0 auto 10px;
    max-height: 120px;
    opacity: 1;
}

/* Once-per-browser welcome message (see SudokuEngine.showWelcomeMessage) -
   shares .hint-toast's collapse/reveal box model (max-height/opacity
   transition, .hint-toast-visible) entirely, just overrides the color so
   it doesn't read as an actual hint. */
.welcome-toast {
    color: #7dd3fc;
    background-color: rgba(56, 189, 248, 0.14);
}

/* Tappable technique name at the end of a tier-2+ hint (see
   SudokuEngine.renderHintToastText) + its floating popover (see
   openHintTechniquePopover - appended to document.body, not nested here,
   since .hint-toast's own max-height/overflow:hidden above would clip it). */
.hint-technique-link {
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    font: inherit;
    font-weight: 700;
    color: #fcd34d;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}
.hint-technique-link:hover {
    color: #fde68a;
}
.hint-technique-popover {
    position: fixed;
    z-index: 50;
    max-width: 260px;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 6px;
    padding: 8px 10px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
.hint-technique-popover-summary {
    margin: 0 0 6px;
    font-size: 13px;
    line-height: 1.4;
    color: #cbd5e1;
}
.hint-technique-popover-link {
    font-size: 13px;
    font-weight: 600;
    color: #0d6efd;
    text-decoration: none;
}
.hint-technique-popover-link:hover {
    text-decoration: underline;
}

.level-label {
    font-size: 13px;
    font-weight: 600;
    color: #94a3b8;
}

.level-btn-group {
    display: flex;
    gap: 8px;
    justify-content: center;
    flex-wrap: wrap;
}

.level-btn {
    background: transparent;
    border: 1px solid #334155;
    border-radius: 6px;
    padding: 6px 14px;
    font-size: 13px;
    color: #cbd5e1;
    cursor: pointer;
    transition: background-color 0.15s ease;
}

.level-btn:hover {
    background-color: #1e293b;
}

/* Distinct from the level-btn group - this isn't a difficulty pick, it
   swaps in the shared daily puzzle instead (see
   SudokuEngine.loadDailyChallenge). Solid accent fill so it doesn't read as
   just another difficulty option. */
.daily-challenge-btn {
    background-color: #0369a1;
    border: 1px solid #0369a1;
    border-radius: 6px;
    padding: 6px 14px;
    font-size: 13px;
    font-weight: 600;
    color: #ffffff;
    cursor: pointer;
    transition: background-color 0.15s ease;
}
.daily-challenge-btn:hover {
    background-color: #075985;
}

/* Start picker (see SudokuEngine.showStartPicker) - the pre-game screen
   shown instead of the board whenever there's nothing to resume, or the
   player backs out mid-play via "New Game". A plain centered column, not a
   modal - it's primary content in its own right, not something floating
   over another screen. */
.start-picker-container {
    max-width: 560px;
    width: 100%;
    margin: 60px auto;
    padding: 0 16px;
    text-align: center;
    box-sizing: border-box;
}

.start-picker-subtitle {
    color: #94a3b8;
    font-size: 14px;
    margin: 0 0 28px;
}

.start-picker-section {
    margin-bottom: 28px;
}
.start-picker-section .level-label {
    display: block;
    margin-bottom: 10px;
}

.start-picker-special {
    display: flex;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
}

/* New Game's inline picker, shown in place of the stats line inside
   .difficulty-card (see SudokuEngine.toggleInlineStartPicker) - same
   .level-btn-group/.level-btn/.daily-challenge-btn as the full-screen
   picker, just without its heading/subtitle, sized to fit the card
   instead of a whole page. Hidden by default; renderDifficultyCard
   toggles it. */
.inline-start-picker {
    display: none;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}
.inline-start-picker .level-btn-group {
    justify-content: center;
    flex-wrap: wrap;
}
.inline-picker-special {
    display: flex;
    justify-content: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* Reuses .daily-challenge-btn's shape/sizing but swaps the accent fill for
   a neutral outline, so "New Game" (a return to the picker, not a puzzle
   pick) doesn't read as a third Daily/Practice-style option in the pencil
   controls row. */
.new-game-btn {
    background-color: transparent;
    border: 1px solid #334155;
    color: #cbd5e1;
}
.new-game-btn:hover {
    background-color: #1e293b;
}

/* .pencil-controls' plain .new-game-btn copy is the only one left below
   1024px (hidden along with the rest of that row there, see its own media
   query) - the wide-screen New Game is a whole card now (#new-game-card
   below), not this button, so no >=1024px sizing override is needed here
   anymore. */

/* Toolbar card (top of the right column - moved here from the left, see
   #toolbar-card-right's own comment in board.html): Settings/Feedback/
   Hint on one row, Undo/Redo on the other - reuses .undo-redo-btn/
   .settings-gear-btn/.undo-btn/.redo-btn/.hint-btn exactly as
   .pencil-controls' fallback copy does, so SudokuEngine.syncUndoRedoUI/
   syncHintUI keep every copy in sync via querySelectorAll, not by id.
   Always exactly two rows tall from its own natural content - no longer
   height-matched to #hint-banner-desktop or anything else (an earlier
   version did, see SudokuEngine.alignSidePadToBoard's own history). */
.toolbar-card {
    width: 100%;
    box-sizing: border-box;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 8px;
    padding: 7px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}
.toolbar-row {
    display: flex;
    justify-content: space-between;
    gap: 5px;
}
.toolbar-row-half {
    justify-content: center;
}
.toolbar-row-half .undo-redo-btn {
    flex: 1;
    max-width: 60px;
}
.toolbar-row .undo-redo-btn {
    flex: 1;
}
/* New Game's card (back at the top of the left column as of 2026-07-30 -
   see board.html's own comment) - a title + one quiet line, deliberately
   not showing "choose a level" or any stats until actually clicked (see
   SudokuEngine.toggleInlineStartPicker, which swaps #normal-pad-left for
   #level-picker-left). */
.new-game-card {
    width: 100%;
    box-sizing: border-box;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 8px;
    padding: 12px 14px;
}
.new-game-trigger {
    background: none;
    border: none;
    color: inherit;
    text-align: left;
    padding: 0;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
}
.ng-title {
    font-size: 21px;
    font-weight: 700;
    color: #f8fafc;
}
.ng-subtitle {
    font-size: 12px;
    color: #94a3b8;
}
.new-game-trigger:hover .ng-title {
    color: #0d6efd;
}

/* Wide-screen copy of the level picker (see .inline-start-picker, the
   mobile/narrow equivalent still living in the difficulty-card) - swaps
   in under New Game's card in place of #normal-pad-left. Same
   setLevel/loadDailyChallenge/openPracticeTechniquePanelFromInlinePicker
   handlers, just its own markup sized for the ~190px column instead of
   the full-width mobile card. */
.level-picker {
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 8px;
    padding: 12px 10px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.level-picker-heading {
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #94a3b8;
    text-align: center;
}
.level-picker .level-btn-wrap {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.level-picker .level-btn {
    flex: 1 1 calc(50% - 3px);
}
.level-picker-outline {
    background-color: transparent;
    border: 1px solid #0d6efd;
    color: #0d6efd;
}

/* Desktop-only hint banner (center column, above the grid) - always
   rendered at the same height, holding the "81Sudoku" wordmark when idle
   and the real hint explanation when one's active, so neither state ever
   shifts the grid below it (see SudokuEngine.renderDifficultyCard/
   renderHintTier/clearHintHighlight - same job .hint-toast/.stats-panel
   do below 1024px, just recombined for the 3-column layout). */
.hint-banner-desktop {
    display: none;
    width: 100%;
    box-sizing: border-box;
    min-height: 58px;
    margin-bottom: 10px;
    border-radius: 8px;
    padding: 10px 14px;
    font-size: 13px;
    line-height: 1.4;
    text-align: center;
    align-items: center;
    justify-content: center;
    transition: background-color 0.2s ease, color 0.2s ease;
}
/* Desktop-only - must come after the base rule above, or this loses the
   equal-specificity tiebreak to its own display:none (see the identical
   note on .difficulty-card's override further up). */
@media (min-width: 1024px) {
    .hint-banner-desktop {
        display: flex;
    }
}
.hint-banner-desktop.idle {
    background-color: rgba(13, 110, 253, 0.08);
    /* The 8%-opacity fill alone barely differs from the page's own dark
       background - added per feedback that the banner's own edges (and so
       its alignment with the toolbar/Skill-Challenge cards flanking it,
       which both already have a solid #1e293b fill + #334155 border) were
       hard to see. Scoped to .idle only, per the same feedback - .active
       (a real hint showing) reads fine already. */
    border: 1px solid rgba(13, 110, 253, 0.35);
    color: #0d6efd;
    font-family: Georgia, "Times New Roman", serif;
    font-size: 17px;
    font-weight: 700;
}
.hint-banner-desktop.idle .hbd-eight {
    margin-right: 1px;
}
.hint-banner-desktop.active {
    background-color: rgba(251, 191, 36, 0.14);
    color: #fcd34d;
}
/* Desktop equivalent of .welcome-toast above - same slot, own color. */
.hint-banner-desktop.welcome {
    background-color: rgba(56, 189, 248, 0.14);
    color: #7dd3fc;
}

/* Real hint text in the narrow ~190px right column wraps harder than in
   the wide center banner this is standing in for - a brief tier-1
   "Naked Single" measured ~75px tall, a fuller tier-2 explanation
   ~129px (confirmed live). Tried widening this state to reduce the
   wrap (commit `fe55676`) - real feedback: it had to overlap the grid
   to do it, and even with an opaque background swapped in afterward
   (`228f817`) to fix legibility, it still covered part of the puzzle.
   Reverted per direct instruction: simpler to just accept the height
   like the mobile hint-toast/difficulty-card already do (see that
   pattern's own reasoning) - #hint-card-right just grows taller in
   normal flow like #hint-banner-desktop does, pushing
   #toolbar-card-right/#skill-challenge-card/.tips-box down with it. On
   a short viewport this can push .tips-box off-screen while a long
   hint is showing - accepted, same "rare case pays its own cost"
   tradeoff, not a bug to chase. */

/* Standby copy of the hint banner in the right column (see board.html's
   own comment on #hint-card-right) - shares every visual rule above via
   the .hint-banner-desktop class both elements carry, but stays hidden
   even at >=1024px except on a short viewport, where it swaps places
   with #hint-banner-desktop to reclaim the ~68px that one reserves above
   the grid (center column's real bottleneck on a laptop-height screen -
   the side columns don't add to that vertical stack at all, only the
   center one does). Both overrides use id selectors specifically so
   specificity (id > class) decides the tie against .hint-banner-desktop's
   own >=1024px display:flex rule regardless of source order - the same
   override-ordering gotcha this stylesheet has hit twice before (see
   .difficulty-card's own history), sidestepped here instead of relied on. */
#hint-card-right {
    display: none;
    /* Negative order (not just 1/2) since skill-challenge-card/the fact-
       line cards/tips-box below have no explicit order of their own
       (default 0) - toolbar/hint need to stay ahead of all of them, not
       just ahead of each other, in both states. Default order (-1)
       matches DOM order (toolbar first, this second) - moved to the very
       top of the right column below on a short viewport instead, so it's
       the first thing in the column rather than sitting under the
       toolbar. The hint icon itself stays put at the top of
       #toolbar-card-right's own first row regardless (see .toolbar-row,
       no row-level order swap) - #toolbar-card-right's own order:-1
       below keeps that icon directly under this banner either way. */
    order: -1;
}
#toolbar-card-right {
    order: -2;
}
/* Lowered from 780px to 700px 2026-07-28 (real user report + a live
   diagnostic HUD nailed their regular, non-maximized Chrome window at
   exactly 1600x777, 3px under the old line), then nudged to 715px same
   day per follow-up user request. max-height rules match everything AT
   OR BELOW the number. Still comfortably above the ~695px point where
   the rest of the page starts treating height as tight (grid shrink
   kicks in there, see .game-dashboard-container's own comment on why
   this needs to stay above that crossover, not below it) and the
   760px nav-fold threshold - keep both independent, don't let them
   drift back together. */
@media (min-width: 1024px) and (max-height: 715px) {
    #hint-banner-desktop {
        display: none;
    }
    #hint-card-right {
        display: flex;
        order: -2;
    }
    #toolbar-card-right {
        order: -1;
    }
}

/* Temporarily hides the "81Sudoku" idle wordmark - used by showResumeToast,
   since .resume-game-toast is absolutely positioned right on top of this
   banner at desktop widths (see its own rule) and both being visible at
   once just reads as overlapping text. Separate from the hidden-attribute
   idle/active toggle renderDifficultyCard owns - this only ever adds a
   temporary opacity override on top of whichever state that logic has
   already set. */
.hint-banner-desktop.hbd-wordmark-suppressed .hbd-idle-content {
    opacity: 0;
}

/* Right column's Hardest Skill/Overall Difficulty combined card, and the
   plainer Time/Mistakes/Hints-used cards below it - same
   label-tier/label-score/label-time/label-mistakes/label-mistake-limit/
   label-hints-used/label-hint-limit classes the mobile stats-panel uses
   (see board.html), kept in sync by the same setter functions via
   querySelectorAll. */
.fact-line {
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 8px;
    padding: 8px 12px;
    text-align: left;
    box-sizing: border-box;
}
.fact-label {
    display: block;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #94a3b8;
    margin-bottom: 2px;
}
.fact-value {
    display: block;
    font-size: 16px;
    font-weight: 700;
    color: #f8fafc;
}
.label-mistakes.mistakes-critical {
    color: rgba(220, 53, 69, 1);
}

/* Unlimited mistakes/hints (see SudokuEngine.renderLimitLabel): the used
   count sits where it always does, "Unlimited" floats to the card's own
   right edge instead of sitting glued to the count with a "/" between
   them - per direct feedback, no slash at all in this state. .fact-value
   already spans the card's full width (display:block), so switching it
   to a flex row here gives space-between real room to work with. */
.fact-value.value-unlimited {
    display: flex;
    justify-content: space-between;
}
.fact-value.value-unlimited .value-slash {
    display: none;
}
/* .stat-cell's own row layout (2026-07-31) sizes .fact-value to its
   content instead of the card's full width, so space-between here has no
   spare room to push "Unlimited" away from the count with - they'd
   render glued together ("0Unlimited") without this. A small fixed gap
   instead, same as every other value in this row-based panel. */
.stat-cell .fact-value.value-unlimited {
    display: flex;
    justify-content: flex-start;
    gap: 4px;
}

/* Keeps the "Resumed" badge on the same line as the clock instead of
   wrapping below it - the fact-value block is nested (label-time +
   timer-resume-toast, see board.html) so they're eligible to share a
   line, but the two together are wider than this narrow card at its
   normal width. white-space:nowrap removes the wrap opportunity at the
   space between them; .fact-value has no overflow rule (default
   visible), and neither does any ancestor up to .side-pad-outer-right,
   so the badge just spills past the card's edge for its brief 2s
   lifetime rather than pushing onto a second line. */
.fact-line.stats-time-group .fact-value {
    white-space: nowrap;
}

/* Hardest Skill + Overall Difficulty as two lines in one card rather than
   two separate cards - more compact. Always exactly two rows tall from
   its own natural content - no longer height-matched to
   #hint-banner-desktop or anything else (an earlier version of
   SudokuEngine.alignSidePadToBoard did). */
.fact-line-tall {
    display: flex;
    flex-direction: column;
    justify-content: center;
}
.combo-card {
    gap: 3px;
}
.combo-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}
.combo-row .fact-label {
    margin-bottom: 0;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    white-space: nowrap;
}
.combo-row .fact-value {
    font-size: 13px;
}

/* Small "?" affordance explaining a term a casual player may not know
   (Hardest Skill/Overall Difficulty) - a custom hover/focus popover rather
   than the native title attribute, which renders inconsistently and is
   invisible on touch. */
.info-btn {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 1px solid #94a3b8;
    background: none;
    color: #94a3b8;
    font-size: 9px;
    line-height: 1;
    font-weight: 700;
    padding: 0;
    cursor: help;
    position: relative;
    flex: none;
}
.info-btn .tip-bubble {
    display: none;
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    width: 190px;
    background-color: #0b1120;
    border: 1px solid #334155;
    color: #cbd5e1;
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.4;
    text-align: left;
    padding: 8px 10px;
    border-radius: 6px;
    /* Above .site-account-bar's own z-index:1000 (position:sticky top
       nav, see _account_nav.html) - Hardest Skill's tooltip sits close
       enough to the page's top edge that the nav bar was rendering over
       it at the old z-index:20, silently swallowing it; Overall
       Difficulty's, one row lower, happened to clear the nav and looked fine. */
    z-index: 1001;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
}
.info-btn:hover .tip-bubble, .info-btn:focus .tip-bubble, .info-btn:active .tip-bubble {
    display: block;
}

/* Fills whatever height the fact-line cards above leave empty on the
   right column with something a player actually benefits from noticing,
   rather than padding for its own sake - rotates through real,
   already-shipped features (see SudokuEngine.rotateTip). */
.tips-box {
    flex: 1;
    /* Reserved for the longest tip in SudokuEngine.TIPS (measured live:
       130-178px across all 5, not a guess) so the auto-rotating carousel
       never subtly reflows the column every 5-10s while a player is just
       reading the board - same "always reserved" approach already used
       for .difficulty-card/#hint-banner-desktop, applied here for the
       same reason. Re-measure and bump this if a longer tip is ever
       added to that array. */
    min-height: 180px;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 8px;
    padding: 12px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    text-align: left;
}
.tips-box .fact-label {
    margin-bottom: 8px;
}
/* Below ~677px viewport height, the right column's own fixed content
   (toolbar+hint+skill card+3 fact-lines+this box, none of which shrinks
   with viewport height) is taller than the grid, becoming the layout's
   binding constraint instead of the grid itself (measured live: right
   column ~590px constant). Used to hide this box past that point rather
   than let the column overflow - reverted 2026-08-01 per direct request:
   the column is now allowed to extend past the grid/Possibilities span
   instead (.board-page-floor already draws its line at whichever column
   is tallest), matching the left column's own #input-mode-card no longer
   docking away for the same reason - see SudokuEngine.alignSidePadToBoard. */
.tip-text {
    font-size: 13px;
    line-height: 1.5;
    color: #cbd5e1;
    flex: 1;
}
.tip-text b {
    color: #f8fafc;
}
.tips-dots {
    display: flex;
    gap: 5px;
    justify-content: center;
    margin-top: 10px;
}
.tips-dots button {
    width: 6px;
    height: 6px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background-color: #334155;
    cursor: pointer;
}
.tips-dots button.active {
    background-color: #0d6efd;
}

/* "Continuing your previous game" banner (see SudokuEngine.showResumeToast) -
   same collapse-when-empty shape as .timer-resume-toast, just a block-level
   centered line instead of an inline badge since this announces something
   page-level (which screen you're on), not a small clock-adjacent detail.
   position:absolute (anchored to .game-dashboard-container, already
   position:relative) rather than sitting in normal flow like it used to -
   it fires unconditionally at page load for any resumed game and fades
   out again ~2.6s later (see showResumeToast), and animating its
   max-width/padding/margin in and back out while still in-flow pushed
   the difficulty card (and the grid below it) down for that whole
   window, then back up again once it hid. Overlays the top of the
   difficulty card instead while visible, which is fine for something
   this transient. */
.resume-game-toast {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 10;
    display: block;
    max-width: 0;
    overflow: hidden;
    white-space: nowrap;
    opacity: 0;
    margin: 0 auto;
    padding: 0;
    text-align: center;
    border-radius: 10px;
    background-color: rgba(13, 110, 253, 0.12);
    color: rgba(13, 110, 253, 1);
    font-size: 13px;
    font-weight: 600;
    transition: opacity 0.3s ease, max-width 0.3s ease, padding 0.3s ease, margin 0.3s ease;
}
.resume-game-toast.resume-game-toast-visible {
    opacity: 1;
    max-width: 320px;
    padding: 6px 14px;
    margin: 0 auto;
}

/* "Number keys: Marks/Values" banner after Space toggles keyboardNumberMode
   (see SudokuEngine.showKeyboardModeToast) - fixed position rather than
   inline in the page flow like the other toasts, since Space can be
   pressed at any scroll position/mode (mid-play, anywhere on the board),
   not just right after a page-level event like a reload or a tab regaining
   focus. */
.keyboard-mode-toast {
    position: fixed;
    left: 50%;
    bottom: 32px;
    transform: translateX(-50%) translateY(8px);
    margin: 0;
    padding: 8px 16px;
    border-radius: 20px;
    background-color: #1e293b;
    border: 1px solid #334155;
    color: #f8fafc;
    font-size: 13px;
    font-weight: 600;
    opacity: 0;
    pointer-events: none;
    z-index: 50;
    transition: opacity 0.2s ease, transform 0.2s ease;
}
.keyboard-mode-toast.keyboard-mode-toast-visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

/* No bottom margin - flush against the grid (hint-toast's own base rule is
   zero-margin/collapsed too, see below), so this row sits right against
   the board's top edge instead of floating with space beneath it. Below
   1024px this is the only copy of New Game/mode-slider/undo/redo/hint/
   settings/feedback that exists at all (the wide-screen side pad, where
   they live instead, is itself display:none there) - hidden entirely at
   >=1024px so the stats-panel above it reads as sitting right above the
   grid, with nothing visible between them. */
/* Three explicit rows (2026-07-28) instead of one flex-wrap row - see
   board.html's own comment on the New Game/Undo/Redo wrap bug this
   replaced. Each .pencil-controls-row centers its own content; stacking
   them in a column removes the ambiguity a flex-wrap row had over
   exactly where the line break would land. */
.pencil-controls {
    /* gap/margin trimmed 10px->6px / 8px->4px (2026-07-31) - same
       reasoning as .difficulty-card's own margin-bottom just above:
       plain breathing room between New Game/Marks/toolbar rows, free to
       give back on short viewports where the Possibilities bar below the
       grid was getting cut off at the bottom (iPhone SE report). */
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    margin: 0 0 4px;
}
.pencil-controls-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
}
/* Previously unlabeled - .side-pad-label is the exact class the desktop
   Marks card already uses (board.html), reused rather than duplicated so
   any future label styling change applies to both automatically. Overrides that copy's
   center-aligned/block-level/margin-bottom rules (meant for sitting
   stacked above its own slider in a narrow side-panel column) back to
   inline, since this row sits label-then-slider side by side instead. */
.pencil-marks-label {
    display: inline;
    text-align: left;
    margin-bottom: 0;
}
.pencil-controls-tools {
    gap: 14px;
}

@media (min-width: 1024px) {
    .pencil-controls {
        display: none;
    }
}

/* Opens the display-settings panel (see SudokuEngine.openSettingsPanel). */
.settings-gear-btn {
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 5px;
    width: 32px;
    height: 32px;
    font-size: 16px;
    line-height: 1;
    color: #cbd5e1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}
.settings-gear-btn:hover { background-color: #334155; }

/* Undo/redo (see SudokuEngine.performUndo/performRedo) - same sizing as
   .settings-gear-btn, native `disabled` (not pointer-events, since these
   are real <button>s) so the browser gets styling/no-click for free at
   either end of the stack. */
.undo-redo-btn {
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 5px;
    width: 32px;
    height: 32px;
    font-size: 18px;
    line-height: 1;
    color: #cbd5e1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}
.undo-redo-btn:hover:not(:disabled) { background-color: #334155; }
.undo-redo-btn:disabled {
    opacity: 0.35;
    cursor: default;
}

/* Display-settings modal - same fixed-overlay/scale-in-card convention as
   .win-overlay/.mistake-limit-overlay (see SudokuEngine.openSettingsPanel/
   closeSettingsPanel). */
.settings-overlay {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(33, 37, 41, 0);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease, background-color 0.25s ease;
    z-index: 20;
}
.settings-overlay.settings-overlay-visible {
    opacity: 1;
    background-color: rgba(33, 37, 41, 0.45);
    pointer-events: auto;
}
.settings-card {
    background-color: #1e293b;
    border-radius: 14px;
    padding: 20px 22px;
    width: 90%;
    max-width: 340px;
    box-sizing: border-box;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
    transform: scale(0.85) translateY(10px);
    transition: transform 0.25s ease;
    text-align: left;
}
.settings-overlay-visible .settings-card {
    transform: scale(1) translateY(0);
}
.settings-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
}
.settings-card-title {
    margin: 0;
    font-size: 18px;
    color: #f8fafc;
}
.settings-card-close-btn {
    background: none;
    border: none;
    font-size: 16px;
    color: #94a3b8;
    cursor: pointer;
    padding: 4px;
    line-height: 1;
}

/* Feedback modal (see SudokuEngine.openFeedbackPanel) - reuses
   .settings-overlay/.settings-card for the chrome, these are just its
   form-specific contents. No login required - see feedback-btn. */
.feedback-intro {
    margin: 0 0 12px;
    font-size: 13px;
    color: #94a3b8;
}
#feedback-form textarea,
#feedback-form input[type="email"] {
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    border: 1px solid #334155;
    border-radius: 6px;
    font-size: 14px;
    font-family: inherit;
    margin-bottom: 10px;
    background-color: #0f172a;
    color: #f8fafc;
}
#feedback-form textarea {
    min-height: 5rem;
    resize: vertical;
}
#feedback-form textarea:focus,
#feedback-form input[type="email"]:focus {
    outline: none;
    border-color: #0d6efd;
}
.feedback-submit-btn {
    width: 100%;
    background-color: #0d6efd;
    border: none;
    border-radius: 6px;
    padding: 10px;
    font-size: 14px;
    font-weight: 600;
    color: #ffffff;
    cursor: pointer;
}
.feedback-submit-btn:hover:not(:disabled) { background-color: #0b5ed7; }
.feedback-submit-btn:disabled { opacity: 0.6; cursor: default; }
.feedback-status {
    margin: 10px 0 0;
    font-size: 13px;
    min-height: 1em;
}
.feedback-status.feedback-status-success { color: #198754; }
.feedback-status.feedback-status-error { color: rgba(220, 53, 69, 1); }

/* Practice-a-technique picker (see SudokuEngine.openPracticeTechniquePanel) -
   reuses .settings-overlay/.settings-card for the modal chrome, these are
   just the button-list contents. */
.practice-technique-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 50vh;
    overflow-y: auto;
}
.practice-technique-btn {
    display: block;
    width: 100%;
    box-sizing: border-box;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 6px;
    padding: 8px 12px;
    font-size: 14px;
    color: #f8fafc;
    cursor: pointer;
    text-align: left;
}
.practice-technique-btn:hover {
    background-color: #334155;
}

/* ⓘ info affordance + its popover (see SudokuEngine.buildTechniqueInfoAffordance/
   openTechniquePopover) - tap-to-toggle rather than CSS :hover, since :hover
   doesn't fire reliably on touchscreens; mouseenter is only layered on for
   desktop pointers as an enhancement. The popover expands inline below the row
   (flex-wrap + flex-basis: 100%) instead of floating/absolute-positioned, so it
   can't get clipped by .practice-technique-list's own overflow-y: auto. */
.practice-technique-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}
.practice-technique-row .practice-technique-btn {
    /* .practice-technique-btn's base rule sets width: 100% - flex-basis:
       auto (the "auto" in flex: 1 1 auto) resolves to that width, so
       without overriding it here the button always claims a full line by
       itself and pushes the ⓘ onto its own line above/below instead of
       sharing the row (found via a Playwright screenshot after reordering
       the two - the wrap happened either way, it just moved which element
       landed alone). */
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
}
.practice-technique-info-btn {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 50%;
    font-size: 13px;
    line-height: 1;
    color: #94a3b8;
    cursor: pointer;
}
.practice-technique-info-btn:hover {
    background-color: #334155;
}
.practice-technique-nextstep-btn {
    flex: 0 0 auto;
    padding: 6px 10px;
    background-color: #1e293b;
    border: 1px solid #0d6efd;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 600;
    color: #0d6efd;
    cursor: pointer;
    white-space: nowrap;
}
.practice-technique-nextstep-btn:hover {
    background-color: rgba(13, 110, 253, 0.08);
}
.practice-technique-popover {
    flex-basis: 100%;
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    box-sizing: border-box;
    background-color: #1e293b;
    border-radius: 6px;
    transition: max-height 0.2s ease, opacity 0.2s ease;
}
.practice-technique-popover.practice-technique-popover-visible {
    max-height: 220px;
    opacity: 1;
    padding: 8px 10px;
    border: 1px solid #334155;
}
.practice-technique-popover-summary {
    margin: 0 0 6px;
    font-size: 13px;
    line-height: 1.4;
    color: #cbd5e1;
}
.practice-technique-popover-link {
    font-size: 13px;
    font-weight: 600;
    color: #0d6efd;
    text-decoration: none;
}
.practice-technique-popover-link:hover {
    text-decoration: underline;
}

/* Anti-spam honeypot (see app.landing.routes._is_spam) - only a bot fills
   this in, hidden from real players entirely. */
.hp-field {
    position: absolute;
    left: -9999px;
    top: -9999px;
    height: 0;
    width: 0;
    opacity: 0;
}

.settings-toggle-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 12px 0;
    border-top: 1px solid #334155;
}
.settings-toggle-row:first-child { border-top: none; }
.settings-toggle-label {
    font-size: 14px;
    color: #f8fafc;
}

/* Mistake Limit/Hint Limit while Always-On Hint is active (see
   renderSettingsPanelBody/setAlwaysOnHintLevel) - visually suspended, not
   just functionally ignored, so it's obvious why they stopped responding. */
.settings-toggle-row.settings-row-dimmed {
    opacity: 0.45;
}
.settings-toggle-row.settings-row-dimmed .settings-toggle-label {
    color: #64748b;
}

.settings-note {
    margin: 0 0 4px;
    padding: 8px 10px;
    font-size: 12px;
    line-height: 1.4;
    color: #fcd34d;
    background-color: rgba(255, 193, 7, 0.15);
    border-radius: 6px;
}

/* Checkbox-driven pill switch - the checkbox itself stays in the DOM
   (for change events, a11y, keyboard toggling) but visually hidden, with
   the track/thumb painted from its :checked state. */
.settings-toggle-switch {
    position: relative;
    display: inline-block;
    width: 40px;
    height: 22px;
    flex: none;
    cursor: pointer;
}
.settings-toggle-switch input {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
}
.settings-toggle-switch-track {
    position: absolute;
    inset: 0;
    background-color: #475569;
    border-radius: 999px;
    transition: background-color 0.15s ease;
    pointer-events: none;
}
.settings-toggle-switch-track::before {
    content: "";
    position: absolute;
    width: 18px;
    height: 18px;
    left: 2px;
    top: 2px;
    background-color: #ffffff;
    border-radius: 50%;
    transition: transform 0.15s ease;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.settings-toggle-switch input:checked + .settings-toggle-switch-track {
    background-color: #0d6efd;
}
.settings-toggle-switch input:checked + .settings-toggle-switch-track::before {
    transform: translateX(18px);
}

/* Mistake Limit's value picker (see renderSettingsPanelBody) - a plain
   select rather than a switch, since it isn't a boolean. */
.settings-select {
    background-color: #1e293b;
    border: 1px solid #334155;
    border-radius: 6px;
    padding: 5px 8px;
    font-size: 14px;
    color: #f8fafc;
    cursor: pointer;
}
.settings-select:hover {
    background-color: #334155;
}

/* Arrow Key Wrap's 3-way slider (see SudokuEngine.buildThreeWaySliderRow) -
   same sliding-pill mechanics as .mark-mode-slider, but its own class so
   syncPencilControlsUI's page-wide query for that class can't sweep this
   up too. Thumb position keyed off SudokuSettings.ARROW_WRAP_MODE's actual
   values (the only caller today) - same pattern as .mark-mode-slider's
   own attribute selectors. */
.arrow-wrap-slider {
    position: relative;
    display: flex;
    width: 168px;
    background-color: #0f172a;
    border: 1px solid #334155;
    border-radius: 18px;
    padding: 3px;
}
.arrow-wrap-thumb {
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: calc(33.333% - 2px);
    background-color: #0d6efd;
    border-radius: 14px;
    transition: transform 0.2s ease;
}
.arrow-wrap-slider[data-active="WRAP_LINE"] .arrow-wrap-thumb { transform: translateX(100%); }
.arrow-wrap-slider[data-active="WRAP_NEXT"] .arrow-wrap-thumb { transform: translateX(200%); }
.arrow-wrap-slider[data-active="NONE"] .arrow-wrap-btn:nth-child(2),
.arrow-wrap-slider[data-active="WRAP_LINE"] .arrow-wrap-btn:nth-child(3),
.arrow-wrap-slider[data-active="WRAP_NEXT"] .arrow-wrap-btn:nth-child(4) {
    color: #ffffff;
}
.arrow-wrap-btn {
    position: relative;
    z-index: 1;
    flex: 1;
    border: none;
    background: transparent;
    padding: 4px 2px;
    font-size: 11px;
    font-weight: 600;
    color: #a8b6c8;
    cursor: pointer;
    border-radius: 14px;
    transition: color 0.15s ease;
    white-space: nowrap;
}
.arrow-wrap-btn:focus-visible {
    outline: 2px solid #0d6efd;
    outline-offset: 2px;
}

/* Two instances share this class/markup (see board.html): a fallback one
   in .pencil-controls for below 1024px (this base rule - hidden along with
   the rest of that row at/above 1024px, see .pencil-controls' own media
   query), and one inside the side pad's Marks box, narrowed to fit that
   column (see the override further down) - visible only >=1024px, since
   the pad itself is display:none below that width and would otherwise
   make the mode switch unreachable there. SudokuEngine.syncPencilControlsUI
   updates every .mark-mode-slider on the page, not just one by id, to keep
   both in sync. */
.mark-mode-slider {
    position: relative;
    display: flex;
    width: 130px;
    background-color: #334155;
    border-radius: 22px;
    padding: 3px;
}

.mark-mode-thumb {
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: calc(50% - 2px);
    background-color: #0d6efd;
    border-radius: 18px;
    transition: transform 0.2s ease;
}

.mark-mode-slider[data-active="AUTO"] .mark-mode-thumb {
    transform: translateX(100%);
}

.mark-mode-btn {
    position: relative;
    z-index: 1;
    flex: 1;
    border: none;
    background: transparent;
    padding: 4px 6px;
    font-size: 12px;
    font-weight: 600;
    color: #a8b6c8;
    cursor: pointer;
    border-radius: 18px;
    transition: color 0.15s ease;
    white-space: nowrap;
}

.mark-mode-slider[data-active="MANUAL"] .mark-mode-btn[data-mode="MANUAL"],
.mark-mode-slider[data-active="AUTO"] .mark-mode-btn[data-mode="AUTO"] {
    color: #ffffff;
}

/* Manual/Auto's desktop copy is gone (2026-07-31) - replaced by
   #input-mode-card's own "Auto Candidate Mode" checkbox (see board.html).
   .mark-mode-slider itself lives on, just for .pencil-controls' own
   below-1024px fallback copy now (see the base rule above and
   SudokuEngine.syncPencilControlsUI, which still needs to stay in sync
   with that one). */

/* Input-mode card (2026-07-31, see board.html's own comment on
   #input-mode-card) - two checkboxes plus one of two sliders, replacing
   the old floating mouse-direct button and two separate per-pad sliders.
   flex column + gap does all the internal spacing; the two sliders below
   supply their own margin-bottom:0 (removed) since this gap already
   covers them - only one is ever visible ([hidden]) at a time anyway. */
#input-mode-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.side-pad-checkbox-row {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 600;
    color: #cbd5e1;
    cursor: pointer;
}
.side-pad-checkbox-row input[type="checkbox"] {
    width: 16px;
    height: 16px;
    accent-color: #0d6efd;
    cursor: pointer;
}

/* Values pad's Digit-First/Cell-First mode slider (see
   SudokuEngine.setValuePadMode/VALUE_PAD_MODE) - moved into
   #input-mode-card (2026-07-31, see board.html's own comment there)
   alongside Direct Click's own Value/Marks slider, sharing the same slot
   in that card rather than living inside the Values pad itself. Only one
   of the two is ever un-hidden at a time - SudokuEngine.syncNumberEntryModeUI
   toggles which. Full width of that card, same as the Marks fallback
   slider in .pencil-controls uses of its own row.

   The [hidden] override just below is load-bearing, not decoration -
   both sliders set their own `display: flex` (right here), and author
   CSS always beats the browser's own [hidden] UA rule regardless of
   which one is later in source order (the exact bug already hit once
   this session with .inline-start-picker) - without it, both sliders
   would render stacked on top of each other at once. */
.value-pad-mode-slider[hidden],
.direct-click-mode-slider[hidden] {
    display: none;
}
.value-pad-mode-slider {
    position: relative;
    display: flex;
    width: 100%;
    background-color: #334155;
    border-radius: 22px;
    padding: 3px;
    box-sizing: border-box;
}
.value-pad-mode-thumb {
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: calc(50% - 3px);
    background-color: #0d6efd;
    border-radius: 18px;
    transition: transform 0.2s ease;
}
.value-pad-mode-slider[data-active="CELL_FIRST"] .value-pad-mode-thumb {
    transform: translateX(100%);
}
.value-pad-mode-btn {
    position: relative;
    z-index: 1;
    flex: 1;
    border: none;
    background: transparent;
    padding: 6px 4px;
    font-size: 12px;
    font-weight: 600;
    color: #a8b6c8;
    cursor: pointer;
    border-radius: 18px;
    transition: color 0.15s ease;
    white-space: nowrap;
}
.value-pad-mode-slider[data-active="DIGIT_FIRST"] .value-pad-mode-btn[data-mode="DIGIT_FIRST"],
.value-pad-mode-slider[data-active="CELL_FIRST"] .value-pad-mode-btn[data-mode="CELL_FIRST"] {
    color: #ffffff;
}
.value-pad-mode-btn:focus-visible {
    outline: 2px solid #0d6efd;
    outline-offset: 2px;
}

/* Values pad hides entirely while Direct Click is active (see
   handleSidePanelCellClick's Direct Click branch, which replaces it
   outright - none of Digit-First/Cell-First's arming applies there). The
   attribute lives on #side-pad-outer-left itself (set by
   SudokuEngine.syncNumberEntryModeUI), not on .side-pad-group, so this
   selector reaches .values-pad-section regardless of how deeply nested
   under it ends up being. */
#side-pad-outer-left[data-number-entry-mode="DIRECT_CLICK"] .values-pad-section {
    display: none;
}

/* Desktop mouse users' marks-number grid (see #marks-desktop-pad,
   SudokuEngine.usesDesktopMouseMarksPad - landed in a parallel phase after
   this rule's sibling above was written) hides the same way and for the
   same reason: Direct Click's own dot clicks replace arm-then-click
   entirely, so there's nothing left for this grid to do. The color
   swatches in the same #marks-desktop-pad container are deliberately NOT
   covered by this selector - Direct Click's Marks position still arms a
   color exactly like Pads mode does, so they stay visible and functional
   either way. !important beats .marks-desktop-pad-numbers' own
   `display: grid` (2026-07-29 desktop marks-pad reuse) regardless of
   which rule happens to come later in the cascade. */
#side-pad-outer-left[data-number-entry-mode="DIRECT_CLICK"] .marks-desktop-number-grid {
    display: none !important;
}

/* Direct Click mode's own Value/Marks slider - lives in #input-mode-card
   now (2026-07-31, see board.html's own comment there) sharing a slot
   with .value-pad-mode-slider above; shown/hidden via the [hidden]
   attribute directly (SudokuEngine.syncNumberEntryModeUI), not a class -
   no separate wrapper card of its own to gate anymore now that both
   sliders share one. Container/thumb below are already identical to
   .value-pad-mode-slider/.value-pad-mode-thumb's own values - only
   .direct-click-mode-btn needed correcting (see its own comment) to
   actually match in the rendered result. */
.direct-click-mode-slider {
    position: relative;
    display: flex;
    width: 100%;
    background-color: #334155;
    border-radius: 22px;
    padding: 3px;
    box-sizing: border-box;
}
.direct-click-mode-thumb {
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: calc(50% - 3px);
    background-color: #0d6efd;
    border-radius: 18px;
    transition: transform 0.2s ease;
}
.direct-click-mode-slider[data-active="MARKS"] .direct-click-mode-thumb {
    transform: translateX(100%);
}
/* Never updated to match .value-pad-mode-btn's own padding/font-size when
   that one was resized (2026-07-31) - left at its original smaller size
   since Direct Click isn't the default-visible state, so it went
   unnoticed until reported directly. Now identical to
   .value-pad-mode-btn on both counts. */
.direct-click-mode-btn {
    position: relative;
    z-index: 1;
    flex: 1;
    border: none;
    background: transparent;
    padding: 6px 4px;
    font-size: 12px;
    font-weight: 600;
    color: #a8b6c8;
    cursor: pointer;
    border-radius: 18px;
    transition: color 0.15s ease;
    white-space: nowrap;
}
.direct-click-mode-slider[data-active="VALUE"] .direct-click-mode-btn[data-mode="VALUE"],
.direct-click-mode-slider[data-active="MARKS"] .direct-click-mode-btn[data-mode="MARKS"] {
    color: #ffffff;
}
.direct-click-mode-btn:focus-visible {
    outline: 2px solid #0d6efd;
    outline-offset: 2px;
}

