/* ============================================================================
   UNIFIED 3D PUZZLE GAME STYLESHEET
   Provides consistent UI/UX across all 3D puzzle games.
   Shares the 2D "Crossword Paper" newspaper scheme (css/index2D/
   newspaper-theme.css) so buttons, panels and HUDs read as one product:
   paper surfaces, ink text, burgundy/vermilion accent, square corners and
   hard offset shadows. --news-* tokens mirror the 2D values exactly.
   ============================================================================ */

/* Fonts (Fraunces + Figtree + JetBrains Mono) are loaded globally via tokens.css. */

:root {
  --page-background: #15141a;
  /* ── Newspaper palette (shared with the 2D games) ── */
  --news-bg: #15141a;
  --news-paper: #1f1c26;
  --news-ink: #ece2cc;
  --news-ink-light: #b1a78f;
  /* Was #8a8478. Measured against the real sidebar surface it composites to
     4.11:1, under the 4.5:1 the 12px labels it styles actually need — flagged
     by an automated contrast audit on /3d/tiles and /3d/gridblock in dark
     theme. This value is the lightest step that still reads as secondary
     against --news-ink-light (#b1a78f). */
  --news-ink-muted: #a29a8a;
  --news-rule: #ece2cc;
  --news-rule-light: rgba(236, 227, 207, 0.16);
  --news-accent: #ff5762;
  --news-btn-bg: #2a2636;
  --news-btn-bg-hover: #ece2cc;
  --news-highlight: rgba(255, 87, 98, 0.18);
  --news-active-bg: #ff5762;
  --news-correct: #5cd68b;
  --news-incorrect: #ff7a7a;
  --news-warn: #f0b450;
  --news-font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --news-font-body: 'Figtree', Georgia, 'Times New Roman', sans-serif;

  /* ── Legacy component tokens, re-mapped to the newspaper scheme so every
        per-game rule referencing them inherits the shared look ── */
  --color-dark-bg: var(--news-bg);
  --color-secondary-bg: var(--news-paper);
  --color-tertiary-bg: #262230;
  --color-accent-primary: var(--news-accent);
  --color-accent-secondary: var(--brand-2);
  --color-accent-tertiary: var(--cat-spatial);
  --color-success: var(--news-correct);
  --color-warning: var(--news-warn);
  --color-error: var(--news-incorrect);
  --color-text-primary: var(--news-ink);
  --color-text-secondary: var(--news-ink-light);
  --color-text-muted: var(--news-ink-muted);
  --color-border: var(--news-rule-light);
  --color-border-light: rgba(236, 227, 207, 0.10);
  --color-card-bg: var(--news-paper);
  --color-hover-overlay: rgba(236, 227, 207, 0.06);
  --color-tile-bg: rgba(236, 227, 207, 0.04);
  --shadow-hard: 2px 2px 0 rgba(0, 0, 0, 0.35);
  --shadow-hard-lg: 6px 6px 0 rgba(0, 0, 0, 0.30);

  /* Typography */
  --font-family-base: var(--news-font-body);
  --font-family-mono: 'JetBrains Mono', 'JetBrains Mono Fallback', ui-monospace, 'Fira Code', Monaco, monospace;
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
  --font-size-2xl: 1.5rem;
  --font-size-3xl: 1.875rem;
  --font-weight-light: 300;
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;

  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  --spacing-2xl: 3rem;

  /* Border Radius — newspaper components are square; kept for legacy refs */
  --g--radius-xs: 0.25rem;
  --g--radius-sm: 0.375rem;
  --radius-md: 0.5rem;
  --g--radius-lg: 0.75rem;
  --g--radius-xl: 1rem;
  --radius-2xl: 1.5rem;
  --radius-full: 9999px;

  /* Shadows */
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --g--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  --g--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
  --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);

  /* Transitions */
  --transition-fast: 100ms ease-in-out;
  --transition-base: 200ms ease-in-out;
  --transition-slow: 300ms ease-in-out;

  /* Z-Index Scale */
  --z-base: 1;
  --z-dropdown: 1000;
  --z-sticky: 1020;
  --z-fixed: 1030;
  --z-modal-backdrop: 10020;
  --z-modal: 10030;
  --z-toast: 10050;
}

html[data-theme='dark'] {
  --page-background: #15141a;
}

html[data-theme='light'] {
  --page-background: #f5f0eb;
  /* ── Newspaper palette (light "paper" mode, shared with the 2D games) ── */
  --news-bg: #f5f0eb;
  --news-paper: #fdfbf9;
  --news-ink: #3d3d3d;
  --news-ink-light: #5a5a5a;
  --news-ink-muted: var(--text-muted);
  --news-rule: #3d3d3d;
  --news-rule-light: rgba(61, 61, 61, 0.16);
  --news-accent: #8b3a3a;
  --news-btn-bg: #fdfbf9;
  --news-btn-bg-hover: #3d3d3d;
  --news-highlight: rgba(139, 58, 58, 0.12);
  --news-active-bg: #8b3a3a;
  --news-correct: #5a8a4a;
  --news-incorrect: #c44d4d;
  --news-warn: #c49a4d;

  --color-dark-bg: var(--news-bg);
  --color-secondary-bg: var(--news-paper);
  --color-tertiary-bg: #e8e0d8;
  --color-success: var(--news-correct);
  --color-warning: var(--news-warn);
  --color-error: var(--news-incorrect);
  --color-border: var(--news-rule-light);
  --color-border-light: rgba(61, 61, 61, 0.10);
  --color-hover-overlay: rgba(61, 61, 61, 0.06);
  --color-tile-bg: rgba(61, 61, 61, 0.04);
  --shadow-hard: 2px 2px 0 rgba(0, 0, 0, 0.08);
  --shadow-hard-lg: 6px 6px 0 rgba(0, 0, 0, 0.10);
}

