:root {
  --c-cream: #FBF1DA;
  --c-mustard: #F2B04C;
  --c-coral: #F08A5D;
  --c-leaf: #7FBF9E;
  --ink: #4A3418;
  --ink-warm: #4A1B0C;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

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

html, body {
  margin: 0; padding: 0; width: 100%; height: 100%;
  overflow: hidden; background: var(--c-cream);
  font-family: var(--font); color: var(--ink);
  overscroll-behavior: none;
}

.screen { position: fixed; inset: 0; display: none; }
.screen.active { display: block; }
.screen.overlay { display: none; }
.screen.overlay.active { display: flex; align-items: center; justify-content: center; background: rgba(74,52,24,0.55); }

/* START / LOADING
   One screen doing two jobs. It holds until every pose image has decoded, so
   the first shrug does not pop in half-drawn — and then it waits for a tap,
   because that tap is the user gesture the audio context and the Web NFC
   permission prompt both require. Never auto-advance it. */
#screen-start.active {
  display: flex; align-items: stretch; justify-content: flex-start;
  flex-direction: column;
  padding: 9vh 28px calc(24px + env(safe-area-inset-bottom));
  background: var(--c-cream);
}
/* Anchored to the TOP, not centred, so Hob does not shift every time the thing
   below him changes height — the bar collapsing, the copy arriving, the card
   slots appearing. */
.start-hedgehog {
  width: 44vmin; max-width: 220px; min-width: 140px; height: auto; display: block;
  margin: 0 auto 4vh;
  opacity: 0;
  transition: opacity 420ms ease;
}
.start-hedgehog.loaded { opacity: 1; }
#screen-start.ready .start-hedgehog {
  animation: start-breathe 2.6s ease-in-out infinite 420ms;
}
@keyframes start-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.045); }
}
#screen-start.ready .start-loading { opacity: 0; }

/* Determinate, and it fills as the poses decode. There is real work behind it:
   with no drawn fallback, a pose that has not loaded renders as nothing. */
.start-loading {
  width: 100%; height: 8px; margin-bottom: 24px; border-radius: 4px;
  background: rgba(74,52,24,0.14); overflow: hidden;
  transition: opacity 300ms ease;
}
.start-loading-bar {
  width: 0%; height: 100%; border-radius: 4px;
  background: var(--c-mustard);
  transition: width 220ms ease;
}

.start-copy { width: 100%; text-align: left; animation: start-copy-in 420ms ease both; }
@keyframes start-copy-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.start-state h1 { font-size: 22px; line-height: 1.3; margin: 0 0 6px; color: var(--ink); }
.start-state p { font-size: 16px; line-height: 1.55; margin: 8px 0; color: rgba(74,52,24,0.78); }
.start-note {
  font-size: 16px; line-height: 1.55; margin: 0 0 24px;
  color: rgba(74,52,24,0.78);
}
.start-note strong { color: var(--ink); }

.start-button {
  display: block; width: 100%;
  font: inherit; font-size: 17px; font-weight: 600; letter-spacing: 0.01em;
  color: var(--c-cream); background: var(--ink);
  border: none; border-radius: 14px; padding: 16px 24px; cursor: pointer;
  box-shadow: 0 4px 0 rgba(74,52,24,0.28);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
}
.start-button:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 rgba(74,52,24,0.28);
}
/* `display:block` beats the UA rule for the `hidden` attribute, so without this
   the Continue button shows before the three cards have been checked. */
.start-button[hidden] { display: none; }
.start-cue {
  margin-top: 20px !important; font-size: 16px !important; font-weight: 600;
  color: var(--ink) !important;
}
.start-cue-live { animation: cue-pulse 1.8s ease-in-out infinite; }
@keyframes cue-pulse {
  0%, 100% { opacity: 0.5; }
  50%      { opacity: 1; }
}
.start-copy .admin-btn { display: block; width: 100%; margin-top: 14px; text-align: center; }

