/* ===========================================================================
   TROPHY ROOM v1
   ---------------------------------------------------------------------------
   The room is a painted 1672x941 JPEG. Every trophy is positioned in PERCENT
   of that image, so the only thing that ever recomputes on resize is the size
   of .tr-stage itself — no per-item math, no layout thrash while dragging.

   Letterbox (contain), not cover: the shelf boards span x 441-1234 of 1672 and
   a cover-crop on a portrait tablet would cut their ends off, which would put
   slots outside the visible room. The bars are painted the curtain red so the
   crop reads as a frame instead of a mistake.
   =========================================================================== */

:root {
  --tr-red:      #4a1220;   /* deep curtain shadow — the letterbox colour   */
  --tr-red-lit:  #7d1f2e;
  --tr-gold:     #f0b849;
  --tr-gold-dim: #b8862c;
  --tr-cream:    #fdf6e6;
  --tr-ink:      #2a0d14;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  height: 100%;
  background: var(--tr-red);
  color: var(--tr-cream);
  font-family: 'Nunito', system-ui, -apple-system, sans-serif;
  overscroll-behavior: none;
}

/* The height here is only a first guess for the very first paint; sizeStage()
   measures the real remaining space and overwrites it, because how much chrome
   the PK-5 shell puts above this box differs between desktop and mobile. */
.tr-app {
  position: relative;
  display: flex; flex-direction: column;
  height: calc(100dvh - 44px);
  overflow: hidden;
}

/* ── Bottombar ───────────────────────────────────────────────────────────────
   Coins and the shop used to live in a bar across the top, with a title and a
   mute button. All four decisions are reversed here, and each for its own
   reason:

     - AT THE BOTTOM, because on an iPad the top edge belongs to iOS. The bar sat
       level with the clock, the battery and the rounded corners, so three
       unrelated systems shared one strip and none of them read cleanly.
     - NO TITLE. A room full of shelves and trophies does not need a caption
       telling a child it is the Trophy Room, and the words cost the scene height.
     - NO MUTE. It was there for classrooms, but the iPad's own volume does the
       same job with a control every child already knows, and it cannot be left
       in a state that makes the app look broken.
     - THE SHOP IS THE POINT. Buying a treasure is why a child comes here, and
       the button for it was the same size as a settings toggle. */