/* ============================================================================
   GLOBAL & BASE STYLES
   ============================================================================ */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  font-family: var(--font-family-base);
  background: var(--page-background);
  color: var(--color-text-primary);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* No overflow: hidden here. Propagated to the viewport it silently ate
     wheel/touch scrolling, so the SEO "About" section below the framed play
     stage could never be reached by a real user (programmatic
     scrollIntoView still worked, which hid the defect in testing). The
     stage itself owns its height; the page scrolls normally to it. */
}

/* ============================================================================
   GAME CONTAINER LAYOUTS
   ============================================================================ */

.game-3d-wrapper {
  position: relative;
  /* 100%, not 100vw: 100vw spans the viewport INCLUDING the vertical
     scrollbar, so on every page tall enough to scroll the wrapper was 15px
     wider than the visible area — document.scrollWidth exceeded
     clientWidth by exactly 15 and the fixed right rail was clipped at the
     viewport edge. A percentage width resolves against the initial
     containing block, which already excludes the scrollbar. */
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--page-background);
}

.game-canvas-container {
  position: relative;
  flex: 1;
  /* Reserve viewport height so the in-flow SEO content section rendered after
     the game cannot starve this flex item and collapse the WebGL canvas to 0.

     This is `height`, not `min-height`, and that distinction matters: the canvas
     inside is sized with `height: 100%`, and a percentage height cannot resolve
     against a parent whose height is only a minimum. With `min-height` the canvas
     fell back to its intrinsic 150px, was painted at that size, and was then
     resized to the real ~800px by three.js — a 0.0947 layout shift, the last one
     on the site above the CLS 0.1 threshold. A definite height lets the
     percentage resolve, so the canvas is correct on the very first paint.
     The min-height floor keeps very short viewports usable.
     --site-header-height is published by the site navigation script, which now
     runs before the canvas is ever laid out. */
  height: calc(100vh - var(--site-header-height, 80px));
  min-height: 320px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* The stage container's sky (both themes) is set further down, with the
   GRIDDED TABLE FLOOR rules — the gradient's stops are tuned to the floor's
   46% horizon, so the two must be read together. */

/* ============================================================================
   FRAMED PLAY STAGE — shared by every 3D game (body.game-3d-wrapper)

   #canvas-container / .game-canvas-container is the box three.js sizes itself
   from (scene-manager.js reads container.clientWidth/clientHeight), and the
   base rule above left it at the full page width while the 300px game sidebar
   sits position:fixed on top of it. Three defects followed, on every 3D page:
   the scene was framed around a centre point hidden under the sidebar (its
   right edge occluded and unclickable — the maze timer was literally behind
   it), and the in-flow About section below the game squeezed the container
   down to its 320px min-height — a full-width letterbox band clipping the
   already-laid-out renderer top and bottom.

   The stage fixes all three: it reserves the sidebar's column, its height is
   a definite shrink-proof value, and it is framed like the site's other
   newspaper surfaces (double ink rule + hard offset shadow) at a ~3:2 picture
   proportion. On phones it goes full-bleed with a thin outer gutter, with the
   hamburger, timer pill and help FAB floating above it as HUD.
   ============================================================================ */

body.game-3d-wrapper {
  --stage-sidebar-w: 300px;
  --stage-gutter: clamp(10px, 1.6vw, 24px);
  /* Some per-game sheets (tile-games/gridblock/tiles3d) centre the body's flex
     column. The stage is a definite, near-full-height block pinned under the
     header, so centring it only adds a stray offset. Pin the column to the top
     so every 3D game frames identically. */
  justify-content: flex-start;
}

@media (min-width: 769px) {

  body.game-3d-wrapper .game-canvas-container {
    /* The About section below the game must never shrink the stage; its
       height is a definite value, not a minimum the flex column can chew
       down to the floor. */
    flex: 0 0 auto;
    /* The 12px is the body.ui-touch / body.ui-mobile-optimized page padding
       that chrome.css applies before first paint; without subtracting it the
       frame's bottom gutter collapses to that padding's width. */
    height: calc(100vh - var(--site-header-height, 80px) - 12px - 2 * var(--stage-gutter));
    min-height: 320px;
    /* Fill the available height but cap the width at ~3:2 so wide viewports
       get a framed picture, not a strip. 100% resolves against the wrapper,
       so subtracting the sidebar column keeps the stage clear of it; the
       gutters hold the frame off the viewport edges and out from under the
       sidebar's hard offset shadow. */
    width: min(
      calc(100% - var(--stage-sidebar-w) - 2 * var(--stage-gutter)),
      calc((100vh - var(--site-header-height, 80px)) * 1.5 - 2 * var(--stage-gutter))
    );
    /* margin auto centers against the FULL page width, so the translate
       then recenters the stage into the region actually visible left of the
       fixed sidebar, by exactly half that sidebar's width. clientWidth/
       clientHeight are unaffected by translate — the renderer reads the
       same box — while getBoundingClientRect, which three.js uses to convert
       pointer events into raycasts, reports the shifted on-screen position. */
    margin: var(--stage-gutter) auto 0;
    translate: calc(var(--stage-sidebar-w) / -2) 0;
    /* Newspaper frame: double ink rule over a paper mat with a hard offset
       shadow — the same language as the modals and the help panel. The mat
       also keeps the frame looking deliberate during the loading spinner
       phase, before the scene paints its own background. */
    padding: clamp(10px, 1.6vmin, 20px);
    border: 3px double var(--news-ink);
    background: var(--news-paper);
    box-shadow: var(--shadow-hard-lg);
  }
}

@media (max-width: 768px) {

  body.game-3d-wrapper .game-canvas-container {
    flex: 0 0 auto;
    /* Full-bleed stage with a thin outer gutter; the hamburger, timer pill
       and help FAB float above it as HUD. */
    width: auto;
    margin: 10px 10px 0;
    /* 100dvh tracks the moving browser URL bar on phones; the plain vh line
       above it is the fallback for older browsers. The 40px is the 12px
       body page padding (chrome.css body.ui-touch) + the 10px top margin +
       18px of air above the bottom-sheet sidebar handle. */
    height: calc(100vh - var(--site-header-height, 80px) - 40px);
    height: calc(100dvh - var(--site-header-height, 80px) - 40px);
    min-height: 300px;
    padding: 8px;
    border: 2px double var(--news-ink);
    box-shadow: var(--shadow-hard);
  }
}

canvas {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
}

canvas:active {
  cursor: grabbing;
}

/* ============================================================================
   GRIDDED TABLE FLOOR — the word scramble stage's signature, for games whose
   stage is DOM rather than WebGL. The .ws3d-grid-floor div is inserted as the
   first child of a stage container: it clips (overflow: hidden) a tilted
   plane that carries the square grid and fogs into the table colour at its
   far edge, exactly like the THREE GridHelper floors. z-index: -1 keeps it
   above the stage background but below the canvas and HUD; every
   .game-canvas-container isolates stacking so the negative z-index resolves
   against its own stage and never behind the paper mat.
   ============================================================================ */

.game-canvas-container {
  isolation: isolate;
  /* Stage v2 sky: the CSS-stage games (sudoku, jigsaw — alpha:true canvases)
     read this as their air, a vertical gradient that warms into a horizon
     glow exactly where the .ws3d-grid-floor horizon sits (46%). The
     opaque-canvas games never show it except through their paper-mat
     border, where the same gradient reads as the frame's depth. */
  background: linear-gradient(180deg,
      #07060c 0%,
      #120f1e 22%,
      #241d3e 40%,
      #393063 46%,
      #2a2150 100%);
}

html[data-theme='light'] .game-canvas-container {
  background: linear-gradient(180deg,
      #f8f2e7 0%,
      #f1e6d2 30%,
      #e0c6a6 46%,
      #d9c2a2 100%);
}

.ws3d-grid-floor {
  position: absolute;
  z-index: -1;
  /* The clipper's top edge is the floor's horizon — mid-stage, like the
     word scramble camera pitch. */
  left: 0;
  right: 0;
  top: 46%;
  bottom: 0;
  overflow: hidden;
  pointer-events: none;
}

.ws3d-grid-floor::before {
  content: "";
  position: absolute;
  inset: 0;
  /* Stage v2: the table is no longer one flat violet — a lit pool glows at
     the horizon centre (where the game sits) and falls off to the dark
     edges, and a warm glow band marks the horizon line itself. The top fade
     is long (24%) so the sky-to-floor horizon reads as atmospheric distance,
     never as a hard band edge; the grid is quiet enough to read as etched. */
  background-color: #1a1526;
  background-image:
    linear-gradient(180deg, rgba(87, 70, 148, 0.5) 0%, rgba(87, 70, 148, 0) 24%),
    radial-gradient(120% 165% at 50% 0%, #372c52 0%, #282135 52%, #1a1526 100%),
    repeating-linear-gradient(0deg, rgba(141, 127, 196, 0.5) 0 2px, transparent 2px 64px),
    repeating-linear-gradient(90deg, rgba(141, 127, 196, 0.5) 0 2px, transparent 2px 64px);
  transform: perspective(900px) rotateX(55deg);
  transform-origin: 50% 0;
}

html[data-theme='light'] .ws3d-grid-floor::before {
  background-color: #2f2942;
  background-image:
    linear-gradient(180deg, rgba(228, 202, 168, 0.75) 0%, rgba(228, 202, 168, 0) 26%),
    radial-gradient(120% 165% at 50% 0%, #5a4c7e 0%, #3d3455 52%, #2f2942 100%),
    repeating-linear-gradient(0deg, rgba(171, 159, 208, 0.26) 0 2px, transparent 2px 64px),
    repeating-linear-gradient(90deg, rgba(171, 159, 208, 0.26) 0 2px, transparent 2px 64px);
}

/* Screen-space vignette over the tilted plane: the table's far corners melt
   into shadow the way the WebGL floors' fog does. */
.ws3d-grid-floor::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 130% at 50% 0%, rgba(7, 6, 12, 0) 42%, rgba(7, 6, 12, 0.55) 100%);
  pointer-events: none;
}

html[data-theme='light'] .ws3d-grid-floor::after {
  background: radial-gradient(120% 130% at 50% 0%, rgba(58, 46, 66, 0) 40%, rgba(58, 46, 66, 0.5) 100%);
}

/* Soft contact shadow for the DOM-side games: size and position it inline
   (it is just an absolutely positioned ellipse) under a board or frame so
   objects read as standing ON the table instead of floating over it. */
.ws3d-contact-shadow {
  position: absolute;
  pointer-events: none;
  background: radial-gradient(50% 50% at 50% 50%,
      rgba(5, 3, 10, 0.5) 0%,
      rgba(5, 3, 10, 0.24) 52%,
      rgba(5, 3, 10, 0) 74%);
}

html[data-theme='light'] .ws3d-contact-shadow {
  background: radial-gradient(50% 50% at 50% 50%,
      rgba(16, 12, 24, 0.42) 0%,
      rgba(16, 12, 24, 0.2) 52%,
      rgba(16, 12, 24, 0) 74%);
}

/* Frames with a paper mat (the clamp padding on .game-canvas-container):
   keep the floor inside the mat instead of painting over it. */
.game-canvas-container > .ws3d-grid-floor {
  left: clamp(10px, 1.6vmin, 20px);
  right: clamp(10px, 1.6vmin, 20px);
  bottom: clamp(10px, 1.6vmin, 20px);
}

@media (max-width: 768px) {
  .game-canvas-container > .ws3d-grid-floor {
    left: 8px;
    right: 8px;
    bottom: 8px;
  }
}

/* ============================================================================
   GAME HEADER/TITLE SECTION
   ============================================================================ */

.game-3d-header {
  padding: var(--spacing-lg) var(--spacing-xl);
  border-bottom: 3px double var(--news-rule-light);
  background: var(--news-paper);
  position: relative;
  z-index: var(--z-sticky);
}

.game-3d-header-content {
  max-width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--spacing-lg);
}

.game-3d-title {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
}

.game-3d-title h1 {
  font-family: var(--news-font-display);
  font-size: var(--font-size-2xl);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-text-primary);
  margin: 0;
}

.game-3d-title p {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  margin: 0;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ============================================================================
   TOP CONTROL BAR
   ============================================================================ */

.game-top-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 60px;
  padding: var(--spacing-md) var(--spacing-lg);
  background: linear-gradient(180deg, var(--news-paper) 0%, transparent 100%);
  border-bottom: 1px solid var(--news-rule-light);
  display: flex;
  align-items: center;
  justify-content: space-between;
  z-index: var(--z-sticky);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.game-top-bar-left,
.game-top-bar-center,
.game-top-bar-right {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
}

.game-top-bar-left {
  flex: 1;
  justify-content: flex-start;
}

.game-top-bar-center {
  flex: 0 0 auto;
}

.game-top-bar-right {
  flex: 1;
  justify-content: flex-end;
}

.game-title-small h3 {
  font-family: var(--news-font-display);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ============================================================================
   SIDE PANELS
   ============================================================================ */

.game-sidebar {
  position: fixed;
  top: 0;
  right: 0;
  width: 300px;
  height: 100vh;
  background: var(--news-paper);
  border-left: 1px solid var(--news-rule-light);
  overflow-y: auto;
  z-index: 100;
  padding: var(--spacing-lg);
  padding-top: var(--site-header-height, 80px);
  box-shadow: var(--shadow-hard-lg);
}

.game-sidebar::-webkit-scrollbar {
  width: 4px;
}

.game-sidebar::-webkit-scrollbar-track {
  background: transparent;
}

.game-sidebar::-webkit-scrollbar-thumb {
  background: var(--news-rule-light);
  border-radius: 2px;
}

.game-sidebar-section {
  margin-bottom: var(--spacing-xl);
}

.game-sidebar-section:first-child {
  margin-top: 0;
}

.game-sidebar-title {
  font-family: var(--news-font-display);
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--color-text-primary);
  margin-bottom: var(--spacing-sm);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-bottom: 1px solid var(--news-rule-light);
  padding-bottom: 6px;
}

/* Sidebar game title (large name at top) */
.game-sidebar-name {
  font-family: var(--news-font-display);
  font-size: var(--font-size-lg);
  font-weight: 900;
  color: var(--color-text-primary);
  margin-bottom: var(--spacing-xs);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* Sidebar stats grid */
.sidebar-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
  gap: var(--spacing-sm);
}

.sidebar-stat-item {
  text-align: center;
  padding: var(--spacing-sm);
  background: var(--color-tile-bg);
  border-radius: 0;
  border: 1px solid var(--color-border-light);
}

.sidebar-stat-label {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 2px;
}

.sidebar-stat-value {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
  font-family: var(--font-family-base);
}

/* Sidebar controls stacked buttons */
.sidebar-controls {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

/* Sidebar how-to-play */
.sidebar-howto {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  line-height: 1.6;
}

.sidebar-howto kbd {
  background: var(--news-paper);
  padding: 1px 5px;
  border-radius: 0;
  font-family: var(--font-family-base);
  font-size: 0.7rem;
  border: 1px solid var(--news-rule);
  color: var(--color-text-primary);
}

/* Divider between sidebar sections */
.game-sidebar-section + .game-sidebar-section {
  padding-top: var(--spacing-md);
  border-top: 1px solid var(--color-border-light);
}

/* Card style for individual sidebar sections */
.game-sidebar-section.card {
  background: var(--news-paper);
  border: 1px solid var(--news-rule-light);
  border-radius: 0;
  padding: var(--spacing-md);
  margin-bottom: var(--spacing-md);
  box-shadow: var(--shadow-hard);
}

.game-sidebar-section.card + .game-sidebar-section.card {
  padding-top: var(--spacing-md);
  border-top: 0;
}

/* ============================================================================
   INFO PANEL / STATUS DISPLAY
   ============================================================================ */

.game-info-panel {
  background: var(--color-tile-bg);
  border: 1px solid var(--color-border-light);
  border-radius: 0;
  padding: var(--spacing-md);
  margin-bottom: var(--spacing-md);
}

.game-stat-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--spacing-sm) 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}

.game-stat-row:not(:last-child) {
  border-bottom: 1px solid var(--color-border-light);
}

.game-stat-label {
  font-weight: var(--font-weight-medium);
}

.game-stat-value {
  color: var(--color-text-primary);
  font-weight: var(--font-weight-semibold);
}

/* ============================================================================
   BUTTON STYLES — newspaper scheme shared with the 2D games:
   paper background, 1.5px ink border, square corners, hard offset shadow.
   ============================================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-sm) var(--spacing-md);
  font-size: var(--font-size-sm);
  font-family: var(--font-family-base);
  font-weight: var(--font-weight-semibold);
  border: 1.5px solid var(--news-ink);
  border-radius: 0;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  white-space: nowrap;
  -webkit-user-select: none;
  user-select: none;
  text-decoration: none;
  gap: var(--spacing-sm);
  letter-spacing: 0.02em;
  background: var(--news-btn-bg);
  color: var(--color-text-primary);
  box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.10);
}

.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.btn:focus-visible {
  outline: 2px solid var(--news-accent);
  outline-offset: 2px;
}

.btn-primary {
  background: var(--news-ink);
  color: var(--news-paper);
  border-color: var(--news-ink);
}

.btn-primary:hover:not(:disabled) {
  background: var(--news-accent);
  border-color: var(--news-accent);
  color: #fff;
}

.btn-primary:active:not(:disabled) {
  transform: translateY(1px);
}

.btn-secondary {
  background: var(--news-btn-bg);
  color: var(--color-text-primary);
  border: 1.5px solid var(--news-rule);
}

.btn-secondary:hover:not(:disabled) {
  background: var(--news-btn-bg-hover);
  color: var(--news-paper);
  border-color: var(--news-accent);
}

.btn-success {
  background: var(--news-correct);
  color: #fff;
  border-color: var(--news-correct);
}

.btn-success:hover:not(:disabled) {
  filter: brightness(0.92);
}

.btn-danger {
  background: var(--news-incorrect);
  color: #fff;
  border-color: var(--news-incorrect);
}

.btn-danger:hover:not(:disabled) {
  filter: brightness(0.92);
}

.btn-warning {
  background: var(--news-warn);
  color: #fff;
  border-color: var(--news-warn);
}

.btn-warning:hover:not(:disabled) {
  filter: brightness(0.92);
}

.btn-ghost {
  background: transparent;
  color: var(--color-text-secondary);
  border: 1.5px solid var(--news-rule-light);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--color-hover-overlay);
  color: var(--color-text-primary);
  border-color: var(--news-rule);
}

.btn-small {
  padding: var(--spacing-xs) var(--spacing-sm);
  font-size: var(--font-size-xs);
}

.btn-large {
  padding: var(--spacing-md) var(--spacing-lg);
  font-size: var(--font-size-base);
}

.btn-block {
  width: 100%;
}

/* ============================================================================
   FORM CONTROLS
   ============================================================================ */

.form-group {
  margin-bottom: var(--spacing-md);
}

.form-label {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text-secondary);
  margin-bottom: var(--spacing-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.form-control {
  width: 100%;
  padding: var(--spacing-sm) var(--spacing-md);
  background: var(--news-paper);
  border: 1px solid var(--news-rule);
  border-radius: 0;
  color: var(--color-text-primary);
  font-size: var(--font-size-sm);
  font-family: var(--font-family-base);
  transition: border-color var(--transition-base), box-shadow var(--transition-base);
}

.form-control:focus {
  outline: none;
  border-color: var(--news-ink);
  box-shadow: none;
}

.form-control:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

select.form-control {
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2368625c' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
  background-repeat: no-repeat;
  background-position: right var(--spacing-sm) center;
  background-size: 1.5em 1.5em;
  padding-right: 2.5em;
}

/* ============================================================================
   STATUS/ALERT MESSAGES — match the 2D message-area tints
   ============================================================================ */

.alert {
  padding: var(--spacing-md);
  border-radius: 0;
  border-left: 4px solid;
  font-size: var(--font-size-sm);
  margin-bottom: var(--spacing-md);
}

.alert-success {
  background: rgba(46, 125, 50, 0.10);
  border-color: var(--news-correct);
  color: var(--news-correct);
}

.alert-warning {
  background: rgba(196, 154, 77, 0.10);
  border-color: var(--news-warn);
  color: var(--news-warn);
}

.alert-error {
  background: rgba(198, 40, 40, 0.10);
  border-color: var(--news-incorrect);
  color: var(--news-incorrect);
}

.alert-info {
  background: var(--news-highlight);
  border-color: var(--news-accent);
  color: var(--news-accent);
}

/* ============================================================================
   MODAL/DIALOG STYLES — newspaper overlay: double-rule border, hard shadow
   ============================================================================ */

.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-modal-backdrop);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  opacity: 0;
  visibility: hidden;
  transition: all var(--transition-base);
}

