/* Everything here past the first block exists to stop a toddler's hands from
   triggering browser gestures instead of the game. */

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #b09a72;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  touch-action: none;          /* no scroll, no pan, no pinch */
  overscroll-behavior: none;   /* no pull-to-refresh */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none; /* no long-press selection menu */
  -webkit-tap-highlight-color: transparent;
}

#stage,
#stage svg {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none; /* not inherited from body, has to be repeated here */
}

/* ---- parent gate ------------------------------------------------------- */

#gate {
  position: fixed;
  top: 0;
  right: 0; /* the back button owns the top-left */
  width: 90px;
  height: 90px;
  z-index: 10;
}

/* ---- back to the menu -------------------------------------------------- */

#back {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 12;
  width: 46px;
  height: 46px;
  padding: 10px;
  border: none;
  border-radius: 50%;
  color: #fff;
  background: rgba(20, 40, 52, 0.28);
  transition: background 120ms ease, transform 120ms ease;
}

#back[hidden] {
  display: none;
}

#back svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Only used when the `backHold` setting is on: fills in over the hold. */
#back.holding {
  background: rgba(20, 40, 52, 0.75);
  transform: scale(1.12);
  transition: background 600ms linear, transform 600ms ease-out;
}

/* ---- menu -------------------------------------------------------------- */

