/* ============================================================================
   Fetti — the Polyfetti mascot.  (Decision N10, owner 2026-08-28.)

   A sheet of paper for a head, a ribbon on its upper corner, a floral dress,
   and four little limbs. Six things it can do: walk, cheer, happy-dance,
   wink, swirl and jump.

   Three rules this file exists to keep:

   1. FETTI HAS A NAME, NOT A JOB TITLE. Nothing here — no class, no comment
      that could leak into a tooltip, no aria-label — describes Fetti as an
      assistant, a bot, or anything that reads as a claim about how the
      translation is done. `assertNoProcessClaims()` fails the build on that
      wording, and it is right to. Fetti reacts to the page; it does not
      perform the service.

   2. EVERY COLOUR COMES FROM A TOKEN in counter.css, so Fetti is theme-aware
      for free. There is not one literal hex in this file. In dark mode the
      paper head goes dark and the ink goes pale, which is correct — it is a
      sheet of paper in a dark room, not a white rectangle burning a hole in
      the page.

   3. LIMBS ROTATE AT THEIR JOINT. `transform-box: fill-box` makes every
      transform-origin below relative to that part's own bounding box rather
      than the SVG's origin, so an arm swings from the shoulder instead of
      orbiting the top-left corner of the drawing. Without this line the
      whole thing flies apart, which is exactly what it did the first time.
   ========================================================================= */

.fetti {
  display: block;
  width: var(--fetti-size, 120px);
  height: auto;
  overflow: visible;
  /* Movement across the page is a transform on the host element, kept apart
     from the per-limb animations inside so the two never fight. */
  transition: transform var(--fetti-travel, 0ms) cubic-bezier(.37, 0, .63, 1);
  will-change: transform;
}

/* Every animated part rotates about its own joint, not the drawing's origin. */
.fetti [id^='fe-'] {
  transform-box: fill-box;
}

.fetti .fe-head   { transform-origin: 50% 100%; }  /* nods from the neck      */
.fetti .fe-ribbon { transform-origin: 50% 100%; }  /* flutters from its knot  */
.fetti .fe-arm-l  { transform-origin: 88% 8%;   }  /* swings from the shoulder */
.fetti .fe-arm-r  { transform-origin: 12% 8%;   }
.fetti .fe-leg-l  { transform-origin: 50% 4%;   }  /* swings from the hip     */
.fetti .fe-leg-r  { transform-origin: 50% 4%;   }
.fetti .fe-body   { transform-origin: 50% 100%; }
.fetti .fe-all    { transform-origin: 50% 78%;  }  /* the whole character     */
.fetti .fe-skirt  { transform-origin: 50% 0%;   }

/* ---------- the face ------------------------------------------------------ */

.fetti .fe-eye  { transition: transform 90ms ease; transform-box: fill-box; transform-origin: 50% 50%; }
.fetti .fe-lid  { opacity: 0; transition: opacity 60ms ease; }
.fetti .fe-blush { opacity: .38; }

/* ---------- idle: it is alive, but only just ------------------------------ */

@keyframes fe-breathe {
  0%, 100% { transform: translateY(0)      rotate(0deg);   }
  50%      { transform: translateY(-1.6px) rotate(-.7deg); }
}
@keyframes fe-ribbon-idle {
  0%, 100% { transform: rotate(0deg);   }
  50%      { transform: rotate(-5deg);  }
}
@keyframes fe-arm-idle-l { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(4deg);  } }
@keyframes fe-arm-idle-r { 0%,100% { transform: rotate(0deg); } 50% { transform: rotate(-4deg); } }

.fetti[data-state='idle'] .fe-head   { animation: fe-breathe     3.4s ease-in-out infinite; }
.fetti[data-state='idle'] .fe-ribbon { animation: fe-ribbon-idle 3.4s ease-in-out infinite; }
.fetti[data-state='idle'] .fe-arm-l  { animation: fe-arm-idle-l  3.4s ease-in-out infinite; }
.fetti[data-state='idle'] .fe-arm-r  { animation: fe-arm-idle-r  3.4s ease-in-out infinite; }