.start-cards {
  display: flex; gap: 14px; justify-content: center;
  margin-top: auto; padding-top: 32px;
}
.start-cards[hidden] { display: none; }
.start-card {
  position: relative; width: 62px; height: 44px; border-radius: 8px;
  border: 2.5px dashed rgba(74,52,24,0.3); background: transparent;
  transition: border-color 260ms ease, background 260ms ease, transform 260ms ease;
}
.start-card::before {
  content: ''; position: absolute; left: 0; right: 0; top: 11px; height: 7px;
  background: rgba(74,52,24,0.18);
  transition: background 260ms ease;
}
.start-card svg {
  position: absolute; inset: 0; margin: auto; width: 26px; height: 26px;
  opacity: 0; transform: scale(0.5);
  transition: opacity 240ms ease, transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.start-card.done {
  border-style: solid; border-color: var(--ink); background: var(--ink);
  transform: translateY(-3px);
}
.start-card.done::before { background: rgba(251,241,218,0.25); }
.start-card.done svg { opacity: 1; transform: scale(1); }

/* INTRO
   Lower third, so the canvas and the pot — where everything happens — are never
   covered. Pointer-events off, so the five-tap corner gesture in the last beat
   still reaches #hidden-zone underneath. */
.intro { position: absolute; inset: 0; z-index: 40; pointer-events: none; }
.intro-bubble {
  position: absolute; left: 4%; right: 4%; bottom: 4%;
  background: var(--c-cream); border: 2px solid var(--ink);
  border-radius: 18px; padding: 16px 18px;
  box-shadow: 0 6px 0 rgba(74,52,24,0.18);
  pointer-events: auto;
  animation: intro-in 320ms ease both;
}
@keyframes intro-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* The tail points UP at Hob. `--tail-x` is written by the intro from his
   measured centre, so it tracks him as he walks rather than guessing. */
.intro-bubble::after {
  content: ''; position: absolute;
  left: var(--tail-x, 50%); margin-left: -9px; top: -10px;
  width: 18px; height: 18px; background: var(--c-cream);
  border-left: 2px solid var(--ink); border-top: 2px solid var(--ink);
  transform: rotate(45deg); border-radius: 4px 0 0 0;
  transition: left 300ms ease;
}
.intro-text p { font-size: 15px; line-height: 1.5; margin: 0 0 10px; color: var(--ink); }
.intro-text p:last-child { margin-bottom: 0; }
.intro-cue {
  margin: 12px 0 0; font-size: 14px; font-weight: 600; color: var(--ink);
  animation: cue-pulse 1.8s ease-in-out infinite;
}
.intro-help { margin: 8px 0 0; font-size: 12px; line-height: 1.4; color: rgba(74,52,24,0.6); }
.intro-help button {
  border: none; background: none; padding: 0; font: inherit;
  color: var(--ink); text-decoration: underline; cursor: pointer;
}
.intro-next {
  margin-top: 12px; width: 100%; padding: 11px; font: inherit; font-size: 15px;
  font-weight: 600; color: var(--c-cream); background: var(--ink);
  border: none; border-radius: 12px; cursor: pointer;
}
.intro-next:active { transform: scale(0.98); }

.intro-corner {
  position: absolute; top: 5px; right: 5px; width: 60px; height: 60px;
  border: 3px dashed var(--ink); border-radius: 16px;
  background: rgba(251,241,218,0.4);
  animation: cue-pulse 1.4s ease-in-out infinite;
}

.pin-hint {
  margin: 0 0 4px; font-size: 14px; font-weight: 600;
  color: var(--ink); text-align: center;
}

/* PLAY SCREEN
   The scene box has the EXACT aspect of the art canvas (1080x1920) and is sized
   by Scene.fit() to COVER the screen, so a percentage in here is the same thing
   as a canvas coordinate on any phone shape. */
.scene-wrap {
  position: relative; width: 100%; height: 100%; overflow: hidden;
  /* Must be the exact colour the sky gradient STARTS at, or the top edge of the
     scene box shows as a hard seam on a tall phone. */
  background: #EDF4F7;
}
.scene-root { position: absolute; }
.scene-root.no-anim, .scene-root.no-anim * { transition: none !important; }
.layer { position: absolute; inset: 0; }
.layer svg, .layer img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; object-fit: cover; }

/* ---------- The world strip ----------
   Three easels stand side by side on one continuous strip of ground, seven
   canvas widths across, starting one width to the LEFT of easel 0 (which is
   where the gallery wall lives). The camera translates these two containers;
   everything outside them — sky, sun — stays put.

   Keep 700%/-100% in step with TILES and TILE_MIN in scene.js. */