.menu {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  touch-action: pan-y;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px;
  background: linear-gradient(#bfe6f7, #e8f5ec);
}

/* Bounded columns rather than 1fr: with a single activity a 1fr tile stretches
   to the full width and stops looking like a tile at all. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 300px));
  justify-content: center;
  gap: 22px;
  width: 100%;
  max-width: 980px;
}

.tile {
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  border-radius: 20px;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 4px 14px rgba(20, 50, 70, 0.16);
  transition: transform 100ms ease;
}

.tile:active {
  transform: scale(0.97);
}

.tile-art {
  display: block;
  aspect-ratio: 16 / 10;
  background: #b09a72;
}

.tile-art svg {
  display: block;
  width: 100%;
  height: 100%;
}

.tile-title {
  padding: 14px 10px 16px;
  font-size: 21px;
  font-weight: 600;
  color: #16323f;
  text-align: center;
}

/* ---- the flap book ----------------------------------------------------- */

/* One page is built at a time; during a turn both the outgoing and the
   incoming page are in the stack, sliding in opposite directions. */
.fb-book {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  touch-action: none;
}

.fb-page {
  position: absolute;
  inset: 0;
  touch-action: none;
}

/* Only carried while a turn is actually running, so a page sitting still
   can't be made to slide by some unrelated style change. */
.fb-page.sliding {
  transition: transform 380ms cubic-bezier(0.3, 0.8, 0.35, 1), opacity 380ms ease;
}

/* Mid-turn the flaps are moving targets. Let them land first. */
.fb-book.turning {
  pointer-events: none;
}

/* Fixed rather than drawn into the scene: the pages are 1000x625 with
   `slice`, so anything parked near the edge of the viewBox is the first thing
   a 4:3 tablet crops away. They sit at mid height, which is the band every
   page deliberately keeps free of flaps. */
.fb-nav {
  position: fixed;
  top: 50%;
  z-index: 11;
  width: 46px;
  height: 74px;
  margin-top: -37px;
  padding: 10px 8px;
  border: none;
  border-radius: 23px;
  color: #fff;
  background: rgba(20, 40, 52, 0.28);
}

.fb-prev { left: 8px; }
.fb-next { right: 8px; }

/* Which page you are on, between the back button and the parent gate. */
.fb-dots {
  position: fixed;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 11;
  display: flex;
  gap: 12px;
}

.fb-pip {
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(20, 40, 52, 0.22);
}

.fb-pip.on {
  background: rgba(255, 255, 255, 0.86);
}

/* ---- lift-the-flap ----------------------------------------------------- */

/* A door "swings" by collapsing to a sliver about its hinge edge. Rotating
   the rectangle would look like a floor plan; foreshortening it looks like a
   door. transform-origin is set inline per door, in viewBox units, which
   is also all that separates a top-hinged roller shutter from a
   bottom-hinged drawer. */
.flap {
  transform-box: view-box;
  transition: transform 300ms cubic-bezier(0.34, 0.9, 0.3, 1);
}

.cell.open .flap-x {
  transform: scaleX(0.08);
}

.cell.open .flap-y {
  transform: scaleY(0.08);
}

.reveal {
  transform-box: view-box;
}

@keyframes reveal-pop {
  0% { transform: scale(0.7); }
  62% { transform: scale(1.09); }
  100% { transform: scale(1); }
}

.reveal.popping {
  animation: reveal-pop 340ms ease-out;
}

/* ---- the creatures behind the doors ------------------------------------ */

/* fill-box so each origin is relative to the part itself — a tail pivots at
   its root, a wing at its shoulder — regardless of where the creature sits. */
.anim {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}

/* Nothing animates behind a closed door: a page of idle creatures would be a
   lot of compositing for a cheap tablet to do for nobody. */
.cell.open .a-wag { animation: cr-wag 0.9s ease-in-out infinite; }
.cell.open .a-sway { animation: cr-sway 1.7s ease-in-out infinite; }
.cell.open .a-twitch { animation: cr-twitch 2.2s ease-in-out infinite; }
.cell.open .a-flap { animation: cr-flap 0.5s ease-in-out infinite; }
.cell.open .a-buzz { animation: cr-flap 0.16s linear infinite; }
.cell.open .a-puff { animation: cr-puff 1.4s ease-in-out infinite; }
.cell.open .a-blink { animation: cr-blink 3.1s ease-in-out infinite; }
.cell.open .a-bob { animation: cr-bob 0.8s ease-in-out infinite; }
.cell.open .a-step { animation: cr-step 0.45s ease-in-out infinite; }
.cell.open .a-open { animation: cr-open 1.3s ease-in-out infinite; }
.cell.open .a-open-r { animation: cr-open-r 1.3s ease-in-out infinite; }
.cell.open .a-spin { animation: cr-spin 1.7s linear infinite; }
.cell.open .a-pulse { animation: cr-pulse 0.55s ease-in-out infinite; }
.cell.open .a-nod { animation: cr-nod 0.9s ease-in-out infinite; }

@keyframes cr-wag { 0%, 100% { transform: rotate(-15deg); } 50% { transform: rotate(15deg); } }
@keyframes cr-sway { 0%, 100% { transform: rotate(-10deg); } 50% { transform: rotate(10deg); } }
@keyframes cr-step { 0%, 100% { transform: rotate(-8deg); } 50% { transform: rotate(8deg); } }
@keyframes cr-flap { 0%, 100% { transform: scaleX(1); } 50% { transform: scaleX(0.32); } }
@keyframes cr-puff { 0%, 100% { transform: scale(1); } 55% { transform: scale(1.32); } }
@keyframes cr-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
@keyframes cr-open { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(-24deg); } }
@keyframes cr-open-r { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(24deg); } }
@keyframes cr-spin { to { transform: rotate(360deg); } }
@keyframes cr-pulse { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
@keyframes cr-nod { 0%, 100% { transform: rotate(0); } 45% { transform: rotate(13deg); } }

/* Mostly still, with an occasional flick — a constant twitch reads as broken. */
@keyframes cr-twitch {
  0%, 84%, 100% { transform: rotate(0); }
  88% { transform: rotate(-17deg); }
  94% { transform: rotate(13deg); }
}

@keyframes cr-blink {
  0%, 90%, 100% { transform: scaleY(1); }
  95% { transform: scaleY(0.08); }
}

/* ---- busyboard --------------------------------------------------------- */

/* One transparent rectangle per gadget, sitting behind its hardware and a
   little larger than it. Toddler aim is bad: a tap that lands in the gap
   between two cogs should still turn the cogs. */
.bb-hit {
  fill: transparent;
  pointer-events: all;
}

/* The squash lives on .bb-body, never on .bb-gadget: the gadget group carries
   a translate as a presentation attribute, and a CSS transform on the same
   element replaces it outright — which parks the gadget on the top-left
   corner of the board. */
@keyframes bb-poke {
  0% { transform: scale(1); }
  38% { transform: scale(0.93); }
  100% { transform: scale(1); }
}

.bb-body {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}

.bb-body.bb-poking {
  animation: bb-poke 240ms ease-out;
}

/* Pivots are given as percentages of each part's own bounding box, so they
   survive being translated across the board. Working them out is a matter of
   where the pivot sits inside the shape as drawn: the switch lever starts at
   its own left edge, so 0% 50%; the padlock shackle turns about the foot of
   its right leg, which is 90% across and 91% down. */
.bb-lever,
.bb-shackle,
.bb-bell,
.bb-clapper {
  transform-box: fill-box;
}

/* light switch */
.bb-lever {
  transform-origin: 0% 50%;
  transform: rotate(-30deg);
  transition: transform 150ms cubic-bezier(0.3, 1.5, 0.5, 1);
}

.bb-on .bb-lever {
  transform: rotate(30deg);
}

.bb-glow,
.bb-rays {
  opacity: 0;
  transition: opacity 170ms ease;
}

.bb-on .bb-glow { opacity: 1; }
.bb-on .bb-rays { opacity: 0.9; }

.bb-glass,
.bb-filament {
  transition: fill 150ms linear, stroke 150ms linear;
}

.bb-on .bb-glass { fill: #ffe8a4; }
.bb-on .bb-filament { stroke: #ff8c1a; }

/* padlock */
.bb-shackle {
  transform-origin: 90% 91%;
  transition: transform 280ms cubic-bezier(0.34, 1.3, 0.4, 1);
}

.bb-open .bb-shackle {
  transform: translateY(-11px) rotate(52deg);
}

/* doorbell */
.bb-button {
  transition: transform 90ms ease;
}

.bb-pressed .bb-button {
  transform: translateY(4px);
}

/* Hung from the top of its own bounding box, and the clapper from the top of
   its. The clapper lags the bell by a beat, which is what makes it read as
   swinging inside rather than being painted on. */
.bb-bell { transform-origin: 50% 0%; }
.bb-clapper { transform-origin: 50% 0%; }

@keyframes bb-ring {
  0% { transform: rotate(0deg); }
  12% { transform: rotate(13deg); }
  30% { transform: rotate(-10deg); }
  50% { transform: rotate(7deg); }
  70% { transform: rotate(-4deg); }
  86% { transform: rotate(2deg); }
  100% { transform: rotate(0deg); }
}

.bb-bell.bb-ringing {
  animation: bb-ring 820ms cubic-bezier(0.36, 0.07, 0.19, 0.97);
}

.bb-bell.bb-ringing .bb-clapper {
  animation: bb-ring 820ms cubic-bezier(0.36, 0.07, 0.19, 0.97) reverse;
}

/* sliding bolt: eased on the way to whichever end it settles at, but never
   while a finger is on it — a transition there means the bolt trails the
   fingertip by 190ms, which feels broken rather than smooth. */
.bb-rod {
  transition: transform 190ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

.bb-rod.bb-drag {
  transition: none;
}

/* ---- overlays ---------------------------------------------------------- */

#splash,
#panel {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

#splash[hidden],
#panel[hidden] {
  display: none;
}

#splash {
  background: #1d3f52;
  color: #fff;
  text-align: center;
}

.splash-bug {
  font-size: 84px;
  line-height: 1;
  margin-bottom: 16px;
}

#splash h1 {
  font-size: 40px;
  font-weight: 700;
  margin-bottom: 8px;
}

#splash p {
  font-size: 20px;
  opacity: 0.85;
}

