/**
 * 3D Maze — page styles.
 *
 * Only what is specific to the maze lives here; the stage frame, the sidebar,
 * the top bar and the shared 3D chrome all come from unified-3d-game.css, which
 * this file never overrides. Colours come from the --news-* tokens so the game
 * follows the site's light and dark themes without a second palette.
 */

/* ------------------------------------------------------------------ the stage */

#game-container {
  position: relative;
  overflow: hidden;
  /* The canvas is the interaction surface: no browser panning, no text select,
     no long-press callout while the player is driving. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

canvas#three-canvas {
  display: block;
  width: 100%;
  height: 100%;
  outline: none;
}

canvas#three-canvas:focus-visible {
  outline: 3px solid var(--news-accent);
  outline-offset: -3px;
}

/* ----------------------------------------------------------------- the minimap */

#minimap-canvas {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 5;
  border: 1px solid var(--news-rule-light);
  /* Slightly translucent so it reads as an overlay rather than a panel pasted
     over the game, but opaque enough to keep the map legible over bright walls. */
  background: color-mix(in srgb, var(--news-paper) 88%, transparent);
  border-radius: 0;
  box-shadow: var(--shadow-hard);
  image-rendering: pixelated;
  /* The map is an output, not a control: clicks belong to the maze behind it. */
  pointer-events: none;
}

/* --------------------------------------------------------------- stage overlays */

#timer-display {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 5;
  padding: 0.35rem 0.7rem;
  font-family: var(--font-family-base);
  font-size: 1.15rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--news-paper);
  background: var(--news-ink);
  border: 1px solid var(--news-ink);
  border-radius: 0;
  box-shadow: var(--shadow-hard);
  pointer-events: none;
}

.maze3d-cue {
  position: absolute;
  left: 50%;
  bottom: 18px;
  z-index: 6;
  transform: translate(-50%, 10px);
  padding: 0.4rem 0.85rem;
  max-width: min(28rem, calc(100% - 2rem));
  font-family: var(--font-family-base);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-align: center;
  color: var(--news-paper);
  background: color-mix(in srgb, var(--news-ink) 88%, transparent);
  border: 1px solid var(--news-ink);
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms ease, transform 220ms ease;
}

.maze3d-cue.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* A crosshair, because pointer lock without one leaves the player unsure whether
   the game is listening. Purely decorative, so it never eats a click. */
.maze3d-crosshair {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  z-index: 4;
  pointer-events: none;
  opacity: 0.5;
  background:
    linear-gradient(var(--news-paper), var(--news-paper)) center / 2px 100% no-repeat,
    linear-gradient(var(--news-paper), var(--news-paper)) center / 100% 2px no-repeat;
  mix-blend-mode: difference;
}

/* ------------------------------------------------------------- text-mode toggle */

.maze3d-text-toggle {
  position: absolute;
  left: 10px;
  bottom: 10px;
  /* On a touch device the thumbstick occupies bottom-left, so the toggle would
     share a corner with it. Pointer capability decides, not width. */
  /* (see the coarse-pointer block below for the swap) */
  z-index: 6;
  padding: 0.3rem 0.6rem;
  font-family: var(--font-family-base);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--news-ink);
  background: color-mix(in srgb, var(--news-paper) 92%, transparent);
  border: 1px solid var(--news-ink);
  border-radius: 0;
  cursor: pointer;
}

.maze3d-text-toggle:hover,
.maze3d-text-toggle:focus-visible {
  color: var(--news-paper);
  background: var(--news-ink);
}

body.maze3d-text-mode .maze3d-text-toggle {
  position: static;
  margin-top: 1rem;
}

/* ---------------------------------------------------------------- touch controls */
/* Shown by pointer capability rather than the user agent string: a phone is not
   "Mobi" in every browser, and a touchscreen laptop is not a phone. */
.maze3d-touch {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 7;
  pointer-events: none;
}

@media (hover: none) and (pointer: coarse) {
  .maze3d-touch { display: block; }

  /* Bottom-left belongs to the stick on touch, so the toggle takes the opposite
     corner. Without this the two sat on top of each other at every width. */
  .maze3d-text-toggle {
    left: auto;
    right: 10px;
    bottom: 86px;
  }
}

