/* ═══════════════════════════════════════════════════════════
   Class Pet Club — wall / setup styles
   Themes swap the block of variables below only.
   ═══════════════════════════════════════════════════════════ */

:root{
  --bg1:#fff8ee; --bg2:#ffe7c9; --bg3:#ffd9a8;
  --ink:#3a2c22; --muted:#9c8a79;
  --card:rgba(255,255,255,.94); --card-edge:rgba(140,90,30,.10);
  --panel:rgba(255,255,255,.86);
  /* the opaque sheet at the foot of a card — near-white in every light
     theme, so it just follows --bg1 instead of needing its own override */
  --sheet:color-mix(in srgb, var(--bg1) 78%, #fff);
  --accent:#f97316; --accent-ink:#fff;
  --good:#16a34a; --bad:#e11d48;
  --bar:#f2e5d4; --bar-ink:linear-gradient(90deg,#fbbf24,#f97316);
  --shadow:0 12px 28px rgba(150,95,30,.14);
  --shadow-lg:0 26px 60px rgba(120,70,20,.28);
  --t1:#94a3b8; --t2:#3b82f6; --t3:#a855f7; --t4:#f59e0b;
}
[data-theme="meadow"]{
  --bg1:#f4fcf3; --bg2:#dcf6dd; --bg3:#c3edc6;
  --ink:#22332a; --muted:#7d9184;
  --card:rgba(255,255,255,.94); --card-edge:rgba(40,120,60,.12);
  --panel:rgba(255,255,255,.86);
  --accent:#16a34a; --accent-ink:#fff;
  --bar:#e2f0e2;
  --shadow:0 12px 28px rgba(40,110,60,.14);
  --shadow-lg:0 26px 60px rgba(30,90,50,.28);
}
[data-theme="ocean"]{
  --bg1:#f2f9ff; --bg2:#d8ecff; --bg3:#bcdcfb;
  --ink:#1c2b3a; --muted:#7a8ea3;
  --card:rgba(255,255,255,.94); --card-edge:rgba(30,90,160,.12);
  --panel:rgba(255,255,255,.86);
  --accent:#2563eb; --accent-ink:#fff;
  --bar:#e0edf9;
  --shadow:0 12px 28px rgba(30,80,150,.14);
  --shadow-lg:0 26px 60px rgba(20,60,120,.28);
}
[data-theme="twilight"]{
  --bg1:#1b1830; --bg2:#241f42; --bg3:#2f2757;
  --ink:#f2ecff; --muted:#a79fc6;
  --card:rgba(255,255,255,.07); --card-edge:rgba(255,255,255,.12);
  --panel:rgba(255,255,255,.06);
  --sheet:#2b2550;   /* the derived one would come out grey here */
  --accent:#a78bfa; --accent-ink:#1b1830;
  --good:#4ade80; --bad:#fb7185;
  --bar:rgba(255,255,255,.14); --bar-ink:linear-gradient(90deg,#facc15,#fb923c);
  --shadow:0 12px 28px rgba(0,0,0,.35);
  --shadow-lg:0 26px 60px rgba(0,0,0,.6);
}

*{box-sizing:border-box}
/* flex/grid display rules below would otherwise beat the hidden attribute */
[hidden]{display:none !important}
html,body{height:100%}
body{
  margin:0; overflow:hidden; color:var(--ink);
  font-family:ui-rounded,"SF Pro Rounded","Segoe UI Variable Display","Segoe UI",system-ui,-apple-system,"Nunito","Helvetica Neue",sans-serif;
  background:
    radial-gradient(120% 90% at 12% 0%, var(--bg2) 0%, transparent 55%),
    radial-gradient(110% 90% at 92% 8%, var(--bg3) 0%, transparent 50%),
    linear-gradient(170deg, var(--bg1) 0%, var(--bg2) 100%);
  background-attachment:fixed;
  -webkit-font-smoothing:antialiased;
}
.muted{color:var(--muted)}
.view{position:fixed; inset:0; display:flex; flex-direction:column; z-index:1}

/* ── buttons ─────────────────────────────────────────────── */
.btn{
  font:inherit; font-weight:700; border-radius:12px; cursor:pointer;
  border:1px solid transparent; padding:9px 14px; line-height:1;
  white-space:nowrap;
  transition:transform .12s ease, background .15s, opacity .15s, border-color .15s;
}
.btn:active{transform:translateY(1px) scale(.98)}
.btn.primary{background:var(--accent); color:var(--accent-ink); box-shadow:0 6px 16px color-mix(in srgb, var(--accent) 35%, transparent)}
.btn.ghost{background:transparent; color:var(--ink); border-color:var(--card-edge)}
.btn.ghost:hover{background:var(--card)}
.btn.danger{background:transparent; color:var(--bad); border-color:color-mix(in srgb, var(--bad) 35%, transparent)}
.btn.danger:hover{background:color-mix(in srgb, var(--bad) 10%, transparent)}
.btn.big{padding:13px 22px; font-size:16px; border-radius:14px}
.btn.on{background:var(--accent); color:var(--accent-ink); border-color:transparent}

/* ── topbar ──────────────────────────────────────────────── */
.topbar{
  position:relative; z-index:3;
  flex:0 0 auto; display:flex; align-items:center; justify-content:space-between;
  gap:12px; padding:10px 16px; opacity:.28; transition:opacity .35s ease;
}
.topbar:hover,.topbar:focus-within{opacity:1}
.brand{display:flex; align-items:center; gap:8px; font-weight:800; letter-spacing:-.01em; font-size:16px; min-width:0}
/* Truncate a long class name instead of wrapping the bar onto two rows —
   the topbar is a thin strip above a full-bleed wall and has no height to spare. */
#className{white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.paw{font-size:19px}
.actions{display:flex; align-items:center; gap:8px}
.selcount{font-weight:800; font-size:14px; color:var(--accent); padding-right:4px}
/* The topbar is already at its limit on a phone — the class name truncates to
   "Class ..." before the overview button exists at all — so the word goes and
   the glyph stays, same as the trophy. The over-cap warning is a toast rather
   than a chip for the same reason: it fires at the moment of the tap, which is
   when it is worth reading, and costs the topbar nothing. */
@media (max-width:899px){
  #btnOverview .ov-txt{display:none}
}

/* ── the wall ────────────────────────────────────────────── */
/* The wall scrolls. Cards are as big as their column gives them — the pet is
   the product, and on a 24-student wall the old "fit everyone on one screen"
   rule was squeezing it to 30px on a phone, at which point every animal looks
   the same.

   The row height is NOT `auto`, tempting as that is: `.pet` is a size
   container, size containment zeroes its intrinsic height, and a row that
   asks the card how tall it is therefore gets "just the sheet" back — the
   card collapsed to 2px and the pet to nothing. `layoutGrid()` measures the
   column and the sheet and sets `grid-auto-rows` in pixels instead; `auto`
   here is only the fallback for the frames before that runs.

   `align-content:start` is not decoration: a grid's default is `stretch`, and
   stretch DOES distribute free space into rows, so without it the final row
   would inflate to fill whatever height was left over.

   The scrollbar gutter is reserved even when nothing overflows, so the column
   width does not jump the moment a class grows past one screen. */
.grid{
  flex:1 1 auto; display:grid; gap:12px;
  padding:2px 16px 16px; min-height:0;
  overflow-y:auto; overflow-x:hidden;
  grid-auto-rows:auto; align-content:start;
  scrollbar-gutter:stable;
}
/* ── the overview ────────────────────────────────────────────
   The one place the old contract survives: fit the whole class on one screen.
   It is a body class rather than a #grid class so the cramped-card container
   query further down can also react to it (a container query cannot reach an
   ancestor's class, but `.card` sits inside both). Everything here undoes the
   scrolling block above and hands the cards back to `1fr` rows, which is what
   makes them shrink instead of overflow.

   Above the cap this is allowed to be too small to enjoy — the teacher asked
   for it, and #ovHint says so in words. Refusing to draw it would leave a
   30-student class with no way to see everyone at once at all. */
body.overview .grid{
  overflow:hidden; grid-auto-rows:1fr; align-content:stretch;
}
/* ...including the card's own flex split, which is the other half of the old
   contract. On a scrolling wall the sheet is exactly as tall as its text and
   the stage takes everything else; on a fitted wall the rows are handed a
   share of the viewport that has nothing to do with how tall the pet wants to
   be, and the sheet is what absorbs the difference. Swapping them is what
   would send an overview card's sheet off the bottom of its row. */
body.overview .card .stage{flex:0 1 auto}
body.overview .card .meta{flex:1 1 auto}
/* The feed and selection bars are fixed to the bottom of the viewport, so on a
   scrolling wall the last row would finish underneath them. Give the scroll
   container back their height while they are up. */
body:has(#selbar:not([hidden])) .grid,
body:has(#hatchbar:not([hidden])) .grid{padding-bottom:84px}
/* Two bands, not an avatar circle: a tinted stage up top where the pet
   stands on the card itself (and fills its full width, so the breed
   still reads from the back of the room), and a sheet of paper below
   holding who it is. `--tc` is the level's tier colour — it tints the
   stage, which is what keeps level legible across a projector. */
.card{
  --tc:var(--t1);
  /* A size container, so the cramped-card rules further down can react to
     THIS card's height. The viewport is the wrong signal for that: a short
     wide window cramps a card exactly as hard as a phone does, and a tall
     phone with 12 students does not cramp one at all. Nothing inside `.pet`
     is affected — `.pet` is its own container and stays the nearest one for
     every cq unit in the file.
     Named, not anonymous: `.pet` is a container too, so an unnamed
     `@container (max-height:…)` reached from `.breed` would resolve against
     the 148px-tall pet instead of the card and fire on a desktop. The name
     pins every query to the card. */
  container:card / size;
  position:relative; display:flex; flex-direction:column;
  border:1px solid var(--card-edge); border-radius:20px;
  box-shadow:var(--shadow); cursor:pointer; overflow:hidden; min-height:0;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--tc) 26%, transparent) 0%,
      color-mix(in srgb, var(--tc) 10%, transparent) 58%,
      transparent 100%),
    var(--card);
  transition:transform .16s ease, box-shadow .2s ease, border-color .2s ease;
}
.card:hover{transform:translateY(-3px); box-shadow:var(--shadow-lg)}
.card:active{transform:scale(.985)}

/* On a scrolling wall the card's height is the pet plus the sheet and nothing
   else, so the split is: the sheet takes exactly the height of its own text,
   the stage takes the rest. The stage MUST grow — the pet is size-contained
   and asks for no height of its own, so with a `flex-grow:0` stage the sheet's
   grow wins the argument, the sheet swells to fill the card, and the pet gets
   the leftovers (measured: stage 230 against a 251 square, art 198 instead of
   251 — a quarter of the picture thrown away at every card size).
   `min-height:0` still lets the stage give height back when a row comes out
   short; overview mode swaps both of these back, see the overview block. */
.card .stage{
  position:relative; flex:1 1 auto; min-height:0;
  display:flex; flex-direction:column; align-items:center;
  padding:8px 8px 0;
}

/* ── the pet rig ─────────────────────────────────────────────
   Nested layers, one transform job each, so the level scale, the
   idle breath and the one-shot motions never clobber each other
   (an element only has one `transform`):
     .body  → a SQUARE the exact size of the rendered art, plus
              the level scale (--s). Square matters: everything
              below measures in %, so it must measure against the
              picture, not against a letterboxed card slot.
     .shadow→ ground contact shadow, anchored to the art box
     .rig   → idle breathing, per-card phase via --d
     .actor → one-shot motions (nibble / hop / micro-*)
   ─────────────────────────────────────────────────────────── */
/* width:100% + aspect-ratio gives .pet a definite height, which is what
   container-type:size needs to resolve the cq units inside .body.
   The square is handed in as --art rather than left to aspect-ratio, because
   on a wall that scrolls it never got the chance to resolve: size containment
   makes .pet's intrinsic height 0, so when a row is not already tall enough
   the flex shrink takes the pet down with it (measured 251x0). JS knows the
   column width, so it knows the square. `auto` is the fallback everywhere JS
   has not spoken — overview mode clears the variable and relies on this. */
.card .pet{flex:0 1 auto; width:100%; height:var(--art,auto); aspect-ratio:1; min-height:0; position:relative; container-type:size}
/* The breed tag is pinned to the bottom edge of the ANIMAL, not to the
   bottom of the stage — anchored to the stage it drifts a hundred pixels
   away from the pet on a short wide card.
   It has to be a child of `.pet` for the cq units to resolve, and the
   *var(--s) term tracks the level scale: .body scales about its centre,
   so the rendered bottom edge is at 50% + min(50cqw,50cqh) × --s. */
.card .breed{
  position:absolute; left:8px; right:8px; z-index:3; text-align:center;
  top:calc(50% + min(50cqw, 50cqh) * var(--s, 1)); padding-top:5px;
  transition:top .55s cubic-bezier(.34,1.56,.64,1);   /* rides with the growth */
  font-size:clamp(9px,.72vw,12px); font-weight:600; letter-spacing:.01em; color:var(--muted);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.card .body{
  position:absolute; inset:0; margin:auto;
  /* min(cqw,cqh) = exactly what `object-fit:contain` gives the square art.
     (aspect-ratio alone can't do this: with a definite width it won't
     transfer a max-height clamp back into the width.) */
  width:min(100cqw,100cqh); aspect-ratio:1;
  transform:scale(var(--s,.8));
  transition:transform .55s cubic-bezier(.34,1.56,.64,1);
}
.card .rig,.card .actor{position:absolute; inset:0; display:flex; align-items:center; justify-content:center}
/* Two idle animations on ONE element — which only works because they touch
   different properties. `breathe` drives translate/scale, `sway` drives
   rotate, and the browser composes translate → rotate → scale by itself.
   Written as one `transform` they would overwrite each other and the pet
   would visibly pick one. Both clocks come from the build (see BUILDS). */
.card .rig{
  transform-origin:50% 86%;                     /* lean about the hips, not the middle */
  animation:
    breathe var(--mBreath,4.4s) ease-in-out infinite,
    sway    var(--mSway,8s)     ease-in-out infinite;
  animation-delay:var(--d,0s), var(--d2,0s);
}
.card .actor{transform-origin:50% 90%}          /* squash pivots at the feet */
.card .pet img{position:absolute; inset:0; width:100%; height:100%; object-fit:contain}
/* cqmin, not vw — the placeholder is the fallback that stands in for the pet,
   so it has to be sized FOR the pet. It was clamp(34px,4.6vw,64px), which is a
   glyph for the window: on a 256px art box (a 5-column desktop wall, now that
   the wall scrolls) 4.6vw tops out at the 64px ceiling and the emoji sits in a
   189px circle looking lost. 34% of the art box is roughly what 4.6vw looked
   like at the sizes this used to run at. `.pet` is the container and
   min(100cqw,100cqh) of it is exactly the art box, so cqmin is the pet — same
   reasoning as the crown. The 110px ceiling keeps a fullscreen wall sane. */
.card .ph{position:absolute; font-size:clamp(18px,34cqmin,110px); line-height:1}
/* cqmin, not vw. The crown has to be a crown FOR THIS PET, and a vw-sized one
   is a crown for the window: measured, it held 34px from a 325px art box down
   to a 147px one, so on a 24-student wall it covered the top 33% of the animal
   — straight over the eyes. 10.5% of the art box is what looked right at
   325px, and it stays 10.5% at every size. `.pet` is the container, and
   min(100cqw,100cqh) of `.pet` is exactly the art box, so cqmin is the pet.
   6px floor: below that the glyph is a speck, and nothing meaningful is
   distorted by it. */
.card .crown{position:absolute; top:-2px; font-size:clamp(6px,10.5cqmin,34px); opacity:0; transform:translateY(-14px) scale(.4); transition:.45s cubic-bezier(.34,1.56,.64,1)}
.card.max .crown{opacity:1; transform:translateY(0) scale(1); animation:crownFloat 3s ease-in-out infinite}

/* ground shadow — makes a hop read as a hop, not a resize */
.card .shadow{
  position:absolute; left:50%; bottom:4%; width:54%; height:9%; z-index:0;
  transform:translateX(-50%); border-radius:50%; pointer-events:none;
  background:radial-gradient(ellipse at 50% 50%, rgba(70,45,20,.32), rgba(70,45,20,0) 72%);
  opacity:.5;
}

/* idle: every pet breathes, each on its own clock and at its own depth.
   --mAmp is per-card; a big dog's chest moves more than a cat's. */
@keyframes breathe{
  0%,100%{translate:0 0; scale:1 1}
  50%    {translate:0 calc(-1.6% * var(--mAmp,1));
          scale:calc(1 + .014 * var(--mAmp,1)) calc(1 - .014 * var(--mAmp,1))}
}
/* idle: and leans, slowly, well out of step with its own breathing. Cats
   sway further; a Samoyed is a boulder. */
@keyframes sway{
  0%,100%{rotate:0deg}
  32%    {rotate:calc(1.1deg * var(--mSwayAmp,1))}
  68%    {rotate:calc(-0.9deg * var(--mSwayAmp,1))}
}

/* Every one-shot duration is multiplied by --mSpeed: a retriever chews and
   lands slowly, a cat flicks through the same motion in 72% of the time. */
/* one-shot: nibble (a point was given) */
.card .pet.nibble .actor{animation:nibble calc(.78s * var(--mSpeed,1)) cubic-bezier(.36,.07,.19,.97)}
@keyframes nibble{
  0%  {transform:translateY(0) scale(1,1)}
  16% {transform:translateY(4%) scale(1.10,.86)}
  34% {transform:translateY(1%) scale(1.06,.94)}
  52% {transform:translateY(-11%) scale(.93,1.10)}
  70% {transform:translateY(0) scale(1.06,.95)}
  85% {transform:translateY(-2.5%) scale(.985,1.015)}
  100%{transform:translateY(0) scale(1,1)}
}
.card .pet.nibble .shadow{animation:shadowNibble calc(.78s * var(--mSpeed,1)) ease}
@keyframes shadowNibble{
  0%,100%{transform:translateX(-50%) scale(1); opacity:.5}
  16%    {transform:translateX(-50%) scale(1.10); opacity:.6}
  52%    {transform:translateX(-50%) scale(.86); opacity:.36}
}

/* one-shot: hop (level up). --mHeft scales how far it leaves the ground —
   a Labrador clears noticeably less air than a Siamese on the same jump. */
.card .pet.hop .actor{animation:hop calc(1.05s * var(--mSpeed,1)) cubic-bezier(.3,1.3,.4,1)}
@keyframes hop{
  0%  {transform:translateY(0) rotate(0) scale(1,1)}
  14% {transform:translateY(6%) rotate(0) scale(1.14,.84)}
  34% {transform:translateY(calc(-30% * var(--mHeft,1))) rotate(-7deg) scale(.92,1.12)}
  50% {transform:translateY(calc(-34% * var(--mHeft,1))) rotate(6deg) scale(1,1)}
  68% {transform:translateY(0) rotate(0) scale(1.14,.86)}
  82% {transform:translateY(calc(-7% * var(--mHeft,1))) rotate(-3deg) scale(.97,1.04)}
  92% {transform:translateY(0) rotate(0) scale(1.04,.97)}
  100%{transform:translateY(0) rotate(0) scale(1,1)}
}
.card .pet.hop .shadow{animation:shadowHop calc(1.05s * var(--mSpeed,1)) cubic-bezier(.3,1.3,.4,1)}
@keyframes shadowHop{
  0%,100%{transform:translateX(-50%) scale(1); opacity:.5}
  34%,50%{transform:translateX(-50%) scale(.64); opacity:.24}
  68%    {transform:translateX(-50%) scale(1.08); opacity:.56}
}

/* one-shot: the treat falling in */
.card .treat{
  position:absolute; left:50%; top:32%; z-index:5; pointer-events:none;
  font-size:clamp(15px,1.6vw,24px); line-height:1;
  animation:treatDrop .72s cubic-bezier(.5,0,.6,1) forwards;
}
@keyframes treatDrop{
  0%  {opacity:0; transform:translate(-50%,-30px) scale(.6) rotate(-28deg)}
  22% {opacity:1; transform:translate(-50%,-8px)  scale(1)   rotate(8deg)}
  52% {opacity:1; transform:translate(-50%,4px)   scale(1)   rotate(0)}
  72% {opacity:1; transform:translate(-50%,4px)   scale(.8)  rotate(0)}
  100%{opacity:0; transform:translate(-50%,5px)   scale(.15) rotate(0)}
}

/* micro-motions — the wall feels alive when one pet twitches now and then */
.card .pet.micWiggle .actor{animation:micWiggle calc(.85s * var(--mSpeed,1)) ease}
@keyframes micWiggle{
  0%,100%{transform:rotate(0) translateY(0)}
  15%{transform:rotate(-5deg) translateY(-1%)}
  35%{transform:rotate(4deg)  translateY(0)}
  55%{transform:rotate(-3deg) translateY(-1%)}
  75%{transform:rotate(2deg)  translateY(0)}
}
.card .pet.micPeek .actor{animation:micPeek calc(1.3s * var(--mSpeed,1)) ease}
@keyframes micPeek{
  0%,100%{transform:rotate(0) scale(1,1)}
  25%{transform:rotate(-10deg) scale(1.02,.98)}
  62%{transform:rotate(-7deg)  scale(1.01,.99)}
}
.card .pet.micHop .actor{animation:micHop calc(.9s * var(--mSpeed,1)) cubic-bezier(.3,1.3,.4,1)}
@keyframes micHop{
  0%,100%{transform:translateY(0) scale(1,1)}
  20%{transform:translateY(3%)  scale(1.07,.93)}
  50%{transform:translateY(calc(-13% * var(--mHeft,1))) scale(.96,1.05)}
  78%{transform:translateY(0) scale(1.05,.95)}
}
.card .pet.micHop .shadow{animation:shadowMicHop calc(.9s * var(--mSpeed,1)) ease}
@keyframes shadowMicHop{
  0%,100%{transform:translateX(-50%) scale(1); opacity:.5}
  50%{transform:translateX(-50%) scale(.84); opacity:.34}
}
.card .pet.micNod .actor{animation:micNod calc(1s * var(--mSpeed,1)) ease}
@keyframes micNod{
  0%,100%{transform:translateY(0) scale(1,1)}
  30%{transform:translateY(4%) scale(1.03,.96)}
  60%{transform:translateY(-2%) scale(1,1)}
}

/* ── the egg ──────────────────────────────────────────────────
   Sibling of .body, NOT a wrapper around it: _test_motion.html asserts
   `.pet > .body > .rig > .actor` with DIRECT child selectors, so nothing
   may be inserted between those. The egg is its own layer on top.
   `display:none` unless body.eggs — a hatched wall pays nothing for this. */
.card .egg{
  position:absolute; inset:0; margin:auto; display:none;
  width:min(100cqw,100cqh); aspect-ratio:1; place-items:center;
  z-index:2; pointer-events:none;
  transform-origin:50% 88%;                 /* wobble pivots near the base */
  filter:drop-shadow(0 6px 9px rgba(120,88,48,.20));
  animation:eggIdle 5.2s ease-in-out infinite;
  animation-delay:var(--d,0s);              /* reuse the breathing desync */
}
body.eggs .card .egg{display:grid}
/* Both halves draw the WHOLE egg and clip to a jagged half, so the gradient
   lines up across the cut with no seam when they come apart.
   The cuts overlap by ~2% on purpose — complementary edges antialias into a
   visible hairline on a projector, overlapping opaque paint can't. */
.card .egg i{
  position:absolute; inset:0; margin:auto; width:60%; height:76%;
  border-radius:50% 50% 50% 50% / 62% 62% 38% 38%;
  background:
    radial-gradient(circle at 33% 24%, rgba(255,255,255,.98) 0 5%, rgba(255,255,255,0) 44%),
    radial-gradient(ellipse at 44% 34%, #fffdf9 0%, #fdf3e3 38%, #f4e2c6 72%, #e0c9a4 100%);
}
/* Fine teeth, not a crown: 25 small jags ±2% around the midline. Coarse ones
   read as a zigzag decoration; small ones read as a crack. */
.card .egg .top{clip-path:polygon(0 0,100% 0,100% 52%,96% 54%,92% 51%,88% 53%,84% 50%,80% 54%,76% 51%,72% 53%,68% 50%,64% 54%,60% 51%,56% 53%,52% 50%,48% 54%,44% 51%,40% 53%,36% 50%,32% 54%,28% 51%,24% 53%,20% 50%,16% 54%,12% 51%,8% 53%,4% 50%,0 52%)}
/* The lower shell is a shade darker, and because the cut is jagged the step
   reads as a hairline crack. Tuned by eye: a whole-number step (.95) reads as
   two-tone, and a drop-shadow on the silhouette is invisible at 200px. */
.card .egg .bot{
  clip-path:polygon(0 50%,4% 48%,8% 51%,12% 49%,16% 52%,20% 48%,24% 51%,28% 49%,32% 52%,36% 48%,40% 51%,44% 49%,48% 52%,52% 48%,56% 51%,60% 49%,64% 52%,68% 48%,72% 51%,76% 49%,80% 52%,84% 48%,88% 51%,92% 49%,96% 52%,100% 50%,100% 100%,0 100%);
  filter:brightness(.962);
}

/* unhatched and waiting — still alive, just not awake yet */
@keyframes eggIdle{
  0%,84%,100%{transform:rotate(0deg)}
  88%{transform:rotate(-3.2deg)}
  92%{transform:rotate(3.2deg)}
  96%{transform:rotate(-1.2deg)}
}

/* the hatch itself. --i comes from renderWall(), --stag is set on #grid so a
   30-name class doesn't take 30×90ms to finish. */
#grid.hatching .card{--fd:calc(var(--i,0) * var(--stag,80ms))}
#grid.hatching .card .egg{
  animation:eggShake .62s cubic-bezier(.36,.07,.19,.97) both;
  animation-delay:var(--fd);
}
@keyframes eggShake{
  0%  {transform:rotate(0deg) scale(1)}
  14% {transform:rotate(-8deg) scale(1.02)}
  30% {transform:rotate(8deg) scale(1.03)}
  46% {transform:rotate(-7deg) scale(1.045)}
  62% {transform:rotate(7deg) scale(1.06)}
  78% {transform:rotate(-4deg) scale(1.05)}
  100%{transform:rotate(0deg) scale(1.06)}
}
#grid.hatching .card .egg .top,
#grid.hatching .card .egg .bot{
  animation-duration:.9s; animation-timing-function:cubic-bezier(.2,.7,.3,1);
  animation-fill-mode:both; animation-delay:calc(var(--fd) + .56s);
}
#grid.hatching .card .egg .top{animation-name:shellTop}
#grid.hatching .card .egg .bot{animation-name:shellBot}
/* The shells are already 60% x 76% of the pet box, so they only need to
   travel a little — more than about half their own height and the card's
   overflow:hidden saws them off mid-flight. Shrinking sells the distance
   instead, and the parent's shake scale adds 6% on top of whatever is here. */
@keyframes shellTop{
  0%  {transform:translate(0,0) rotate(0deg) scale(1); opacity:1}
  100%{transform:translate(-30%,-44%) rotate(-42deg) scale(.78); opacity:0}
}
@keyframes shellBot{
  0%  {transform:translate(0,0) rotate(0deg) scale(1); opacity:1}
  100%{transform:translate(24%,22%) rotate(26deg) scale(.78); opacity:0}
}
/* pet grows out of the shell — .actor is safe to animate here, it carries no
   animation of its own (one-shots are opt-in via .pet classes) */
#grid.hatching .card .body{animation:petIn .45s ease both; animation-delay:calc(var(--fd) + .70s)}
#grid.hatching .card .pet .actor{
  animation:hatchPop .78s cubic-bezier(.34,1.56,.64,1) both;
  animation-delay:calc(var(--fd) + .70s);
}
@keyframes petIn{from{opacity:0}to{opacity:1}}
@keyframes hatchPop{
  0%  {transform:scale(.42); opacity:0}
  55% {transform:scale(1.07); opacity:1}
  100%{transform:scale(1); opacity:1}
}