.hint {
  margin-top: 28px;
  font-size: 14px;
  opacity: 0.5;
}

/* A failure has to be readable from across the room, on a device with no
   console attached. */
.hint.error {
  opacity: 1;
  color: #ff9b9b;
  font-family: ui-monospace, monospace;
  font-size: 13px;
  text-align: left;
  max-width: 640px;
  word-break: break-word;
}

#panel {
  background: rgba(10, 24, 32, 0.9);
  overflow-y: auto;
  touch-action: pan-y; /* the one place scrolling is allowed */
}

.panel-card {
  background: #fff;
  border-radius: 16px;
  padding: 24px;
  width: 100%;
  max-width: 520px;
  color: #16323f;
}

.panel-card h1 {
  font-size: 22px;
  margin-bottom: 18px;
}

.field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 0;
  border-top: 1px solid #e7edf0;
  font-size: 16px;
}

.field:first-child {
  border-top: none;
}

.field label {
  flex: 1;
}

.field .note {
  display: block;
  font-size: 12px;
  opacity: 0.55;
  margin-top: 2px;
}

.field input[type="checkbox"] {
  width: 30px;
  height: 30px;
  flex: none;
}

.field input[type="range"] {
  width: 46%;
  flex: none;
}

.field output {
  width: 46px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  opacity: 0.7;
}