.maze3d-stick {
  position: absolute;
  left: 16px;
  bottom: 16px;
  width: 110px;
  height: 110px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--news-paper) 55%, transparent);
  background: color-mix(in srgb, var(--news-ink) 26%, transparent);
  pointer-events: auto;
  touch-action: none;
}

.maze3d-stick-knob {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  border-radius: 50%;
  border: 2px solid var(--news-ink);
  background: color-mix(in srgb, var(--news-paper) 88%, transparent);
  transition: transform 60ms linear;
}

.maze3d-touch-hint {
  position: absolute;
  right: 16px;
  bottom: 22px;
  padding: 0.28rem 0.6rem;
  font-family: var(--font-family-base);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--news-paper);
  background: color-mix(in srgb, var(--news-ink) 55%, transparent);
}

/* ------------------------------------------------------------------ resume card */

.maze3d-resume {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 8;
  width: min(22rem, calc(100% - 2rem));
  transform: translate(-50%, -50%);
  padding: 1rem 1.1rem;
  font-family: var(--font-family-base);
  color: var(--news-ink);
  background: var(--news-paper);
  border: 2px solid var(--news-ink);
  box-shadow: var(--shadow-hard);
}

.maze3d-resume-title {
  margin: 0 0 0.35rem;
  font-family: var(--news-font-display);
  font-size: 1.05rem;
  font-weight: 700;
}

.maze3d-resume-body {
  margin: 0 0 0.85rem;
  font-size: 0.86rem;
  color: var(--news-ink-light);
}

.maze3d-resume-actions {
  display: flex;
  gap: 0.5rem;
}

.maze3d-resume-actions .btn {
  flex: 1;
}

/* --------------------------------------------------------------- result summary */

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

.maze3d-result-grade {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.7rem 0.9rem;
  margin-bottom: 0.85rem;
  border: 2px solid var(--news-ink);
  background: var(--news-paper);
}

.maze3d-result-grade-letter {
  font-family: var(--news-font-display);
  font-size: 2.6rem;
  font-weight: 800;
  line-height: 1;
  min-width: 1.6em;
  text-align: center;
}

.maze3d-result-grade[data-grade='S'] .maze3d-result-grade-letter { color: var(--news-correct); }
.maze3d-result-grade[data-grade='A'] .maze3d-result-grade-letter { color: var(--news-accent); }
.maze3d-result-grade[data-grade='B'] .maze3d-result-grade-letter { color: var(--news-warn); }
.maze3d-result-grade[data-grade='C'] .maze3d-result-grade-letter { color: var(--news-ink-light); }

.maze3d-result-grade-label {
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1.3;
}

.maze3d-result-best {
  margin: 0 0 0.75rem;
  padding: 0.4rem 0.6rem;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--news-ink);
  background: var(--news-highlight);
  border-left: 3px solid var(--news-accent);
}

.maze3d-result-table {
  display: grid;
  gap: 0.3rem;
}

.maze3d-result-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.3rem;
  font-size: 0.88rem;
  border-bottom: 1px solid var(--news-rule-light);
}

.maze3d-result-row span {
  color: var(--news-ink-light);
}

.maze3d-result-row strong {
  font-variant-numeric: tabular-nums;
}

.maze3d-result-note {
  margin: 0.8rem 0 0;
  font-size: 0.82rem;
  font-style: italic;
  color: var(--news-ink-light);
}

/* ------------------------------------------------------------- no-WebGL fallback */

/* With no renderer there is no canvas to look at, so the stage becomes a plain
   panel holding the text maze rather than an empty box with a border. */
#game-container.maze3d-text-only {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--news-paper);
  border: 2px solid var(--news-ink);
  overflow: auto;
}

#game-container.maze3d-text-only .maze3d-crosshair,
#game-container.maze3d-text-only .maze3d-touch,
#game-container.maze3d-text-only #minimap-canvas {
  display: none;
}

#game-container.maze3d-text-only .maze3d-cue {
  position: static;
  transform: none;
  opacity: 1;
  max-width: 40rem;
}