/* the blind box must not leak its contents */
body.eggs .card .body,
body.eggs .card .breed{opacity:0}

.hatchbar{
  position:fixed; left:50%; bottom:26px; transform:translateX(-50%); z-index:46;
  padding:10px; border-radius:20px;
  background:color-mix(in srgb, var(--card) 90%, transparent);
  backdrop-filter:blur(12px);
  border:1px solid var(--card-edge); box-shadow:var(--shadow);
  animation:hatchbarIn .5s cubic-bezier(.34,1.56,.64,1);
}
.hatchbar[hidden]{display:none}
/* The drawer anchors to the same bottom edge, and it sits BELOW the bar in
   z-order (40 vs 46) — a card tapped while the eggs are up would put the
   hatch button on top of its own score list. These three own the screen
   while they are open; step aside. */
.drawer:not([hidden]) ~ .hatchbar,
#ceremony:not([hidden]) ~ .hatchbar,
.board:not([hidden]) ~ .hatchbar{display:none}
@keyframes hatchbarIn{from{opacity:0; transform:translateX(-50%) translateY(16px)}}
#btnHatch{animation:hatchPulse 2.6s ease-in-out infinite}
@keyframes hatchPulse{0%,100%{transform:scale(1)} 50%{transform:scale(1.045)}}