/* ---------- walk ---------------------------------------------------------- */
/* Legs alternate, arms counter-swing, the whole body bobs on the off-beat.
   Facing is a scaleX on the outer group so one cycle serves both directions. */

@keyframes fe-walk-leg-l {
  0%, 100% { transform: rotate(22deg);  }
  50%      { transform: rotate(-22deg); }
}
@keyframes fe-walk-leg-r {
  0%, 100% { transform: rotate(-22deg); }
  50%      { transform: rotate(22deg);  }
}
@keyframes fe-walk-arm-l {
  0%, 100% { transform: rotate(-26deg); }
  50%      { transform: rotate(26deg);  }
}
@keyframes fe-walk-arm-r {
  0%, 100% { transform: rotate(26deg);  }
  50%      { transform: rotate(-26deg); }
}
@keyframes fe-walk-bob {
  0%, 100% { transform: translateY(0);      }
  25%      { transform: translateY(-2.4px); }
  50%      { transform: translateY(0);      }
  75%      { transform: translateY(-2.4px); }
}
@keyframes fe-walk-head {
  0%, 100% { transform: rotate(-2deg); }
  50%      { transform: rotate(2deg);  }
}

.fetti[data-state='walk'] .fe-leg-l { animation: fe-walk-leg-l .62s ease-in-out infinite; }
.fetti[data-state='walk'] .fe-leg-r { animation: fe-walk-leg-r .62s ease-in-out infinite; }
.fetti[data-state='walk'] .fe-arm-l { animation: fe-walk-arm-l .62s ease-in-out infinite; }
.fetti[data-state='walk'] .fe-arm-r { animation: fe-walk-arm-r .62s ease-in-out infinite; }
.fetti[data-state='walk'] .fe-all   { animation: fe-walk-bob   .62s ease-in-out infinite; }
.fetti[data-state='walk'] .fe-head  { animation: fe-walk-head  .62s ease-in-out infinite; }

/* ---------- cheer --------------------------------------------------------- */
/* Both arms up, two hops, and the confetti fires. The name of the company is
   confetti, so this is the one animation that had to exist. */

@keyframes fe-cheer-arm-l {
  0%       { transform: rotate(0deg);    }
  22%      { transform: rotate(-142deg); }
  40%      { transform: rotate(-128deg); }
  60%      { transform: rotate(-146deg); }
  82%      { transform: rotate(-132deg); }
  100%     { transform: rotate(0deg);    }
}
@keyframes fe-cheer-arm-r {
  0%       { transform: rotate(0deg);   }
  22%      { transform: rotate(142deg); }
  40%      { transform: rotate(128deg); }
  60%      { transform: rotate(146deg); }
  82%      { transform: rotate(132deg); }
  100%     { transform: rotate(0deg);   }
}
@keyframes fe-cheer-hop {
  0%, 100%      { transform: translateY(0);    }
  18%, 58%      { transform: translateY(-11px); }
  36%, 76%      { transform: translateY(0);    }
}
@keyframes fe-cheer-head {
  0%, 100% { transform: rotate(0deg) translateY(0);      }
  30%      { transform: rotate(-4deg) translateY(-1.5px); }
  65%      { transform: rotate(4deg)  translateY(-1.5px); }
}

.fetti[data-state='cheer'] .fe-arm-l { animation: fe-cheer-arm-l 1.4s ease-in-out; }
.fetti[data-state='cheer'] .fe-arm-r { animation: fe-cheer-arm-r 1.4s ease-in-out; }
.fetti[data-state='cheer'] .fe-all   { animation: fe-cheer-hop   1.4s ease-in-out; }
.fetti[data-state='cheer'] .fe-head  { animation: fe-cheer-head  1.4s ease-in-out; }

/* the confetti burst, only while cheering */
@keyframes fe-confetti {
  0%   { opacity: 0; transform: translate(0, 0) rotate(0deg) scale(.4); }
  12%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--dr)) scale(1); }
}
.fetti .fe-confetti { opacity: 0; transform-box: fill-box; transform-origin: 50% 50%; }
.fetti[data-state='cheer'] .fe-confetti {
  animation: fe-confetti 1.25s cubic-bezier(.15, .7, .35, 1) forwards;
  animation-delay: var(--delay, 0s);
}

