/*
 * Number Climb — board, tiles and controls.
 *
 * Not one colour is written in this file: no hex, no rgb(), not even a neutral
 * black or white. Every fill, edge, sheen and ink arrives as a custom property
 * set on the element by game.js — computed there as a measured ramp between
 * the surface colour and --la-primary, with --la-ink and --la-surface for the
 * numerals — and everything else reads a --la-* token directly. Shadows are
 * tinted with --la-line and --la-line-strong, which are already the brand's
 * ink at low alpha, and lit edges with --la-surface-edge. (`transparent`
 * appears in one keyframe; it is alpha, not a colour choice.) A buyer changing
 * primaryColour in brand.config.js restyles the whole board without opening
 * this file.
 *
 * The board sizes itself from one container query unit: the stage is a square
 * container, and every cell, gap and type size is a multiple of its width.
 * That is why it survives 360px phones and 4K screens with no media queries.
 *
 * Depth is the house style, borrowed from shared/style.css: layered shadows, a
 * lit top edge, a solid base. Tiles are raised counters, cells are wells cut
 * into a sunken tray, keycaps have a skirt to press into.
 */

.nc-wrap {
  /* One measure shared by the goal line, the tray and the controls, so the
     three pieces read as a single composed unit rather than three widths.
     A fixed length rather than a min(): the shell sizes the board panel to
     its contents, and only a definite width tells it how wide the board
     wants to be. Each user pairs it with max-width: 100% for small screens. */
  --nc-measure: 24rem;
  display: grid;
  /* One column that may grow to the measure but never past the space it is
     given — an auto column would take its max-content width and push a 24rem
     board off the side of a 360px phone. */
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(9px, 2.1vw, 14px);
  justify-items: center;
  width: 100%;
}

/* ---- Goal meter -------------------------------------------------------
   The target line and the bar under it are one instrument: the figures use
   the shell's own stat styling, and the track is ticked once per rung so the
   climb can be counted at a glance instead of estimated from a stripe. */
.nc-goal {
  width: var(--nc-measure);
  max-width: 100%;
  display: grid;
  gap: 8px;
  padding: 0 2px;
}
.nc-goal__text {
  margin: 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.nc-goal__track {
  position: relative;
  height: 10px;
  border-radius: 999px;
  border: 1px solid var(--la-line);
  background-color: var(--la-surface-sunken);
  /* A hairline at the end of every rung's share of the track. */
  background-image: linear-gradient(90deg, transparent calc(100% - 1px), var(--la-line-strong) calc(100% - 1px));
  background-size: calc(100% / var(--nc-rungs, 10)) 100%;
  background-repeat: repeat-x;
  box-shadow: inset 0 1px 2px var(--la-line);
}
/* The target: a post at the top of the ladder, standing slightly proud of
   the track so the bar has somewhere to arrive. */
.nc-goal__track::after {
  content: "";
  position: absolute;
  top: -4px;
  bottom: -4px;
  right: -1px;
  width: 2px;
  border-radius: 999px;
  background: var(--la-primary);
}

.nc-goal__fill {
  position: relative;
  height: 100%;
  width: var(--nc-progress, 0%);
  border-radius: 999px;
  background-image: linear-gradient(90deg, var(--la-primary-hover), var(--la-primary));
  box-shadow: inset 0 1px 0 var(--la-surface-edge);
  transition: width 0.34s cubic-bezier(0.25, 0.8, 0.35, 1);
}
/* The best rung reached, marked on the ladder as a needle rather than merely
   implied by where the colour stops. It reads against the target post at the
   far end, so the gap between the two IS the climb left to do. */
.nc-goal__fill::after {
  content: "";
  position: absolute;
  top: -3px;
  bottom: -3px;
  right: -1px;
  width: 3px;
  border-radius: 999px;
  background: var(--la-primary-hover);
  box-shadow: 0 0 0 2px var(--la-primary-glow);
}

/* ---- Board ------------------------------------------------------------ */
.nc-frame {
  width: var(--nc-measure);
  max-width: 100%;
  padding: clamp(7px, 1.9vw, 12px);
  border-radius: calc(var(--la-radius) + 5px);
  border-color: var(--la-line-strong);
  /* .la-tray supplies the sunken treatment; this deepens it and tints it
     with the brand so the wells have real material to be cut into. */
  background-color: var(--nc-tray, var(--la-surface-sunken));
  box-shadow:
    inset 0 2px 5px var(--la-line),
    inset 0 -1px 0 var(--la-surface-edge);
}

.nc-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  /* Everything inside measures itself against this square in cqw units. */
  container-type: inline-size;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.nc-cells,
.nc-tiles {
  position: absolute;
  inset: 0;
}

.nc-cell,
.nc-tile {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--nc-cell, 22cqw);
  height: var(--nc-cell, 22cqw);
  /* --nc-pitch is one cell plus one gap, so whole-number coordinates land
     exactly on the backing grid at any board size. */
  transform: translate(calc(var(--c, 0) * var(--nc-pitch, 24cqw)), calc(var(--r, 0) * var(--nc-pitch, 24cqw)));
}

