/* Field Station — the shared surface for every live game.
 *
 * One system, two worlds. The screen is a lit tray on a dark bench: the ground
 * is deep and quiet, one lamp falls on the tray, and the maths is the only lit
 * thing she is looking at. That is the whole idea, and it is the fix for the
 * thing that was wrong before — a white page with grey rules reads as a
 * worksheet, and a child who has decided maths is boring believes the page.
 *
 * Loaded by BOTH the single-player games and the duel, before their own
 * stylesheet. Tokens live here; manipulative-specific rules live in play.css
 * and board-specific rules in duel.css.
 *
 * ONE COMMITTED THEME on purpose: no prefers-color-scheme anywhere. Her screen
 * must look the same whatever her iPad decided about the time of day.
 */

@font-face {
  font-family: "Lexend";
  src: url("/static/fonts/lexend.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Fraunces";
  src: url("/static/fonts/fraunces.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/static/fonts/plexmono.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}

:root {
  /* the room */
  --page:       #070F14;
  --bench-deep: #0C181D;
  --bench-line: #22404C;
  --bench-ink:  #A9C2CC;
  --bench-dim:  #5D7783;

  /* the lit tray — everything the maths needs sits on this */
  --tray-face: #F5EEE0;
  --tray-2:    #E9DFCA;
  --tray-edge: #C6B99C;
  --ink:       #16232A;
  --ink-soft:  #61707A;

  /* Verdicts. Identical in every world: right and wrong are the one pair of
     colours a student must never have to re-learn. */
  --yes: #3E9C6B;
  --no:  #C2544C;

  /* Duel seats. Also skin-independent — whose square is whose is not a
     stylistic decision. */
  --seat-a: #2E6FB0;
  --seat-b: #C4562F;
  /* Seats three and four, for a shared board with more than two players.
     Chosen to sit clear of BOTH the first two seats and of --yes / --no: a
     seat colour has to survive BOTH worlds: the field world's accent is aqua
     and the dig world's is ochre, so a teal or gold seat would read as the
     skin's own accent in one of them. Violet and green are the two hues
     neither world uses, and neither is the red that means wrong. */
  --seat-c: #6D4C9F;
  --seat-d: #2F7D4F;

  --display: "Fraunces", Georgia, "Songti TC", serif;
  --ui: "Lexend", "PingFang TC", "Noto Sans TC", "Microsoft JhengHei",
        system-ui, -apple-system, sans-serif;
  --mono: "IBM Plex Mono", "PingFang TC", ui-monospace, monospace;

  /* One counter: a ten-frame cell, a rod segment, a take-away counter. Every
     manipulative is a multiple of this, so a 6-rod is visibly six times a
     1-rod. Floor is big enough for a seven-year-old to hit. */
  --unit: clamp(1.6rem, 6vw, 2.7rem);

  /* Defaults, so a page that forgets to set a world still renders. */
  --find:      #34C9BF;
  --find-deep: #0E6F6A;
  --lamp-tint: rgba(52, 201, 191, .16);
}

/* ── the worlds ──────────────────────────────────────────────────────────────
   A world is a colour pair and five finds. Adding one is these four lines plus
   an entry in games/skins.py — novelty is meant to be cheap, because it is a
   resource the teacher spends weekly. */
[data-skin="field"] {
  --find:      #34C9BF;
  --find-deep: #0E6F6A;
  --lamp-tint: rgba(52, 201, 191, .16);
}
[data-skin="dig"] {
  --find:      #E0A32F;
  --find-deep: #8A5D0E;
  --lamp-tint: rgba(224, 163, 47, .17);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bench-deep);
  color: var(--bench-ink);
  font-family: var(--ui);
  font-size: 17px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  min-height: 100vh;
}
/* The work lamp, hung above the tray. Fixed rather than scrolled: it is the
   room's light, not part of the page content. */
body::before {
  content: ""; position: fixed; inset: 0 0 auto 0; height: 70vh;
  background: radial-gradient(ellipse at 50% -8%, var(--lamp-tint), transparent 62%);
  pointer-events: none; z-index: 0;
}
body > * { position: relative; z-index: 1; }

/* ── the page column ─────────────────────────────────────────────────────── */

.play {
  max-width: 54rem;
  margin: 0 auto;
  padding: clamp(.9rem, 3vw, 1.8rem) clamp(.8rem, 3vw, 1.4rem) 3rem;
  display: flex; flex-direction: column;
  gap: clamp(.8rem, 2.4vw, 1.15rem);
}

.phead { display: flex; align-items: flex-end; gap: 1rem; flex-wrap: wrap; }
.phead .who { flex: 1; min-width: 10rem; }

.world {
  display: block; font-family: var(--mono); font-weight: 500;
  font-size: .66rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--find); margin-bottom: .3rem;
}
.world::before {
  content: ""; display: inline-block; width: .42rem; height: .42rem;
  border-radius: 50%; background: var(--find); margin-right: .55rem;
  vertical-align: middle; box-shadow: 0 0 10px var(--find);
}
.phead h1 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.35rem, 4.4vw, 1.95rem); line-height: 1.05;
  color: #EDF4F7; margin: 0; letter-spacing: -.01em;
}

