/* static/styles/base.css */


/* ---------- Base theme ---------- */
:root {
    --bg: #090c13;
    --fg: #e8e8e8;
    --muted: #7a7f87;
    --tile-muted: #5a6070;
    --pin: #ffcf33;
    --boom: #ff4d4d;
    --zero-bg: #1a1d24;
    --btn-bg: #2a2f3f;
    --btn-bg-hover: #353c4e;
    --accent: #6aa0ff;
    --accent-hover: #82b4ff;
    --box-overlay: rgba(26, 28, 51, 0.55);
    --radius: 6px;

    /* Text drawn on top of an --accent fill. Both themes' accents are blues of
       very different lightness, so this flips: dark ink on the dark theme's
       pale blue, white on the light theme's deeper one. It replaces `--black`
       and `--white`, which were referenced in three places and defined in
       none -- an undefined var() makes the whole declaration invalid, so the
       active tool button was inheriting --fg onto its own accent fill at about
       1.9:1. */
    --on-accent: #0b1b33;

    /* One hairline. There were five spellings of this before: var(--fg) at full
       strength, a bare #dce3ec, a color-mix of --fg, var(--tile-muted), and a
       raw rgba in the header. */
    --border: rgba(232, 232, 232, 0.22);

    /* The winning result message. Its counterpart is --boom, which the board's
       mines already use; the message used to hard-code the light theme's red,
       so on the dark theme a loss was announced in a different red from the
       mine that caused it. */
    --win: #57c95e;

    /* The two entanglement-probe regions. Deliberately the same on both themes:
       they are identity colours for A and B, not part of the surface palette,
       and they are drawn as rings rather than text. */
    --probe-a: #38bdf8;
    --probe-b: #f59e0b;

    --shadow-subtle: 0 1px 2px rgba(0, 0, 0, 0.25);

    --header-bg: rgba(32, 36, 49, 0.95);

    /* Tile size is not fixed here: game.css derives it from the width the board
       container actually has, so a wide board shrinks its cells to fit instead
       of overflowing the page. These are only the bounds that derivation is
       held between -- the comfortable maximum, and the floor below which a
       cell stops being readable and the board scrolls sideways instead. */
    --tile-max: 40px;
    --tile-min: 16px;
    --tile-gap: clamp(1px, 0.4vw, 3px);

    /* Clue colour ramp: low clues green, high clues red, as before. The hue
       sweep is the palette; what lives here is the lightness and chroma that
       hue is drawn at, because those are what have to differ between the
       themes -- the same colour cannot read on a near-black and a near-white
       tile. game.css builds the final colour from these plus the per-cell
       --clue-t that render.js sets.

       --clue-l-drop is how much lightness the ramp sheds on the way to red. A
       saturated red does not exist at the lightness a green wants; holding one
       lightness across the whole sweep ends it at a pale salmon instead. */
    --clue-l: 0.87;
    --clue-l-drop: 0.17;
    --clue-c: 0.18;
}

/* ---------- Light theme: variables only ---------- */
html.light {
    /* The three board greys used to sit within about three percent luminance of
       each other, which left the grid invisible: you could only tell where a
       cell was by whether it had a number in it. They are now spread out, and
       ordered the way the dark theme orders them -- the page is the ground, an
       unexplored cell sits proud of it, and an explored cell is sunk below it. */
    --bg: #e6ebf3;
    --fg: #1e1e1e;
    --muted: #5c636e;
    --tile-muted: #9ba7b9;
    --pin: #d99000;
    --boom: #e64545;
    --zero-bg: #d7dfea;
    --btn-bg: #fbfcfe;
    --btn-bg-hover: #edf2fa;
    --accent: #3f7ad6;
    --accent-hover: #5f95ea;
    --header-bg: rgba(255, 255, 255, 0.82);
    --box-overlay: rgba(255, 255, 255, 0.62);
    --on-accent: #ffffff;
    --border: #dce3ec;
    --win: #2e7d32;
    --shadow-subtle: 0 1px 2px rgba(30, 45, 80, 0.10);

    /* Much darker than the dark theme's: these sit on a near-white tile, where
       the dark theme's bright ramp washes out. Only a small lightness drop is
       needed on the way to red, because these are dark colours on a light
       ground and red is already at home at this lightness.

       The exact lightness is a judgement call, not a free choice. A green light
       enough to look green cannot reach 4.5:1 on any light background -- the
       contrast equation has no solution above about L 0.52 -- so holding strict
       AA across the whole ramp forces the middle of it into olive and khaki.
       This sits just below that line. The numbers are set for the explored
       cell's background, not the page's, because that is what clue text is
       actually drawn on: clues from about 1.5 up clear 4.5:1 there, and the
       lowest bottom out at 4.2:1, carried by semibold digits on a tile at least
       16px square. The alternative considered was keeping AA and desaturating
       the yellow band to slate, which is accessible but much less colourful. */
    --clue-l: 0.50;
    --clue-l-drop: 0.06;
    --clue-c: 0.19;
}