/* An empty cell is a well, not a grey square: shaded where the tray's edge
   overhangs it and lit along the floor at the bottom. */
.nc-cell {
  border-radius: 21%;
  background-color: var(--nc-well, var(--la-surface-sunken));
  box-shadow:
    inset 0 max(1px, 0.7cqw) max(2px, 1.5cqw) var(--la-line-strong),
    inset 0 calc(-1 * max(1px, 0.45cqw)) 0 var(--nc-well-lip, var(--la-surface-edge));
}

.nc-tile {
  display: grid;
  place-items: stretch;
}
.nc-tile--sliding {
  /* Weighted: quick off the mark, settling into the cell rather than
     stopping dead on it. */
  transition: transform 0.12s cubic-bezier(0.3, 0.74, 0.22, 1);
}

/*
 * The face carries the look and every animation. Keeping it separate from the
 * positioned parent means a pop or a spawn can scale freely without fighting
 * the translate that puts the tile on its cell.
 */
.nc-tile__face {
  position: relative;
  border-radius: 21%;
  background-color: var(--face-base);
  /* Lighter at the top, heavier at the base: a moulded counter catching the
     light, not a flat swatch. game.js measures the numeral's contrast across
     the whole of this gradient, not at its convenient middle. */
  background-image: linear-gradient(180deg, var(--face-top), var(--face-base));
  color: var(--ink);
  display: grid;
  place-items: center;
  /* A lit top lip, a solid base and a shadow underneath: the tiles read as
     pressed counters sitting in the tray, which is this pack's own
     handwriting. Bevels are in container units so they thin out on a 5x5
     board. */
  --nc-bevel: max(2px, calc(1cqw * var(--nc-type, 1)));
  box-shadow:
    inset 0 var(--nc-bevel) 0 var(--lip),
    inset 0 calc(-1 * var(--nc-bevel)) 0 var(--edge),
    0 max(1px, calc(0.8cqw * var(--nc-type, 1))) max(2px, calc(1.8cqw * var(--nc-type, 1)))
      calc(-0.4cqw * var(--nc-type, 1)) var(--la-line-strong);
  overflow: hidden;
}

/*
 * Rung meter: a track with a fill that grows as the tile climbs the ladder, so
 * a glance across the board shows how far each tile is from the target without
 * having to read every number.
 */
.nc-tile__face::before,
.nc-tile__face::after {
  content: "";
  position: absolute;
  /* Short and centred, well clear of the numeral: long enough to read as a
     gauge, short enough that it can never be mistaken for an underline. */
  left: 31%;
  right: 31%;
  bottom: 11%;
  height: 2.8%;
  min-height: 2px;
  border-radius: 999px;
  background: currentColor;
}
.nc-tile__face::before {
  opacity: 0.16;
}
.nc-tile__face::after {
  opacity: 0.55;
  transform: scaleX(var(--rank, 0));
  transform-origin: left center;
}

