:root {
  color-scheme: only dark;
  --page-bg: #292637;
  --board-bg: #474553;
  --grid-line: #292637;
  --cell-empty: #474553;
  --grid-gap: 4px;
  /* Cell size is bounded by BOTH width and height so the 9x9 board always fits
     the viewport without scrolling, on any aspect ratio. */
  --cell-size: min(
    64px,
    calc((100vw - 32px - var(--grid-gap) * 10) / 9),
    calc((100dvh - 220px - var(--grid-gap) * 10) / 9)
  );
  --board-width: calc(var(--cell-size) * 9 + var(--grid-gap) * 10);
  --ball-size: calc(var(--cell-size) * 0.875);
  --text: #ffffff;
  --muted: #cfc9dc;
  --accent: #53ff81;

  /* Contrast palette (colorblind-aware): distinct hue AND lightness. */
  --c0: #e6194b; /* red    */
  --c1: #1f6fe6; /* blue   */
  --c2: #28a745; /* green  */
  --c3: #ffd500; /* yellow */
  --c4: #9b26b6; /* purple */
  --c5: #19c6d6; /* cyan   */
  --c6: #f0f0f0; /* white  */
}

* {
  box-sizing: border-box;
}

/* No browser scroll, no pull-to-refresh, no rubber-banding — ever. */
html,
body {
  margin: 0;
  height: 100%;
  width: 100%;
  overflow: hidden;
  position: fixed;
  inset: 0;
  overscroll-behavior: none;
  touch-action: none;
  /* Yandex moderation: no element of the game may be selectable, and a long-tap
     must not raise the system selection/callout menu. The game is not text, so
     selection is disabled everywhere (contextmenu is also blocked in JS). */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  background: var(--page-bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.app {
  height: 100%;
  height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px;
}

.page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.topbar {
  width: var(--board-width);
  /* Match the board's inner padding so text/icons line up with the cells, not
     with the board's outer edge (which sits one grid-gap further out). */
  padding: 0 var(--grid-gap);
  display: flex;
  align-items: center;
  /* Title removed — keep the score/best block on the right edge. */
  justify-content: flex-end;
  gap: 12px;
}

.topbar-stats {
  display: flex;
  gap: 16px;
  text-align: right;
}

.panel {
  width: var(--board-width);
  padding: 0 var(--grid-gap);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  /* On very narrow screens the toolbar wraps to its own line instead of the
     last button getting clipped. */
  flex-wrap: wrap;
  row-gap: 10px;
}

.label {
  font-size: 12px;
  letter-spacing: 1px;
  color: var(--muted);
  text-transform: uppercase;
}

.score,
.best,
.next {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.value {
  font-size: 22px;
  font-weight: 700;
}

/* Pull the "Следующие" label down toward the balls (which are bottom-anchored,
   so without this the label floats too high above them). */
.next .label {
  margin-bottom: calc(var(--ball-size) * -0.35);
}

.next-balls {
  display: flex;
  gap: 8px;
  min-height: var(--ball-size);
  /* Sit the balls on the bottom so their baseline lines up with the toolbar
     icons (the panel aligns its children at flex-end). */
  align-items: flex-end;
}

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

.tool-btn {
  width: 40px;
  height: 40px;
  border: none;
  border-radius: 8px;
  background: var(--board-bg);
  color: #fff;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.tool-btn:disabled {
  opacity: 0.4;
  cursor: default;
}
.tool-btn:focus-visible,
.btn:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.board-wrap {
  position: relative;
}

.board {
  display: grid;
  grid-template-columns: repeat(9, var(--cell-size));
  grid-template-rows: repeat(9, var(--cell-size));
  gap: var(--grid-gap);
  padding: var(--grid-gap);
  background: var(--grid-line);
  touch-action: manipulation;
}

.cell {
  width: var(--cell-size);
  height: var(--cell-size);
  background: var(--cell-empty);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.cell:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
}

.ball {
  width: var(--ball-size);
  height: var(--ball-size);
  border-radius: 50%;
  /* Sphere shading: top-left highlight + bottom shadow over the base color
     gives depth so the balls don't read as flat discs. The edge-falloff layer
     keeps light colors (yellow/cyan/white) reading as spheres too. */
  background:
    radial-gradient(circle at 33% 28%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 40%),
    radial-gradient(circle at 50% 45%, rgba(0, 0, 0, 0) 55%, rgba(0, 0, 0, 0.3) 100%),
    radial-gradient(circle at 50% 118%, rgba(0, 0, 0, 0.38) 0%, rgba(0, 0, 0, 0) 62%),
    var(--ball-color, #888);
  box-shadow:
    inset 0 2px 3px rgba(255, 255, 255, 0.28),
    inset 0 -4px 7px rgba(0, 0, 0, 0.3),
    0 2px 4px rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 120ms ease, box-shadow 120ms ease;
}

/* Pattern symbol — second cue beyond color (colorblind support). Drawn as an
   inline SVG (injected by JS) so size/centering are pixel-consistent across
   shapes — unicode glyphs had uneven metrics. Hidden unless .show-patterns. */
/* White ball: a pure-white highlight over a white body is invisible, so darken
   the body to light grey and let the highlight pop against it. */
.ball.c6,
.next-ball.c6 {
  background:
    radial-gradient(circle at 33% 28%, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0) 42%),
    radial-gradient(circle at 50% 45%, rgba(0, 0, 0, 0) 55%, rgba(0, 0, 0, 0.28) 100%),
    radial-gradient(circle at 50% 118%, rgba(0, 0, 0, 0.32) 0%, rgba(0, 0, 0, 0) 62%),
    #cfd2d6;
}

.ball svg.pattern {
  width: 52%;
  height: 52%;
  display: none;
  pointer-events: none;
}
.show-patterns .ball svg.pattern,
.show-patterns .next-ball svg.pattern {
  display: block;
}

/* Preview ball: small, sitting in the cell where it will grow next turn. */
.ball.preview {
  width: 42%;
  height: 42%;
  opacity: 0.8;
}

/* Double ring (dark contour + bright accent) so the selection reads on any
   color, including the white ball where a plain white ring vanished. */
.ball.selected {
  box-shadow:
    inset 0 2px 3px rgba(255, 255, 255, 0.28),
    inset 0 -4px 7px rgba(0, 0, 0, 0.3),
    0 0 0 2px rgba(0, 0, 0, 0.45),
    0 0 0 5px var(--accent);
  animation: selectedPulse 1.2s ease-in-out infinite;
}
.ball.pop {
  animation: pop 180ms ease-out;
}
.ball.clearing {
  animation: clearOut 220ms ease-in forwards;
}

.next-ball {
  width: calc(var(--ball-size) * 0.6);
  height: calc(var(--ball-size) * 0.6);
  border-radius: 50%;
  background:
    radial-gradient(circle at 33% 28%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 40%),
    radial-gradient(circle at 50% 118%, rgba(0, 0, 0, 0.38) 0%, rgba(0, 0, 0, 0) 62%),
    var(--ball-color, #888);
  box-shadow:
    inset 0 1px 2px rgba(255, 255, 255, 0.28),
    inset 0 -3px 5px rgba(0, 0, 0, 0.3),
    0 1px 3px rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
}
.next-ball svg.pattern {
  width: 55%;
  height: 55%;
  display: none;
  pointer-events: none;
}

.cell--valid {
  background: #666475;
}
.cell--active {
  background: #3e3c4a;
}
/* Hint booster: highlight the suggested from/to cells. */
.cell--hint {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
  animation: hintPulse 0.9s ease-in-out infinite;
}
@keyframes hintPulse {
  50% { outline-color: transparent; }
}
/* 3×3-removal targeting: dim the board and force a crosshair everywhere
   (cells/balls otherwise show the pointer cursor). */
.board--targeting,
.board--targeting .cell,
.board--targeting .ball {
  cursor: crosshair;
}
.board--targeting {
  opacity: 0.9;
}
/* The 9 cells that would be removed, previewed under the cursor. */
.cell--target {
  background: rgba(255, 80, 80, 0.18);
  box-shadow: inset 0 0 0 2px rgba(255, 110, 110, 0.95);
}
@media (prefers-reduced-motion: reduce) {
  .cell--hint { animation: none; }
}

/* Overlays / modals */
.overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(20, 18, 30, 0.92);
  z-index: 10;
  padding: 16px;
  overflow: hidden; /* no scroll anywhere — content is compacted to always fit */
}
.overlay-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
  max-width: 360px;
  width: 100%;
}
.overlay-title {
  font-size: clamp(28px, 8vw, 56px);
  font-weight: 700;
}
.overlay-row {
  font-size: 18px;
}

.btn {
  background: var(--board-bg);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 12px 20px;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  width: 100%;
  max-width: 260px;
}
.btn-primary {
  background: var(--accent);
  color: #1a1a1a;
}

.rules-list,
.records-list {
  text-align: left;
  margin: 0;
  padding-left: 22px;
  line-height: 1.5;
}
.records-list {
  list-style: none;
  padding-left: 0;
  width: 100%;
  max-width: 280px;
}
.record-row {
  display: grid;
  grid-template-columns: 2.2em 1fr auto;
  align-items: baseline;
  gap: 8px;
  padding: 6px 4px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.record-row:last-child {
  border-bottom: none;
}
.record-rank {
  font-size: 16px;
  text-align: center;
  opacity: 0.7;
  font-weight: 700;
}
.rank-1 .record-rank { color: #ffd700; opacity: 1; } /* gold   */
.rank-2 .record-rank { color: #c0c0c0; opacity: 1; } /* silver */
.rank-3 .record-rank { color: #cd7f32; opacity: 1; } /* bronze */
.record-score {
  font-weight: 700;
  font-size: 17px;
}
.rank-1 .record-score { color: #ffd700; }
.rank-2 .record-score { color: #c0c0c0; }
.rank-3 .record-score { color: #cd7f32; }
.record-meta {
  font-size: 12px;
  opacity: 0.6;
  text-align: right;
  white-space: nowrap;
}
.iap-msg {
  font-size: 13px;
  opacity: 0.8;
  min-height: 1em;
  text-align: center;
}
.toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(12px);
  background: rgba(20, 18, 28, 0.95);
  color: #fff;
  padding: 10px 18px;
  border-radius: 10px;
  font-size: 14px;
  font-weight: 600;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s, transform 0.2s;
  z-index: 50;
}
.toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  max-width: 260px;
  gap: 12px;
}
.setting-label {
  color: var(--muted);
}
/* Compact one-liner under the difficulty dropdown explaining the selection. */
.setting-hint {
  width: 100%;
  max-width: 260px;
  margin: -2px 0 2px;
  color: var(--muted);
  font-size: 12px;
  text-align: left;
}
/* Same small text, but centered under a booster button (free vs ad). */
.booster-note {
  margin: -8px 0 2px;
  text-align: center;
}
.setting-control {
  font: inherit;
}
select.setting-control {
  background: var(--board-bg);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 6px 8px;
  /* Equal width for both selects, scales with the viewport (so it never
     overflows on narrow screens) and stays identical because both use the
     same clamp. Doesn't depend on parent width like a % would. */
  flex: none;
  width: clamp(96px, 34vw, 140px);
}

.hidden {
  display: none !important;
}

/* Loading gate */
#loading-screen {
  z-index: 100;
}
.loader-spinner {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 5px solid rgba(255, 255, 255, 0.2);
  border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
.loader-text {
  color: var(--muted);
}
.rules-hint {
  display: block;
  width: 100%;
  max-width: 260px;
  height: auto;
  margin-top: 6px;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .loader-spinner { animation-duration: 2s; }
}

@keyframes selectedPulse {
  0%, 100% { transform: scale(0.82); }
  50% { transform: scale(1); }
}
@keyframes pop {
  from { transform: scale(0.6); }
  to { transform: scale(1); }
}
@keyframes clearOut {
  to { transform: scale(0); opacity: 0; }
}

/* Respect vestibular sensitivity. */
@media (prefers-reduced-motion: reduce) {
  .ball,
  .ball.selected,
  .ball.pop,
  .ball.clearing {
    animation: none !important;
    transition: none !important;
  }
}

/* Short viewports (landscape phones): compact every modal so it fits fully
   without scrolling — moderation forbids any scroll, so we shrink instead. */
@media (max-height: 520px) {
  .overlay {
    padding: 10px;
  }
  .overlay-content {
    gap: 7px;
  }
  .overlay-title {
    font-size: clamp(18px, 6.5vh, 28px);
  }
  .overlay-row {
    font-size: 14px;
  }
  .rules-list,
  .records-list {
    font-size: 12px;
    line-height: 1.25;
  }
  .rules-hint {
    height: 40px;
    width: auto;
  }
  .btn {
    padding: 8px 18px;
    font-size: 14px;
  }
  .setting {
    gap: 8px;
  }
}

/* Landscape on short screens (phones held sideways): stack the controls in a
   side column so the board can use the full height instead of being squeezed
   by the vertically stacked header/panel. */
@media (orientation: landscape) and (max-height: 600px) {
  :root {
    /* Side column scales with the viewport; the upper bound is the toolbar's
       intrinsic width (5 × 40px buttons + 4 × 8px gaps = 232px) so it never
       reserves more space than it needs. */
    --col-gap: 24px;
    --side-col: clamp(140px, 34vw, 232px);
    /* Bound the cell by BOTH axes so nothing ever overflows or clips, at any
       aspect ratio: by height, and by the width left after the side column,
       the column gap and the app's own padding (2 × 12px). */
    --cell-size: min(
      64px,
      calc((100dvh - 24px - var(--grid-gap) * 10) / 9),
      calc((100vw - var(--side-col) - var(--col-gap) - 24px - var(--grid-gap) * 10) / 9)
    );
  }
  .page {
    display: grid;
    grid-template-columns: auto auto;
    grid-template-rows: auto auto;
    grid-template-areas:
      "topbar board"
      "panel board";
    align-items: center;
    column-gap: var(--col-gap);
    row-gap: 12px;
  }
  .topbar {
    grid-area: topbar;
    align-self: end;
  }
  .panel {
    grid-area: panel;
    align-self: start;
  }
  .board-wrap {
    grid-area: board;
  }
  /* Side column: stacked, fixed to --side-col so the cell-size math above is
     exact. */
  .topbar,
  .panel {
    width: var(--side-col);
    max-width: var(--side-col);
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
  /* Safety net: if the side column is ever narrower than the toolbar (very
     narrow landscape), the buttons wrap to a new row instead of clipping. */
  .toolbar {
    flex-wrap: wrap;
  }
}