/* ── the rail ────────────────────────────────────────────────────────────────
   The old five stars, rebuilt as a row of empty mounting slots. Each correct
   round mounts one find. It is the progress bar, the reward and the collection
   in one object — those used to be three unrelated things — and the EMPTY
   slots are the part that works: she can see what is still missing. */
.rail { display: flex; flex-direction: column; align-items: flex-end;
        gap: .38rem; padding-bottom: .95rem; }
.rail-label {
  font-family: var(--mono); font-size: .6rem; letter-spacing: .2em;
  text-transform: uppercase; color: var(--bench-dim);
}
.slots { display: flex; gap: .4rem; flex-wrap: wrap; }
.slot {
  width: clamp(2.3rem, 7vw, 3rem); height: clamp(2.3rem, 7vw, 3rem);
  border-radius: 7px; display: grid; place-items: center; position: relative;
  background: #0A151A; border: 1px solid #1D3843;
  box-shadow: inset 0 2px 5px rgba(0,0,0,.55);
}
.slot::after {
  content: attr(data-n); position: absolute; bottom: -.95rem; left: 0; right: 0;
  text-align: center; font-family: var(--mono); font-size: .55rem;
  color: #34525E; letter-spacing: .06em;
}
/* Scoped to .slot: `find` is a word two surfaces want, and unscoped it painted
   the rail's card over every claimed square on the duel board. */
.slot .find {
  width: 100%; height: 100%; border-radius: 6px;
  display: grid; place-items: center; line-height: 1;
  font-size: clamp(1.15rem, 3.6vw, 1.55rem);
  background: linear-gradient(160deg, var(--tray-face), var(--tray-2));
  border: 1px solid var(--tray-edge);
  box-shadow: 0 3px 9px rgba(0,0,0,.45), inset 0 1px 0 #FFFDF6;
}
/* The one orchestrated moment on the page: the find drops out of the tray and
   settles into its slot. Everything else stays still. */
.slot .find.landing { animation: mount .55s cubic-bezier(.3, 1.4, .5, 1); }
@keyframes mount {
  0%   { transform: translateY(-2.4rem) scale(.5) rotate(-14deg); opacity: 0; }
  55%  { transform: translateY(.16rem) scale(1.09) rotate(3deg); opacity: 1; }
  100% { transform: none; }
}

/* ── the tray ────────────────────────────────────────────────────────────────
   Named .stage because that is what the markup has always called it — and
   because .tray already means the Cuisenaire piece tray inside a round. */
.stage {
  background: radial-gradient(ellipse at 50% -10%, #FFFCF4, transparent 55%), var(--tray-face);
  border: 1px solid var(--tray-edge);
  border-radius: 12px;
  padding: clamp(1rem, 3.4vw, 2rem) clamp(.8rem, 3vw, 1.8rem);
  color: var(--ink);
  min-height: 20rem;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(.7rem, 2.2vw, 1.15rem);
  box-shadow: 0 18px 40px -20px rgba(0,0,0,.85), inset 0 1px 0 #FFFDF7;
  animation: lampup .7s ease both;
}
@keyframes lampup { from { opacity: .3; filter: brightness(.75); } to { opacity: 1; } }

.ask {
  font-family: var(--ui); font-weight: 600; letter-spacing: -.01em;
  font-size: clamp(1.25rem, 4.6vw, 1.85rem); text-align: center; margin: 0;
}
.counter {
  font-family: var(--mono); font-size: .72rem; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-soft); margin: 0;
}
.say {
  min-height: 1.7rem; text-align: center; font-size: 1rem;
  color: var(--ink-soft); margin: 0;
}
.say.good { color: var(--find-deep); font-weight: 700; }
.say.hint { color: var(--ink-soft); }

/* ── buttons ─────────────────────────────────────────────────────────────── */

button { font-family: inherit; cursor: pointer; border: 0; background: none; color: inherit; }
button:focus-visible { outline: 3px solid var(--find-deep); outline-offset: 3px; }