/* teacher can switch the whole thing off (and respect OS settings) */
body.no-motion .rig,
body.no-motion .actor,
body.no-motion .shadow,
body.no-motion .treat,
body.no-motion .egg,
body.no-motion .egg i,
body.no-motion .hatchbar,
body.no-motion #btnHatch{animation:none !important}
body.no-motion .treat{display:none}
@media (prefers-reduced-motion:reduce){
  .rig,.actor,.shadow,.treat,.egg,.egg i,.hatchbar,#btnHatch{animation:none !important}
  .body{transition:none}
  .treat{display:none}
}

.card[data-tier="1"]{--tc:var(--t1)}
.card[data-tier="2"]{--tc:var(--t2)}
.card[data-tier="3"]{--tc:var(--t3)}
.card[data-tier="4"]{--tc:var(--t4)}
.card[data-tier="2"] .pet img,.card[data-tier="2"] .ph{filter:drop-shadow(0 0 10px color-mix(in srgb,var(--t2) 40%,transparent))}
.card[data-tier="3"] .pet img,.card[data-tier="3"] .ph{filter:drop-shadow(0 0 14px color-mix(in srgb,var(--t3) 50%,transparent))}
.card[data-tier="4"] .pet img,.card[data-tier="4"] .ph{filter:drop-shadow(0 0 18px rgba(251,191,36,.75))}
.card.max{border-color:rgba(251,191,36,.65); box-shadow:0 0 0 2px rgba(251,191,36,.35), var(--shadow)}