.modal-overlay.active {
  opacity: 1;
  visibility: visible;
}

.modal-dialog {
  position: relative;
  background: var(--news-paper);
  border: 3px double var(--news-ink);
  border-radius: 0;
  padding: var(--spacing-xl);
  max-width: 500px;
  width: 90%;
  box-shadow: var(--shadow-hard-lg);
  z-index: var(--z-modal);
  animation: slideIn var(--transition-slow) ease-out;
}

.modal-overlay.active .modal-dialog {
  transform: scale(1);
}

.modal-header {
  margin-bottom: var(--spacing-lg);
  padding-bottom: var(--spacing-md);
  border-bottom: 1px solid var(--news-rule-light);
}

.modal-header h2 {
  font-family: var(--news-font-display);
  font-size: var(--font-size-xl);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0;
  color: var(--color-text-primary);
}

.modal-body {
  margin-bottom: var(--spacing-lg);
  font-size: var(--font-size-sm);
  color: var(--color-text-secondary);
}

.modal-footer {
  display: flex;
  gap: var(--spacing-md);
  justify-content: flex-end;
}

.modal-close {
  position: absolute;
  top: var(--spacing-md);
  right: var(--spacing-md);
  background: none;
  border: none;
  color: var(--color-text-muted);
  cursor: pointer;
  font-size: var(--font-size-lg);
  padding: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color var(--transition-base);
}