.scene-far, .scene-world {
  position: absolute; top: 0; left: -100%; width: 700%; height: 100%;
  will-change: transform;
}

.sun-rays { animation: sun-spin 90s linear infinite; }
@keyframes sun-spin { to { transform: rotate(360deg); } }

/* ---------- The picture ----------
   A part that is waiting is a wash of the colour it wants; the one being asked
   for pulses. That pulse is the only instruction a child who cannot read ever
   gets, so it has to be unmistakable without being frantic. */
.part-waiting { transition: fill-opacity 400ms ease; }
.part-asking { animation: part-ask 1.5s ease-in-out infinite; }
@keyframes part-ask {
  0%, 100% { fill-opacity: 0.22; stroke-opacity: 0.55; }
  50%      { fill-opacity: 0.46; stroke-opacity: 1; }
}

/* Lifted off the easel and carried away. The board is redrawn without it while
   this plays, so the pop belongs to the picture leaving, not to it vanishing. */
.layer-canvas.lift { animation: canvas-lift 620ms cubic-bezier(0.34, 1.4, 0.5, 1); }
@keyframes canvas-lift {
  0%   { transform: translateY(0) scale(1); }
  40%  { transform: translateY(-26px) scale(1.03); }
  100% { transform: translateY(0) scale(1); }
}

/* ---------- The pot ----------
   What colour is in it IS the game state, so every change to it is animated:
   paint arriving, two colours mixing, the whole lot tipped out. */
/* EVERY scaled or rotated SVG shape in here needs `transform-box: fill-box`.
   The default for an SVG element is `view-box`, which puts the transform origin
   at the corner of the world strip's viewBox — 1080 units to the LEFT of canvas
   zero and up to six canvas widths away from the shape itself. A scale about
   that point does not shrink a shape, it flings it across the world. */