/* ---------- happy dance --------------------------------------------------- */
/* Hips lead, shoulders answer, head tilts against the beat. Loops until told
   to stop, because a dance that stops on its own is not a dance. */

@keyframes fe-dance-body {
  0%, 100% { transform: rotate(0deg)  translateX(0);    }
  25%      { transform: rotate(6deg)  translateX(3px);  }
  50%      { transform: rotate(0deg)  translateX(0);    }
  75%      { transform: rotate(-6deg) translateX(-3px); }
}
@keyframes fe-dance-head {
  0%, 100% { transform: rotate(0deg)  translateY(0);    }
  25%      { transform: rotate(-9deg) translateY(-2px); }
  75%      { transform: rotate(9deg)  translateY(-2px); }
}
@keyframes fe-dance-arm-l {
  0%, 100% { transform: rotate(-14deg);  }
  25%      { transform: rotate(-108deg); }
  50%      { transform: rotate(-14deg);  }
  75%      { transform: rotate(24deg);   }
}
@keyframes fe-dance-arm-r {
  0%, 100% { transform: rotate(14deg);  }
  25%      { transform: rotate(-24deg); }
  50%      { transform: rotate(14deg);  }
  75%      { transform: rotate(108deg); }
}
@keyframes fe-dance-leg-l {
  0%, 100% { transform: rotate(0deg);   }
  25%      { transform: rotate(-15deg); }
  75%      { transform: rotate(9deg);   }
}
@keyframes fe-dance-leg-r {
  0%, 100% { transform: rotate(0deg);  }
  25%      { transform: rotate(-9deg); }
  75%      { transform: rotate(15deg); }
}
@keyframes fe-dance-hop {
  0%, 100%   { transform: translateY(0);    }
  25%, 75%   { transform: translateY(-5px); }
  50%        { transform: translateY(0);    }
}

.fetti[data-state='dance'] .fe-body  { animation: fe-dance-body  1.0s ease-in-out infinite; }
.fetti[data-state='dance'] .fe-head  { animation: fe-dance-head  1.0s ease-in-out infinite; }
.fetti[data-state='dance'] .fe-arm-l { animation: fe-dance-arm-l 1.0s ease-in-out infinite; }
.fetti[data-state='dance'] .fe-arm-r { animation: fe-dance-arm-r 1.0s ease-in-out infinite; }
.fetti[data-state='dance'] .fe-leg-l { animation: fe-dance-leg-l 1.0s ease-in-out infinite; }
.fetti[data-state='dance'] .fe-leg-r { animation: fe-dance-leg-r 1.0s ease-in-out infinite; }
.fetti[data-state='dance'] .fe-all   { animation: fe-dance-hop   1.0s ease-in-out infinite; }

/* ---------- wink ---------------------------------------------------------- */
/* The smallest one, and the one that has to be exactly right: a wink that
   lingers is a leer. The lid is down for a quarter of a second. */

@keyframes fe-wink-lid {
  0%, 22%   { opacity: 0; }
  30%, 62%  { opacity: 1; }
  72%, 100% { opacity: 0; }
}
@keyframes fe-wink-eye {
  0%, 22%   { transform: scaleY(1);  }
  30%, 62%  { transform: scaleY(.06); }
  72%, 100% { transform: scaleY(1);  }
}
@keyframes fe-wink-head {
  0%, 100% { transform: rotate(0deg);   }
  45%      { transform: rotate(-6deg);  }
}
@keyframes fe-wink-arm {
  0%, 100% { transform: rotate(0deg);   }
  45%      { transform: rotate(-34deg); }
}

.fetti[data-state='wink'] .fe-eye-r { animation: fe-wink-eye  .78s ease-in-out; }
.fetti[data-state='wink'] .fe-lid-r { animation: fe-wink-lid  .78s ease-in-out; }
.fetti[data-state='wink'] .fe-head  { animation: fe-wink-head .78s ease-in-out; }
.fetti[data-state='wink'] .fe-arm-r { animation: fe-wink-arm  .78s ease-in-out; }

/* ---------- swirl --------------------------------------------------------- */
/* A full turn with the skirt flaring out behind it. The skirt scales wider at
   the halfway point, which is what sells the spin — without it the character
   just rotates like a sticker. */