.modal-close:hover {
  color: var(--news-accent);
}

/* ============================================================================
   BADGES & LABELS
   ============================================================================ */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  padding: var(--spacing-xs) var(--spacing-sm);
  border-radius: 0;
  border: 1px solid;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.badge-primary {
  background: var(--news-highlight);
  color: var(--news-accent);
  border-color: var(--news-accent);
}

.badge-success {
  background: rgba(46, 125, 50, 0.10);
  color: var(--news-correct);
  border-color: var(--news-correct);
}

.badge-warning {
  background: rgba(196, 154, 77, 0.12);
  color: var(--news-warn);
  border-color: var(--news-warn);
}

.badge-danger {
  background: rgba(198, 40, 40, 0.10);
  color: var(--news-incorrect);
  border-color: var(--news-incorrect);
}

/* ============================================================================
   PROGRESS INDICATOR
   ============================================================================ */

.progress-bar {
  width: 100%;
  height: 6px;
  background: var(--news-rule-light);
  border-radius: 0;
  overflow: hidden;
  border: 0;
}

.progress-bar-fill {
  height: 100%;
  background: var(--news-ink);
  transition: width var(--transition-slow) ease-out;
}

.progress-text {
  display: flex;
  justify-content: space-between;
  margin-bottom: var(--spacing-sm);
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
}