/* the level pill — solid, high contrast, top-right. It is the one thing
   a kid at the back of the room can read, so it does not get a tint. */
.card .lvl{
  position:absolute; top:8px; right:8px; z-index:4;
  font-size:clamp(10px,.85vw,14px); font-weight:800; letter-spacing:.01em;
  padding:4px 10px; border-radius:999px;
  background:var(--accent); color:var(--accent-ink);
  box-shadow:0 3px 10px color-mix(in srgb,var(--accent) 38%,transparent);
}

.card .meta{
  flex:0 0 auto; position:relative; z-index:2;
  display:flex; flex-direction:column; justify-content:space-between;
  background:var(--sheet); border-radius:16px 16px 0 0; padding:13px 12px 16px;
  box-shadow:0 -5px 14px rgba(70,40,10,.06);
}
.card .name{
  font-weight:800; font-size:clamp(14px,1.35vw,22px); letter-spacing:-.01em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-bottom:6px;
}
.card .row{display:flex; align-items:center; gap:6px; margin-bottom:6px}
.card .dot{flex:0 0 auto; width:7px; height:7px; border-radius:50%; background:var(--accent)}
/* The label yields, the number never does — and neither ever wraps. Measured
   at 420x900 with 28 students: the card is 74px wide, which leaves 56px inside
   the sheet, and "12 / 20" alone wants 31 of the 6px that were left. Without
   nowrap the count folded onto a second line, the row grew 28px -> 42, and
   that 14px came straight off the stage: the pet's box fell 52 -> 38 in a card
   whose height had not changed at all. The orange dot already marks the row,
   so a truncated "Tre…" is the cheapest thing on screen to lose. */