@keyframes fe-swirl-spin {
  0%   { transform: rotate(0deg)   scale(1);    }
  35%  { transform: rotate(180deg) scale(1.04); }
  100% { transform: rotate(360deg) scale(1);    }
}
@keyframes fe-swirl-skirt {
  0%, 100% { transform: scaleX(1) skewX(0deg);    }
  30%      { transform: scaleX(1.16) skewX(-7deg); }
  65%      { transform: scaleX(1.16) skewX(7deg);  }
}
@keyframes fe-swirl-arm-l { 0%,100% { transform: rotate(-8deg); } 50% { transform: rotate(-64deg); } }
@keyframes fe-swirl-arm-r { 0%,100% { transform: rotate(8deg);  } 50% { transform: rotate(64deg);  } }
@keyframes fe-swirl-hair  { 0%,100% { transform: rotate(0deg);  } 50% { transform: rotate(-22deg); } }

.fetti[data-state='swirl'] .fe-all    { animation: fe-swirl-spin  .95s cubic-bezier(.42, 0, .3, 1); }
.fetti[data-state='swirl'] .fe-skirt  { animation: fe-swirl-skirt .95s ease-in-out; }
.fetti[data-state='swirl'] .fe-arm-l  { animation: fe-swirl-arm-l .95s ease-in-out; }
.fetti[data-state='swirl'] .fe-arm-r  { animation: fe-swirl-arm-r .95s ease-in-out; }
.fetti[data-state='swirl'] .fe-ribbon { animation: fe-swirl-hair  .95s ease-in-out; }

/* ---------- jump ---------------------------------------------------------- */
/* Crouch, launch, hang, land, settle. The squash on the landing is what stops
   it reading as a floating cut-out. */

@keyframes fe-jump-all {
  0%   { transform: translateY(0)     scaleY(1);    }
  16%  { transform: translateY(3px)   scaleY(.9);   }  /* crouch  */
  34%  { transform: translateY(-26px) scaleY(1.08); }  /* launch  */
  52%  { transform: translateY(-30px) scaleY(1);    }  /* hang    */
  76%  { transform: translateY(0)     scaleY(.88);  }  /* land    */
  88%  { transform: translateY(-3px)  scaleY(1.03); }
  100% { transform: translateY(0)     scaleY(1);    }
}
@keyframes fe-jump-arm-l {
  0%, 100% { transform: rotate(0deg);    }
  16%      { transform: rotate(22deg);   }
  40%      { transform: rotate(-124deg); }
  70%      { transform: rotate(-96deg);  }
}
@keyframes fe-jump-arm-r {
  0%, 100% { transform: rotate(0deg);   }
  16%      { transform: rotate(-22deg); }
  40%      { transform: rotate(124deg); }
  70%      { transform: rotate(96deg);  }
}
@keyframes fe-jump-leg-l {
  0%, 100% { transform: rotate(0deg);   }
  16%      { transform: rotate(12deg);  }
  45%      { transform: rotate(-24deg); }
}
@keyframes fe-jump-leg-r {
  0%, 100% { transform: rotate(0deg);   }
  16%      { transform: rotate(-12deg); }
  45%      { transform: rotate(24deg);  }
}
@keyframes fe-jump-ribbon {
  0%, 100% { transform: rotate(0deg);   }
  40%      { transform: rotate(26deg);  }
  70%      { transform: rotate(-12deg); }
}

.fetti[data-state='jump'] .fe-all    { animation: fe-jump-all    .86s cubic-bezier(.3, .1, .3, 1); }
.fetti[data-state='jump'] .fe-arm-l  { animation: fe-jump-arm-l  .86s ease-in-out; }
.fetti[data-state='jump'] .fe-arm-r  { animation: fe-jump-arm-r  .86s ease-in-out; }
.fetti[data-state='jump'] .fe-leg-l  { animation: fe-jump-leg-l  .86s ease-in-out; }
.fetti[data-state='jump'] .fe-leg-r  { animation: fe-jump-leg-r  .86s ease-in-out; }
.fetti[data-state='jump'] .fe-ribbon { animation: fe-jump-ribbon .86s ease-in-out; }

