/*
 * Number Hunt — child game styling.
 *
 * TWO RULES THAT OVERRIDE EVERY AESTHETIC PREFERENCE
 *
 * 1. NOTHING IS DISTINGUISHED BY COLOUR ALONE. Not a button state, not progress,
 *    not which key is pressed. Everything is legible through shape, position,
 *    size, icon and text. This is an app for a child whose colour vision is the
 *    open question — an interface he cannot read would be both cruel and a
 *    confound.
 *
 * 2. NOTHING SATURATED GOES NEAR A PLATE. The interface is greyscale during
 *    play. Colour adjacent to the stimulus shifts the eye's adaptation state,
 *    and adaptation is precisely what the plates measure. The creatures and the
 *    end screen are colourful; the play screen is not.
 */

:root {
  --ink: #1f2024;
  --ink-soft: #5a5c66;
  --paper: #ececef;
  --card: #ffffff;
  --line: #c9cad1;
  --key: #f7f7f9;
  --key-press: #dcdde3;
  --focus: #1f2024;

  --radius: 18px;
  --tap: 64px;

  /* Fallback for older iOS, where 100dvh is unreliable. Set from JS. */
  --vh: 1vh;
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  background: var(--paper);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* No accidental text selection or magnifier from a stray long-press. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  /* Removes the 300ms tap delay without disabling pinch-zoom at the OS level. */
  touch-action: manipulation;
}

/* Body is fixed rather than scrollable: on iOS a scrollable body rubber-bands,
   and a plate that slides under your thumb mid-answer is its own problem. */
body {
  position: fixed;
  inset: 0;
  width: 100%;
}

#app {
  height: 100dvh;
  min-height: calc(var(--vh) * 100);
  display: flex;
  flex-direction: column;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom)
    env(safe-area-inset-left);
}

.screen {
  flex: 1;
  /* Without min-height:0 a flex child refuses to shrink below its content, so
     it grows past the viewport instead of scrolling. */
  min-height: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Scroll inside the screen without the whole page rubber-banding. */
  overscroll-behavior: contain;
  padding: 16px;
  gap: 14px;
  text-align: center;
}
.screen.active {
  display: flex;
}

/*
 * Centre with auto margins rather than justify-content.
 *
 * justify-content:center on a scrollable flex column clips BOTH ends of
 * anything taller than the container, and the clipped part cannot be scrolled
 * to — it is unreachable, not merely hidden. On the pre-session screen, which
 * carries two explanatory cards, that put the Start button permanently off the
 * bottom of a tablet in portrait.
 *
 * Auto margins on the first and last child give the behaviour actually wanted:
 * content sits centred when there is room, and when there is not the autos
 * collapse to zero so everything remains scrollable.
 *
 * The play screen is excluded — it manages its own vertical distribution and
 * must not scroll while a plate is on screen.
 */
.screen.active:not(#screen-play) > :first-child {
  margin-top: auto;
}
.screen.active:not(#screen-play) > :last-child {
  margin-bottom: auto;
}

h1 {
  font-size: clamp(24px, 6vw, 34px);
  margin: 0;
  letter-spacing: -0.01em;
}
h2 {
  font-size: clamp(19px, 4.6vw, 25px);
  margin: 0;
}
p {
  margin: 0;
  max-width: 34ch;
  line-height: 1.5;
  color: var(--ink-soft);
  font-size: clamp(15px, 3.6vw, 18px);
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

button {
  font: inherit;
  color: inherit;
  border: 2px solid var(--line);
  background: var(--card);
  border-radius: var(--radius);
  min-height: var(--tap);
  padding: 0 22px;
  cursor: pointer;
  transition: transform 0.06s ease, background 0.12s ease;
}
button:active {
  transform: scale(0.97);
  background: var(--key-press);
}
button:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

.btn-primary {
  font-size: clamp(19px, 4.6vw, 23px);
  font-weight: 650;
  min-width: 200px;
  border-width: 3px;
  border-color: var(--ink);
}

.btn-quiet {
  border-style: dashed;
  color: var(--ink-soft);
  min-height: 48px;
  font-size: 15px;
}

/* --------------------------------------------------------------------------
   Play screen
   -------------------------------------------------------------------------- */

#screen-play {
  justify-content: space-between;
  /*
   * Not `overflow: hidden`. The plate is sized from this container's measured
   * height so the layout should always fit, but if it ever does not, clipping
   * would put the Check button permanently out of reach. Scrolling is the lesser
   * evil: it is a fallback that should never trigger, whereas a clipped control
   * is unusable the moment it happens.
   */
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 8px 12px 12px;
  gap: 8px;
}

.topbar {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 40px;
}

/*
 * The ONLY state visible during play.
 *
 * Trials completed, nothing else. No points counter, no streak, no multiplier:
 * a number that jumps by 5 instead of 20, or a streak falling from 5 to 0, tells
 * the child he got one wrong just as plainly as a red cross would. All of that
 * is revealed on the end screen, where it reads as a summary rather than a
 * verdict on the plate he just saw.
 */
.progress {
  flex: 1;
  height: 14px;
  background: var(--card);
  border: 2px solid var(--line);
  border-radius: 999px;
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  width: 0%;
  background: var(--ink);
  border-radius: 999px;
  transition: width 0.35s ease;
}
.progress-text {
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  color: var(--ink-soft);
  min-width: 62px;
  text-align: right;
}

.plate-area {
  flex: 1;
  /* min-height:0 is load-bearing, not tidying. A flex item defaults to
     min-height:auto, which refuses to shrink below its content — so the plate
     canvas from the previous trial kept this box propped open, the measurement
     of "space available" came back too large, and the next plate was sized to
     overflow. The canvas must never be scaled by CSS to compensate: that would
     resample it and interpolate the very colours the engine computed exactly. */
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  position: relative;
}