/* Past the target the tiles are crowned so a long climb still feels like progress. */
.nc-tile--crown .nc-tile__face {
  outline: max(1px, calc(0.7cqw * var(--nc-type, 1))) solid var(--ink);
  outline-offset: calc(-2.4cqw * var(--nc-type, 1));
}

.nc-tile__value {
  font-family: var(--la-numeric);
  font-weight: 800;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  /* Fallback for engines without container query units. */
  font-size: 1.5rem;
  font-size: calc(9.6cqw * var(--nc-type, 1));
  padding-bottom: 4%;
}
/* The ladder runs into two digits well before any target, so the numerals
   step down once and never need to again. */
.nc-tile[data-digits="2"] .nc-tile__value {
  font-size: calc(8.2cqw * var(--nc-type, 1));
}

/* ---- Motion -----------------------------------------------------------
   A slide is one gesture: about 120ms of travel, then the merged tile lands
   with a short pop. Nothing here outlives the move it belongs to. */
@keyframes nc-appear {
  from {
    transform: scale(0.42);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}
@keyframes nc-pop {
  0% {
    transform: scale(0.9);
    filter: brightness(1.12);
  }
  55% {
    transform: scale(1.07);
    filter: brightness(1.05);
  }
  100% {
    transform: scale(1);
    filter: none;
  }
}
.nc-tile--new .nc-tile__face {
  animation: nc-appear 0.14s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}
.nc-tile--merged .nc-tile__face {
  animation: nc-pop 0.09s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

/* ---- Controls --------------------------------------------------------- */
.nc-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 3vw, 20px);
  width: var(--nc-measure);
  max-width: 100%;
}

/* The four keys sit in their own housing so the cluster reads as a D-pad
   rather than four loose buttons. */
.nc-pad {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 4px;
  justify-items: center;
  padding: 6px;
  border-radius: calc(var(--la-radius) + 2px);
  border: 1px solid var(--la-line-strong);
  background: var(--la-surface-sunken);
  box-shadow: inset 0 1px 4px var(--la-line-strong);
}
.nc-pad__hint {
  display: grid;
  place-items: center;
  color: var(--la-ink-faint);
}

.nc-dir {
  font: inherit;
  /* 44px is the smallest comfortable touch target, so that is the floor —
     and the last 2px are the skirt the cap presses into. */
  width: clamp(46px, 13vw, 52px);
  height: clamp(46px, 13vw, 52px);
  padding: 0 0 2px;
  border: 0;
  background: none;
  border-radius: 14px;
  display: grid;
  place-items: stretch;
  cursor: pointer;
}
.nc-dir__cap {
  display: grid;
  place-items: center;
  border-radius: 13px;
  border: 1px solid var(--la-line);
  background-image: linear-gradient(180deg, var(--la-surface), var(--la-surface-sunken));
  color: var(--la-ink);
  box-shadow:
    0 2px 0 var(--la-line-strong),
    var(--la-shadow-sm),
    inset 0 1px 0 var(--la-surface-edge);
  transition:
    transform 0.09s cubic-bezier(0.3, 0.8, 0.3, 1),
    box-shadow 0.09s ease,
    border-color 0.15s ease,
    background-image 0.15s ease;
}
.nc-dir:hover .nc-dir__cap {
  border-color: var(--la-primary);
  background-image: linear-gradient(180deg, var(--la-surface), var(--la-primary-soft));
}
/* Pressed: the cap travels into its skirt and the shadow underneath goes.
   nc-dir--tapped is the same state driven by the matching arrow key, so the
   pad and the keyboard look like one control instead of two. */
.nc-dir:active .nc-dir__cap,
.nc-dir--tapped .nc-dir__cap {
  transform: translateY(2px);
  border-color: var(--la-primary);
  background-image: linear-gradient(180deg, var(--la-primary-soft), var(--la-primary-soft));
  box-shadow: inset 0 1px 3px var(--la-line-strong);
}
.nc-dir svg {
  width: 19px;
  height: 19px;
  display: block;
}
.nc-dir--right svg {
  transform: rotate(90deg);
}
.nc-dir--down svg {
  transform: rotate(180deg);
}
.nc-dir--left svg {
  transform: rotate(270deg);
}