/* the shadow tells you it left the ground */
.fetti .fe-shadow { transform-box: fill-box; transform-origin: 50% 50%; opacity: .2; }
@keyframes fe-shadow-jump {
  0%, 100% { transform: scale(1);   opacity: .2;  }
  45%      { transform: scale(.55); opacity: .07; }
}
.fetti[data-state='jump'] .fe-shadow { animation: fe-shadow-jump .86s cubic-bezier(.3, .1, .3, 1); }
@keyframes fe-shadow-walk {
  0%, 100% { transform: scale(1);   }
  25%, 75% { transform: scale(.88); }
}
.fetti[data-state='walk'] .fe-shadow { animation: fe-shadow-walk .62s ease-in-out infinite; }

/* ---------- reduced motion ------------------------------------------------ */
/*
   Not a token gesture. A character with six looping animations is exactly the
   kind of thing that makes a page unusable for someone with a vestibular
   disorder, and this site's whole audience is people already stressed about
   paperwork.
   Everything stops. Fetti stays drawn, stays cheerful, and stays put — the
   states still change, so any code keyed on `data-state` still works, it just
   does not move. Travel across the page collapses to an instant jump rather
   than a slide.
*/
@media (prefers-reduced-motion: reduce) {
  .fetti, .fetti * {
    animation: none !important;
    transition: none !important;
  }
}

/* ============================================================================
   The stage, the bubble and the announcement.

   Three separate things on purpose:

     .fetti-stage   where the character stands. Fixed to the corner on a wide
                    screen, and folded back into the flow on a narrow one — a
                    mascot pinned over a form on a phone is a mascot covering
                    the button you are trying to press.
     .fetti-bubble  what Fetti appears to say. aria-hidden, because it is
                    decoration sitting next to a decorative character.
     .fetti-live    the same words, for a screen reader, exactly once.
   ========================================================================= */

.fetti-stage {
  position: fixed;
  right: clamp(.6rem, 2vw, 1.6rem);
  bottom: clamp(.6rem, 2vw, 1.4rem);
  z-index: 20;
  display: flex;
  align-items: flex-end;
  gap: .5rem;
  pointer-events: none;   /* never in the way of the form behind it */
}

.fetti-stage .fetti { pointer-events: auto; }

.fetti-bubble {
  pointer-events: none;
  order: -1;              /* to the left of the character */
  max-width: min(17rem, 52vw);
  margin-bottom: 1.6rem;
  padding: .6rem .85rem;
  border-radius: var(--r-md, 14px);
  border: 1px solid var(--rule-strong);
  background: var(--paper-raised);
  color: var(--ink);
  font-size: .86rem;
  line-height: 1.5;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .1);
  animation: fetti-bubble-in .28s cubic-bezier(.2, .9, .3, 1.2);
  /* The tail below is absolutely positioned, so the bubble has to be the thing
     it positions against - without this it anchors to the page instead. */
  position: relative;
}

@keyframes fetti-bubble-in {
  from { opacity: 0; transform: translateY(6px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

/* the little tail, pointing at Fetti */
.fetti-bubble::after {
  content: '';
  position: absolute;
  right: -13px;
  bottom: 12px;
  border: 7px solid transparent;
  border-left-color: var(--paper-raised);
  filter: drop-shadow(1px 0 0 var(--rule-strong));
}
@media (max-width: 620px) {
  /* Stacked layout: the tail points down at Fetti rather than sideways. */
  .fetti-bubble::after {
    right: auto;
    left: 50%;
    bottom: -13px;
    margin-left: -7px;
    border-left-color: transparent;
    border-top-color: var(--paper-raised);
    filter: drop-shadow(0 1px 0 var(--rule-strong));
  }
}

/* The announcement is for screen readers only. Not display:none - that would
   remove it from the accessibility tree and it would never be read at all. */
.fetti-live {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 620px) {
  .fetti-stage {
    position: static;
    justify-content: center;
    margin: 1.4rem auto 0;
  }
  .fetti-bubble { margin-bottom: 2.2rem; }
}

@media (prefers-reduced-motion: reduce) {
  .fetti-bubble { animation: none; }
}