.tr-bottombar {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  gap: 14px;
  padding: 10px 20px calc(10px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(180deg, #40101c, #2a0d14);
  border-top: 2px solid #2a0d14;
  z-index: 20;
}

.tr-btn {
  font-family: inherit; font-size: 14px; font-weight: 800;
  padding: 9px 16px; border-radius: 999px; border: 0;
  cursor: pointer; color: var(--tr-ink);
  background: #e8dcc4;
  transition: transform 0.12s, filter 0.12s;
  -webkit-tap-highlight-color: transparent;
}
.tr-btn:hover  { filter: brightness(1.06); }
.tr-btn:active { transform: scale(0.96); }
.tr-btn-primary {
  background: linear-gradient(180deg, var(--tr-gold), #dda034);
  box-shadow: 0 3px 0 #8a6218;
}
.tr-btn-ghost {
  background: transparent; color: var(--tr-cream);
  border: 2px solid rgba(253,246,230,0.35);
}

/* Purse chip. The coin is an inline SVG, never the emoji — emoji 13 is missing
   on the older Chromebooks in classrooms and degrades to a grey disc, which is
   exactly what a currency must never look like. */
.tr-purse {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit; font-size: 15px; font-weight: 900;
  padding: 7px 14px 7px 9px; border-radius: 999px;
  border: 2px solid rgba(240,184,73,0.5);
  background: rgba(0,0,0,0.28); color: var(--tr-gold);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.tr-coin { width: 18px; height: 18px; flex: 0 0 auto; display: block; }


/* ── Stage ───────────────────────────────────────────────────────────────── */

.tr-stagewrap {
  flex: 1 1 auto;
  position: relative;
  display: grid;
  /* Top-aligned, not centred. The room is letterboxed inside whatever space is
     left, and centring split the leftover height above and below it — so the
     scene floated in the middle with a band of flat red over the shelves.
     Collecting the slack at the bottom, just above the bar, lifts the whole room
     the way Rob asked and puts the empty red where nothing is drawn anyway. */
  place-items: start center;
  min-height: 0;
  /* The room is 1.78:1 and an iPad is 1.33:1, so at full width there is always
     leftover height — 110px on a 13" screen — and with the scene pushed up it
     all collects in one band above the bar. Darkening downward turns that band
     into floor shadow receding under the shelves instead of a flat offcut of
     red. Nothing is drawn there either way; this only decides whether the eye
     reads it as depth or as a gap. */
  background: linear-gradient(180deg, var(--tr-red) 0%, var(--tr-red) 62%, #3a0d17 100%);
}

.tr-stage {
  position: relative;
  background: #000 url("../art/room.jpg?v=20260815-tr1") center/100% 100% no-repeat;
  box-shadow: 0 0 60px rgba(0,0,0,0.5);
  touch-action: none;               /* the room itself never pans */
  user-select: none; -webkit-user-select: none;
}

/* ── Trophies ────────────────────────────────────────────────────────────── */

/* One rule for every trophy on a shelf: absolutely placed, bottom-centre
   anchored on the painted board line. left/top/width are set inline in % by
   the renderer. */
.tr-trophy {
  position: absolute;
  transform: translate(-50%, -100%);       /* bottom-centre anchor */
  transform-origin: 50% 100%;
  cursor: grab;
  touch-action: none;
  -webkit-user-drag: none; user-select: none;
  /* THE LONG-PRESS FIX. Holding a treasure on an iPad raised iOS's own image
     menu — Copy, Look Up, Share, Save to Photos — instead of picking the
     treasure up, because a long press on an <img> is a system gesture and
     -webkit-user-drag alone does not switch it off. A drag is precisely a press
     that lasts, so the two collided every time a child held on long enough to
     mean it. */
  -webkit-touch-callout: none;
  filter: drop-shadow(0 4px 5px rgba(0,0,0,0.45));
  transition: filter 0.15s;
}
.tr-trophy:active { cursor: grabbing; }
.tr-trophy.is-dragging { opacity: 0; }     /* the ghost stands in for it */

/* Tap feedback: a quick wiggle. Suppressed under reduced-motion. */
@keyframes tr-wiggle {
  0%,100% { transform: translate(-50%,-100%) rotate(0deg); }
  25%     { transform: translate(-50%,-100%) rotate(-5deg) scale(1.05); }
  75%     { transform: translate(-50%,-100%) rotate(5deg)  scale(1.05); }
}
.tr-trophy.is-wiggle { animation: tr-wiggle 0.34s ease-in-out; }

/* Slot highlight while a trophy is in the air. */
.tr-slot {
  position: absolute;
  border-radius: 10px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s;
}
.tr-slot.is-open {
  opacity: 1;
  background: rgba(240,184,73,0.12);
  box-shadow: inset 0 0 0 2px rgba(240,184,73,0.45);
}
.tr-slot.is-target {
  opacity: 1;
  background: rgba(240,184,73,0.3);
  box-shadow: inset 0 0 0 3px var(--tr-gold);
}

/* ── The treasure shelf ──────────────────────────────────────────────────────
   A full-bleed row in the dark band between the carpet and the bar.

   It used to be absolutely positioned onto a painted bench INSIDE the room, in
   image-space percentages, which had two costs: it was only ~1095 of 1672 image
   px wide, and its contents were scaled by the stage's own scale factor, so on a
   4:3 iPad each treasure came out about 68px tall. Out here it owns the entire
   width and sets its own size, so the row really does run off both edges of the
   screen and the treasures are big enough to see.

   `pan-x` is what lets the browser take a sideways swipe for scrolling while a
   vertical drag still reaches us as pointermove — see the intent test in the JS.
   overflow-y is hidden now rather than visible: nothing should poke up over the
   carpet, and the items are sized to the band instead of overflowing it. */
.tr-tray {
  flex: 0 0 auto;
  position: relative;
  /* Tall enough to make the treasures worth looking at, capped so it cannot eat
     the room on a short screen. The band it sits in is the stage's leftover
     height, ~110px on a 13" iPad. */
  height: clamp(74px, 14vh, 132px);
  overflow-x: auto; overflow-y: hidden;
  touch-action: pan-x;
  scrollbar-width: thin;
  scrollbar-color: rgba(240,184,73,0.5) transparent;
  -webkit-overflow-scrolling: touch;
  background: linear-gradient(180deg, #3a0d17, #2f0b13);
}
.tr-tray::-webkit-scrollbar { height: 6px; }
.tr-tray::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 3px; }
.tr-tray::-webkit-scrollbar-thumb { background: rgba(240,184,73,0.5); border-radius: 3px; }

.tr-tray-inner {
  position: relative;
  height: 100%;
  display: flex; align-items: center;
  /* px, not %, now that the row is as wide as the screen: a percentage gap grew
     with the viewport and left the treasures marooned from each other. */
  gap: 14px;
  /* No side padding, deliberately. Rob wants the row to disappear off the right
     edge and then off the left once it has scrolled, which is what a flush
     full-width scroller does. */
  padding: 0;
  min-width: 100%;
}

/* Tray trophies are laid out by flex, not absolutely — so they are plain
   centred images, not the translate-anchored kind. Height comes from the band
   and width follows the artwork, which is why the renderer no longer writes
   inline pixel sizes here: the row is independent of the stage's scale now. */
.tr-tray .tr-trophy {
  position: relative;
  transform: none;
  flex: 0 0 auto;
  align-self: center;
  height: calc(100% - 16px);
  width: auto;
  filter: drop-shadow(0 3px 4px rgba(0,0,0,0.5));
  /* pan-x here, overriding the base rule's `none`. The base is right for a
     trophy on a shelf, where we want every pixel of the gesture — but in this
     row it meant the browser could never claim a sideways swipe that STARTED on
     a treasure, and since the treasures are almost the whole row, that was
     nearly every swipe. Scrolling then fell to the JS fallback, which sets
     scrollLeft by hand: no momentum, no rubber band, and it stops dead when the
     finger stops. That is a large part of why dragging in here felt off.

     With pan-x the browser takes horizontal swipes and scrolls them properly,
     while a vertical drag is still delivered to us as pointermove — which is
     exactly the up-to-lift gesture. When the browser does claim a gesture it
     sends pointercancel, which onPointerCancel already treats as a normal
     outcome rather than an error. */
  touch-action: pan-x;
}

/* ALREADY ON A SHELF. Rob's rule: an owned treasure never leaves this row, it
   just goes grey once it is on display. Two things follow from that. The child
   always sees everything they own in one place, which a disappearing-item tray
   never showed them; and the row stops being a queue with a mysterious length.

   Not draggable while greyed, because it is already up there — the copy that
   matters is the one on the shelf, and dragging a second one out of here would
   have to either duplicate it or teleport it, both of which are worse than
   nothing happening. */
.tr-tray .tr-trophy.is-placed {
  filter: grayscale(1) brightness(0.62);
  opacity: 0.5;
  cursor: default;
  pointer-events: none;
}
.tr-tray .tr-trophy.is-wiggle { animation: none; }
@keyframes tr-wiggle-flat {
  0%,100% { transform: rotate(0deg); }
  25%     { transform: rotate(-5deg) scale(1.05); }
  75%     { transform: rotate(5deg)  scale(1.05); }
}
.tr-tray .tr-trophy.is-wiggle { animation: tr-wiggle-flat 0.34s ease-in-out; }

.tr-tray.is-droptarget { box-shadow: inset 0 0 0 3px rgba(240,184,73,0.7); border-radius: 12px; }

/* The drag ghost: a free-flying copy that follows the finger. */
.tr-ghost {
  position: fixed; z-index: 300;
  pointer-events: none;
  transform: translate(-50%, -100%) scale(1.08);
  filter: drop-shadow(0 10px 14px rgba(0,0,0,0.55));
}

.tr-empty {
  position: absolute; left: 50%; top: 38%;
  transform: translate(-50%, -50%);
  margin: 0; text-align: center; pointer-events: none;
  font-size: clamp(14px, 1.6vw, 20px); font-weight: 800;
  color: rgba(253,246,230,0.85); text-shadow: 0 2px 6px rgba(0,0,0,0.6);
}
.tr-empty span { font-size: 0.75em; font-weight: 600; opacity: 0.8; }

/* ── Shop + buy sheets ───────────────────────────────────────────────────── */

/* Absolute, not fixed: the PK-5 shell puts a fixed sidebar down the left of
   every page, and a viewport-fixed overlay centres itself underneath it. The
   sheet belongs to the game area, so it is positioned against .tr-app. */
.tr-modal {
  position: absolute; inset: 0; z-index: 200;
  display: grid; place-items: center;
  background: rgba(20,4,9,0.72);
  backdrop-filter: blur(3px);
  padding: 16px;
}
.tr-modal[hidden] { display: none; }

.tr-sheet {
  background: linear-gradient(180deg, #fdf6e6, #f2e6cc);
  color: var(--tr-ink);
  border-radius: 20px;
  border: 3px solid var(--tr-gold-dim);
  box-shadow: 0 20px 60px rgba(0,0,0,0.55);
  width: min(920px, 100%);
  max-height: min(86vh, 720px);
  display: flex; flex-direction: column;
  overflow: hidden;
}
.tr-sheet-small { width: min(370px, 100%); text-align: center; }

.tr-sheet-head {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 14px 18px;
  border-bottom: 2px solid rgba(184,134,44,0.3);
}
.tr-sheet-head h2 { margin: 0; font-size: 20px; font-weight: 900; }
.tr-sheet-head .tr-purse { color: var(--tr-gold-dim); border-color: rgba(184,134,44,0.5); background: rgba(0,0,0,0.06); }

.tr-close {
  border: 0; background: rgba(0,0,0,0.08); color: var(--tr-ink);
  width: 34px; height: 34px; border-radius: 50%;
  font-size: 20px; line-height: 1; cursor: pointer; font-family: inherit;
}

.tr-shop-body { flex: 1 1 auto; overflow-y: auto; padding: 14px 18px 20px; }

.tr-shop-cat {
  margin: 16px 0 8px; font-size: 11px; font-weight: 900;
  letter-spacing: 2px; text-transform: uppercase; color: var(--tr-gold-dim);
}
.tr-shop-cat:first-child { margin-top: 0; }

.tr-shop-grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
}

/* A locked tile is NEVER `disabled`. At this age a tap that does nothing is
   the worst possible feedback — it must always open the sheet and explain. */
.tr-card {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 8px 6px 6px; border-radius: 14px; cursor: pointer;
  background: rgba(255,255,255,0.55);
  border: 2px solid rgba(184,134,44,0.28);
  font-family: inherit; color: inherit;
  transition: transform 0.12s, box-shadow 0.12s;
}
.tr-card:hover  { transform: translateY(-2px); box-shadow: 0 6px 14px rgba(0,0,0,0.14); }
.tr-card:active { transform: scale(0.97); }
.tr-card img { width: 100%; height: 74px; object-fit: contain; display: block; }
.tr-card-name { font-size: 11px; font-weight: 700; text-align: center; line-height: 1.2; }

.tr-card.is-owned { background: rgba(240,184,73,0.22); border-color: var(--tr-gold-dim); }
.tr-card.is-locked img { opacity: 0.9; }
.tr-card.is-locked:not(.can-afford) img { filter: saturate(0.35) brightness(0.9); }

.tr-chip {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 12px; font-weight: 900; padding: 2px 8px;
  border-radius: 999px; background: rgba(0,0,0,0.08); color: #6b4a12;
}
.tr-chip .tr-coin { width: 13px; height: 13px; }
.tr-chip-owned { background: var(--tr-gold-dim); color: #fff; }
.can-afford .tr-chip { background: var(--tr-gold); color: var(--tr-ink); }

.tr-buy-art { height: 130px; display: grid; place-items: center; padding: 16px 16px 0; }
.tr-buy-art img { max-width: 100%; max-height: 130px; object-fit: contain; }
.tr-sheet-small h2 { margin: 10px 16px 2px; font-size: 21px; font-weight: 900; }
.tr-buy-price { margin: 4px 0 2px; font-size: 20px; font-weight: 900; display: flex; align-items: center; justify-content: center; gap: 5px; }
.tr-buy-math  { margin: 2px 16px 12px; font-size: 14px; opacity: 0.85; }
.tr-buy-math b { display: inline-flex; align-items: center; gap: 3px; vertical-align: middle; }
.tr-buy-short { color: #a33; }
.tr-sheet-actions { display: flex; gap: 8px; justify-content: center; padding: 0 16px 16px; flex-wrap: wrap; }
.tr-sheet-actions .tr-btn { text-decoration: none; display: inline-flex; align-items: center; gap: 5px; }
.tr-sheet-actions .tr-btn-ghost { color: var(--tr-ink); border-color: rgba(42,13,20,0.3); }

.tr-log { margin: 0 16px 14px; padding: 0; list-style: none; text-align: left; font-size: 13px; max-height: 240px; overflow-y: auto; }
.tr-log li { display: flex; justify-content: space-between; gap: 10px; padding: 5px 0; border-bottom: 1px solid rgba(0,0,0,0.07); }
.tr-log .d-earn  { color: #2c7a3f; font-weight: 800; }
.tr-log .d-spend { color: #a3502c; font-weight: 800; }
.tr-note { margin: 0 16px 14px; font-size: 12px; opacity: 0.7; }

/* ── Toast ───────────────────────────────────────────────────────────────── */

.tr-toast {
  position: absolute; left: 50%; bottom: 22px; transform: translateX(-50%);
  z-index: 400; pointer-events: none;
  background: rgba(20,4,9,0.94); color: var(--tr-cream);
  border: 2px solid var(--tr-gold-dim);
  padding: 11px 20px; border-radius: 999px;
  font-size: 15px; font-weight: 800;
  box-shadow: 0 8px 24px rgba(0,0,0,0.45);
  animation: tr-toast-in 0.22s ease-out;
}
.tr-toast[hidden] { display: none; }
@keyframes tr-toast-in { from { opacity: 0; transform: translate(-50%, 12px); } }

/* ── Dev overlay (?dev=1) ────────────────────────────────────────────────── */

.tr-dev-band { position: absolute; pointer-events: none; outline: 1px dashed rgba(0,255,255,0.8); }
.tr-dev-line { position: absolute; pointer-events: none; border-top: 1px solid rgba(255,0,255,0.9); }
.tr-dev-tag  {
  position: absolute; pointer-events: none;
  font: 10px/1 'JetBrains Mono', monospace; color: #0ff;
  background: rgba(0,0,0,0.6); padding: 2px 4px;
}

@media (prefers-reduced-motion: reduce) {
  .tr-trophy.is-wiggle, .tr-toast { animation: none; }
  .tr-btn, .tr-card { transition: none; }
}

@media (max-width: 620px) {
  .tr-bottombar { padding: 8px 12px calc(8px + env(safe-area-inset-bottom, 0px)); gap: 10px; }
  .tr-btn { font-size: 13px; padding: 8px 12px; }
  /* The shop CTA keeps its own size: on the smallest screen it is even more the
     only thing worth pressing, so it must not inherit the shrink above. */
  .tr-shop-cta { font-size: 16px; padding: 13px 22px; min-height: 52px; }
  .tr-shop-grid { grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); }
}

/* ── The Treasure Shop button ────────────────────────────────────────────────
   Deliberately the largest thing in the chrome. It is the only control in this
   room that gives a child something new, and at 14px in a top bar it looked
   like a preference.

   THE PULSE fires in exactly two situations, both of which mean "there is
   something for you in here": the shelves are empty (a first visit, nothing to
   arrange yet), or the coins on hand can actually buy something. It stops as
   soon as neither holds, so it never becomes background noise a child learns to
   ignore -- which is the only way an attention cue keeps working.

   transform and box-shadow only, and it is a 2.4s cycle: slow enough to read as
   breathing rather than blinking. */
.tr-shop-cta {
  font-size: clamp(17px, 2.6vw, 22px);
  font-weight: 900;
  letter-spacing: 0.2px;
  padding: 15px 34px;
  min-height: 58px;
  border-radius: 999px;
  box-shadow: 0 4px 0 #8a6218, 0 6px 18px rgba(0, 0, 0, 0.35);
}

.tr-shop-cta:active { transform: scale(0.97); }

.tr-shop-cta.is-calling {
  animation: tr-shop-pulse 2.4s ease-in-out infinite;
}

@keyframes tr-shop-pulse {
  0%, 100% {
    transform: scale(1);
    box-shadow: 0 4px 0 #8a6218, 0 6px 18px rgba(0, 0, 0, 0.35),
                0 0 0 0 rgba(240, 184, 73, 0.55);
  }
  50% {
    transform: scale(1.045);
    box-shadow: 0 4px 0 #8a6218, 0 8px 22px rgba(0, 0, 0, 0.4),
                0 0 0 14px rgba(240, 184, 73, 0);
  }
}

/* A cue that moves is a cue some children cannot tolerate; the button stays
   larger and gold, which was already the point. */
@media (prefers-reduced-motion: reduce) {
  .tr-shop-cta.is-calling { animation: none; }
}

/* The purse sits beside a much bigger button now, so it grows a little to stay
   a peer rather than a footnote. */
.tr-bottombar .tr-purse {
  font-size: 17px;
  padding: 10px 18px 10px 12px;
  min-height: 48px;
}

.tr-bottombar .tr-coin { width: 21px; height: 21px; }
