/* Orble's buddy: a gumdrop of glass, shiny eyes, two little oval hands (two
   plump ovals of arms when he holds an edge). How he
   looks and every pose he takes; when he takes it is ui/buddy_model.js, and
   the pages that drive him set the classes below on his root (.buddy).

   The look is the approved mock-up (testkit/buddy_mockup.html). Colours come
   from the --buddy-* tokens (tokens.css), so the day picker can recolour him.

   Where he is. A host puts his root so its top-left corner is at the island's
   black shape bottom edge (peek, listen, still, swing, talk, hello) or at the
   spot he sits (board) or hops out from (rummage); a pose moves him from
   there. The island's window bleeds 10 px below its black shape, and each pose
   fits STAGE_H[pose] + that bleed (buddy_model.js). The host also decides who
   is on top: hanging (buddy--hang) he holds the edge from the front, behind it
   (buddy--behind) he is behind it.

   Which way up he is follows where he is (buddy_model.js hangs, behind):
   hanging from the edge he is right side up, his two oval arms raised to it;
   behind it he leans down over it head first, upside down, his hands on it.
   Between the
   two he turns over once (buddy--drop, buddy--climb on the root, OrbleBuddy.turnFor).

   Host knobs, all optional (custom properties on the root or above it):
     --swing-span  how far the swing travels, px (60 px if unset)
     --turn-p      how much of him was behind the edge where a drop starts, px (9 px if unset)
     --hop         how far the rummage hops out, px (74 px if unset)
     --gx, --gy    where his pupils sit, px (OrbleBuddy.eyes)
   Nothing here turns animation off: tokens.css runs every animation to its end
   at once under Reduce Motion, so a one-shot ends in its final pose (forwards)
   and a loop ends where it began. */

/* ------------------------------------------------------------- the sprite */

.buddy-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.buddy-stop-hi { stop-color: var(--buddy-hi); }
.buddy-stop-light { stop-color: var(--buddy-light); }
.buddy-stop-mid { stop-color: var(--buddy); }
.buddy-stop-deep { stop-color: var(--buddy-deep); }
.buddy-line { stroke: var(--buddy-ink); stroke-opacity: .75; }
.buddy-mark { fill: none; stroke: var(--buddy-ink); stroke-opacity: .55; stroke-width: .9; stroke-linecap: round; stroke-linejoin: round; }

/* ---------------------------------------------------------------- his body */

.buddy {
  position: absolute; left: 0; top: 0; width: 30px; height: 28px;
  pointer-events: none; overflow: visible;
  --lift: 0px; /* the eyes' lift: up while he thinks */
  --face-y: 0px; /* where his eyes are on the body drawn the other way up (buddy--behind) */
  --eye: translate(var(--gx, 0px), calc(var(--gy, 0px) + var(--lift) + var(--face-y)));
  transition: opacity .18s ease, visibility 0s linear, transform .35s cubic-bezier(.34, 1.3, .4, 1), clip-path .35s ease;
}
.buddy .turn { position: absolute; inset: 0; transform-origin: 15px 14px; }
.buddy .wob { position: absolute; inset: 0; transform-origin: 50% 100%; }
.buddy .face, .buddy .rear, .buddy .side { position: absolute; left: 0; top: 0; width: 30px; height: 28px; overflow: visible; }
.buddy .face { transition: transform .12s ease-in; }
.buddy .rear { transform: scaleX(0); transition: transform .12s ease-out; }
.buddy .rear use { transform-box: fill-box; transform-origin: 50% 100%; }
.buddy .side { display: none; }
.buddy .joy { opacity: 0; }
.buddy .eyes { transform: var(--eye); transform-box: fill-box; transform-origin: 50% 50%; transition: transform .25s ease-out; }

.buddy .hand {
  position: absolute; width: 7.5px; height: 6px; border-radius: 50%;
  background: radial-gradient(circle at 40% 35%, var(--buddy-light), var(--buddy) 75%);
  box-shadow: 0 0 0 .9px rgba(14, 18, 32, .8);
}
.buddy .hand { top: 19.5px; }
.buddy .hand.l { left: -1.5px; }
.buddy .hand.r { left: 24px; }

/* His arms, shown only while he holds an edge (hanging, riding a line): each one oval, as plump and
   round-ended as a hand, from its foot on his side to its tip where it holds (buddy_model.js HANG,
   ARM_W, armTo), turned about its foot. Drawn over his body as his hands are, so the whole oval
   shows; while an arm shows, its hand's own oval does not (the turns fade one into the other). */
.buddy .arm {
  display: none; position: absolute; width: 9.36px; height: 18.22px; border-radius: 50%;
  background: radial-gradient(ellipse at 40% 30%, var(--buddy-light), var(--buddy) 75%);
  box-shadow: 0 0 0 .9px rgba(14, 18, 32, .8); transform-origin: 50% 100%;
}