.stats {
  margin-top: 18px;
  font-size: 13px;
  opacity: 0.6;
  line-height: 1.5;
}

#panel-close {
  margin-top: 20px;
  width: 100%;
  padding: 15px;
  font-size: 17px;
  font-weight: 600;
  color: #fff;
  background: #1d7fa8;
  border: none;
  border-radius: 11px;
}

/* ---- wimmelbook -------------------------------------------------------- */

/* One transparent rectangle per prop, sitting behind its drawing and a little
   larger than it — the busyboard's lesson, that being hard to see is fine and
   being hard to hit is not. The ball reuses this class for the target it
   carries around with it. */
.wm-hit {
  fill: transparent;
  pointer-events: all;
}

/* Same trap as .bb-body, and worth restating: the prop group carries its
   placement as a transform *attribute*, and a CSS transform on the same
   element replaces it outright rather than composing with it — which parks
   the prop in the top-left corner of the sky. So the squash lives one group
   in, and every JS-driven part below sets attributes, never styles. */
.wm-body {
  transform-box: fill-box;
  transform-origin: 50% 50%;
}

@keyframes wm-poke {
  0% { transform: scale(1); }
  38% { transform: scale(0.93); }
  100% { transform: scale(1); }
}

.wm-body.wm-poking {
  animation: wm-poke 240ms ease-out;
}

/* Anything rotated or scaled by CSS needs fill-box, or the origin is measured
   from the corner of the whole 1000x625 picture and the part swings off it. */
.wm-canopy,
.wm-leaf,
.wm-sun-rays,
.wm-flutter,
.wm-castle,
.wm-bucket,
.wm-pour,
.wm-ripple,
.wm-tail,
.wm-ear,
.wm-duck-wing,
.wm-slider-body,
.wm-tap-ring {
  transform-box: fill-box;
}

/* ---- the sky ----------------------------------------------------------- */

/* Two ambient loops and no more. A kitchen full of creatures behind closed
   doors was worth switching off; a picture is meant to be looked at, so the
   sky is allowed to move — but everything else in here waits to be touched. */
@keyframes wm-drift {
  from { transform: translateX(-460px); }
  to { transform: translateX(1500px); }
}

.wm-cloud {
  animation-name: wm-drift;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

@keyframes wm-spin {
  to { transform: rotate(360deg); }
}

.wm-sun-rays {
  transform-origin: 50% 50%;
  animation: wm-spin 90s linear infinite;
}

/* ---- the tree ---------------------------------------------------------- */

/* Pivoted at the fork of the trunk, so the canopy swings the way a canopy
   does rather than sliding sideways. */
.wm-canopy {
  transform-origin: 50% 100%;
}

@keyframes wm-shiver {
  0%, 100% { transform: rotate(0deg); }
  16% { transform: rotate(1.6deg); }
  38% { transform: rotate(-1.3deg); }
  60% { transform: rotate(0.8deg); }
  82% { transform: rotate(-0.4deg); }
}

.wm-canopy.wm-shiver {
  animation: wm-shiver 900ms ease-out;
}

/* Three birds, three paths, and all three end where they began — the point of
   the whole prop is that they come back to the branch they left. */
@keyframes wm-fly-0 {
  0% { transform: translate(0, 0); }
  28% { transform: translate(74px, -52px); }
  54% { transform: translate(150px, -18px); }
  78% { transform: translate(92px, -62px); }
  100% { transform: translate(0, 0); }
}

/* Drawn mirrored, so +x in here carries it leftwards across the page — which
   is the way it is facing. */
@keyframes wm-fly-1 {
  0% { transform: translate(0, 0); }
  30% { transform: translate(48px, -30px); }
  58% { transform: translate(92px, -8px); }
  80% { transform: translate(40px, -36px); }
  100% { transform: translate(0, 0); }
}

@keyframes wm-fly-2 {
  0% { transform: translate(0, 0); }
  26% { transform: translate(58px, -74px); }
  52% { transform: translate(150px, -92px); }
  80% { transform: translate(196px, -30px); }
  100% { transform: translate(0, 0); }
}

.wm-bird.wm-fly-0 { animation: wm-fly-0 2.6s ease-in-out; }
.wm-bird.wm-fly-1 { animation: wm-fly-1 2.9s ease-in-out 0.1s; }
.wm-bird.wm-fly-2 { animation: wm-fly-2 2.4s ease-in-out 0.22s; }

@keyframes wm-wingbeat {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(-0.5); }
}