/* ============================================================================
   TIMER / SCORE DISPLAY
   ============================================================================ */

.game-timer,
.game-score {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-xs);
}

.timer-label,
.score-label {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: var(--font-weight-semibold);
}

.timer-value,
.score-value {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  color: var(--color-text-primary);
  font-family: var(--font-family-base);
  letter-spacing: 0.05em;
}

.timer-value.warning {
  color: var(--news-warn);
  animation: pulse 1s ease-in-out infinite;
}

.timer-value.critical {
  color: var(--news-incorrect);
  animation: pulse 500ms ease-in-out infinite;
}

/* ============================================================================
   DIFFICULTY SELECTOR — flat ink chips like the 2D newspaper toolbar
   ============================================================================ */

.difficulty-selector {
  display: flex;
  gap: var(--spacing-sm);
  margin: var(--spacing-md) 0;
}

.difficulty-btn {
  flex: 1;
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--news-rule);
  border-radius: 0;
  background: transparent;
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
  font-family: var(--font-family-base);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  transition: all var(--transition-base);
  text-transform: capitalize;
}

.difficulty-btn:hover {
  border-color: var(--news-ink);
  color: var(--color-text-primary);
}

.difficulty-btn.active {
  background: var(--news-ink);
  border-color: var(--news-ink);
  color: var(--news-paper);
}

