/* Reaction burst: a chat message lands, typing dots run, the text takes over, reactions arrive; then a
   cursor opens the quick-pick bar, picks a heart, the heart chip grows into the row and a ring of tiny
   particles sprays off it. One clock: everything waits for .is-live on the root (part.js adds it when the
   scene reaches the middle of the screen) and then holds its last frame.

   Classes (put them on your own markup; see demo.html):
   .rb              root (scene). Gets .is-live.
   .rb-msg          the message (avatar + name + bubble + reactions): pops in first.
   .rb-bubble       the bubble: grows out of its top-left corner (the avatar side).
     .rb-typing     three <i> dots inside the bubble, absolutely placed over the text.
     .rb-text       the message text: fades in as the dots finish.
   .rb-reacts       the reaction row (position: relative; part.js measures it).
     .rb-react      a reaction chip that pops in (e.g. the team's).
     .rb-mine       wrapper around the chip that gets picked: holds .rb-react.rb-pickd + .rb-burst.
       .rb-pickd    the picked chip: takes no width until the pick, then grows in place.
       .rb-burst    the particle spray, centred on the chip's icon. Empty = part.js adds the source's 6.
     .rb-add        the add-reaction button the cursor clicks.
     .rb-pick       the quick-pick bar that opens above .rb-add (its 4th item is the one picked).
     .rb-cursor     the cursor (an arrow + optional .rb-tag name tag).

   Measured from the source (Decimals how-it-works, scene 5: hm-react / hm-quickpick; times here start at the message pop,
   source 2.9s):
   message  .65s cubic-bezier(.34,1.4,.5,1): translateY(10px) scale(.94) -> rest, at 0s.
   bubble   .55s cubic-bezier(.34,1.4,.5,1): scale(.6) -> 1 from 0 0, at .3s.
   typing   holder .95s ease at .4s (0% hidden, 12% shown, 86% shown, 100% hidden); dots 7px,
            .5s ease-in-out x2 alternate (opacity .5 -> 1, up 3px), .14s apart. Text fades .3s at 1.3s.
   chips    add button pops .3s ease at 1.55s (scale .6 -> 1); team chip .35s cubic-bezier(.34,1.36,.64,1)
            at 1.7s.
   cursor   2.3s cubic-bezier(.3,.6,.3,1) at 1.55s: from (200px, 14px), opaque by 16%, on the add button
            by 30%, press 35% (1px,1px scale .95), up to the picked icon (-18px,-34px) by 56%, press 62%,
            then drifts off the bar by 100% (source -8px,-20px from a rest point right of its button; here
            --rb-end-x/y = 28px,-10px puts it in the same place: just right of the grown chip).
   pick     1.2s cubic-bezier(.34,1.4,.5,1) at 2.45s: scale(.6) translateY(6px) -> rest by 12%, holds to 88%,
            leaves at scale .8 by 100%; grows from 84% 100% (its corner above the add button).
   chip     .45s cubic-bezier(.34,1.4,.5,1) at 3.25s: max-width 0 -> 60px and padding 0 -> 8px by 45%,
            scale .6 -> 1.1 by 70%, rest by 100%.
   burst    6 dots 2.5px, .6s ease-out at 3.5s: scale .4 -> 1 at a quarter of the way by 20%, then out to
            the full offset (about 20px) at scale .6 while fading out.
   Eases are literals: var() is ignored for a keyframe's timing function. */

.rb {
  --rb-ink: #2b2a27;                  /* text + cursor */
  --rb-chip-bg: #fafafa;
  --rb-chip-line: #e9eaeb;
  --rb-burst-color: #f0426b;
  --rb-burst-size: 2.5px;
  --rb-burst-dur: 0.6s;
  --rb-bubble-at: 0.3s;
  --rb-typing-at: 0.4s;
  --rb-text-at: 1.3s;
  --rb-add-at: 1.55s;
  --rb-team-at: 1.7s;
  --rb-cursor-at: 1.55s;
  --rb-pick-at: 2.45s;
  --rb-react-at: 3.25s;
  --rb-burst-at: 3.5s;
  --rb-cursor-x: 200px;               /* where the cursor comes from, relative to the add button */
  --rb-cursor-y: 14px;
  --rb-end-x: 28px;                   /* where it comes to rest after the pick, relative to the add button: */
  --rb-end-y: -10px;                  /* just right of the grown chip, as in the source (see part.json) */
  position: relative;
}