/* Counted rather than infinite, so nothing has to take the class off again
   when the bird lands. */
.wm-bird[class*="wm-fly-"] .wm-wing {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: wm-wingbeat 0.17s linear 16;
}

@keyframes wm-leaf-fall {
  0% { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  10% { opacity: 1; }
  100% { transform: translate(-26px, 160px) rotate(220deg); opacity: 0; }
}

.wm-leaf {
  transform-origin: 50% 50%;
  opacity: 0;
}

.wm-leaf.wm-leaf-fall {
  animation: wm-leaf-fall 2.2s ease-in;
}

/* ---- the slide --------------------------------------------------------- */

/* One child, two poses, one of them hidden. Cheaper than redrawing a rider
   mid-ride and it keeps the two poses honestly the same child. */
.wm-pose-stand { display: none; }
.wm-walking .wm-pose-sit { display: none; }
.wm-walking .wm-pose-stand { display: inline; }

@keyframes wm-landed {
  0% { transform: scale(1, 1); }
  30% { transform: scale(1.22, 0.76); }
  62% { transform: scale(0.94, 1.08); }
  100% { transform: scale(1, 1); }
}

.wm-slider-body {
  transform-origin: 50% 100%;
}

.wm-slider-body.wm-landed {
  animation: wm-landed 420ms ease-out;
}

/* ---- the sandpit ------------------------------------------------------- */

/* The bucket tips out of the way to the left and the castle grows where it
   was standing, which is the whole cause-and-effect of the prop. */
.wm-bucket {
  transform-origin: 50% 96%;
  transition: transform 420ms cubic-bezier(0.3, 1.2, 0.4, 1);
}

.wm-built .wm-bucket {
  transform: translate(-62px, 6px) rotate(-124deg);
}

.wm-castle {
  transform-origin: 50% 100%;
  transform: scaleY(0.04);
  opacity: 0;
  transition: transform 360ms cubic-bezier(0.34, 1.5, 0.42, 1), opacity 160ms linear 120ms;
}

.wm-built .wm-castle {
  transform: scaleY(1);
  opacity: 1;
  transition: transform 460ms cubic-bezier(0.34, 1.5, 0.42, 1) 200ms,
              opacity 120ms linear 200ms;
}

.wm-pour {
  transform-origin: 50% 0%;
  opacity: 0;
}

@keyframes wm-pouring {
  0% { transform: scaleY(0.1); opacity: 0; }
  30% { transform: scaleY(1); opacity: 0.95; }
  100% { transform: scaleY(1.15) translateY(14px); opacity: 0; }
}

.wm-pour.wm-pouring {
  animation: wm-pouring 620ms ease-in 180ms;
}

/* ---- the pond ---------------------------------------------------------- */

/* The rings are moved to the fingertip with cx/cy, which are attributes and
   not transforms, so the scale animation composes with the placement instead
   of fighting it. */
.wm-ripple {
  transform-origin: 50% 50%;
  opacity: 0;
}

@keyframes wm-rippling {
  0% { transform: scale(0.3); opacity: 0.85; }
  100% { transform: scale(4.6); opacity: 0; }
}

.wm-ripple.wm-rippling { animation: wm-rippling 1.1s ease-out; }
.wm-ripple-2.wm-rippling { animation: wm-rippling 1.3s ease-out 0.22s; }

.wm-pond-duck {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.3, 1);
}