/*
 * No filter, opacity, transform, box-shadow or blend mode on this element, ever.
 * Any compositing step can shift the colours the colour engine computed to five
 * decimal places, and this canvas is the last place those numbers are still
 * true. The border-radius is safe because it only clips.
 */
#plate {
  border-radius: 50%;
  display: block;
}

.sparkle {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Answer entry
   -------------------------------------------------------------------------- */

.answer-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: 420px;
}

.answer-display {
  flex: 1;
  min-height: 56px;
  border: 3px solid var(--line);
  border-radius: 14px;
  background: var(--card);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
}
.answer-display:empty::before {
  content: '–';
  color: var(--line);
}

.numpad {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
  width: 100%;
  max-width: 420px;
}
.numpad button {
  min-height: var(--tap);
  font-size: 26px;
  font-weight: 650;
  padding: 0;
  background: var(--key);
}

.action-row {
  display: flex;
  gap: 10px;
  width: 100%;
  max-width: 420px;
}
.action-row button {
  flex: 1;
}

/*
 * The "?" button.
 *
 * Deliberately given equal visual weight to Check, and a different shape, so it
 * never reads as the lesser option. Pressing it when there is genuinely nothing
 * there is a win worth the same points as finding a digit — and, on a plate he
 * cannot see, it is the single most informative response the app can record.
 */
#btn-nodigit {
  border-style: dashed;
  border-width: 3px;
  font-size: 17px;
  line-height: 1.15;
}
#btn-check {
  border-width: 3px;
  border-color: var(--ink);
  font-weight: 700;
  font-size: 19px;
}

/* --------------------------------------------------------------------------
   Setup / gate screens
   -------------------------------------------------------------------------- */

.card {
  background: var(--card);
  border: 2px solid var(--line);
  border-radius: var(--radius);
  padding: 18px;
  max-width: 520px;
  width: 100%;
  text-align: left;
}

.check-row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 0;
  cursor: pointer;
}
.check-row input {
  width: 28px;
  height: 28px;
  margin: 2px 0 0;
  flex: none;
}
.check-row span {
  font-size: 16px;
  line-height: 1.4;
}
.check-row small {
  display: block;
  color: var(--ink-soft);
  font-size: 13.5px;
  margin-top: 3px;
}

.problem-list {
  margin: 8px 0 0;
  padding-left: 20px;
  color: var(--ink-soft);
  font-size: 15px;
}
.problem-list li {
  margin-bottom: 6px;
}

/* --------------------------------------------------------------------------
   End screen and album
   -------------------------------------------------------------------------- */

.score-big {
  font-size: clamp(38px, 11vw, 60px);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.level-bar {
  width: 100%;
  max-width: 320px;
  height: 16px;
  border: 2px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  overflow: hidden;
}
.level-fill {
  height: 100%;
  background: var(--ink);
  transition: width 0.6s ease;
}

.album {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(86px, 1fr));
  gap: 10px;
  width: 100%;
  max-width: 520px;
  overflow-y: auto;
  padding: 4px;
}
.album figure {
  margin: 0;
  text-align: center;
}
.album figcaption {
  font-size: 12px;
  color: var(--ink-soft);
  margin-top: 2px;
}

.mute-toggle {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 8px);
  right: calc(env(safe-area-inset-right) + 8px);
  min-height: 44px;
  min-width: 44px;
  padding: 0;
  font-size: 20px;
  border-radius: 50%;
  z-index: 5;
}

/* --------------------------------------------------------------------------
   Motion
   -------------------------------------------------------------------------- */

@keyframes pop {
  0% { transform: scale(0.6); opacity: 0; }
  60% { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
.pop {
  animation: pop 0.35s ease-out both;
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/*
 * The app sets its own colours for a reason and must not be re-themed. A dark
 * mode override would change the surround the plates sit in, which changes the
 * eye's adaptation state and therefore the measurement. The pre-session check
 * warns the parent if the device is in dark mode.
 */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: light;
  }
}

/* --------------------------------------------------------------------------
   Short landscape — a phone turned sideways
   --------------------------------------------------------------------------
   Stacked vertically, the plate plus progress bar plus answer box plus numpad
   plus action row need more height than a phone has in landscape, so the Check
   button ends up below the fold. Shrinking the plate to compensate would make
   it too small to scan, and shrinking the keys would break the 56px minimum
   touch target this app depends on.

   So the layout turns instead: plate on the left, controls on the right. Both
   keep their proper size, and nothing needs to scroll. Only applied when the
   screen is genuinely short — a tablet in landscape has plenty of height and
   keeps the stacked layout. */
@media (orientation: landscape) and (max-height: 620px) {
  #screen-play {
    display: grid;
    grid-template-columns: minmax(0, auto) minmax(230px, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    column-gap: 16px;
    row-gap: 8px;
    align-content: stretch;
  }
  #screen-play .topbar {
    grid-column: 1 / -1;
    grid-row: 1;
  }
  #screen-play .plate-area {
    grid-column: 1;
    grid-row: 2 / span 3;
  }
  /* The teaching prompt only appears during onboarding; in landscape the space
     is better spent on the plate. */
  #screen-play #play-prompt {
    display: none;
  }
  #screen-play .answer-row {
    grid-column: 2;
    grid-row: 2;
  }
  #screen-play .numpad {
    grid-column: 2;
    grid-row: 3;
    align-self: center;
  }
  #screen-play .action-row {
    grid-column: 2;
    grid-row: 4;
  }
}