.card .glabel{font-size:clamp(10px,.82vw,13px); font-weight:600; color:var(--muted);
  min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.card .treats{
  flex:0 0 auto; white-space:nowrap;
  margin-left:auto; font-size:clamp(10px,.82vw,13px); font-weight:800;
  font-variant-numeric:tabular-nums;   /* the numbers stop jittering as they tick */
}
/* space-between on .meta spreads the three rows over whatever height the
   sheet ends up with; the margins are what keeps them legible when the
   sheet is tight and there is no free space to spread. */
.card .bar{position:relative; height:7px; border-radius:999px; background:var(--bar)}
.card .bar i{
  display:block; position:relative; height:100%; width:0; border-radius:999px;
  background:var(--bar-ink); transition:width .6s cubic-bezier(.22,1,.36,1);
}
/* the knob marking where the pet is on the bar */
.card .bar i::after{
  content:''; position:absolute; right:0; top:50%; width:13px; height:13px; border-radius:50%;
  transform:translate(50%,-50%); background:var(--sheet);
  border:3px solid var(--accent); box-shadow:0 1px 4px rgba(70,40,10,.28);
  transition:opacity .3s;
}
.card .bar.zero i::after{opacity:0}    /* a bar at 0% gets no knob (nb: not `.empty` —
                                          that class is the no-students screen) */
.card.max .bar i{background:linear-gradient(90deg,#fde047,#f59e0b)}
.card.max .bar i::after{border-color:#f59e0b}

/* ── cramped cards ─────────────────────────────────────────────
   Measured at 420x900 with 24 students: the card is 95x133, and the sheet
   took 95 of those 133 pixels — 71% — leaving the stage 36 and shrinking
   the pet to 25px. Nothing was going to give that back on its own: `.meta`
   has an implicit min-height:auto, so it holds its content height, and
   `.stage` (min-height:0) absorbs the entire shortfall.

   The pet is the product; the caption is not. So on a card this short the
   sheet gives up its breathing room — padding and gaps first, then a couple
   of pixels of type — which hands the stage 26px and roughly doubles the
   pet. A container query rather than a media query because the card, not
   the window, is what is short: a tall window with 30 students cramps a
   card exactly as hard as a phone does.

   **182, not 170 — and do not move it down without re-running the scan.**
   Above the threshold the pet is width-limited, below it height-limited,
   and the sheet's 26px of extra height only costs the pet once height has
   become the binding constraint. Switch too early and the art INVERTS:
   forcing card heights through the scanner, 170px rendered the pet at 76
   and 175px at 62 — a bigger card with a smaller animal. 182 is where the
   full layout first fits a width-limited pet, so both branches compute the
   same art and the step disappears.

   Note what is deliberately absent: `.stage` keeps its 8px horizontal
   padding here. Trimming it to 4 would win 8px of pet width, but that 8px
   applies the instant the branch flips and cannot be smoothed by choosing a
   better threshold — it is a step in the pet's own width. It cost 2px of
   art at the target size, which is a cheap price for no inversion. */
@container card (max-height:182px){
  .card .meta{padding:6px 8px 8px}
  .card .name{font-size:12px; margin-bottom:3px}
  .card .row{margin-bottom:3px}
  .card .treats{font-size:10px}
  /* The word, not the count. A card this narrow holds the dot, "Treats" and
     "8 / 10" in about 58px of sheet, and the three of them want ~82 — so the
     label ellipsised to "Tre…" on every card, which reads as a rendering fault
     rather than a caption. The dot is what marks the row, so the word is the
     one part that can go without costing the teacher any information: the
     count stays whole and right-aligned. Same reasoning as .breed below. */
  .card .glabel{display:none}
  /* The breed caption sits in the slack under the art. A height-limited pet
     slot has no slack, so the label lands on the sheet and collides with the
     name. It is the third thing anyone needs here — picture and name come
     first — and at this size the string would truncate to "Golden Ret…". */
  .card .breed{display:none}
}

.card .undo{
  position:absolute; top:8px; left:8px; z-index:4; width:28px; height:28px; border-radius:50%;
  display:grid; place-items:center; cursor:pointer; font-size:15px; line-height:1;
  border:1px solid var(--card-edge); background:color-mix(in srgb,var(--sheet) 82%,transparent);
  color:var(--muted); opacity:.5; transition:.2s;
}
.card:hover .undo{opacity:1}
.card .undo:hover{color:var(--accent); border-color:var(--accent)}
.card .check{
  position:absolute; top:8px; left:8px; z-index:4; width:28px; height:28px; border-radius:50%;
  display:none; align-items:center; justify-content:center; font-size:15px; font-weight:900;
  background:var(--accent); color:var(--accent-ink);
  box-shadow:0 2px 8px color-mix(in srgb,var(--accent) 45%,transparent);
}
body.selecting .card .check{display:flex; opacity:.22}
body.selecting .card.sel .check{opacity:1; transform:scale(1.12)}
body.selecting .card .undo{display:none}
body.selecting .card.sel{outline:3px solid var(--accent); outline-offset:-3px}

/* animations */
.card.gain{animation:gain .6s ease-out}
@keyframes gain{
  0%{transform:scale(1)} 22%{transform:scale(1.06)} 46%{transform:scale(.99)} 100%{transform:scale(1)}
}
.card.levelup{animation:lvlup 1.1s cubic-bezier(.34,1.56,.64,1)}
@keyframes lvlup{
  0%{transform:scale(1)} 30%{transform:scale(1.14); box-shadow:0 0 0 6px rgba(251,191,36,.35),0 20px 44px rgba(200,140,20,.35)}
  60%{transform:scale(.97)} 100%{transform:scale(1)}
}
.card.shake{animation:shake .5s ease}
@keyframes shake{
  0%,100%{transform:translateX(0)} 20%{transform:translateX(-5px)} 40%{transform:translateX(5px)}
  60%{transform:translateX(-3px)} 80%{transform:translateX(3px)}
}
@keyframes crownFloat{0%,100%{transform:translateY(0) scale(1)} 50%{transform:translateY(-4px) scale(1.05)}}

.float{
  position:absolute; left:50%; top:34%; z-index:5; pointer-events:none;
  font-weight:900; font-size:clamp(22px,2.4vw,34px); color:var(--good);
  text-shadow:0 3px 12px rgba(0,0,0,.18);
  animation:floatUp 1.15s cubic-bezier(.22,1,.36,1) forwards;
}
.float.neg{color:var(--bad)}
@keyframes floatUp{
  0%{opacity:0; transform:translate(-50%,12px) scale(.6)}
  22%{opacity:1; transform:translate(-50%,-6px) scale(1.18)}
  100%{opacity:0; transform:translate(-50%,-84px) scale(1)}
}
.card .burst{
  position:absolute; inset:0; z-index:3; pointer-events:none; border-radius:20px;
  background:radial-gradient(circle at 50% 45%, rgba(253,224,71,.85), transparent 62%);
  animation:burst .8s ease-out forwards;
}
@keyframes burst{0%{opacity:0; transform:scale(.4)} 35%{opacity:1} 100%{opacity:0; transform:scale(1.5)}}

/* ── empty state ─────────────────────────────────────────── */
.empty{
  position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:10px; text-align:center; padding:24px; z-index:2;
}
.empty-art{font-size:74px; filter:drop-shadow(0 12px 24px rgba(150,95,30,.28))}
.empty h1{margin:0; font-size:clamp(24px,3vw,38px); letter-spacing:-.02em}
.empty p{margin:0; max-width:460px; color:var(--muted); font-size:16px; line-height:1.5}
.empty-btns{display:flex; gap:10px; margin-top:12px; flex-wrap:wrap; justify-content:center}

/* ── drawer ──────────────────────────────────────────────── */
.drawer{
  position:fixed; inset:0; z-index:40; display:flex; align-items:flex-end;
  background:rgba(20,12,4,.32); backdrop-filter:blur(3px);
  animation:fade .18s ease;
}
.drawer[hidden]{display:none}
@keyframes fade{from{opacity:0}to{opacity:1}}
.drawer-inner{
  width:100%; max-height:78vh; overflow-y:auto;
  background:linear-gradient(180deg,var(--card),var(--card));
  border-top:1px solid var(--card-edge); border-radius:26px 26px 0 0;
  box-shadow:0 -20px 60px rgba(80,50,10,.28); padding:16px 18px 22px;
  animation:slideUp .26s cubic-bezier(.22,1,.36,1);
}
@keyframes slideUp{from{transform:translateY(30px); opacity:.6}to{transform:translateY(0); opacity:1}}
.drawer-head{display:flex; align-items:center; justify-content:space-between; gap:14px; margin-bottom:14px}
.drawer-name{font-size:clamp(20px,2.2vw,28px); font-weight:900; letter-spacing:-.02em}
.drawer-meta{font-size:14px; font-weight:600}
.items{display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:10px}
.item{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
  padding:12px 8px; border-radius:16px; cursor:pointer; font:inherit; color:inherit;
  background:var(--card); border:1px solid var(--card-edge);
  transition:transform .12s ease, border-color .15s, box-shadow .15s, background .15s;
}
.item:hover{border-color:var(--accent); transform:translateY(-2px); box-shadow:var(--shadow)}
.item:active{transform:scale(.96)}
.item .ic{font-size:24px; line-height:1}
.item .lb{font-size:12.5px; font-weight:700; text-align:center; line-height:1.25}
.item .dl{font-size:15px; font-weight:900; color:var(--good)}
.item.neg .dl{color:var(--bad)}
.item.flash{animation:flash .45s ease}
@keyframes flash{0%{background:color-mix(in srgb,var(--accent) 30%,transparent)}100%{background:var(--card)}}

/* ── ceremony ────────────────────────────────────────────── */
.ceremony{
  position:fixed; inset:0; z-index:70; display:flex; align-items:center; justify-content:center;
  background:rgba(20,12,4,.45); backdrop-filter:blur(5px); animation:fade .25s ease; padding:20px;
}
.ceremony[hidden]{display:none}
.ceremony-card{
  position:relative; z-index:2; text-align:center; padding:30px 40px 26px; border-radius:28px;
  background:var(--card); border:1px solid var(--card-edge); box-shadow:var(--shadow-lg);
  animation:pop .5s cubic-bezier(.34,1.56,.64,1); max-width:min(560px,92vw);
}
@keyframes pop{from{transform:scale(.7) translateY(20px); opacity:0}to{transform:scale(1) translateY(0); opacity:1}}
.crown-big{font-size:72px; animation:crownFloat 2.2s ease-in-out infinite; line-height:1}
.ceremony-card h2{margin:6px 0 4px; font-size:clamp(26px,3.4vw,40px); letter-spacing:-.02em;
  background:linear-gradient(90deg,#f59e0b,#fbbf24,#f97316); -webkit-background-clip:text; background-clip:text; color:transparent}
.ceremony-card p{margin:0 0 16px; font-size:16px; font-weight:600}
.cer-pet{font-size:64px; margin-bottom:18px; line-height:1; animation:cerBounce 1.5s ease-in-out infinite}
@keyframes cerBounce{
  0%,100%{transform:translateY(0) scale(1,1)}
  28%{transform:translateY(-9%) scale(.96,1.05)}
  50%{transform:translateY(0)   scale(1.06,.95)}
  72%{transform:translateY(-3%) scale(1,1)}
}
.cer-pet img{max-height:150px}
@media (prefers-reduced-motion:reduce){.cer-pet{animation:none}}

canvas.confetti{position:fixed; inset:0; z-index:65; pointer-events:none; display:none}
canvas.confetti.on{display:block}

/* ── selection bar ───────────────────────────────────────── */
.selbar{
  position:fixed; left:50%; bottom:26px; transform:translateX(-50%); z-index:45;
  display:flex; align-items:center; gap:14px; padding:11px 13px 11px 22px;
  background:var(--card); border:1px solid var(--card-edge); border-radius:999px;
  box-shadow:var(--shadow-lg); backdrop-filter:blur(10px);
  animation:toastIn .22s ease;
}
.selbar-text{font-weight:800; font-size:15px; white-space:nowrap}

/* ── toast ───────────────────────────────────────────────── */
.toast{
  position:fixed; left:50%; bottom:26px; transform:translateX(-50%); z-index:80;
  background:rgba(28,20,10,.92); color:#fff; font-weight:700; font-size:14px;
  padding:11px 18px; border-radius:999px; box-shadow:0 12px 30px rgba(0,0,0,.3);
  animation:toastIn .25s ease;
}
.toast[hidden]{display:none}
@keyframes toastIn{from{opacity:0; transform:translate(-50%,12px)}to{opacity:1; transform:translate(-50%,0)}}

/* ── leaderboard ─────────────────────────────────────────────
   Podium on top, whole class below it. The tail of that list is
   deliberately plain: a child shown in last place, in red, on a
   screen the whole room can see, is the fastest way to lose the
   teachers this is built for. Ranks stay (teachers need them),
   the bottom just doesn't get pointed at. */
/* Fixed header over a scrolling body, rather than a sticky header: sticky
   needs its own background to hide the rows sliding under it, and that
   background never quite matches the blurred page behind it — a visible
   seam across the screen. Splitting the two means no overlap, no seam. */
.board{
  position:fixed; inset:0; z-index:50;
  display:flex; flex-direction:column;
  background:color-mix(in srgb, var(--bg1) 94%, transparent);
  backdrop-filter:blur(10px);
}
.board[hidden]{display:none}
.board-inner{
  flex:1 1 auto; min-height:0; width:100%; max-width:900px; margin:0 auto;
  display:flex; flex-direction:column; padding:0 18px;
}
.board-scroll{
  flex:1 1 auto; min-height:0; overflow:auto; padding-bottom:44px;
  scrollbar-width:thin; scrollbar-color:var(--card-edge) transparent;
}
.board-head{
  flex:0 0 auto;
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:22px 0 14px;
}
.board-head h2{margin:0; font-size:clamp(20px,2.2vw,30px); letter-spacing:-.02em}
.board-sub{margin:4px 0 0; font-size:13px; font-weight:600; color:var(--muted)}
.board-none{margin:6px 2px; font-weight:600; color:var(--muted)}

/* -- podium -- */
.podium{display:flex; justify-content:center; align-items:flex-end; gap:12px; margin-bottom:22px}
.podium:empty{display:none; margin:0}
.pod{
  flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center; gap:7px;
  padding:16px 10px; border-radius:20px; text-align:center;
  background:var(--card); border:1px solid var(--card-edge); box-shadow:var(--shadow);
}
/* keyed on place, not on position: three children tied for first all get
   gold, which is the honest reading — none of them is above the others. */
.pod[data-place="1"]{
  padding-top:28px; padding-bottom:28px; border-color:#f0b429;
  box-shadow:0 12px 34px rgba(240,180,41,.3);
}
.pod[data-place="2"]{border-color:#b9c2cf}
.pod[data-place="3"]{border-color:#dda06a}
.medal{font-size:clamp(24px,3vw,38px); line-height:1}
.podname{
  max-width:100%; font-weight:800; font-size:clamp(13px,1.3vw,18px); letter-spacing:-.01em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.podtreats{font-size:clamp(11px,.95vw,14px); font-weight:700; color:var(--muted); font-variant-numeric:tabular-nums}

/* -- the class list -- */
.board-list{display:flex; flex-direction:column; gap:6px}
.brow{
  position:relative; display:flex; align-items:center; gap:10px;
  padding:clamp(7px,.72vw,12px) 14px; border-radius:14px; overflow:hidden;
  background:var(--card); border:1px solid var(--card-edge);
}
/* A fill behind the row, proportional to the leader.
   Flat tint across the bar, then a short fade over the last 36px. A ramp
   across the whole bar looks softer but ends on a 10-unit step where the
   gradient runs out — a hard vertical cut mid-row, which reads as a
   rendering glitch rather than as the end of a bar. Fading to nothing means
   there is no edge to notice, and 36px is short enough that you can still
   see where the bar ends.
   No transition: renderBoard() rebuilds these from scratch, so the width is
   already final by the time the element lands in the document. */
.bfill{
  position:absolute; left:0; top:0; bottom:0; z-index:0; width:0;
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--accent) 17%, transparent) calc(100% - 36px),
    transparent 100%);
}
.brow > :not(.bfill){position:relative; z-index:1}
.brank{
  flex:0 0 auto; min-width:24px; text-align:right;
  font-weight:800; font-size:clamp(12px,1vw,17px); color:var(--muted); font-variant-numeric:tabular-nums;
}
.bname{
  flex:1 1 auto; min-width:0; font-weight:700; font-size:clamp(14px,1.15vw,19px);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.blv{flex:0 0 auto; font-size:clamp(11px,.85vw,14px); font-weight:800; color:var(--muted)}
.btreats{
  flex:0 0 auto; min-width:48px; text-align:right;
  font-weight:800; font-size:clamp(14px,1.15vw,19px); font-variant-numeric:tabular-nums;
}

/* -- pet thumbnails (wall-sized cards use .card .ph/.pet img instead) -- */
.bthumb{
  --th:clamp(34px,3.1vw,50px); position:relative; flex:0 0 auto;
  width:var(--th); height:var(--th);
  border-radius:50%; display:grid; place-items:center; overflow:hidden;
}
.bthumb .bph{font-size:calc(var(--th) * .56); line-height:1}
.bthumb img{position:absolute; inset:0; width:100%; height:100%; object-fit:contain}
.pod .bthumb{--th:clamp(44px,6vw,76px)}

/* ── setup ───────────────────────────────────────────────── */
#setup-view{overflow-y:auto; display:block}
#setup-view .topbar{position:sticky; top:0; z-index:5; opacity:1; backdrop-filter:blur(8px)}
.setup-wrap{
  max-width:860px; margin:0 auto; padding:8px 20px 60px;
  display:grid; gap:16px;
}
.panel{
  background:var(--panel); border:1px solid var(--card-edge); border-radius:20px;
  padding:18px 20px; box-shadow:var(--shadow); backdrop-filter:blur(6px);
}
.panel h2{margin:0 0 14px; font-size:17px; letter-spacing:-.01em}
.panel h2 em{font-style:normal; font-weight:600; color:var(--muted); font-size:13px}
.field{display:block; margin-bottom:14px}
.field > span{display:block; font-size:13px; font-weight:700; margin-bottom:6px}
.field > span em{font-style:normal; font-weight:600; color:var(--muted)}
.field input[type=text], .field textarea{
  width:100%; font:inherit; font-size:15px; padding:11px 13px; border-radius:12px;
  border:1px solid var(--card-edge); background:var(--card); color:var(--ink); resize:vertical;
}
.field input:focus, .field textarea:focus{outline:2px solid var(--accent); outline-offset:-1px}
.row-btns{display:flex; gap:9px; flex-wrap:wrap}
.hint{margin:10px 0 0; font-size:12.5px; color:var(--muted); line-height:1.5}
.switch{display:flex; align-items:center; gap:10px; font-size:14px; font-weight:600; margin-bottom:10px; cursor:pointer}
.switch input{width:18px; height:18px; accent-color:var(--accent)}
.switch em{font-style:normal; font-weight:500; color:var(--muted); font-size:13px}
.themepicker{display:flex; gap:10px; flex-wrap:wrap}
.swatch{
  width:52px; height:40px; border-radius:12px; cursor:pointer; border:2px solid transparent;
  box-shadow:var(--shadow); transition:transform .15s;
}
.swatch:hover{transform:translateY(-2px)}
.swatch.on{border-color:var(--accent); transform:translateY(-2px)}
.swatch[data-t="sunrise"]{background:linear-gradient(135deg,#ffe7c9,#f97316)}
.swatch[data-t="meadow"]{background:linear-gradient(135deg,#dcf6dd,#16a34a)}
.swatch[data-t="ocean"]{background:linear-gradient(135deg,#d8ecff,#2563eb)}
.swatch[data-t="twilight"]{background:linear-gradient(135deg,#2f2757,#a78bfa)}

.itemlist{display:grid; gap:8px; margin-bottom:12px}
.itemrow{display:grid; grid-template-columns:52px 1fr 96px 40px; gap:8px; align-items:center}
.itemrow input{
  width:100%; font:inherit; font-size:14px; padding:9px 10px; border-radius:10px;
  border:1px solid var(--card-edge); background:var(--card); color:var(--ink);
}
.itemrow input.emoji{text-align:center; font-size:18px}
.itemrow input.delta{text-align:center; font-weight:800}
.itemrow button{
  border:1px solid var(--card-edge); background:transparent; color:var(--bad);
  border-radius:10px; height:36px; cursor:pointer; font-size:15px;
}

@media (max-width:640px){
  .grid{gap:8px; padding:2px 8px 8px}
  /* No padding: the stage is meant to run full-bleed to the card edge, and
     every pixel taken here comes straight off the pet. */
  .card{border-radius:14px}
  .topbar{opacity:.75; gap:8px; padding:8px 10px}
  .brand{font-size:14px}
  .actions{gap:6px}
  .btn{padding:8px 10px; border-radius:10px}
  /* Six controls on a 407px bar left the class name at "C." — a class name
     truncated to one letter reads as a bug, not as a name. Undo is the widest
     of the six and the only one whose glyph carries it alone, so the word goes
     there and the name gets its back. */
  #btnUndo .undo-txt{display:none}
  .itemrow{grid-template-columns:44px 1fr 74px 36px}
}
