/* Mascot rig. Behaviour lives in js/mascot.js; see docs/mascot.md.
   Each transform gets its own element because one element only carries one
   animation per property, and bob, physics and breathe/sway all want transform. */

.mascot {
  position: relative;
  flex: none;
  line-height: 0;
  cursor: pointer;
  transform-origin: 50% 100%;
  animation: mascot-bob 3.1s ease-in-out infinite;
}

/* JS owns this element's transform outright — no CSS animation here, or the
   two fight over the same property. */
.mascot-poke {
  transform-origin: 50% 92%;
  will-change: transform;
}

.mascot-inner {
  position: relative;
  transform-origin: 50% 100%;
  animation: mascot-breathe 2.6s ease-in-out infinite,
             mascot-sway 7.3s ease-in-out infinite;
}

.mascot img {
  display: block;
  width: 100%;
  height: auto;
  -webkit-user-drag: none;
  user-select: none;
}

.mascot .layer {
  position: absolute;
  inset: 0;
  opacity: 0;
}

.mascot .layer.is-visible {
  opacity: 1;
}

/* A blink is a hard cut, not a fade: two states, no in-between. */
.mascot .face-blink {
  animation: mascot-blink 4.5s steps(1, end) infinite;
}

/* She must not blink over a reaction. */
.mascot.is-reacting .face-blink {
  animation: none;
}

/* Only `idle` was drawn for each outfit, so expression layers would show the
   default clothes over the new ones. Hide them while an outfit is on. */
.mascot.has-outfit .layer {
  display: none;
}

.mascot.has-outfit .face-blink {
  animation: none;
}

/* Secondary chest motion: a masked copy of the sprite springing a beat behind
   the body. The chest is pixel-identical across every expression frame, so a
   single copy of `idle` composites seamlessly under all of them. The soft mask
   falloff is what hides the seam at these amplitudes. */
.mascot-bounce {
  position: absolute;
  inset: 0;
  transform-origin: 47% 47%;
  will-change: transform;
  -webkit-mask-image: radial-gradient(ellipse 34% 11% at 46% 59%, #000 50%, rgba(0, 0, 0, 0) 82%);
  mask-image: radial-gradient(ellipse 34% 11% at 46% 59%, #000 50%, rgba(0, 0, 0, 0) 82%);
}

/* The art is cropped at the waist, so left alone it ends on a hard horizontal
   edge. Dissolving the bottom lets her sit in a hero corner without reading as
   a cut-out pasted on top. --mascot-fade is where the dissolve starts. */
.mascot--fade {
  /* Starts below her folded arms. Any higher dissolves the pose itself, which
     is the most characterful thing about the render. */
  --mascot-fade: 80%;
  -webkit-mask-image: linear-gradient(to bottom, #000 var(--mascot-fade), rgba(0, 0, 0, 0) 97%);
  mask-image: linear-gradient(to bottom, #000 var(--mascot-fade), rgba(0, 0, 0, 0) 97%);
}

@keyframes mascot-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-9px); }
}

@keyframes mascot-breathe {
  0%, 100% { transform: scale(1, 1); }
  50%      { transform: scale(0.995, 1.012); }
}

@keyframes mascot-sway {
  0%, 100% { rotate: -0.9deg; }
  50%      { rotate: 0.9deg; }
}

/* ~110ms closed, twice in quick succession — people blink in pairs, and a
   single slow fade reads as a droop. */
@keyframes mascot-blink {
  0%, 92.4%    { opacity: 0; }
  92.5%, 94.9% { opacity: 1; }
  95%, 96.9%   { opacity: 0; }
  97%, 98.9%   { opacity: 1; }
  99%, 100%    { opacity: 0; }
}

/* Reactions still swap frames under reduced motion — a frame change is not
   motion. Only the movement stops; the physics opts out in JS. */
@media (prefers-reduced-motion: reduce) {
  .mascot,
  .mascot-inner,
  .mascot .layer {
    animation: none;
  }
}

/* --- placement: landing hero ------------------------------------------- */

.landing-mascot {
  position: absolute;
  right: clamp(8px, 4vw, 72px);
  bottom: 0;
  /* Sized against the shared canvas, which carries transparent padding for the
     widest outfit (the witch hat's brim). Her apparent size is smaller than the
     box, hence the larger numbers than the figure alone would need. */
  width: clamp(200px, 26vw, 350px);
  z-index: 1;
}

/* The chibi is a redraw, not a costume: its canvas is a different shape, so at
   the shared width she would render taller than her adult self. Shrink her, or
   the joke lands backwards. */
.landing-mascot[data-outfit="chibi"] {
  width: clamp(130px, 17vw, 230px);
}

/* Down to ~700px the headline still clears her. Below that they collide, so she
   drops behind it as a watermark rather than fighting for the space. */
@media (max-width: 700px) {
  .landing-mascot {
    right: -24px;
    width: 190px;
    opacity: 0.16;
    pointer-events: none;
  }
}

@media (max-width: 560px) {
  .landing-mascot {
    display: none;
  }
}