#game-container.maze3d-text-only .maze3d-text-panel {
  display: block;
  margin: auto;
}

/* --------------------------------------------------------------------- text mode */

/* Text mode replaces the canvas view with a plain grid. The canvas keeps
   rendering underneath but is taken out of the accessibility tree, so a screen
   reader is given the maze as text and nothing else. */
body.maze3d-text-mode #game-container {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: var(--news-paper);
  /* A Hard maze is 43 mono columns, wider than a phone-stage; the container's
     hidden overflow would silently clip the columns on the right. The panel's
     own margin:auto (not justify-content) does the centring, because a centred
     flex item that overflows a scroll container is unreachable on its left. */
  overflow: auto;
}

/* THREE sets an inline display on the canvas it creates. An inline style beats
   any selector, so short of rewriting the element's style attribute the hide has
   to be declared important — otherwise text mode renders the grid underneath a
   canvas that is still covering it. */
body.maze3d-text-mode canvas#three-canvas {
  display: none !important;
}

body.maze3d-text-mode #minimap-canvas,
body.maze3d-text-mode .maze3d-crosshair,
body.maze3d-text-mode .maze3d-touch {
  display: none;
}

body.maze3d-text-mode .maze3d-cue {
  position: static;
  transform: none;
  opacity: 1;
  max-width: 40rem;
}

.maze3d-text-panel {
  display: none;
  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
  font-size: 0.8rem;
  line-height: 1.35;
  white-space: pre;
  color: var(--news-ink);
  text-align: left;
}

body.maze3d-text-mode .maze3d-text-panel {
  display: block;
  margin: auto;
}

.maze3d-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* -------------------------------------------------------------------- mobile */

@media (max-width: 768px) {
  /* The shared .mobile-timer pill replaces the in-stage timer on phones;
     keeping both stacked two identical pills in the same corner. */
  #timer-display {
    display: none;
  }

  #minimap-canvas {
    top: 8px;
    left: 8px;
    border-width: 1px;
    box-shadow: none;
    background: color-mix(in srgb, var(--news-paper) 84%, transparent);
  }

  /* The cue lives in the free strip under the map, not across it. As a centred
     pill at the top it covered the mini-map at every phone width. */
  /* Sits on the row *below* the timer pill, which owns the top-right corner.
     Sharing that row put the cue's right edge under the pill at every phone
     width. */
  .maze3d-cue {
    top: 8px;
    bottom: auto;
    /* Between the mini-map (top-left, ~104px wide) and the timer pill (top-right,
       ~100px wide), so it shares the top row instead of covering either. */
    left: 130px;
    right: 116px;
    transform: translateY(10px);
    max-width: none;
    font-size: 0.68rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.25;
    padding: 0.28rem 0.5rem;
    text-align: left;
  }

  .maze3d-cue.is-visible {
    transform: translateY(0);
  }

  .maze3d-stick {
    width: 96px;
    height: 96px;
    left: 12px;
    bottom: 12px;
  }

  .maze3d-stick-knob {
    width: 40px;
    height: 40px;
    margin: -20px 0 0 -20px;
  }

  /* Bottom-right belongs to the help FAB, which floats at 10px from the corner.
     The toggle stacks directly above it rather than sharing the corner, and both
     stay clear of the thumbstick, which owns bottom-left on touch. */
  .maze3d-text-toggle {
    left: auto;
    right: 10px;
    bottom: 86px;
  }

  .maze3d-touch-hint {
    right: 10px;
    bottom: 124px;
    font-size: 0.62rem;
  }
}

/* Very narrow phones: at 320 the stick, the hint and the text toggle all wanted
   the same 90px of bottom gutter and overlapped. The hint goes, the toggle moves
   up under the timer, and the stick keeps the corner to itself. */
@media (max-width: 380px) {
  .maze3d-touch-hint {
    display: none;
  }

  .maze3d-text-toggle {
    right: 8px;
    bottom: auto;
    top: 46px;
    font-size: 0.64rem;
    padding: 0.22rem 0.42rem;
  }

  .maze3d-touch-hint {
    display: none;
  }

  .maze3d-stick {
    width: 88px;
    height: 88px;
    left: 10px;
    bottom: 10px;
  }

  .maze3d-cue {
    max-width: calc(100% - 120px);
    font-size: 0.66rem;
  }
}