/* ============================================================================
   MOBILE HUD (shared by every 3D game)
   Timer pill, help FAB + modal and confetti layer. Previously copy-pasted
   into each game's <head>; now styled here so all games stay consistent.
   ============================================================================ */

.mobile-timer {
  display: none;
}

.help-toggle,
.help-modal {
  display: none;
}

@media (max-width: 768px) {
  .mobile-timer {
    display: block !important;
    position: fixed;
    top: auto;
    right: 12px;
    z-index: 10002;
    font-family: var(--font-family-base);
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--news-paper);
    background: var(--news-ink);
    padding: 6px 14px;
    border-radius: 0;
    border: 1px solid var(--news-ink);
    letter-spacing: 0.08em;
    pointer-events: none;
    -webkit-user-select: none;
    user-select: none;
    box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.25);
  }

  .help-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    bottom: 28px;
    right: 28px;
    z-index: 10003;
    width: 68px;
    height: 68px;
    border-radius: 50%;
    background: var(--news-paper);
    border: 2.5px solid var(--news-ink);
    box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.30);
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.2s ease, background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
    color: var(--news-ink);
    padding: 0;
    font-family: var(--news-font-display);
    font-size: 28px;
    font-weight: 900;
  }

  .help-toggle:active {
    transform: scale(0.88);
    background: var(--news-accent);
    border-color: var(--news-accent);
    color: #fff;
  }

  .help-modal {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 10050;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.8);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s ease;
  }

  .help-modal.active {
    opacity: 1;
    visibility: visible;
  }

  .help-modal-content {
    position: relative;
    background: var(--news-paper);
    border: 3px double var(--news-ink);
    border-radius: 0;
    padding: 28px 24px;
    max-width: 380px;
    width: 85%;
    box-shadow: var(--shadow-hard-lg);
    color: var(--color-text-secondary);
    font-size: 0.95rem;
    line-height: 1.6;
  }

  .help-modal-close {
    position: absolute;
    top: 12px;
    right: 16px;
    background: none;
    border: none;
    color: var(--color-text-muted);
    font-size: 24px;
    cursor: pointer;
    padding: 0;
    line-height: 1;
  }

  .help-modal-close:active {
    color: var(--news-accent);
  }

  .help-modal-title {
    font-family: var(--news-font-display);
    font-size: 1.2rem;
    font-weight: 900;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin: 0 0 16px 0;
    color: var(--color-text-primary);
  }

  .help-modal kbd {
    display: inline-block;
    background: var(--news-paper);
    border: 1px solid var(--news-rule);
    border-radius: 0;
    padding: 1px 6px;
    font-size: 0.8rem;
    font-family: inherit;
    color: var(--color-text-primary);
  }
}