/* The tool he draws with (data-tool on the root): a pen by default. */
.buddy .tool {
  position: absolute; left: 26px; top: 7px; width: 3.5px; height: 17px; border-radius: 2px;
  opacity: 0; transform: rotate(-22deg) scale(0); box-shadow: 0 0 0 .6px rgba(0, 0, 0, .5);
  background: linear-gradient(#2f6fd6 0 68%, #e6e6ea 68% 84%, #0b0f19 84%);
}
.buddy[data-tool="ruler"] .tool {
  height: 19px; top: 5px;
  background: repeating-linear-gradient(#e6e6ea 0 1.5px, #8b909a 1.5px 2.3px);
}
.buddy[data-tool="pencil"] .tool {
  background: linear-gradient(#f19191 0 12%, #c9ccd3 12% 20%, #efc690 20% 78%, #e8d2b0 78% 90%, #2d3340 90%);
}

/* ------------------------------------------------------------------ moods */

.buddy--joy .eyes { opacity: 0; }
.buddy--joy .joy { opacity: 1; }
.buddy--up, .buddy--swing, .buddy--still { --lift: -2.2px; }
.buddy--over { opacity: .15; } /* the pointer is on him: out of its way */

/* ------------------------------------------------- where he is, pose by pose */

/* Gone, not display:none: a display switch never runs a transition, and under
   Reduce Motion he must fade in and out. Visibility flips after the fade out
   (the delay) and at once when he comes back (the base rule's 0 s). */
.buddy--off, .buddy--away { opacity: 0; visibility: hidden; transition: opacity .18s ease, visibility 0s linear .18s; }

/* Behind the island's edge (the island adds buddy--behind for the poses OrbleBuddy.behind names): he
   leans down over it from behind, head first, upside down. The body is drawn the other way up, still
   lit from above (buddy-bU), his eyes where a face the other way up has them (--face-y), his hands on
   the edge either side of his head. --p is how much of him is still behind the edge (OrbleBuddy.BEHIND),
   and the clip hides it; the host puts him behind the island. */
.buddy .body-up { display: none; }
.buddy--behind { --face-y: -6px; }
.buddy--behind .body { display: none; }
.buddy--behind .body-up { display: inline; }
.buddy--behind .hand { top: calc(var(--p, 9px) - 2.5px); }

/* In the notch, as in Orble's mark: only the top of his head under its edge, his eyes hanging from
   it (the edge cuts their tops: --face-y puts them there) and his hands on it either side. */
.buddy--peek, .buddy--doze { --p: 16px; --face-y: 1px; transform: translateY(-16px); clip-path: inset(16px -8px -8px -8px); }
.buddy--doze .eyes { transform: var(--eye) scaleY(.4); transform-origin: 50% 0; }

/* Leaning out further, eyes wide and on you; the still thinking face is the same, eyes up. */
.buddy--listen, .buddy--still { --p: 6px; transform: translateY(-6px); clip-path: inset(6px -8px -8px -8px); }
.buddy--listen .eyes { transform: var(--eye) scale(1.12); }

/* Answering: a gentle bounce with his voice. */
.buddy--talk { transform: translateY(4px); }
.buddy--talk .wob { animation: buddy-breathe 1.1s ease-in-out infinite; }
/* Sitting on the board's edge while it is spoken. */
.buddy--board .wob { animation: buddy-breathe 2.4s ease-in-out infinite; }
.buddy--board .hand { top: 21px; }

/* Hanging from the island's bottom edge (the island adds buddy--hang for the poses OrbleBuddy.hangs
   names: the swing, talking with no mark to go to, the hello), right side up: his body HANG.y below
   the edge, just clear of it, both oval arms raised from his sides, their tips over the edge in front
   of it (the host puts him in front). The hands are where the arms' tips are, not drawn. The arm and
   hand classed `l` are on his right while he hangs, so turning over keeps each on its side. */
.buddy--hang .hand { top: -5.5px; opacity: 0; }
.buddy--hang .hand.l { left: 32.6px; }
.buddy--hang .hand.r { left: -10.1px; }
.buddy--hang .arm { display: block; }
.buddy--hang .arm.l { left: 24.17px; top: -4.12px; transform: rotate(24.31deg); }
.buddy--hang .arm.r { left: -3.53px; top: -4.12px; transform: rotate(-24.31deg); }
.buddy--hang .wob { transform-origin: 50% 0; }

/* Thinking, the swing: hand over hand along the edge, 60 pt (--swing-span) centred where he
   dropped, there and back. Each hand holds still against the edge while the body passes under it,
   then reaches ahead, dipping off the edge (swingL, swingR: linear like the body, so a holding hand
   stays put), and its arm follows it (swingArmL, swingArmR); going right the right hand leads. The
   body trails a little between catches and squishes at each, turning about the edge above him. */
.buddy--swing { transform-origin: 15px -0.5px; transform: translateY(0.5px); animation: buddy-swingBody 4.5s linear infinite; }
.buddy--swing .hand.l { --x0: -7.5px; animation: buddy-swingR 4.5s linear infinite; }
.buddy--swing .hand.r { --x0: 7.5px; animation: buddy-swingL 4.5s linear infinite; }
.buddy--swing .arm.l { animation: buddy-swingArmR 4.5s linear infinite; }
.buddy--swing .arm.r { animation: buddy-swingArmL 4.5s linear infinite; }

/* Talking with nothing to point at, held at the edge: a gentle stretch down from his hands with his voice. */
.buddy--talk.buddy--hang { transform: translateY(0.5px); }
.buddy--talk.buddy--hang .wob { animation: buddy-dangle 1.1s ease-in-out infinite; }

/* Turning over, once, between behind the edge and hanging from it (OrbleBuddy.turnFor; the island
   sets the class for the change and takes it off when it ends). The drop: he lets go behind the
   edge, falls out head first, flips over and catches the edge with both hands, squishing. The
   climb: he pulls himself up to the edge, flips back over it, and leans down from behind it again.
   Each starts as the pose before it left him (upside down where he was behind, --turn-p or --p
   of him behind the edge; right side up where he hung). The hands let go and tuck in against his
   sides while he turns (`translate`, which adds to the swing's own hand frames); as they take the
   edge they become his oval arms (the drop) and the arms become hands again as he pulls himself up
   (the climb), one fading out as the other fades in; his eyes are shut through the flip. */
.buddy--drop .turn { animation: buddy-drop .42s both; }
.buddy--climb .turn { animation: buddy-climb .42s both; }
.buddy--drop .hand.l { animation: buddy-dropHandL .42s both; }
.buddy--drop .hand.r { animation: buddy-dropHandR .42s both; }
.buddy--swing.buddy--drop .hand.l { animation: buddy-swingR 4.5s linear infinite, buddy-dropHandL .42s both; }
.buddy--swing.buddy--drop .hand.r { animation: buddy-swingL 4.5s linear infinite, buddy-dropHandR .42s both; }
.buddy--drop .arm { animation: buddy-dropArm .42s both; }
.buddy--swing.buddy--drop .arm.l { animation: buddy-swingArmR 4.5s linear infinite, buddy-dropArm .42s both; }
.buddy--swing.buddy--drop .arm.r { animation: buddy-swingArmL 4.5s linear infinite, buddy-dropArm .42s both; }
.buddy--climb .hand.l { animation: buddy-climbHandL .42s both; }
.buddy--climb .hand.r { animation: buddy-climbHandR .42s both; }
/* Climbing, he starts where he hung, below what the behind pose's clip keeps: his box is --p above
   the edge, so from a peek (16) its bottom must give 17 for his body hung 0.5 under the edge, outline
   and all (28.6 below it), and its sides 12 for his arms. */
.buddy--climb { clip-path: inset(var(--p, 9px) -12px -17px -12px); }
.buddy--climb .arm { display: block; top: 13.9px; transform-origin: 50% 0; }
.buddy--climb .arm.l { left: -3.53px; animation: buddy-climbArmL .42s both; }
.buddy--climb .arm.r { left: 24.17px; animation: buddy-climbArmR .42s both; }
.buddy--drop .eyes, .buddy--climb .eyes { animation: buddy-turnBlink .42s both; }

/* Thinking, the rummage: seen from the side, inside the widened notch. */
.buddy--rummage { animation: buddy-s3Hop .9s ease-in-out forwards; }
.buddy--rummage .face { display: none; }
.buddy--rummage .side { display: block; }
.buddy--rummage .hand.r { display: none; }
.buddy--rummage .hand.l { z-index: 2; animation: buddy-s3Reach 2.24s ease-in-out infinite, buddy-s3Digging 14s infinite; }
/* Found it: the digging hand goes and a second one holds the tool up (the mock-up's 9px, -5px). */
.buddy--rummage .wob::after {
  content: ""; position: absolute; left: 9px; top: -5px; width: 7.5px; height: 6px; border-radius: 50%; z-index: 2; opacity: 0;
  background: radial-gradient(circle at 40% 35%, var(--buddy-light), var(--buddy) 75%);
  box-shadow: 0 0 0 .9px rgba(14, 18, 32, .8); animation: buddy-s3Held 14s infinite;
}
.buddy--rummage .wob { animation: buddy-s3Rock 1.12s ease-in-out infinite; }

/* Hello: he drops out from behind the notch, flips over and catches its edge (the drop), lets go
   with the arm on his right and waves it beside his head, then holds on again; the island then has
   him climb back behind it. He hangs (buddy--hang) the whole time. */
.buddy--hello { transform: translateY(0.5px); }
.buddy--hello .turn { animation: buddy-drop .42s both; }
.buddy--hello .hand.l { animation: buddy-dropHandL .42s both; }
.buddy--hello .hand.r { animation: buddy-dropHandR .42s both; }
.buddy--hello .arm { animation: buddy-dropArm .42s both; }
.buddy--hello .arm.l { animation: buddy-dropArm .42s both, buddy-helloArm 3.6s ease-in-out forwards; }
.buddy--hello .eyes { animation: buddy-helloEye 3.6s forwards; }
.buddy--hello .joy { animation: buddy-helloJoy 3.6s forwards; }

/* Delivering: he flips through the air to the mark and lands squished,
   turns his back to draw it (jiggling), and turns round to say it. */
.buddy--fly .wob { transform-origin: 50% 50%; animation: buddy-flip .52s ease-in-out forwards; }
.buddy--rear .face { transform: scaleX(0); }
.buddy--rear .rear { transform: none; transition-delay: .1s; }
.buddy--rear .rear use { animation: buddy-jiggle 1.1s ease-in-out infinite; }
.buddy--rear .tool { opacity: 1; transform: rotate(-22deg); animation: buddy-toolPop .22s ease-out forwards; }

/* Riding a line the overlay draws (buddy_model.js ridePlan): his back to you, both oval arms up and
   into the screen, their tips above his head (RIDE_POSE, from their HANG feet, armReach). The arm
   classed `l`, on your right from behind, holds the pen with its nib on the line's tip (PEN); the
   other is braced on the screen beside it. The hands are where the tips are, not drawn. */
.buddy--ride .arm { display: block; }
.buddy--ride .arm.l { left: 24.17px; top: -4.12px; transform: rotate(-17.03deg) scaleY(1.097); }
.buddy--ride .arm.r { left: -3.53px; top: -4.12px; transform: rotate(16.76deg) scaleY(.923); }
.buddy--ride .hand { opacity: 0; }
.buddy--ride .hand.l { left: 19.25px; top: -8px; }
.buddy--ride .hand.r { left: 2.25px; top: -5px; }
.buddy.buddy--ride .tool { left: 21.25px; top: -15px; height: 10px; transform-origin: 50% 100%; }
.buddy--rear.buddy--ride .tool { transform: rotate(28deg); animation: buddy-ridePen .16s ease-out forwards; }
/* Riding, he turns his back at once: the line does not wait for him to turn. */
.buddy--ride .face { transition-duration: .06s; }
.buddy--ride .rear { transition-duration: .06s; transition-delay: .03s; }

/* ------------------------------------------------------ idle gestures */
/* Each ends by itself; the page takes the class off on animationend. */

.buddy--g-blink .eyes { animation: buddy-blink .2s ease-in-out forwards; }
.buddy--g-blink2 .eyes { animation: buddy-blink2 .6s ease-in-out forwards; }
.buddy--g-glance .eyes { animation: buddy-glance 1s ease-in-out forwards; }
.buddy--g-tapL .hand.l { animation: buddy-tap .7s ease-in-out forwards; }
.buddy--g-tapR .hand.r { animation: buddy-tap .7s ease-in-out forwards; }

/* Under Reduce Motion he arrives and leaves with a fade and nothing else of
   him moves (tokens.css lands every animation at its end). */
[data-motion="reduce"] .buddy { transition: opacity 200ms ease, visibility 200ms linear; }

/* ------------------------------------------------------------ the hello's bubble */
/* The one thing he says in writing. The host puts it beside him and sets its text
   when the hello starts; it shows while he waves and is joyful, as in the mock-up. */
.buddy-bubble {
  position: absolute; z-index: 1; background: var(--ink); color: var(--text); font-size: 15px; font-weight: 700;
  padding: 5px 13px; border-radius: 12px; white-space: nowrap; opacity: 0;
  animation: buddy-helloHi 3.6s forwards;
}

/* --------------------------------------------- the rummage's room (the host) */
/* What the widened notch holds: a little door, what he throws behind him, and
   the tool he finds. The host (buddy_view.js `makeBand`) builds .buddy-band and
   puts it over the widened part of the panel, its left edge the notch's right
   edge and its top the lip's; everything in it is placed from that corner, and
   it clips what it holds, so nothing thrown ever leaves the band. The widening
   itself is the island's own panel. One 14 s loop: the door opens once, the
   things are thrown from 0.84 s on, as the door finishes opening, and stay until they fade, one
   by one, to be thrown again, and the tool is found and held up at 44 % of every loop.
   Each .buddy-item and .buddy-puff sets its own --d (delay, seconds), and each
   item --px, --ex (how far across its arc's apex is, how far it lands) and --r1, --r2 (turns).
   Shapes are boxes, borders, gradients and clip-paths: no image, no character. */

/* Its bottom right follows the wide panel's corner (styles.css --r-panel), 2 px up, so a toss never shows outside the black. */
.buddy-band { position: absolute; overflow: hidden; pointer-events: none; border-bottom-right-radius: calc(var(--r-panel, 18px) - 2px); }
.buddy-doorway {
  position: absolute; left: 12px; top: 8px; width: 16px; height: 24px; border-radius: 2px; opacity: 0;
  background: radial-gradient(circle at 40% 60%, #6b5436, #2a2118); animation: buddy-show .25s forwards;
}
.buddy-door {
  position: absolute; left: 12px; top: 8px; width: 16px; height: 24px; border-radius: 2px; opacity: 0; background: #1d2028;
  box-shadow: inset 0 0 0 1px #444a57; transform-origin: 0 50%;
  animation: buddy-show .25s forwards, buddy-s3Door .4s .5s ease-in-out forwards;
}
.buddy-door::after { content: ""; position: absolute; right: 3px; top: 11px; width: 2px; height: 2px; border-radius: 50%; background: #8b909a; }
.buddy-pile { position: absolute; left: 16px; top: 12px; width: 0; height: 0; opacity: 0; animation: buddy-s3Pile 14s forwards; }
.buddy-pile > * { position: absolute; left: 0; top: 0; }
.buddy-item { opacity: 0; animation: buddy-toss 14s ease-out infinite both; animation-delay: var(--d, 0s); }
.buddy-puff { width: 7px; height: 7px; border-radius: 50%; background: rgba(205, 210, 220, .7); opacity: 0; animation: buddy-puff 14s ease-out infinite both; animation-delay: var(--d, 0s); }

/* The six things, from the mock-up's drawings, each a little box of its own. */
.buddy-item--book { width: 11px; height: 8px; box-sizing: border-box; background: #b9adf3; border: 1px solid #0b0f19; border-radius: 1.5px; }
.buddy-item--book::before { content: ""; position: absolute; left: 2.5px; top: 0; width: 1px; height: 100%; background: #0b0f19; }
.buddy-item--sock { width: 5px; height: 10px; box-sizing: border-box; background: #f19191; border: 1px solid #0b0f19; border-bottom: 0; border-radius: 1px 1px 0 0; }
.buddy-item--sock::after {
  content: ""; position: absolute; left: -1px; top: 7px; width: 8px; height: 4px; box-sizing: border-box;
  background: #f19191; border: 1px solid #0b0f19; border-radius: 1px 3px 3px 3px;
}
.buddy-item--duck { width: 10px; height: 8px; background: #efc690; border: 1px solid #0b0f19; box-sizing: border-box; border-radius: 50%; margin-top: 3px; }
.buddy-item--duck::before {
  content: ""; position: absolute; left: 4px; top: -5px; width: 6px; height: 6px; box-sizing: border-box;
  background: #efc690; border: 1px solid #0b0f19; border-radius: 50%;
}
.buddy-item--duck::after { content: ""; position: absolute; left: 8.5px; top: -3px; width: 0; height: 0; border-left: 3px solid #f19191; border-top: 1.5px solid transparent; border-bottom: 1.5px solid transparent; }
.buddy-item--gear { width: 6px; height: 6px; margin: 2px; border-radius: 50%; background: #2c3446; box-shadow: 0 0 0 1.5px #8b909a, 0 0 0 2.5px #0b0f19; }
.buddy-item--gear::before { content: ""; position: absolute; inset: -4px; border: 2px dashed #8b909a; border-radius: 50%; }
.buddy-item--spring { width: 11px; height: 7px; background: #9fd0a8; clip-path: polygon(0 50%, 22% 4%, 40% 80%, 52% 4%, 70% 80%, 86% 4%, 100% 50%, 100% 74%, 86% 36%, 70% 100%, 52% 36%, 40% 100%, 22% 36%, 0 74%); }
.buddy-item--cup { width: 9px; height: 9px; box-sizing: border-box; background: #ebecef; border: 1px solid #0b0f19; border-radius: 0 0 4px 4px; }
.buddy-item--cup::after { content: ""; position: absolute; left: 7.5px; top: 1.5px; width: 3px; height: 4px; box-sizing: border-box; border: 1px solid #0b0f19; border-left: 0; border-radius: 0 3px 3px 0; }

/* The tool he finds: lifted out of the pile and held up, with a spark, at 44 % of the loop. */
.buddy-pencil {
  position: absolute; left: 16px; top: 17px; width: 18px; height: 3.5px; border-radius: 2px; opacity: 0;
  box-shadow: 0 0 0 .7px rgba(0, 0, 0, .5);
  background: linear-gradient(90deg, #2d3340 0 10%, #e8d2b0 10% 22%, #efc690 22% 80%, #c9ccd3 80% 88%, #f19191 88%);
  animation: buddy-s3Pc 14s ease-in-out infinite;
}
.buddy-spark {
  position: absolute; left: 62px; top: 1px; width: 11px; height: 11px; background: #efc690; opacity: 0;
  clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
  animation: buddy-s3Spark 14s infinite;
}

/* ---------------------------------------------------------------- keyframes */

@keyframes buddy-breathe { 0%, 100% { transform: none; } 50% { transform: scale(1.04, .96); } }
@keyframes buddy-jiggle { 0%, 40%, 100% { transform: none; } 45% { transform: scale(1.05, .95); } 50% { transform: scale(.96, 1.04); } 55% { transform: scale(1.04, .96); } 60% { transform: scale(.98, 1.02); } 65% { transform: none; } }
@keyframes buddy-flip {
  0% { transform: rotate(0deg); } 85% { transform: rotate(360deg); }
  89% { transform: rotate(360deg) scale(1.18, .8); } 95% { transform: rotate(360deg) scale(.94, 1.07); } 100% { transform: rotate(360deg); }
}
@keyframes buddy-ridePen { 0% { opacity: 0; transform: rotate(28deg) scale(0); } 100% { opacity: 1; transform: rotate(28deg); } }
@keyframes buddy-toolPop { 0% { opacity: 0; transform: rotate(-22deg) scale(0); } 55% { opacity: 1; transform: rotate(-22deg) scale(1.4); } 100% { opacity: 1; transform: rotate(-22deg); } }
@keyframes buddy-show { from { opacity: 0; } to { opacity: 1; } }

@keyframes buddy-blink { 0%, 100% { transform: var(--eye); } 45%, 55% { transform: var(--eye) scaleY(.08); } }
@keyframes buddy-blink2 {
  0%, 40%, 100% { transform: var(--eye); }
  10%, 20% { transform: var(--eye) scaleY(.08); } 15% { transform: var(--eye); }
  60%, 70% { transform: var(--eye) scaleY(.08); } 65% { transform: var(--eye); }
}
@keyframes buddy-glance {
  0%, 100% { transform: var(--eye); }
  15%, 40% { transform: translate(calc(var(--gx, 0px) + 1.6px), calc(var(--gy, 0px) + var(--lift))); }
  55%, 80% { transform: translate(calc(var(--gx, 0px) - 1.6px), calc(var(--gy, 0px) + var(--lift))); }
}
@keyframes buddy-tap { 0%, 100% { transform: none; } 15%, 45%, 75% { transform: translateY(2px); } 30%, 60%, 90% { transform: translateY(0); } }

@keyframes buddy-s3Hop {
  0% { opacity: 0; transform: scale(0); }
  22% { opacity: 1; transform: none; }
  33% { opacity: 1; transform: translate(calc(var(--hop, 74px) * .25), -6px); }
  44% { transform: translate(calc(var(--hop, 74px) * .5), 0) scale(1.1, .88); }
  55% { transform: translate(calc(var(--hop, 74px) * .75), -5px); }
  66%, 100% { opacity: 1; transform: translate(var(--hop, 74px), 0); }
}
@keyframes buddy-s3Rock {
  0%, 100% { transform: rotate(-5deg) scale(1.04, .96); }
  50% { transform: rotate(5deg) scale(.97, 1.03); }
}
@keyframes buddy-s3Reach {
  0%, 25%, 50%, 75% { transform: translate(-19.5px, -13.5px); }
  12.5%, 37.5%, 87.5% { transform: translate(23.5px, -22.5px); }
  62.5% { transform: translate(19.5px, -20.5px); }
  100% { transform: translate(-19.5px, -13.5px); }
}
@keyframes buddy-s3Door { from { transform: none; } to { transform: scaleX(.12); } }
@keyframes buddy-s3Pile { 0%, 5% { opacity: 0; } 6%, 100% { opacity: 1; } }
@keyframes buddy-toss {
  0% { opacity: 0; transform: translate(0, 0) rotate(0deg); }
  .3% { opacity: 1; }
  2% { transform: translate(var(--px), -12px) rotate(var(--r1)); }
  4% { transform: translate(var(--ex), 14px) rotate(var(--r2)); }
  4.8% { transform: translate(calc(var(--ex) + 3px), 9px) rotate(var(--r2)); }
  5.6%, 96% { opacity: 1; transform: translate(calc(var(--ex) + 5px), 14px) rotate(var(--r2)); }
  100% { opacity: 0; transform: translate(calc(var(--ex) + 5px), 14px) rotate(var(--r2)); }
}
@keyframes buddy-s3Pc {
  0%, 42.9% { opacity: 0; transform: scale(.5); }
  43% { opacity: 1; transform: scale(.5); }
  46.5%, 57.5% { opacity: 1; transform: translate(25px, -14px) rotate(-8deg); }
  58%, 100% { opacity: 0; transform: translate(25px, -14px) rotate(-8deg); }
}
@keyframes buddy-s3Spark {
  0%, 46.4% { opacity: 0; transform: scale(.4); }
  47.2% { opacity: 1; transform: scale(1.25) rotate(20deg); }
  49.9%, 57.5% { opacity: 1; transform: none; }
  58%, 100% { opacity: 0; transform: none; }
}
@keyframes buddy-s3Held { 0%, 44% { opacity: 0; } 46.5%, 57.5% { opacity: 1; } 58%, 100% { opacity: 0; } }
@keyframes buddy-s3Digging { 0%, 44% { opacity: 1; } 46.5%, 57.5% { opacity: 0; } 58%, 100% { opacity: 1; } }
@keyframes buddy-puff { 0% { opacity: 0; transform: scale(.4); } .4% { opacity: .75; } 5% { opacity: 0; transform: translate(10px, -3px) scale(2.2); } 100% { opacity: 0; transform: translate(10px, -3px) scale(2.2); } }

/* The swing, right side up (generated from buddy_model.js HANG: each arm frame turns its arm to end
   on its hand's frame, testkit/buddy_model_test.mjs checks it): the body travels its span from the
   middle, right, back left and to the middle again in eight catches (swingBody); each hand holds
   while the body passes under it (its offset runs against the body's 15 px a catch, so it stays put
   on the edge) and then reaches ahead, dipping off the edge (swingL, swingR: named by the side the
   hand is on). */
@keyframes buddy-swingBody {
  0% { transform: translate(calc(var(--swing-span, 60px) * 0), 0.5px) rotate(0deg) scale(1.04, .96); }
  6.25% { transform: translate(calc(var(--swing-span, 60px) * 0.13), -1px) rotate(5deg) scale(.98, 1.03); }
  12.5% { transform: translate(calc(var(--swing-span, 60px) * 0.25), 0.5px) rotate(0deg) scale(1.04, .96); }
  18.75% { transform: translate(calc(var(--swing-span, 60px) * 0.38), -1px) rotate(5deg) scale(.98, 1.03); }
  25% { transform: translate(calc(var(--swing-span, 60px) * 0.5), 0.5px) rotate(0deg) scale(1.04, .96); }
  31.25% { transform: translate(calc(var(--swing-span, 60px) * 0.38), -1px) rotate(-5deg) scale(.98, 1.03); }
  37.5% { transform: translate(calc(var(--swing-span, 60px) * 0.25), 0.5px) rotate(0deg) scale(1.04, .96); }
  43.75% { transform: translate(calc(var(--swing-span, 60px) * 0.13), -1px) rotate(-5deg) scale(.98, 1.03); }
  50% { transform: translate(calc(var(--swing-span, 60px) * 0), 0.5px) rotate(0deg) scale(1.04, .96); }
  56.25% { transform: translate(calc(var(--swing-span, 60px) * -0.12), -1px) rotate(-5deg) scale(.98, 1.03); }
  62.5% { transform: translate(calc(var(--swing-span, 60px) * -0.25), 0.5px) rotate(0deg) scale(1.04, .96); }
  68.75% { transform: translate(calc(var(--swing-span, 60px) * -0.37), -1px) rotate(-5deg) scale(.98, 1.03); }
  75% { transform: translate(calc(var(--swing-span, 60px) * -0.5), 0.5px) rotate(0deg) scale(1.04, .96); }
  81.25% { transform: translate(calc(var(--swing-span, 60px) * -0.37), -1px) rotate(5deg) scale(.98, 1.03); }
  87.5% { transform: translate(calc(var(--swing-span, 60px) * -0.25), 0.5px) rotate(0deg) scale(1.04, .96); }
  93.75% { transform: translate(calc(var(--swing-span, 60px) * -0.12), -1px) rotate(5deg) scale(.98, 1.03); }
  100% { transform: translate(calc(var(--swing-span, 60px) * 0), 0.5px) rotate(0deg) scale(1.04, .96); }
}
@keyframes buddy-swingR {
  0% { transform: translate(-7.5px, 0px); }
  6.25% { transform: translate(0px, 3px); }
  12.5% { transform: translate(7.5px, 0px); }
  18.75% { transform: translate(0px, 0px); }
  25% { transform: translate(-7.5px, 0px); }
  31.25% { transform: translate(-7.5px, 3px); }
  37.5% { transform: translate(-7.5px, 0px); }
  43.75% { transform: translate(0px, 0px); }
  50% { transform: translate(7.5px, 0px); }
  56.25% { transform: translate(0px, 3px); }
  62.5% { transform: translate(-7.5px, 0px); }
  68.75% { transform: translate(0px, 0px); }
  75% { transform: translate(7.5px, 0px); }
  81.25% { transform: translate(7.5px, 3px); }
  87.5% { transform: translate(7.5px, 0px); }
  93.75% { transform: translate(0px, 0px); }
  100% { transform: translate(-7.5px, 0px); }
}
@keyframes buddy-swingL {
  0% { transform: translate(7.5px, 0px); }
  6.25% { transform: translate(0px, 0px); }
  12.5% { transform: translate(-7.5px, 0px); }
  18.75% { transform: translate(-7.5px, 3px); }
  25% { transform: translate(-7.5px, 0px); }
  31.25% { transform: translate(0px, 0px); }
  37.5% { transform: translate(7.5px, 0px); }
  43.75% { transform: translate(0px, 3px); }
  50% { transform: translate(-7.5px, 0px); }
  56.25% { transform: translate(0px, 0px); }
  62.5% { transform: translate(7.5px, 0px); }
  68.75% { transform: translate(7.5px, 3px); }
  75% { transform: translate(7.5px, 0px); }
  81.25% { transform: translate(0px, 0px); }
  87.5% { transform: translate(-7.5px, 0px); }
  93.75% { transform: translate(0px, 3px); }
  100% { transform: translate(7.5px, 0px); }
}
@keyframes buddy-swingArmR {
  0% { transform: rotate(0deg) scaleY(0.91); }
  6.25% { transform: rotate(28.88deg) scaleY(0.85); }
  12.5% { transform: rotate(42.1deg) scaleY(1.23); }
  18.75% { transform: rotate(24.31deg) scaleY(1); }
  25% { transform: rotate(0deg) scaleY(0.91); }
  31.25% { transform: rotate(0deg) scaleY(0.75); }
  37.5% { transform: rotate(0deg) scaleY(0.91); }
  43.75% { transform: rotate(24.31deg) scaleY(1); }
  50% { transform: rotate(42.1deg) scaleY(1.23); }
  56.25% { transform: rotate(28.88deg) scaleY(0.85); }
  62.5% { transform: rotate(0deg) scaleY(0.91); }
  68.75% { transform: rotate(24.31deg) scaleY(1); }
  75% { transform: rotate(42.1deg) scaleY(1.23); }
  81.25% { transform: rotate(47.8deg) scaleY(1.11); }
  87.5% { transform: rotate(42.1deg) scaleY(1.23); }
  93.75% { transform: rotate(24.31deg) scaleY(1); }
  100% { transform: rotate(0deg) scaleY(0.91); }
}
@keyframes buddy-swingArmL {
  0% { transform: rotate(0deg) scaleY(0.91); }
  6.25% { transform: rotate(-24.31deg) scaleY(1); }
  12.5% { transform: rotate(-42.1deg) scaleY(1.23); }
  18.75% { transform: rotate(-47.8deg) scaleY(1.11); }
  25% { transform: rotate(-42.1deg) scaleY(1.23); }
  31.25% { transform: rotate(-24.31deg) scaleY(1); }
  37.5% { transform: rotate(0deg) scaleY(0.91); }
  43.75% { transform: rotate(-28.88deg) scaleY(0.85); }
  50% { transform: rotate(-42.1deg) scaleY(1.23); }
  56.25% { transform: rotate(-24.31deg) scaleY(1); }
  62.5% { transform: rotate(0deg) scaleY(0.91); }
  68.75% { transform: rotate(0deg) scaleY(0.75); }
  75% { transform: rotate(0deg) scaleY(0.91); }
  81.25% { transform: rotate(-24.31deg) scaleY(1); }
  87.5% { transform: rotate(-42.1deg) scaleY(1.23); }
  93.75% { transform: rotate(-28.88deg) scaleY(0.85); }
  100% { transform: rotate(0deg) scaleY(0.91); }
}
/* The turns (buddy--drop, buddy--climb), .42 s each, about his middle. The drop's first frame is the
   behind pose turned over: --turn-p of him behind the edge, HANG.y above where he will hang; the
   climb's is the hang turned over: HANG.y below where he will be behind it. The hands start where
   the pose before left them (the swing's own first frame taken off, --x0), tuck in against his
   sides where his arms grow from, and take hold, becoming his arms as they do (the arms fade in as
   the hands fade out, 66 % to 86 %); climbing, the arms shorten as he pulls himself up with his hands
   still on the edge, and become hands again (32 % to 46 %). Every number is buddy_model.js HANG's
   worked through the turn (testkit/buddy_model_test.mjs checks them). */
@keyframes buddy-drop {
  0% { transform: translateY(calc(-0.5px - var(--turn-p, 9px))) rotate(180deg); animation-timing-function: cubic-bezier(.45, 0, .9, .55); }
  45% { transform: translateY(1px) rotate(70deg) scale(.95, 1.06); animation-timing-function: ease-out; }
  70% { transform: translateY(3px) rotate(-10deg) scale(.96, 1.05); }
  84% { transform: translateY(-1px) rotate(3deg) scale(1.07, .93); }
  100% { transform: none; }
}
@keyframes buddy-climb {
  0% { transform: translateY(calc(var(--p, 9px) + 0.5px)) rotate(180deg); }
  32% { transform: translateY(calc(var(--p, 9px) - 4px)) rotate(180deg) scale(.95, 1.05); animation-timing-function: ease-in; }
  72% { transform: translateY(-5px) rotate(-14deg); }
  86% { transform: translateY(1px) rotate(4deg) scale(1.05, .95); }
  100% { transform: none; }
}
@keyframes buddy-dropHandL {
  0% { translate: calc(-8.6px - var(--x0, 0px)) calc(30px - var(--turn-p, 9px)); opacity: 1; }
  30%, 60% { translate: calc(-7.5px - var(--x0, 0px)) 16.6px; }
  66% { opacity: 1; }
  82% { translate: 0px 1.5px; }
  86% { opacity: 0; }
  100% { translate: 0px 0px; opacity: 0; }
}
@keyframes buddy-dropHandR {
  0% { translate: calc(8.6px - var(--x0, 0px)) calc(30px - var(--turn-p, 9px)); opacity: 1; }
  30%, 60% { translate: calc(7.5px - var(--x0, 0px)) 16.6px; }
  66% { opacity: 1; }
  82% { translate: 0px 1.5px; }
  86% { opacity: 0; }
  100% { translate: 0px 0px; opacity: 0; }
}
@keyframes buddy-dropArm { 0%, 66% { opacity: 0; } 86%, 100% { opacity: 1; } }
@keyframes buddy-climbHandL {
  0% { translate: -8.6px calc(30px - var(--p, 9px)); opacity: 0; }
  32% { translate: -8.6px calc(25.5px - var(--p, 9px)); opacity: 0; }
  46% { opacity: 1; }
  72% { translate: .5px 1px; }
  100% { translate: 0px 0px; opacity: 1; }
}
@keyframes buddy-climbHandR {
  0% { translate: 8.6px calc(30px - var(--p, 9px)); opacity: 0; }
  32% { translate: 8.6px calc(25.5px - var(--p, 9px)); opacity: 0; }
  46% { opacity: 1; }
  72% { translate: -.5px 1px; }
  100% { translate: 0px 0px; opacity: 1; }
}
/* Behind the edge, turned over, each arm hangs from its foot turned over (1.15, 13.9 and 28.85, 13.9)
   to its hand, and shortens as he pulls himself up; then it is a hand again. */
@keyframes buddy-climbArmL {
  0% { opacity: 1; transform: rotate(24.31deg); }
  32% { opacity: 1; transform: rotate(31.79deg) scaleY(.78); }
  46%, 100% { opacity: 0; transform: rotate(31.79deg) scaleY(.78); }
}
@keyframes buddy-climbArmR {
  0% { opacity: 1; transform: rotate(-24.31deg); }
  32% { opacity: 1; transform: rotate(-31.79deg) scaleY(.78); }
  46%, 100% { opacity: 0; transform: rotate(-31.79deg) scaleY(.78); }
}
@keyframes buddy-turnBlink { 0%, 64% { transform: var(--eye) scaleY(.08); } 82%, 100% { transform: var(--eye); } }
/* The hello's wave: once he holds on (the drop's .42 s is 12 %), the arm on his right lets go, comes
   down beside his head (its tip where the hold is, 1 out and 10 down: a short, round oval) and waves
   to and fro from his side, between straight up and 42°, the furthest it goes and stays 1 pt off the
   "Hi!" bubble beside him; then it takes hold again. */
@keyframes buddy-helloArm {
  0%, 22% { transform: rotate(24.31deg); }
  30% { transform: rotate(0deg) scaleY(.59); }
  38% { transform: rotate(42deg) scaleY(.59); }
  46% { transform: rotate(0deg) scaleY(.59); }
  54% { transform: rotate(42deg) scaleY(.59); }
  62% { transform: rotate(0deg) scaleY(.59); }
  70% { transform: rotate(42deg) scaleY(.59); }
  80% { transform: rotate(21deg) scaleY(.59); }
  92%, 100% { transform: rotate(24.31deg); }
}
@keyframes buddy-dangle { 0%, 100% { transform: none; } 50% { transform: scale(.97, 1.05); } }
@keyframes buddy-helloEye { 0%, 40.83% { opacity: 1; } 42.78%, 94% { opacity: 0; } 97%, 100% { opacity: 1; } }
@keyframes buddy-helloJoy { 0%, 40.83% { opacity: 0; } 42.78%, 94% { opacity: 1; } 97%, 100% { opacity: 0; } }
@keyframes buddy-helloHi { 0%, 42.78% { opacity: 0; } 50.56%, 94% { opacity: 1; } 97%, 100% { opacity: 0; } }