/* ---------- message + bubble ---------- */
.rb-msg { opacity: 0; }
.rb.is-live .rb-msg { animation: rb-msg-pop 0.65s cubic-bezier(0.34, 1.4, 0.5, 1) 0s both; }
@keyframes rb-msg-pop {
  from { opacity: 0; transform: translateY(10px) scale(0.94); }
  to { opacity: 1; transform: none; }
}

.rb-bubble { position: relative; transform-origin: 0 0; opacity: 0; }
.rb.is-live .rb-bubble { animation: rb-pop-big 0.55s cubic-bezier(0.34, 1.4, 0.5, 1) var(--rb-bubble-at) both; }
@keyframes rb-pop-big {
  from { opacity: 0; transform: scale(0.6); }
  to { opacity: 1; transform: none; }
}

.rb-text { opacity: 0; }
.rb.is-live .rb-text { animation: rb-fade 0.3s ease var(--rb-text-at) both; }
@keyframes rb-fade { from { opacity: 0; } to { opacity: 1; } }

.rb-typing {
  position: absolute;
  top: 15px;
  left: 14px;
  display: flex;
  gap: 4px;
  opacity: 0;
  pointer-events: none;
}
.rb-typing i { width: 7px; height: 7px; border-radius: 50%; background: #b3ada2; }
.rb.is-live .rb-typing { animation: rb-typing-hold 0.95s ease var(--rb-typing-at) both; }
/* two cycles land the dots exactly when the text takes over */
.rb.is-live .rb-typing i { animation: rb-dot 0.5s ease-in-out var(--rb-typing-at) 2 alternate; }
.rb.is-live .rb-typing i:nth-child(2) { animation-delay: calc(var(--rb-typing-at) + 0.14s); }
.rb.is-live .rb-typing i:nth-child(3) { animation-delay: calc(var(--rb-typing-at) + 0.28s); }
@keyframes rb-typing-hold {
  0% { opacity: 0; }
  12% { opacity: 1; }
  86% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes rb-dot {
  from { transform: none; opacity: 0.5; }
  to { transform: translateY(-3px); opacity: 1; }
}

/* ---------- reaction row ---------- */
.rb-reacts {
  position: relative;
  display: flex;
  align-items: center;
  gap: 4px;
  /* add-button centre inside the row; part.js measures it on play (these are fallbacks) */
  --rb-ax: 60px;
  --rb-ay: 12px;
}
.rb-react {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 4px 8px;
  background: var(--rb-chip-bg);
  border: 1px solid var(--rb-chip-line);
  border-radius: 999px;
  white-space: nowrap;
  opacity: 0;
}
.rb-react svg { flex: none; width: 14px; height: 14px; }
.rb-react em { font-style: normal; font-size: 13px; line-height: 1; font-weight: 500; color: #414651; }
.rb.is-live .rb-react:not(.rb-pickd) { animation: rb-pop-sm 0.35s cubic-bezier(0.34, 1.36, 0.64, 1) var(--rb-team-at) both; }
@keyframes rb-pop-sm {
  from { opacity: 0; transform: scale(0.6); }
  to { opacity: 1; transform: none; }
}

/* the picked chip takes no space until the pick, then grows in place */
.rb-mine { position: relative; display: inline-flex; }
.rb-pickd {
  max-width: 0;
  padding: 4px 0;
  margin-left: -4px;
  border-color: transparent;
  overflow: hidden;
}
.rb.is-live .rb-pickd { animation: rb-react-in 0.45s cubic-bezier(0.34, 1.4, 0.5, 1) var(--rb-react-at) both; }
@keyframes rb-react-in {
  0% { opacity: 0; max-width: 0; padding: 4px 0; margin-left: -4px; border-color: transparent; transform: scale(0.6); }
  45% { max-width: 60px; padding: 4px 8px; margin-left: 0; border-color: var(--rb-chip-line); }
  70% { opacity: 1; transform: scale(1.1); }
  100% { opacity: 1; max-width: 60px; padding: 4px 8px; margin-left: 0; border-color: var(--rb-chip-line); transform: none; }
}

/* the spray: centred on the picked chip's icon (8px padding + half a 14px icon) */
.rb-burst {
  position: absolute;
  left: 15px;
  top: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
  color: var(--rb-burst-color);
}
.rb-burst i {
  position: absolute;
  left: calc(var(--rb-burst-size) / -2);
  top: calc(var(--rb-burst-size) / -2);
  width: var(--rb-burst-size);
  height: var(--rb-burst-size);
  border-radius: 50%;
  background: currentColor;
  opacity: 0;
}
.rb.is-live .rb-burst i { animation: rb-burst var(--rb-burst-dur) ease-out var(--rb-burst-at) both; }
@keyframes rb-burst {
  0% { opacity: 0; transform: translate(0, 0) scale(0.4); }
  20% { opacity: 1; transform: translate(calc(var(--px) * 0.25), calc(var(--py) * 0.25)) scale(1); }
  100% { opacity: 0; transform: translate(var(--px), var(--py)) scale(0.6); }
}

/* add-reaction affordance */
.rb-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 24px;
  padding: 4px 7px;
  background: var(--rb-chip-bg);
  border: 1px solid var(--rb-chip-line);
  border-radius: 999px;
  opacity: 0;
}
.rb-add svg { width: 16px; height: 16px; }
.rb.is-live .rb-add { animation: rb-pop-sm 0.3s ease var(--rb-add-at) both; }

/* quick-pick bar: five 21px items, 6px gap, 6/9px padding, 1px border (149 x 35px). Its 4th item's centre sits
   18px left of and 34px above the add button's centre, where the cursor clicks it. */
.rb-pick {
  position: absolute;
  z-index: 3;
  left: calc(var(--rb-ax) - 119.5px);
  top: calc(var(--rb-ay) - 51.5px);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 9px;
  background: #fff;
  border: 1px solid #e2e0da;
  border-radius: 999px;
  box-shadow: 0 10px 24px -8px rgba(30, 26, 18, 0.22);
  opacity: 0;
  transform-origin: 84% 100%;
  pointer-events: none;
}
.rb-pick > * { flex: none; width: 21px; height: 21px; display: grid; place-items: center; }
.rb-pick svg { width: 19px; height: 19px; }
.rb.is-live .rb-pick { animation: rb-pick 1.2s cubic-bezier(0.34, 1.4, 0.5, 1) var(--rb-pick-at) both; }
@keyframes rb-pick {
  0% { opacity: 0; transform: scale(0.6) translateY(6px); }
  12% { opacity: 1; transform: none; }
  88% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: scale(0.8); }
}

/* ---------- cursor: its tip (4px, 3px into the arrow) sits on the add button's centre ---------- */
.rb-cursor {
  position: absolute;
  z-index: 4;
  left: calc(var(--rb-ax) - 4px);
  top: calc(var(--rb-ay) - 3px);
  width: 24px;
  height: 24px;
  opacity: 0;
  pointer-events: none;
}
.rb-cursor svg { display: block; width: 24px; height: 24px; }
.rb.is-live .rb-cursor { animation: rb-cursor 2.3s cubic-bezier(0.3, 0.6, 0.3, 1) var(--rb-cursor-at) both; }
@keyframes rb-cursor {
  0% { opacity: 0; transform: translate(var(--rb-cursor-x), var(--rb-cursor-y)); }
  16% { opacity: 1; }
  30% { transform: none; }
  35% { transform: translate(1px, 1px) scale(0.95); }
  40% { transform: none; }
  56% { transform: translate(-18px, -34px); }
  62% { transform: translate(-17px, -33px) scale(0.95); }
  68% { transform: translate(-18px, -34px); }
  88% { transform: translate(-18px, -34px); }
  100% { opacity: 1; transform: translate(var(--rb-end-x), var(--rb-end-y)); }
}
.rb-tag {
  position: absolute;
  top: 17px;
  left: 14px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--rb-ink);
  color: #fff;
  font: 500 11px/16px ui-sans-serif, system-ui, -apple-system, sans-serif;
  white-space: nowrap;
}

/* ---------- reduced motion: the finished scene, still ---------- */
@media (prefers-reduced-motion: reduce) {
  .rb .rb-msg, .rb .rb-bubble, .rb .rb-text, .rb .rb-react, .rb .rb-add, .rb .rb-cursor {
    opacity: 1; animation: none !important; transform: none;
  }
  .rb .rb-pickd { max-width: none; padding: 4px 8px; margin-left: 0; border-color: var(--rb-chip-line); }
  .rb .rb-typing, .rb .rb-burst, .rb .rb-pick { display: none; }
}