/* ---------- Layout ---------- */
/* No height: 100% here. It used to be harmless, but it caps the body's box at
   the viewport, and a sticky header can only stay pinned for as long as its
   containing block lasts -- on a page taller than the screen the header would
   scroll away partway down. min-height on body below still guarantees a
   full-height page, and html's background paints the canvas regardless. */
html,
body {
    margin: 0;
}

html {
    background: var(--bg);
}

body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background: var(--bg);
    color: var(--fg);
    font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, 'Helvetica Neue', Arial, sans-serif;
}

.app-body {
    flex: 1;
    display: flex;
    flex-direction: column;
}

main {
    flex: 1;
    padding: 16px 24px 0;
    max-width: 1000px;
    width: 100%;
    margin: 0 auto;
    box-sizing: border-box;
    overflow-x: hidden;
}

/* ---------- Headings ---------- */
h1 {
    color: var(--accent);
    font-size: clamp(1.4rem, 5vw, 2rem);
    margin-bottom: 4px;
    text-align: center;
}

h2 {
    font-size: clamp(1rem, 1vw, 2rem);
    font-weight: bold;
    opacity: 0.85;
    margin-top: 0;
    text-align: center;
}

/* ---------- Header ---------- */
/* Sticky, not fixed. A fixed header is out of flow, so the page has to be
   pushed down by a hard-coded --header-height -- and that number was a lie at
   any width where the header wrapped to two lines, which put the first row of
   content underneath it. Sticky keeps the header on screen while occupying its
   own real height, whatever that turns out to be. */
.app-header {
    position: sticky;
    top: 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 18px;
    background: var(--header-bg);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border-radius: 0 0 var(--radius) var(--radius);
    z-index: 100;
}

.app-header h1 {
    margin: 0;
    font-size: clamp(1.2rem, 3vw, 1.4rem);
    font-weight: 600;
    color: var(--accent);
    /* The title is one line at every width. Wrapping it broke the header's
       height and orphaned the bomb glyph onto a line of its own. */
    white-space: nowrap;
}

.header-right {
    display: flex;
    align-items: center;
    gap: 10px;
}

.header-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    background: none;
    border: none;
    border-radius: var(--radius);
    color: var(--fg);
    font-size: 1.25rem;
    cursor: pointer;
    transition: color 0.2s ease, transform 0.15s ease;
    text-decoration: none;
}

.header-action:hover {
    color: var(--accent);
    transform: scale(1.15);
}

/* Light: component overrides */
html.light .app-header {
    background: var(--header-bg);
    border-bottom: 1px solid var(--border);
}

html.light .app-header h1 {
    color: var(--accent);
}

html.light .header-action:hover {
    color: var(--accent-hover);
}

/* ---------- Help Toggle Button ---------- */
#toggle-help {
    cursor: pointer;
    transition: background-color 0.2s ease, box-shadow 0.2s ease;
}

#toggle-help.active {
    background-color: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 0 6px var(--accent);
    border-radius: 4px;
}

/* ---------- Base Buttons ---------- */
.btn {
    gap: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Transparent rather than `none`: the light theme paints this edge and the
       dark theme does not, and keeping the 1px reserved either way means
       toggling the theme never nudges a button's size. Dark separates a button
       from its panel by fill instead -- see --btn-bg. */
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: var(--btn-bg);
    color: var(--fg);
    cursor: pointer;
    box-shadow: var(--shadow-subtle);
    transition: background 0.2s, transform 0.05s, color 0.2s;
    padding: 8px 12px;
    font-size: 0.95rem;
    min-width: 44px;
    text-decoration: none;
}

/* ---------- Size Modifiers ---------- */
.btn.tool {
    padding: 6px 12px;
    font-size: 0.85rem;
    min-width: 36px;
}

.btn.help {
    padding: 5px 10px;
    font-size: 0.85rem;
    min-width: 32px;
}

/* ---------- States ---------- */
.btn:hover {
    background: var(--btn-bg-hover);
}

.btn:active {
    transform: scale(0.96);
}

.btn:focus-visible,
.header-action:focus-visible,
.board button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ---------- Light Theme Overrides ---------- */
/* Light cannot separate button from panel by fill: --btn-bg is #fbfcfe and the
   panel composites to about #fafbfd. It uses an edge, where dark uses fill. */
html.light .btn {
    border-color: var(--border);
}

html.light .btn:hover {
    border-color: var(--accent);
}