.confetti-canvas {
  position: fixed;
  inset: 0;
  z-index: 10040;
  pointer-events: none;
}

/* Mobile numpad (3D Sudoku) */
.mobile-numpad {
  display: none;
}

@media (max-width: 768px) {
  .mobile-numpad {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 10030;
    background: var(--news-paper);
    border-top: 1px solid var(--news-rule);
    padding: 8px;
    padding-bottom: max(8px, env(safe-area-inset-bottom));
  }

  .mobile-numpad.visible {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 6px;
  }

  .numpad-btn {
    height: 44px;
    border: 1px solid var(--news-rule);
    border-radius: 0;
    background: var(--news-btn-bg);
    color: var(--color-text-primary);
    font-size: 1.15rem;
    font-weight: 700;
    font-family: var(--font-family-base);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    -webkit-user-select: none;
    user-select: none;
    transition: background 0.15s;
  }

  .numpad-btn:active {
    background: var(--news-highlight);
    border-color: var(--news-accent);
    color: var(--news-accent);
  }

  .numpad-erase {
    font-size: 1.4rem;
  }
}

/* ============================================================================
   ANIMATIONS
   ============================================================================ */

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(-20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes fadeOut {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes pulse {
  0%, 100% {
    opacity: 1;
  }
  50% {
    opacity: 0.6;
  }
}

@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

@keyframes float {
  0%, 100% {
    transform: translateY(0px);
  }
  50% {
    transform: translateY(-10px);
  }
}

@keyframes shake {
  0%, 100% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-5px);
  }
  75% {
    transform: translateX(5px);
  }
}

/* ============================================================================
   UTILITY CLASSES
   ============================================================================ */

.text-center {
  text-align: center;
}

.text-left {
  text-align: left;
}

.text-right {
  text-align: right;
}

.hidden {
  display: none !important;
}

.invisible {
  visibility: hidden;
}

.flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

.flex-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.gap-sm {
  gap: var(--spacing-sm);
}