.nc-dir--up {
  grid-column: 2;
  grid-row: 1;
}
.nc-dir--left {
  grid-column: 1;
  grid-row: 2;
}
.nc-pad__hint {
  grid-column: 2;
  grid-row: 2;
}
.nc-dir--right {
  grid-column: 3;
  grid-row: 2;
}
.nc-dir--down {
  grid-column: 2;
  grid-row: 3;
}

.nc-actions {
  display: grid;
  gap: 9px;
  align-content: center;
}
.nc-actions .la-btn {
  width: 100%;
  min-width: 7.5rem;
}

/* ---- Undo -------------------------------------------------------------
   The signature mechanic, so it wears its state: a coin in the bank, an empty
   socket waiting on a slide worth taking back, or a spent slot. The pip is a
   different SHAPE in each, never just a different colour. */
.nc-undo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
}
.nc-undo[disabled] {
  /* Lighter than the shell's default disabled state: the pip still has to be
     readable when the button is out of action. */
  opacity: 0.62;
  cursor: not-allowed;
}
.nc-undo__pip {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--la-ink-faint);
  transition:
    background 0.2s ease,
    box-shadow 0.2s ease,
    height 0.2s ease;
}
/* Held: a coin, sitting proud with a halo. */
.nc-undo--ready {
  border-color: var(--la-primary);
  background-image: linear-gradient(180deg, var(--la-surface), var(--la-primary-soft));
}
.nc-undo--ready .nc-undo__pip {
  background: var(--la-primary);
  box-shadow: 0 0 0 3px var(--la-primary-glow);
}
/* On hold: an empty socket — there is one in the bank, but no slide to spend
   it on yet. */
.nc-undo--held .nc-undo__pip {
  background: none;
  box-shadow: inset 0 0 0 2px var(--la-ink-faint);
}
/* Spent: the socket has collapsed to a bare slot. */
.nc-undo--spent .nc-undo__pip {
  height: 3px;
  border-radius: 999px;
  background: var(--la-ink-faint);
  box-shadow: none;
}

/* Earning one back is the round's small reward, so it is worth a beat: the
   button lifts and the coin drops in with a ring. */
@keyframes nc-restore {
  0% {
    transform: scale(1);
  }
  38% {
    transform: scale(1.05);
  }
  100% {
    transform: scale(1);
  }
}
@keyframes nc-restore-pip {
  0% {
    transform: scale(0.4);
    box-shadow: 0 0 0 0 var(--la-primary-glow);
  }
  45% {
    transform: scale(1.3);
    box-shadow: 0 0 0 9px transparent;
  }
  100% {
    transform: scale(1);
    box-shadow: 0 0 0 3px var(--la-primary-glow);
  }
}
.nc-undo--struck {
  animation: nc-restore 0.34s cubic-bezier(0.2, 0.8, 0.3, 1);
}
.nc-undo--struck .nc-undo__pip {
  animation: nc-restore-pip 0.5s cubic-bezier(0.2, 0.8, 0.3, 1);
}

/* ---- Footnote --------------------------------------------------------- */
.nc-hint kbd {
  font: inherit;
  font-weight: 700;
  padding: 1px 5px;
  color: var(--la-ink-soft);
  border: 1px solid var(--la-line);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--la-surface);
}

@media (prefers-reduced-motion: reduce) {
  .nc-tile--sliding {
    transition: none;
  }
  .nc-tile--new .nc-tile__face,
  .nc-tile--merged .nc-tile__face,
  .nc-undo--struck,
  .nc-undo--struck .nc-undo__pip {
    animation: none;
  }
  .nc-goal__fill,
  .nc-dir__cap,
  .nc-undo__pip {
    transition: none;
  }
}