.btn:disabled {
    background: var(--btn-bg);
    color: var(--muted);
    opacity: 0.6;
    box-shadow: none;
    cursor: not-allowed;
}

.btn.active {
    outline: 2px solid var(--accent);
    border-color: var(--accent);
    background: var(--accent);
    color: var(--on-accent);
}

/* ---------- Forms ---------- */

.actions form button,
.actions-form button {
    flex: 1 1 calc(50% - 10px);
    min-width: 120px;
}

@media (min-width: 720px) {
    .actions form button,
    .actions-form button {
        flex: 0 0 auto;
    }
}

/* ---------- Rendered document content ---------- */
/* Setup explainers, the About page, the About overlay and the admin DB notes
   all render the same package-owned content markup. It lives here rather than in
   setup.css because base.html mounts the About overlay on every page: while
   these rules sat in setup.css the overlay was completely unstyled anywhere
   that sheet was not loaded, so its links fell back to the browser default
   blue and visited purple. */
.doc-content {
  max-width: 900px;
  margin: 28px auto;
  padding: 10px 20px;
  border-radius: var(--radius);
  background: var(--box-overlay);
  backdrop-filter: blur(2px);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}

.doc-content h1 {
  text-align: center;
  margin-top: 0;
}

.doc-content h2 {
  font-weight: 700;
  font-size: clamp(1.1rem, 1.5vw, 1.25rem);
}

.doc-content p {
  margin: 0.6rem 0;
}

.doc-content ul {
  margin: 0.3rem 0 0.8rem 1.2rem;
}

.doc-content li {
  margin: 0.25rem 0;
}

.doc-content a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in oklab, var(--accent), transparent 60%);
}

.doc-content a:hover {
  border-bottom-color: var(--accent);
}

/* Soft divider */
.doc-content .rule {
  height: 1px;
  background: linear-gradient(to right,
      transparent,
      color-mix(in oklab, var(--fg), transparent 80%),
      transparent);
  margin: 1rem 0;
}

@media (max-width: 640px) {
  .doc-content {
    margin: 18px auto;
    padding: 10px 14px;
  }
}

/* ---------- Footer ---------- */
.footer {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    flex-wrap: wrap;
    row-gap: 6px;
    padding: 12px 0;
    font-size: 0.85rem;
    color: var(--muted);
    opacity: 0.8;
    background: var(--bg);
}

.footer-logo {
    height: clamp(20px, 1.25vh + 1.25vw, 28px);
    width: auto;
    vertical-align: middle;
    object-fit: contain;
    display: inline-block;
}

/* The Nordita logo is an inlined single-colour SVG (templates/_nordita_logo.svg)
   painted entirely in `currentColor`. That is what makes it theme-tunable: it takes
   the footer link colour below, so it is muted grey on both themes and picks up the
   accent colour on hover. Sizing happens on this link; the SVG just fills it. */
.footer-logo--nordita {
    display: inline-flex;
    align-items: center;
}

.footer-logo--nordita svg {
    display: block;
    height: 100%;
    width: auto;
}

/* The GitHub mark. Sized in em so it stays in proportion to the footer text it
   sits between, and painted in currentColor by the SVG itself, so it follows
   the footer link colour and its accent on hover like the Nordita logo does. */
.footer-icon {
    display: inline-flex;
    align-items: center;
    vertical-align: -0.18em;
}

.footer-icon svg {
    display: block;
    width: 1.15em;
    height: 1.15em;
}

/* Logos are not text: the footer's link underline (see .footer a:hover below)
   would cut straight through them. Three shapes to cover, because the class
   sits in a different place in each: on the link itself for Nordita and for the
   GitHub mark, whose SVGs are inlined, and on the <img> inside the link for
   WINQ. */
.footer a.footer-logo:hover,
.footer a.footer-logo-link:hover,
.footer a.footer-icon:hover {
    text-decoration: none;
}

.footer a {
    color: var(--muted);
    text-decoration: none;
}

.footer a:hover {
    color: var(--accent);
    text-decoration: underline;
}

/* ---------- Forms ---------- */

label {
  justify-self: end;
  font-size: 0.95rem;
  opacity: 0.9;
}

input[type="number"],
select {
  padding: 8px 10px;
  border-radius: var(--radius);
  border: 1px solid transparent;
  background: var(--btn-bg);
  color: var(--fg);
  font-size: 0.95rem;
}

input[type="number"]:focus,
select:focus {
  outline: none;
  border-color: var(--accent);
}

input[type="number"]:focus-visible,
select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}


/* ---------- Animations ---------- */
@keyframes pop {
    0% {
        transform: scale(0.8);
        opacity: 0;
    }

    100% {
        transform: scale(1);
        opacity: 1;
    }
}