.gap-md {
  gap: var(--spacing-md);
}

.gap-lg {
  gap: var(--spacing-lg);
}

.mt-sm {
  margin-top: var(--spacing-sm);
}

.mt-md {
  margin-top: var(--spacing-md);
}

.mt-lg {
  margin-top: var(--spacing-lg);
}

.mb-sm {
  margin-bottom: var(--spacing-sm);
}

.mb-md {
  margin-bottom: var(--spacing-md);
}

.mb-lg {
  margin-bottom: var(--spacing-lg);
}

/* ============================================================================
   RESPONSIVE DESIGN
   ============================================================================ */

/* Mobile Hamburger Menu Button */
.mobile-menu-toggle {
  display: none;
  position: fixed;
  top: 52px;
  left: 12px;
  z-index: 10001;
  width: 40px;
  height: 40px;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
}

.site-header .mobile-menu-toggle {
  position: absolute;
  top: 52px;
  left: 12px;
  z-index: 10001;
}

.mobile-menu-toggle span {
  display: block;
  width: 25px;
  height: 2px;
  background: var(--color-text-primary);
  border-radius: 1px;
  transition: all 0.3s ease;
}

.mobile-menu-toggle[aria-expanded="true"] span:nth-child(1) {
  transform: rotate(45deg) translateY(11px);
}

.mobile-menu-toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}

.mobile-menu-toggle[aria-expanded="true"] span:nth-child(3) {
  transform: rotate(-45deg) translateY(-11px);
}

@media (max-width: 768px) {
  .mobile-menu-toggle {
    display: flex;
  }

  .game-sidebar {
    position: fixed;
    top: auto;
    bottom: 0;
    right: 0;
    left: 0;
    width: 100%;
    height: auto;
    max-height: 0;
    border-left: none;
    border-top: 1px solid var(--news-rule-light);
    overflow-y: auto;
    padding-top: var(--spacing-md);
    border-radius: 0;
    visibility: hidden;
    opacity: 0;
    transition: max-height 0.3s ease, opacity 0.3s ease, visibility 0.3s ease;
    box-shadow: 0 -3px 0 rgba(0, 0, 0, 0.08);
  }

  .game-sidebar.menu-open {
    max-height: 50vh;
    visibility: visible;
    opacity: 1;
  }

  .game-top-bar-center {
    flex: 1;
  }

  .modal-dialog {
    width: 95%;
    padding: var(--spacing-lg);
    background: var(--news-paper);
    border-color: var(--news-ink);
  }

  .modal-overlay {
    background: rgba(0, 0, 0, 0.8);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
  }

  .modal-dialog h2,
  .modal-dialog [class$="-title"],
  .modal-dialog .completion-title {
    font-family: var(--news-font-display);
    font-size: 1.4rem;
    color: var(--color-text-primary);
    line-height: 1.3;
  }

  .modal-dialog p,
  .modal-dialog [class$="-copy"],
  .modal-dialog .completion-subtitle {
    font-size: 0.95rem;
    color: var(--color-text-secondary);
    line-height: 1.5;
  }

  .modal-dialog .completion-stats,
  .modal-dialog [class$="-stats"] {
    color: var(--color-text-primary);
  }

  .ui-modal-backdrop .ui-modal {
    padding: var(--spacing-lg);
    background: var(--news-paper);
    border: 3px double var(--news-ink);
    border-radius: 0;
    box-shadow: var(--shadow-hard-lg);
  }
  .ui-modal-backdrop .ui-modal h2,
  .ui-modal-backdrop .ui-modal h3 {
    font-family: var(--news-font-display);
    font-size: 1.25rem;
    color: var(--color-text-primary);
    line-height: 1.3;
  }
  .ui-modal-backdrop .ui-modal p {
    font-size: 0.95rem;
    color: var(--color-text-secondary);
    line-height: 1.5;
  }
}

@media (max-width: 480px) {
  .game-top-bar {
    height: auto;
    padding: var(--spacing-sm) var(--spacing-md);
  }

  .game-top-bar-left,
  .game-top-bar-center,
  .game-top-bar-right {
    flex-direction: column;
    width: 100%;
  }

  .btn-block {
    width: 100%;
  }

  .difficulty-selector {
    flex-direction: column;
  }
}

/* Touch-optimized: increase tap targets to minimum 44x44px */
@media (hover: none) and (pointer: coarse) {
  .btn,
  .control-btn,
  .modal-close,
  button[class*="btn"] {
    min-height: 44px;
    min-width: 44px;
    padding: var(--spacing-sm) var(--spacing-md);
  }

  .modal-close {
    width: 44px;
    height: 44px;
  }

  .mobile-menu-toggle {
    width: 44px;
    height: 44px;
  }

  /* A touch device gets a full-width sidebar because below the mobile
     breakpoint it is a bottom sheet. Above that breakpoint it is still a
     right-hand rail, and forcing the width there made it cover the whole
     viewport — a tablet or a phone in landscape saw a sidebar and no game.
     The width rule therefore belongs to the same range as the sheet rules. */
  @media (max-width: 768px) {
    .game-sidebar {
      width: 100% !important;
    }
  }
}
