/*!
 * Melody Animals — look.
 *
 * Flat, low-information, no dark lines or heavy borders: the colored tangram
 * pieces are the loudest thing on screen, everything else stays quiet. The
 * background is a pale sky with two soft pastel blobs (the reference game's
 * calm), the silhouette is warm navy with a soft light halo, and every color
 * that means something is a NOTE color painted by the engine from
 * BellsCore.NOTE_COLORS — this stylesheet never restates a note hex.
 */

:root {
  --ma-navy: #1b233e;          /* the silhouette */
  --ma-seam: #39425f;          /* internal seams inside the silhouette */
  --ma-halo: #dce9f6;          /* soft ring around the whole animal */
  /* maple tray (Rob, 2026-09-14) — the pieces are cut wood now */
  --ma-bg1: #e9d7b4;
  --ma-bg2: #f2e5cb;
  --ma-kerf: #5b3d22;
  --ma-ink: #1f2a44;
  --ma-ink-soft: #5a6479;
  --ma-paper: #ffffff;
  --ma-green: #6abb4a;
  --ma-green-edge: #4f9636;
  --ma-teal: #099695;
  --ma-gold: #ffd25b;
  /* the house spring */
  --ma-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ma-land: cubic-bezier(0.22, 1.4, 0.4, 1);
  --ma-settle: cubic-bezier(0.22, 1, 0.36, 1);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body { margin: 0; height: 100%; }

body {
  font-family: 'Nunito', 'Avenir Next Rounded', 'Trebuchet MS', sans-serif;
  color: var(--ma-ink);
  background: linear-gradient(180deg, var(--ma-bg1) 0%, var(--ma-bg2) 100%);
  overflow: hidden;                 /* the PLAY screen is one screen, never scrolls */
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
  /* touch-action lives on the puzzle table (.ma-play) and NOT here. It is not
     an inherited property, but the browser intersects the values from the
     touched element up through its ancestors, so `none` on body silently
     kills panning for every descendant — including a scroll container that
     looks perfectly configured. That is exactly what stranded the collection
     when it grew past two rows: the menu could not scroll no matter what
     overflow said (Rob, 2026-09-17: "i cant scroll down"). */
  touch-action: pan-y;
}

/* soft pastel blobs, pure decoration */
.ma-blobs { position: fixed; inset: 0; pointer-events: none; z-index: 0; }
.ma-blobs i { position: absolute; border-radius: 50%; }
.ma-blobs .b1 { width: 42vmin; height: 42vmin; left: -10vmin; top: 16vmin; background: #f7e9cd; opacity: 0.55; }
.ma-blobs .b2 { width: 34vmin; height: 34vmin; right: -8vmin; bottom: -6vmin; background: #efdfc0; opacity: 0.6; }
.ma-blobs .b3 { width: 16vmin; height: 16vmin; right: 18vmin; top: 8vmin; background: #f5ead4; opacity: 0.7; }

.ma-app { position: relative; z-index: 1; height: 100svh; display: flex; flex-direction: column; }

/* ── rotate prompt (landscape only, per Rob) ─────────────────────────────── */
.ma-rotate {
  position: fixed; inset: 0; z-index: 300;
  display: none;
  background: var(--ma-bg1);
  text-align: center;
  align-items: center; justify-content: center; flex-direction: column; gap: 10px;
}
.ma-rotate .ma-rotate-icon { font-size: 64px; animation: maRotateHint 1.6s ease-in-out infinite; }
.ma-rotate p { margin: 0; font-size: 20px; font-weight: 900; color: var(--ma-ink); }
@keyframes maRotateHint {
  0%, 100% { transform: rotate(0deg); }
  50% { transform: rotate(90deg); }
}
@media (orientation: portrait) {
  .ma-rotate { display: flex; }
}

/* ── menu (the collection) ───────────────────────────────────────────────── */
/* The collection scrolls; the game does not. It was written as one fixed
   screen back when eight animals fit in two rows, and every animal added
   since has been unreachable rather than merely off-screen. The grid below
   packs as many columns as will fit so it usually still lands on one screen —
   this is the safety net for when it does not. */
.ma-menu {
  flex: 1;
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(6px, 1.6vh, 16px);
  padding: calc(52px + env(safe-area-inset-top)) 20px
           calc(24px + env(safe-area-inset-bottom));
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;     /* no rubber-banding the page behind it */
  touch-action: pan-y;
}
.ma-title { margin: 0; font-size: clamp(26px, 4.4vw, 40px); font-weight: 900; color: var(--ma-navy); letter-spacing: -0.5px; }
.ma-sub { margin: 0; font-size: clamp(13px, 1.8vw, 16px); font-weight: 800; color: var(--ma-ink-soft); }

/* Columns are fitted, not counted. Four was right for eight animals and
   wrong for thirteen; auto-fit means the next one Rob draws costs a little
   tile width instead of a row nobody can reach. */
.ma-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(clamp(112px, 13.5vw, 172px), 1fr));
  gap: clamp(10px, 1.6vw, 18px);
  width: min(1160px, 100%);
  margin-top: clamp(2px, 1vh, 10px);
  flex: 0 0 auto;                   /* never squeezed by the flex column */
}
.ma-tile {
  appearance: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit;
  background: var(--ma-paper);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 5px 0 rgba(27, 35, 62, 0.10), 0 10px 22px rgba(27, 35, 62, 0.10);
  transition: transform 160ms var(--ma-spring);
  position: relative;
}
.ma-tile:active { transform: scale(0.96); }
.ma-tile svg { display: block; width: 100%; height: auto; }
/* unsolved: the vague tangram — pieces barely darker than the tile */
.ma-tile.is-mystery svg .ma-mini-piece { fill: #cdd6e6; }
.ma-tile.is-mystery { background: #f2f5fa; }
/* locked: even fainter, not tappable yet — no padlocks in a kids game */
.ma-tile.is-locked { pointer-events: none; }
.ma-tile.is-locked svg .ma-mini-piece { fill: #e3e9f2; }
.ma-tile.is-locked { background: #f6f8fb; box-shadow: 0 4px 0 rgba(27, 35, 62, 0.05); }
/* solved: full color + star */
.ma-tile .ma-tile-star {
  position: absolute; top: 7px; right: 9px;
  font-size: clamp(15px, 2vw, 21px);
  filter: drop-shadow(0 1px 0 rgba(0,0,0,0.15));
}
.ma-tile .ma-tile-name {
  position: absolute; left: 0; right: 0; bottom: 6px;
  font-size: clamp(11px, 1.5vw, 14px); font-weight: 900; color: var(--ma-ink-soft);
  text-align: center; letter-spacing: 0.4px;
}
.ma-tile.is-mystery .ma-tile-name, .ma-tile.is-locked .ma-tile-name { opacity: 0; }

/* ── play screen ─────────────────────────────────────────────────────────── */
/* The puzzle table, and the only thing that must swallow touch: a drag
   across the board is moving a piece, never scrolling the page. */
.ma-play { flex: 1; position: relative; min-height: 0; touch-action: none; }

/* one SVG owns the whole play area = one coordinate space for everything */
#maStage { position: absolute; inset: 96px 0 0; width: 100%; height: calc(100% - 96px); display: block; }

/* silhouette */
.ma-halo polygon { fill: var(--ma-navy); stroke: var(--ma-halo); stroke-width: 22; stroke-linejoin: round; }
.ma-slots polygon { fill: var(--ma-navy); }
.ma-slots.has-seams polygon { stroke: var(--ma-seam); stroke-width: 3; stroke-linejoin: round; }

/* slot feedback while a compatible piece is in the air */
.ma-slots polygon.is-open { fill: #232d4e; }
.ma-slots polygon.is-target {
  fill: #2c3a63;
  animation: maBreathe 900ms ease-in-out infinite;
}
@keyframes maBreathe {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.35); }
}

/* THE KERF. The dark cut line between pieces is what actually sells wood —
   more than the grain does. Seated pieces keep it: in the reference set the
   assembled figure still reads as separate inlaid pieces, not one poured
   shape. The grain overlay never takes a stroke of its own or the line
   doubles up and goes muddy. */
.ma-placed polygon { stroke: var(--ma-kerf); stroke-width: 2.4; stroke-linejoin: round; }
.ma-piece polygon.ma-grain,
.ma-placed polygon.ma-grain { stroke: none; pointer-events: none; }

/* loose pieces */
.ma-piece { cursor: grab; }
.ma-piece polygon {
  stroke: var(--ma-kerf);
  stroke-width: 2.4;
  stroke-linejoin: round;
  /* a whisper of lift so pieces read as sitting ON the table */
  filter: drop-shadow(0 3px 4px rgba(27, 35, 62, 0.18));
}
/* one shadow per piece, not one per face */
.ma-piece polygon.ma-grain { filter: none; }
.ma-piece.is-held { cursor: grabbing; }
.ma-piece.is-held polygon {
  opacity: 0.78;                    /* see the silhouette through the piece */
  filter: drop-shadow(0 14px 16px rgba(27, 35, 62, 0.28));
}
/* seat + return are engine-driven transform tweens; this transition covers
   the hop-home and the final seat glide */
.ma-piece.is-animating { transition: transform 300ms var(--ma-land); pointer-events: none; }
.ma-piece.is-hint { animation: maBob 900ms ease-in-out infinite; }
@keyframes maBob {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 -14px; }
}

/* the seat squash — applied to the polygon so it doesn't fight the g transform */
.ma-seat-pop { animation: maSeatPop 360ms var(--ma-spring); transform-box: fill-box; transform-origin: center; }
@keyframes maSeatPop {
  0% { transform: scale(1.05, 0.9); }
  55% { transform: scale(0.97, 1.06); }
  100% { transform: scale(1, 1); }
}

/* melody performance pulse */
.ma-perform { animation: maPerform 300ms var(--ma-spring); transform-box: fill-box; transform-origin: center; filter: brightness(1.25); }
@keyframes maPerform {
  0% { transform: scale(1); }
  45% { transform: scale(1.13); }
  100% { transform: scale(1); }
}

/* star burst */
.ma-burst { position: fixed; z-index: 210; width: 0; height: 0; pointer-events: none; }
.ma-burst i {
  position: absolute; left: 0; top: 0; width: 9px; height: 9px; border-radius: 50%;
  animation: maStarFly 620ms cubic-bezier(0.32, 0.72, 0.42, 1) forwards;
}
@keyframes maStarFly {
  0% { opacity: 1; transform: translate(0, 0) scale(1); }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(0.4); }
}

/* confetti (bell-sort layer + note-palette pieces) */
.ma-confetti { position: fixed; inset: 0; pointer-events: none; overflow: hidden; z-index: 210; }
.ma-confetti i {
  position: absolute; top: -20px; width: 10px; height: 14px; border-radius: 2px;
  opacity: 0.95;
  animation: maConfettiFall var(--dur, 2400ms) linear forwards;
}
@keyframes maConfettiFall {
  0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  100% { transform: translate(var(--drift, 0), 110vh) rotate(720deg); opacity: 0.85; }
}

/* ── reveal / results ────────────────────────────────────────────────────── */
.ma-veil {
  position: fixed; inset: 0; z-index: 220;
  display: grid; place-items: center; padding: 18px;
  background: rgba(27, 35, 62, 0.35);
  opacity: 0; transition: opacity 300ms ease;
}
.ma-veil.is-in { opacity: 1; }
.ma-card {
  width: min(460px, 92vw);
  background: var(--ma-paper); border-radius: 26px;
  padding: 22px 22px 20px; text-align: center;
  box-shadow: 0 22px 50px rgba(0, 0, 0, 0.28);
  display: grid; gap: 6px; justify-items: center;
  transform: scale(0.7);
  transition: transform 360ms var(--ma-spring);
}
.ma-veil.is-in .ma-card { transform: scale(1); }
.ma-card h2 {
  margin: 0; font-size: 30px; font-weight: 900; color: var(--ma-navy); letter-spacing: 0.5px;
}
.ma-card .ma-made { margin: 0; font-size: 18px; font-weight: 800; color: var(--ma-ink-soft); }
.ma-card .ma-animal-art { width: min(300px, 60vw); }
.ma-card .ma-animal-art svg { width: 100%; height: auto; display: block; }
.ma-next {
  border: 0; cursor: pointer; font-family: inherit;
  background: var(--ma-green); color: #fff;
  font-size: 24px; font-weight: 900;
  border-radius: 999px; padding: 15px 52px; margin-top: 4px;
  box-shadow: 0 6px 0 var(--ma-green-edge);
  min-height: 56px;
}
.ma-next:active { transform: translateY(3px); box-shadow: 0 3px 0 var(--ma-green-edge); }

/* flying coin (reveal card → purse) */
.ma-coin {
  position: fixed; z-index: 260; width: 26px; height: 26px; border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #ffe9a8, var(--ma-gold) 60%, #e5a934);
  box-shadow: 0 2px 4px rgba(0,0,0,0.25);
  pointer-events: none;
  transition: transform 700ms var(--ma-settle), opacity 700ms ease;
}

/* ── dev harness (?dev=1) ────────────────────────────────────────────────── */
.ma-dev {
  position: fixed; inset: 0; z-index: 400; overflow: auto;
  background: #fff; padding: 18px;
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
}
.ma-dev figure { margin: 0; border: 1px solid #e3e9f2; border-radius: 12px; padding: 6px; }
.ma-dev figcaption { font-size: 12px; font-weight: 800; color: var(--ma-ink-soft); text-align: center; }
.ma-dev svg { width: 100%; height: auto; display: block; }

@media (prefers-reduced-motion: reduce) {
  .ma-piece.is-hint, .ma-slots polygon.is-target, .ma-rotate .ma-rotate-icon,
  .ma-hint-btn.is-nudge { animation: none; }
  .ma-confetti, .ma-burst { display: none; }
  .ma-piece.is-animating { transition-duration: 0.01ms; }
  .ma-card, .ma-veil { transition-duration: 0.01ms; }
}

/* back-to-collection chip: bottom-left, out of the puzzle's way (the
   reference game keeps its menu there too). 56px = a finger. */
.ma-grid-btn {
  position: absolute;
  left: calc(14px + env(safe-area-inset-left));
  bottom: calc(14px + env(safe-area-inset-bottom));
  width: 56px; height: 56px;
  border: 0; border-radius: 18px;
  background: #ffffff;
  color: #8fa0bd;
  cursor: pointer;
  box-shadow: 0 4px 0 rgba(27, 35, 62, 0.12), 0 8px 18px rgba(27, 35, 62, 0.12);
}
.ma-grid-btn svg { width: 26px; height: 26px; display: block; margin: 0 auto; }
.ma-grid-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(27, 35, 62, 0.12); }

/* the 💡 hint button — bottom-right twin of the menu grid chip. Hints are
   asked for, never volunteered: when a kid is stuck this WIGGLES, and only a
   press reveals anything (Rob, 2026-08-26). */
.ma-hint-btn {
  position: absolute;
  right: calc(14px + env(safe-area-inset-right));
  bottom: calc(14px + env(safe-area-inset-bottom));
  width: 56px; height: 56px;
  border: 0; border-radius: 18px;
  background: #ffffff;
  font-size: 26px; line-height: 1;
  cursor: pointer;
  box-shadow: 0 4px 0 rgba(27, 35, 62, 0.12), 0 8px 18px rgba(27, 35, 62, 0.12);
}
.ma-hint-btn:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(27, 35, 62, 0.12); }
.ma-hint-btn.is-nudge { animation: maHintNudge 620ms ease-in-out 3; }
@keyframes maHintNudge {
  0%, 100% { transform: rotate(0deg) scale(1); }
  30% { transform: rotate(-9deg) scale(1.08); }
  60% { transform: rotate(8deg) scale(1.08); }
}


/* Selection is information; it stays visible even with reduced motion. */
.ma-play-cue { position: absolute; top: max(64px, env(safe-area-inset-top)); left: 16px; right: 16px; z-index: 2; margin: 0; text-align: center; color: #203450; font-size: 18px; font-weight: 800; pointer-events: none; }
/* ── selection follows the PIECE, not its bounding box ───────────────────
   A focusable <g> gets the browser's own focus ring, and that ring is always
   a rounded RECTANGLE around the element's bounds — so a triangle or a
   parallelogram ends up wearing a box it does not fill (Rob, 2026-09-17:
   "looks a little broken/weird"). It is suppressed and REPLACED, never just
   removed: the highlight below is cut from the piece's own outline, so it is
   at least as visible as the ring was and keyboard focus stays obvious.

   Every piece carries two polygons — the coloured face and the wood grain
   over it — which is two strokes to work with and needs no new element (the
   engine reaches for querySelector("polygon") and must keep finding the face
   first, so nothing may be inserted ahead of it). The face takes a thick navy
   halo painted BEHIND its own fill, so it reads as a glow around the piece
   instead of a fat border eating into it; the grain, drawn on top, takes a
   thin white keyline that separates the halo from the colour. No drop-shadow:
   a filter on every selected piece is not worth it on an iPad. */
.ma-piece:focus,
.ma-piece:focus-visible,
.ma-slots polygon:focus,
.ma-slots polygon:focus-visible { outline: none; }

.ma-piece.is-selected polygon:not(.ma-grain),
.ma-piece:focus-visible polygon:not(.ma-grain) {
  stroke: var(--ma-navy);
  stroke-width: 15;
  stroke-linejoin: round;
  paint-order: stroke fill;
}
.ma-piece.is-selected polygon.ma-grain,
.ma-piece:focus-visible polygon.ma-grain {
  stroke: #ffffff;
  stroke-width: 4;
  stroke-linejoin: round;
}
.ma-slots polygon[role="button"] { cursor: pointer; pointer-events: all; }
.ma-slots polygon:focus-visible { stroke: #173c70; stroke-width: 7px; }
.ma-piece[aria-hidden="true"], .ma-slots polygon[aria-hidden="true"] { pointer-events: none; }

/* ── tangram → animal morph ───────────────────────────────────────────────
   The reveal illustration is laid over the solved puzzle in the SAME board
   coordinates (see L.revealFrame), so a plain opacity crossfade reads as the
   shapes turning into the animal. The wooden pieces leave a touch faster than
   the animal arrives, which keeps a beat where both are half-there — that
   overlap is the whole effect; give them identical timing and it looks like a
   dissolve between two slides instead. */
#maStage .ma-reveal {
  opacity: 0;
  transition: opacity 760ms ease-in;
  pointer-events: none;
}
#maStage.is-morphing .ma-reveal { opacity: 1; }

#maStage .ma-placed,
#maStage .ma-halo,
#maStage .ma-slots {
  transition: opacity 620ms ease-out;
}
#maStage.is-morphing .ma-placed,
#maStage.is-morphing .ma-halo,
#maStage.is-morphing .ma-slots { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  #maStage .ma-reveal,
  #maStage .ma-placed,
  #maStage .ma-halo,
  #maStage .ma-slots { transition-duration: 0.01ms; }
}

/* Results card, after a board morph: stand aside and let the animal show. */
.ma-veil.is-morphed {
  place-items: end center;
  padding-bottom: calc(20px + env(safe-area-inset-bottom));
  background: rgba(27, 35, 62, 0.12);
}
.ma-veil.is-morphed .ma-card {
  padding: 16px 22px 18px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.3);
}
.ma-veil.is-morphed .ma-card h2 { font-size: 24px; }

/* Free tier: a gated tile reads as "not yet", never as broken — and it stays
   tappable (it opens the unlock wall), unlike the progression `is-locked`. */
.ma-tile.is-gated { opacity: .72; background: #f2f5fa; }
.ma-tile.is-gated svg .ma-mini-piece { fill: #d9e0ec; }
.ma-tile .ma-tile-lock { position: absolute; left: 0; right: 0; bottom: 6px; display: flex; justify-content: center; }