/* Short viewports — landscape phones.
   The base rules' min-height floors (300px under 768, 320px over) push the
   stage bottom — and the thumbstick with it — below the fold on a 360px-tall
   viewport, leaving the page hundreds of pixels tall and
   `elementFromPoint` at the stick's own coordinates returning null. Removing
   the floor is enough: the shared height calcs already subtract the real
   published --site-header-height, so the stage fits above the fold.
   (An earlier version of this block also overrode the height with
   `100dvh - 12px`, written for a header the shared chrome supposedly collapses
   to 1px on short viewports. It collapses nothing — the header keeps its full
   height — so that calc left the stage exactly one header height too low.) */
@media (max-height: 520px) and (max-width: 1024px) {
  body.game-3d-wrapper .game-canvas-container {
    min-height: 0;
    border-width: 2px;
  }

  body.game-3d-wrapper .site-header {
    /* The shared chrome leaves the header full-size here, and its children
       keep their own box and overflow it, so the nav bar, breadcrumb and
       theme toggle still lie across the top of the stage and swallow taps
       meant for the thumbstick ("elementFromPoint" at the stick's own centre
       returned the breadcrumb row). Scoping the header under the stage for
       short viewports puts the game back on top; every header control stays
       reachable by scrolling, and nothing changes at any other size. */
    position: relative;
    z-index: 1;
  }

  body.game-3d-wrapper .game-canvas-container {
    z-index: 2;
  }

  #minimap-canvas {
    top: 6px;
    left: 6px;
  }

  /* Over 768px the sidebar is a fixed right rail rather than a bottom sheet,
     so the stage must not full-bleed under it — that hid the in-stage timer
     and the right side of the view beneath the sidebar. Reserve the sidebar's
     column instead, the same treatment wordladder3d gives its board. The text
     toggle's low perch belongs to this range too: under 768px the help FAB
     owns the bottom-right corner, and the base rule already stacks the toggle
     above it. */
  @media (min-width: 769px) {
    body.game-3d-wrapper .game-canvas-container {
      width: auto;
      translate: none;
      margin: 4px 6px 0;
      /* +16 clears the rail's own right offset, which is ~15px at these
         widths, not the 0 the sidebar width alone assumes. */
      margin-right: calc(var(--stage-sidebar-w, 300px) + 16px);
      padding: 4px;
    }

    .maze3d-text-toggle {
      left: auto;
      right: 10px;
      bottom: 8px;
      padding: 0.22rem 0.5rem;
    }
  }
}

/* Touch geometry for short viewports only, and only where a stick is actually
   shown. The previous version set stick sizes for any viewport under 900px wide,
   which leaked onto desktop windows where the stick is hidden but still measured
   — and overlapped the text toggle. */
@media (max-height: 520px) and (max-width: 1024px) and (hover: none) and (pointer: coarse) {
  .maze3d-stick {
    width: 76px;
    height: 76px;
    left: 10px;
    bottom: 8px;
  }

  .maze3d-stick-knob {
    width: 30px;
    height: 30px;
    margin: -15px 0 0 -15px;
  }

  .maze3d-touch-hint {
    display: none;
  }
}

/* Landscape phones: the stage is short, so the controls shrink and the cue moves
   out of the way of the view. */
@media (max-height: 480px) and (hover: none) {
  .maze3d-stick {
    width: 78px;
    height: 78px;
    bottom: 10px;
  }

  .maze3d-stick-knob {
    width: 32px;
    height: 32px;
    margin: -16px 0 0 -16px;
  }

  .maze3d-cue {
    top: 6px;
    left: 50%;
    right: auto;
    max-width: calc(100% - 120px);
  }

  .maze3d-touch-hint {
    display: none;
  }
}

/* ---------------------------------------------------------------- preferences */

@media (prefers-reduced-motion: reduce) {
  .maze3d-cue,
  .maze3d-stick-knob {
    transition: none;
  }
}