.pot-paint {
  transform-box: fill-box; transform-origin: center;
  animation: pot-settle 520ms cubic-bezier(0.34, 1.5, 0.5, 1);
}
@keyframes pot-settle {
  0%   { transform: scale(0.6); opacity: 0.4; }
  60%  { transform: scale(1.08); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
/* Two colours chasing each other round. It resolves into one flat disc when the
   swirl ends — the moment a colour that is on no card comes into existence. */
.pot-swirl {
  transform-box: fill-box; transform-origin: center;
  animation: pot-swirl 900ms cubic-bezier(0.3, 0, 0.4, 1);
}
@keyframes pot-swirl {
  0%   { transform: rotate(0deg) scale(1); }
  100% { transform: rotate(720deg) scale(0.35); }
}
/* Too many colours: the pot tips itself out and rinses. The way back is never a
   card the child has to find. Origin is set from JS — it has to sit on the
   CURRENT easel's pot, which moves. */
.layer-pot.tip { animation: pot-tip 1100ms cubic-bezier(0.36, 0.07, 0.25, 1); }
@keyframes pot-tip {
  0%   { transform: rotate(0deg); }
  30%  { transform: rotate(-38deg); }
  62%  { transform: rotate(-30deg); }
  100% { transform: rotate(0deg); }
}

/* Paint falling into the pot. `--drop` is the distance in SVG user units, which
   is the same thing as a canvas coordinate — see Scene.pour(). */
.pour-blob {
  animation: pour-drop 640ms cubic-bezier(0.45, 0, 0.75, 1) forwards;
  transform-box: fill-box; transform-origin: center;
}
@keyframes pour-drop {
  0%   { transform: translateY(-40px) scaleY(0.8); opacity: 0; }
  15%  { opacity: 1; }
  85%  { transform: translateY(calc(var(--drop) * 0.92)) scaleY(1.15); opacity: 1; }
  100% { transform: translateY(var(--drop)) scaleY(0.55); opacity: 0; }
}
.pour-ring {
  transform-box: fill-box; transform-origin: center;
  animation: pour-ring 620ms cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}
@keyframes pour-ring {
  0%   { transform: scale(0.3); opacity: 0.9; }
  100% { transform: scale(2.1); opacity: 0; }
}

/* ---------- Mud ----------
   The biggest thing that happens in the game, and it costs nothing. */
.layer-splat { opacity: 0; pointer-events: none; }
.layer-splat.on { opacity: 1; }
/* Flies "up" in its own rotated frame — the SVG rotate on the wrapper group
   turns that into a fan. Deliberately free of var(), see splat() in scene.js. */
.layer-splat .drop {
  transform-box: fill-box; transform-origin: center;
  animation-name: splat-drop; animation-timing-function: cubic-bezier(0.2, 0.7, 0.4, 1);
  animation-fill-mode: forwards;
}
@keyframes splat-drop {
  0%   { transform: translateY(0) scale(0.3); opacity: 0; }
  12%  { opacity: 1; }
  55%  { transform: translateY(-460px) scale(1.15); opacity: 1; }
  100% { transform: translateY(-90px) scale(0.5); opacity: 0; }
}
.layer-splat .ring {
  transform-box: fill-box; transform-origin: center;
  animation: splat-ring 1300ms cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}
@keyframes splat-ring {
  0%   { transform: scale(0.15); opacity: 1; }
  100% { transform: scale(3.4); opacity: 0; }
}
/* The whole scene takes the hit. Small enough not to be alarming, big enough
   that mud feels like an EVENT. */
.scene-root.quake { animation: quake 620ms cubic-bezier(0.36, 0.07, 0.19, 0.97); }
@keyframes quake {
  0%, 100% { translate: 0 0; }
  15% { translate: -7px 5px; }
  32% { translate: 6px -4px; }
  50% { translate: -5px 3px; }
  68% { translate: 4px -2px; }
  85% { translate: -2px 1px; }
}

/* ---------- The recipe hint ----------
   Two dots and a plus. It escalates and NEVER resolves — reaching for the
   second card is the only lesson in the game. */
.recipe {
  transform-box: fill-box; transform-origin: center bottom;
  animation: recipe-in 400ms cubic-bezier(0.34, 1.5, 0.5, 1) both;
}
@keyframes recipe-in {
  0%   { opacity: 0; transform: translateY(18px) scale(0.8); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* ---------- Sparkles ----------
   Fired the moment a part is painted. This is the payoff for the mixing puzzle,
   so it is the brightest thing on the screen. */
/* Opacity only, deliberately. Each spark already carries a `transform`
   ATTRIBUTE that places and sizes it; a CSS transform here would replace that
   attribute outright and stack every spark at the origin. */
.spark { animation: spark 900ms cubic-bezier(0.2, 0.8, 0.3, 1) both; }
@keyframes spark {
  0%   { opacity: 0; }
  25%  { opacity: 1; }
  100% { opacity: 0; }
}

/* A picture arriving on the wall. */
.hung { animation: hung-in 620ms cubic-bezier(0.34, 1.5, 0.5, 1) both; transform-box: fill-box; transform-origin: center top; }
@keyframes hung-in {
  0%   { opacity: 0; transform: translateY(-24px) rotate(-6deg); }
  60%  { opacity: 1; transform: translateY(4px) rotate(3deg); }
  100% { opacity: 1; transform: translateY(0) rotate(0deg); }
}

/* Feet land on the soil line at y=1300. `left` and `width` are BOTH set in JS:
   the creature lives inside the world strip, which is seven canvas widths
   across, so a percentage in here is not a percentage of the canvas. */
.creature {
  position: absolute; bottom: 31.5%;
  transform: translateX(-50%);
  transition: left 620ms cubic-bezier(0.4, 0.1, 0.3, 1);
}
.creature svg, .creature img { width: 100%; height: auto; display: block; }

/* The drawing and anything layered on it — the sleep bubbles, the brush, the
   picture he is carrying — live together in here, so they mirror as ONE thing
   when he turns round and the brush stays in his paw. */
.creature-body { position: relative; }
.creature-extra { position: absolute; pointer-events: none; }
.creature.flip .creature-body { transform: scaleX(-1); }

.creature.asleep { animation: breathe 3.6s ease-in-out infinite; }
@keyframes breathe {
  0%, 100% { transform: translateX(-50%) scale(1); }
  50% { transform: translateX(-50%) scale(1.035); }
}
.sleep-bubbles { animation: sleep-rise 3.4s ease-in-out infinite; }
@keyframes sleep-rise {
  0%   { opacity: 0; transform: translateY(6px); }
  30%  { opacity: 0.9; }
  100% { opacity: 0; transform: translateY(-16px); }
}

/* Every creature keyframe MUST carry the translateX(-50%) that centres it on
   its `left` position — transform is one property, so omitting it here snaps
   the creature half a body-width sideways the moment it reacts to anything. */
.creature.bounce { animation: bounce 700ms ease; }
@keyframes bounce {
  0%, 100% { transform: translateX(-50%) translateY(0) scale(1); }
  35% { transform: translateX(-50%) translateY(-14px) scale(1.04); }
  70% { transform: translateX(-50%) translateY(0) scale(0.98); }
}
.creature.shrug { animation: shrug 700ms ease; }
@keyframes shrug {
  0%, 100% { transform: translateX(-50%) rotate(0deg); }
  25% { transform: translateX(-50%) rotate(-7deg) translateY(-3px); }
  75% { transform: translateX(-50%) rotate(7deg) translateY(-3px); }
}
/* Looking from the pot to the picture. Must not read as the shrug — that one
   means "I don't know this card", and the child has to tell them apart. */
.creature.look-up { animation: look-up 1600ms ease; }
@keyframes look-up {
  0% { transform: translateX(-50%) rotate(0) translateY(0); }
  18%, 78% { transform: translateX(-50%) rotate(-9deg) translateY(-5px); }
  100% { transform: translateX(-50%) rotate(0) translateY(0); }
}
/* Splattered in mud: shaking it off, like a dog. */
.creature.shake { animation: shake 620ms ease 3; }
@keyframes shake {
  0%, 100% { transform: translateX(-50%) rotate(0); }
  20% { transform: translateX(calc(-50% - 6px)) rotate(-6deg); }
  45% { transform: translateX(calc(-50% + 6px)) rotate(6deg); }
  70% { transform: translateX(calc(-50% - 4px)) rotate(-4deg); }
  88% { transform: translateX(calc(-50% + 4px)) rotate(4deg); }
}

/* Picture counters, top centre — one per picture, all three there from the
   start so the whole run has a visible shape. The one being painted is lit. */
.picture-counter {
  position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 4px;
  background: rgba(255,255,255,0.82); border-radius: 999px;
  padding: 4px 10px; z-index: 40;
  box-shadow: 0 2px 0 rgba(74,52,24,0.12);
}
.picture-chip {
  display: flex; align-items: center; gap: 4px;
  padding: 2px 7px; border-radius: 999px;
  transition: background 300ms ease, opacity 300ms ease;
}
.picture-chip.empty { opacity: 0.42; }
.picture-chip.active { background: rgba(240,140,54,0.18); opacity: 1; }
.picture-icon { width: 24px; height: 24px; display: block; }
.picture-counter svg { width: 100%; height: 100%; display: block; }
.picture-count {
  font-size: 19px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums; line-height: 1;
}
/* The chip scales, not the bar — the bar is centred with its own transform, and
   animating that fights the centring. */
.picture-chip.bump { animation: counter-bump 520ms cubic-bezier(0.34, 1.6, 0.5, 1); }
@keyframes counter-bump {
  0%, 100% { transform: scale(1); }
  40% { transform: scale(1.24); }
}

/* ---------- The finish ----------
   The only screen in the game with a sentence on it. It is aimed at the adult
   and the older child; the row of pictures on the wall carries it for the one
   who cannot read. */
.confetti { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 60; }
.confetti[hidden] { display: none; }
.confetti i {
  position: absolute; top: -14%; display: block;
  animation-name: confetti-fall; animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.confetti i svg { width: 100%; height: auto; display: block; }
@keyframes confetti-fall {
  0%   { transform: translateY(0) rotate(0deg); opacity: 0; }
  8%   { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translateY(118vh) rotate(420deg); opacity: 0; }
}

.finale {
  position: absolute; inset: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;   /* a stray touch must never read as a card tap */
}
.finale[hidden] { display: none; }
.finale-card {
  background: rgba(255,255,255,0.93); border-radius: 26px;
  padding: 20px 26px 22px; text-align: center; min-width: 210px;
  box-shadow: 0 6px 0 rgba(74,52,24,0.14);
  animation: finale-in 620ms cubic-bezier(0.34, 1.5, 0.5, 1);
}
@keyframes finale-in {
  0%   { transform: scale(0.7); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
.finale-pictures { display: flex; gap: 12px; justify-content: center; margin-bottom: 6px; }
.finale-picture { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.finale-picture svg { width: 34px; height: 34px; display: block; }
.finale-picture span {
  font-size: 14px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.finale-total {
  margin: 4px 0 2px; font-size: 52px; font-weight: 800; color: var(--ink);
  font-variant-numeric: tabular-nums; line-height: 1;
}
.finale-best { margin: 0 0 8px; font-size: 12px; color: rgba(74,52,24,0.6); }
.finale-best[hidden] { display: none; }
.finale-restart { margin: 0; font-size: 14px; color: var(--ink); }

/* Lock indicator — lock used to be completely invisible. */
.lock-indicator {
  position: absolute; top: 14px; right: 56px; width: 30px; height: 30px;
  opacity: 0.9; z-index: 40;
}
.lock-indicator svg { width: 100%; height: 100%; display: block; }

.hidden-zone { position: absolute; top: 0; right: 0; width: 70px; height: 70px; background: transparent; border: none; padding: 0; }

.part-dots { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; }
.part-dots span { width: 10px; height: 10px; border-radius: 50%; background: rgba(90,70,50,0.22); box-shadow: 0 0 0 2px rgba(255,255,255,0.45); }
.part-dots span.filled { background: #F6E3CE; }

/* `display:flex` beats the UA rule for the `hidden` attribute, so without this
   the admin "hide test buttons" toggle sets hidden and nothing happens. */
.debug-panel[hidden], .picture-counter[hidden] { display: none; }
/* Above the finale overlay, or the test buttons become unreachable exactly when
   they are needed to prove the restart works. */
.debug-panel { position: absolute; bottom: 14px; right: 14px; display: flex; gap: 8px; z-index: 80; }
/* Desktop dev path: with no NFC these buttons stand in for card taps, so they
   cannot sit on top of the intro bubble. Clear of the top-right corner too —
   that one belongs to the five-tap gesture. */
#intro:not([hidden]) ~ .debug-panel { bottom: auto; top: 45%; flex-direction: column; }
.debug-panel button {
  width: 44px; height: 44px; border-radius: 50%; border: 3px solid rgba(255,255,255,0.9);
  box-shadow: 0 2px 0 rgba(74,52,24,0.25); cursor: pointer;
}
/* The three cards are colours, so the test buttons are those colours. A label
   would have to be read; this does not. */
.dbg-red { background: #E24E3C; }
.dbg-yellow { background: #F5C33F; }
.dbg-blue { background: #3E8CD6; }

/* PIN */
.pin-card { background: var(--c-cream); border-radius: 20px; padding: 24px; width: 260px; text-align: center; }
.pin-dots { font-size: 22px; letter-spacing: 8px; margin: 0 0 16px; color: var(--ink); }
.pin-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.pin-pad button {
  padding: 14px 0; border-radius: 12px; border: none; background: #fff;
  font-size: 18px; color: var(--ink); cursor: pointer;
}
.pin-pad button:active { background: var(--c-mustard); }
.pin-cancel, .pin-back { font-size: 13px !important; background: transparent !important; color: var(--ink); }

/* ADMIN */
.admin-card {
  background: var(--c-cream); border-radius: 20px; padding: 24px;
  width: 300px; max-height: 80vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: 10px;
}
.admin-card h1 { font-size: 18px; margin: 0 0 6px; color: var(--ink); text-align: center; }
.admin-btn {
  padding: 12px 16px; border-radius: 12px; border: none; background: #fff;
  font-size: 15px; text-align: center; color: var(--ink); cursor: pointer;
}
.admin-exit { background: transparent; color: var(--ink); margin-top: 4px; }
/* The one coloured button on a screen is the one the parent came to press.
   That is Save — never Delete, which sits in white with everything else. */
.admin-accent { background: var(--c-coral); color: #fff; font-weight: 600; }

.confirm-message {
  margin: 0 0 6px; font-size: 14px; line-height: 1.5;
  color: rgba(74,52,24,0.75); text-align: center;
}

.card-list { display: flex; flex-direction: column; gap: 8px; max-height: 260px; overflow-y: auto; }
.card-row {
  position: relative;
  border: 1px solid rgba(74,52,24,0.15); border-radius: 10px; padding: 10px 26px;
  display: flex; justify-content: center; align-items: center; cursor: pointer;
  text-align: center;
}
.card-row > span { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); }
.card-row .card-meta p { margin: 0; }
.card-row .card-name { font-size: 14px; font-weight: 600; }
.card-row .card-action { font-size: 12px; color: rgba(74,52,24,0.6); margin-top: 2px; }

.tap-zone {
  border: 2px dashed rgba(74,52,24,0.3); border-radius: 12px; padding: 16px;
  text-align: center; font-size: 13px; color: rgba(74,52,24,0.7);
}
.tap-zone.listening { border-color: var(--c-leaf); color: var(--ink); }

.field-label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--ink); text-align: center; }
.field-label input, .field-label select {
  padding: 10px; border-radius: 10px; border: 1px solid rgba(74,52,24,0.2); font-size: 15px;
  text-align: center;
}

.toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  background: var(--ink); color: #fff; padding: 10px 16px; border-radius: 10px;
  font-size: 13px; z-index: 999; max-width: 80vw; text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .creature.shrug, .creature.bounce, .creature.look-up, .creature.shake,
  .creature.asleep, .sleep-bubbles, .sun-rays, .scene-root.quake,
  .picture-chip.bump, .layer-canvas.lift, .layer-pot.tip,
  .part-asking, .pot-paint { animation: none; }
  /* These must still REACH their end state — a blob of paint left hanging in
     mid-air is not a reduced animation, it is a broken pour. */
  .pour-blob { animation-duration: 300ms; }
  .pour-ring, .spark, .recipe, .hung { animation-duration: 400ms; }
  /* A part that is being asked for still has to stand out from one that is
     waiting, so the pulse becomes a static brighter wash. */
  .part-asking { fill-opacity: 0.46; stroke-opacity: 1; }
  .confetti i { animation-duration: 6s !important; }
}

/* ---------- NFC diagnostics ---------- */
.diag-card { max-width: 620px; width: 92vw; }
.diag-checks { display: flex; flex-direction: column; gap: 2px; }
.diag-row {
  display: grid; grid-template-columns: 18px 1fr; grid-template-areas: "mark label" ". detail";
  gap: 0 6px; padding: 5px 0; border-bottom: 1px solid rgba(74,52,24,0.08); font-size: 13px;
}
.diag-mark { grid-area: mark; font-weight: 700; }
.diag-label { grid-area: label; font-weight: 600; }
.diag-detail {
  grid-area: detail; font-size: 11px; line-height: 1.35; color: rgba(74,52,24,0.7);
  word-break: break-word;
}
.diag-ok .diag-mark { color: var(--c-leaf); }
.diag-bad .diag-mark { color: var(--c-coral); }
.diag-bad .diag-detail { color: var(--ink-warm); }

.diag-actions { display: flex; gap: 8px; }
.admin-btn.diag-inline { flex: 1; margin: 0; font-size: 13px; padding: 10px 8px; }
.diag-hint { font-size: 11px; color: rgba(74,52,24,0.65); margin: 2px 0 0; line-height: 1.4; }
.diag-log {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word; background: rgba(74,52,24,0.06);
  border-radius: 8px; padding: 8px; height: 130px; overflow-y: auto; user-select: text;
}

/* raw UID readout on the play screen, only while test mode is on */
.read-flash {
  position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
  background: var(--ink); color: #fff; padding: 4px 10px; border-radius: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
  opacity: 0; pointer-events: none; z-index: 50;
}
.read-flash.flash { animation: readflash 1.4s ease-out; }
@keyframes readflash {
  0% { opacity: 0; transform: translateX(-50%) translateY(-4px); }
  12% { opacity: 1; transform: translateX(-50%) translateY(0); }
  75% { opacity: 1; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .read-flash.flash { animation: none; opacity: 1; }
}