@keyframes wm-flapping {
  0%, 100% { transform: scaleY(1) rotate(0deg); }
  25% { transform: scaleY(0.5) rotate(-16deg); }
  60% { transform: scaleY(1.1) rotate(8deg); }
}

.wm-duck-wing {
  transform-origin: 50% 90%;
}

.wm-duck-wing.wm-flapping {
  animation: wm-flapping 0.42s ease-in-out 3;
}

@keyframes wm-bobbing {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-3px) rotate(2.5deg); }
}

.wm-duckling {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: wm-bobbing 3.4s ease-in-out infinite;
}

/* ---- the molehill ------------------------------------------------------ */

/* Parked below the rim of its own hole. Nothing clips it — the front of the
   mound is simply drawn over the top, the same way the leaves hide the bug. */
.wm-mole {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transform: translateY(16px);
}

@keyframes wm-popping {
  0% { transform: translateY(16px); }
  16% { transform: translateY(-42px); }
  24% { transform: translateY(-34px); }
  40% { transform: translateY(-34px) rotate(-8deg); }
  58% { transform: translateY(-34px) rotate(8deg); }
  74% { transform: translateY(-36px) rotate(0deg); }
  100% { transform: translateY(16px); }
}

.wm-mole.wm-popping {
  animation: wm-popping 2.3s cubic-bezier(0.3, 0.9, 0.4, 1);
}

/* ---- the dog ----------------------------------------------------------- */

/* The tail is the one thing here that never stops. A dog that only moves when
   it is touched is a statue of a dog. */
@keyframes wm-wag {
  0%, 100% { transform: rotate(-13deg); }
  50% { transform: rotate(15deg); }
}

.wm-tail {
  transform-origin: 100% 100%;
  animation: wm-wag 1.5s ease-in-out infinite;
}

@keyframes wm-hop {
  0%, 100% { transform: translateY(0); }
  30% { transform: translateY(-15px); }
  55% { transform: translateY(0); }
  72% { transform: translateY(-6px); }
}

.wm-dog.wm-barking {
  animation: wm-hop 620ms ease-out;
}

.wm-dog.wm-barking .wm-tail {
  animation: wm-wag 0.28s ease-in-out infinite;
}

.wm-ear {
  transform-origin: 100% 0%;
}

.wm-dog.wm-barking .wm-ear {
  animation: wm-wag 0.3s ease-in-out 4;
}

.wm-tongue {
  opacity: 0;
}

.wm-dog.wm-barking .wm-tongue {
  animation: wm-showing 620ms ease-out;
}

@keyframes wm-showing {
  0%, 100% { opacity: 0; }
  15%, 70% { opacity: 1; }
}

/* ---- the butterflies --------------------------------------------------- */

@keyframes wm-flit-a {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(74px, -52px); }
  42% { transform: translate(146px, 16px); }
  63% { transform: translate(86px, 62px); }
  84% { transform: translate(-34px, 28px); }
}

@keyframes wm-flit-b {
  0%, 100% { transform: translate(0, 0); }
  24% { transform: translate(-92px, 34px); }
  48% { transform: translate(-142px, -46px); }
  70% { transform: translate(-52px, -84px); }
  88% { transform: translate(28px, -30px); }
}

.wm-flit-a { animation-name: wm-flit-a; }
.wm-flit-b { animation-name: wm-flit-b; }