/* The header icon alternates between the bomb and the blast. Both glyphs live
   in the same single-cell grid so they stack without the header width jumping,
   and each runs the same fade cycle half a period apart. */
.mine-icon {
    display: inline-grid;
    place-items: center;
    vertical-align: text-bottom;
}

.mine-icon > span {
    grid-area: 1 / 1;
    animation: quantum-fade 4s ease-in-out infinite;
}

/* Negative delay starts the blast mid-cycle, so it is already faded out at
   page load instead of flashing on top of the bomb. */
.mine-icon > .mine-blast {
    animation-delay: -2s;
}

/* Each glyph is visible for exactly half the period, so with the half-period
   offset the two windows are exact complements: one is fully faded out before
   the other starts fading in, and the loop is symmetric in both directions
   (1.2s held, 0.4s fade out, 2s hidden, 0.4s fade in). */
@keyframes quantum-fade {
    0%,
    15% {
        opacity: 1;
    }

    25%,
    75% {
        opacity: 0;
    }

    85%,
    100% {
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    /* The global reduced-motion rule freezes animations on their last keyframe,
       where both glyphs are opaque; show only the bomb instead. */
    .mine-icon > .mine-blast {
        display: none;
    }
}

/* ---------- Responsive tweaks ---------- */
/* The header's three groups stop fitting on one line well before the 480px
   phone breakpoint below: the title alone is ~250px at its full size, and the
   player count and the icon row take the rest. Compact it from 600px down, and
   drop the player count -- the least load-bearing of the three -- rather than
   let the row wrap. */
@media (max-width: 600px) {
    .app-header {
        padding: 8px 12px;
        gap: 8px;
    }

    .app-header h1 {
        font-size: 1.05rem;
    }

    .header-left {
        display: none;
    }

    .header-right {
        gap: 4px;
    }

    .header-action {
        width: 32px;
        height: 32px;
        font-size: 1.1rem;
    }
}

@media (max-width: 480px) {
    main {
        padding-left: 8px;
        padding-right: 8px;
    }

    .app-header {
        padding: 8px 10px;
    }

    .app-header h1 {
        font-size: 1rem;
    }

    .board-container {
        margin: 0.5em 0;
    }

    .board td {
        padding: clamp(0px, 0.2vw, 2px);
    }

    .board button {
        border-radius: 4px;
    }

    .footer {
        font-size: 0.8rem;
    }

    .footer-logo {
        height: 20px;
    }
}

@media (max-width: 360px) {
    main {
        padding: 12px 4px 0;
    }

    .board button {
        border-radius: 2px;
    }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
        transition-duration: 0.01ms !important;
    }
}

/* ---------- Shared modal overlay (About) ---------- */
.app-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.25rem;
    background: rgba(0, 0, 0, 0.55);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
}

/* `hidden` must beat the display:flex above. */
.app-overlay[hidden] {
    display: none;
}

.app-overlay__panel {
    position: relative;
    width: min(720px, 100%);
    max-height: 85vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 1.5rem 1.75rem;
    background: var(--bg);
    color: var(--fg);
    border-radius: 10px;
    box-shadow: 0 12px 48px rgba(0, 0, 0, 0.45);
}

.app-overlay__close {
    position: absolute;
    top: 0.5rem;
    right: 0.6rem;
    border: none;
    background: transparent;
    color: var(--fg);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0.65;
    padding: 0.25rem 0.4rem;
}
.app-overlay__close:hover {
    opacity: 1;
}

/* The bottom "Close" is for small screens only; hidden on wider screens (which use
   the ✕). */
.app-overlay__footer {
    display: none;
}

/* Lock background scrolling while the overlay is open (esp. on mobile). */
body.overlay-open {
    overflow: hidden;
}

/* On small screens the panel fills the viewport so the content has room to breathe
   and the ✕ stays reachable, instead of a cramped floating card. */
@media (max-width: 600px) {
    .app-overlay {
        padding: 0;
    }
    .app-overlay__panel {
        width: 100%;
        height: 100%;
        max-height: 100%;
        border-radius: 0;
        /* Leave room for the sticky bottom Close bar so content isn't hidden. */
        padding: 1.25rem 1.1rem 0;
    }
    /* On small screens the ✕ gives way to a bottom "Close" bar. */
    .app-overlay__close {
        display: none;
    }
    .app-overlay__footer {
        display: block;
        position: sticky;
        bottom: 0;
        margin: 1rem -1.1rem 0;
        padding: 0.75rem 1.1rem calc(0.75rem + env(safe-area-inset-bottom, 0px));
        background: var(--bg);
        border-top: 1px solid var(--btn-bg);
    }
    .app-overlay__footer .btn {
        display: block;
        width: 100%;
        text-align: center;
    }
}