.act {
  background: var(--find-deep); color: #fff; border: 0; border-radius: 9px;
  padding: .72rem 1.5rem; font-size: 1.05rem; font-weight: 600;
}
.act:hover:not(:disabled) { filter: brightness(1.14); }
.act.big { font-size: 1.25rem; padding: .9rem 2rem; }
.act.ghost { background: #FFFCF3; color: var(--ink); border: 1px solid var(--tray-edge); }
.act.ghost:hover:not(:disabled) { background: #fff; border-color: var(--find-deep); }
.act:disabled { opacity: .45; cursor: default; }
.row { display: flex; gap: .7rem; flex-wrap: wrap; justify-content: center; }

/* Answer keys. Kept as .choice — the renderers and the test suite both know
   that name. */
.choices { display: flex; flex-wrap: wrap; gap: .55rem; justify-content: center; }
.choiceslot { display: flex; justify-content: center; width: 100%; }
.choice {
  background: #FFFCF3; color: var(--ink);
  border: 1px solid var(--tray-edge); border-bottom-width: 3px; border-radius: 9px;
  min-width: 4.2rem; padding: .62rem 1rem;
  font-size: clamp(1.45rem, 5vw, 1.8rem); font-weight: 700;
  font-variant-numeric: tabular-nums;
  transition: transform .1s ease, background .12s ease, border-color .12s ease;
}
.choice:hover:not(:disabled) { border-color: var(--find-deep); transform: translateY(-1px); }
.choice:active:not(:disabled) { transform: translateY(1px); border-bottom-width: 1px; }
.choice.right { background: var(--yes); border-color: #2E7A52; color: #fff; }
/* Struck through and left in place: she can see what she has already ruled
   out, which is the difference between narrowing down and guessing again.
   Never a buzzer, never a flash of red across the tray. */
.choice.wrong {
  opacity: .45; text-decoration: line-through;
  text-decoration-thickness: 2px; text-decoration-color: var(--no);
}
.choice:disabled { cursor: default; }

/* ── finish, the present, the collection ─────────────────────────────────── */

.finish { display: flex; flex-direction: column; align-items: center;
          gap: .9rem; text-align: center; }
.finish .huge {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.9rem, 7vw, 2.8rem); line-height: 1.1; margin: 0;
}
.finish p { margin: 0; color: var(--ink-soft); }
/* Calm, not a dead end: she is between games, not locked out. */
.waiting { font-weight: 600; color: var(--ink-soft); margin: 0; }
.dots { font-size: 1.5rem; letter-spacing: .35em; color: var(--find-deep);
        margin: .2rem 0 0; animation: breathe 1.9s ease-in-out infinite; }
@keyframes breathe { 0%, 100% { opacity: .38; } 50% { opacity: 1; } }

/* She unwraps it herself. The pause between the tap and the reveal is doing
   the motivational work, so nothing here reveals early. */
.gift {
  display: flex; flex-direction: column; align-items: center; gap: .35rem;
  padding: 1rem 1.7rem; border-radius: 12px;
  background: #FFFCF3; border: 1px dashed var(--find-deep);
  transition: transform .15s ease;
}
.gift:hover:not(:disabled) { transform: scale(1.04); }
.gift:disabled { cursor: default; }
.giftbox { font-size: clamp(3rem, 12vw, 4.6rem); line-height: 1; display: block; }
.gifthint {
  font-family: var(--mono); font-size: .68rem; letter-spacing: .17em;
  text-transform: uppercase; color: var(--find-deep);
}
.gift.shaking .giftbox { animation: wiggle .32s ease-in-out 3; }
@keyframes wiggle {
  0%, 100% { transform: rotate(0deg) scale(1); }
  25%      { transform: rotate(-11deg) scale(1.06); }
  75%      { transform: rotate(11deg) scale(1.06); }
}

.prize {
  display: flex; flex-direction: column; align-items: center; gap: .15rem;
  padding: 1rem 2rem; border-radius: 12px;
  background: #FFFCF3; border: 1px solid var(--tray-edge);
}
.prize.rare { background: linear-gradient(135deg, #FFFBF0, #FCEFCF, #FFFBF0);
              border-color: #D8A93A; }
.prize-emoji { font-size: clamp(3.2rem, 13vw, 4.8rem); line-height: 1; }
img.prize-emoji { width: clamp(6rem, 26vw, 9rem); height: auto; display: block; }
/* The line that says something of hers changed. */
.prize-grew {
  font-family: var(--mono); font-size: .68rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--yes); margin: 0; font-weight: 500;
}
.prize-name { font-family: var(--display); font-weight: 700; font-size: 1.35rem;
              margin: 0; color: var(--ink); }
.prize-rare, .prize-note {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-soft); margin: 0;
}
.prize-rare { color: #8A5D0E; }

.book { display: flex; flex-direction: column; align-items: center; gap: .5rem; }
.book-count { font-family: var(--mono); font-size: .7rem; letter-spacing: .13em;
              text-transform: uppercase; color: var(--ink-soft); margin: 0; }
.book-grid {
  display: flex; flex-wrap: wrap; gap: .3rem; justify-content: center;
  max-width: 24rem;
}
.book-sticker {
  width: 2.6rem; height: 2.6rem; border-radius: 8px;
  display: grid; place-items: center; font-size: 1.25rem; line-height: 1;
  background: #FFFCF3; border: 1px solid var(--tray-edge); overflow: hidden;
}
.bookpic { width: 100%; height: 100%; display: block; }
.book-sticker.rare { background: #FCF2D9; }
.book-sticker.justwon { border-color: var(--yes); box-shadow: 0 0 0 2px rgba(62,156,107,.25); }
/* Empty slots stay visible — the gaps are the reason to come back. */
.book-sticker.locked {
  color: #C3B79E; background: var(--tray-2); border-style: dashed;
  font-family: var(--mono); font-size: .85rem;
}
.book-sticker.locked .bookpic { opacity: .8; }

.pop { animation: pop .32s ease; }
@keyframes pop { 0% { transform: scale(.86); } 60% { transform: scale(1.06); } 100% { transform: scale(1); } }

/* On a phone the rail drops under the title, so it stops being right-aligned. */
@media (max-width: 30rem) {
  .rail { align-items: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
}