.wm-flit-a,
.wm-flit-b {
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes wm-flutter {
  0%, 100% { transform: scaleX(1); }
  50% { transform: scaleX(0.3); }
}

.wm-flutter {
  transform-origin: 50% 50%;
  animation: wm-flutter 0.28s ease-in-out infinite;
}

/* ---- a tap that hit nothing -------------------------------------------- */

/* Acknowledged, but quietly and with no sound: a wimmel page should never
   feel dead under a finger, and a miss should never be worth as much as a
   hit. */
.wm-tap-ring {
  transform-origin: 50% 50%;
  opacity: 0;
}

@keyframes wm-tapped {
  0% { transform: scale(0.4); opacity: 0.75; }
  100% { transform: scale(2.4); opacity: 0; }
}

.wm-tap-ring.wm-tapped {
  animation: wm-tapped 520ms ease-out;
}

/* ---- friends: peekaboo ------------------------------------------------- */

.pk {
  width: 100%;
  height: 100%;
  touch-action: none;
}

/* Arms and blanket pivot in viewBox units: origins are set inline at the
   shoulder and the middle of the head. The slow default is the hide; the
   reveal overrides it with something snappy, because the waiting is the
   game and the "ta-da" is the payoff. */
.pk-arm,
.pk-blanket {
  transform-box: view-box;
  transition: transform 900ms cubic-bezier(0.45, 0, 0.3, 1);
}

.pk.pk-shown .pk-arm,
.pk.pk-shown .pk-blanket {
  transition: transform 260ms cubic-bezier(0.3, 1.4, 0.5, 1);
}

/* Out wide is the resting pose; hiding is what the mode adds. */
.pk-arm-l { transform: rotate(-55deg); }
.pk-arm-r { transform: rotate(55deg); }

.pk[data-mode="hands"]:not(.pk-shown) .pk-arm-l { transform: rotate(13deg); }
.pk[data-mode="hands"]:not(.pk-shown) .pk-arm-r { transform: rotate(-13deg); }

/* Peeking: hands slide apart just far enough to show half an eye. */
.pk.pk-peek[data-mode="hands"]:not(.pk-shown) .pk-arm-l { transform: rotate(1deg); }
.pk.pk-peek[data-mode="hands"]:not(.pk-shown) .pk-arm-r { transform: rotate(-1deg); }

.pk[data-mode="blanket"]:not(.pk-shown) .pk-arm-l { transform: rotate(-165deg); }
.pk[data-mode="blanket"]:not(.pk-shown) .pk-arm-r { transform: rotate(165deg); }

/* Thrown up and away off the top of the screen, and dropped back on. */
.pk-blanket { transform: translate(320px, -640px) rotate(40deg); }
.pk[data-mode="blanket"]:not(.pk-shown) .pk-blanket { transform: none; }
/* Peeking from under it: lifted enough to show the grin, not the eyes. */
.pk.pk-peek[data-mode="blanket"]:not(.pk-shown) .pk-blanket { transform: translateY(-150px); }

.pk.pk-peek .pk-arm,
.pk.pk-peek .pk-blanket {
  transition-duration: 320ms;
}

/* Expressions: all drawn, one visible. */
.pk-e,
.pk-m {
  opacity: 0;
  transition: opacity 90ms linear;
}

.pk[data-face="smile"] .pk-e-smile,
.pk[data-face="smile"] .pk-m-smile,
.pk[data-face="surprise"] .pk-e-surprise,
.pk[data-face="surprise"] .pk-m-surprise,
.pk[data-face="laugh"] .pk-e-laugh,
.pk[data-face="laugh"] .pk-m-laugh {
  opacity: 1;
}

.pk-brows {
  transition: transform 160ms ease-out;
}

.pk[data-face="surprise"] .pk-brows { transform: translateY(-16px); }
.pk[data-face="laugh"] .pk-brows { transform: translateY(-8px); }

.pk-kid { transform-box: view-box; transform-origin: 500px 625px; }
.pk-head { transform-box: view-box; }

@keyframes pk-bounce {
  0%, 100% { transform: translateY(0); }
  40% { transform: translateY(-18px) scaleY(1.02); }
  70% { transform: translateY(2px) scaleY(0.99); }
}

@keyframes pk-wobble {
  0%, 100% { transform: rotate(0); }
  30% { transform: rotate(-5deg); }
  70% { transform: rotate(5deg); }
}

.pk[data-face="laugh"] .pk-kid { animation: pk-bounce 480ms ease-in-out 3; }
.pk[data-face="laugh"] .pk-head { animation: pk-wobble 480ms ease-in-out 3; }
