/*
 * pulse: a soft halo that swells and fades behind a status dot. It beats, then rests: a 2s cycle
 * where the halo is gone by 60% and the dot sits still for the rest, so it reads as a heartbeat,
 * not an alarm. Sized in em, so it scales with the text beside it. Never on buttons.
 */
@keyframes pulse {
  0%   { transform: scale(1); opacity: .6; }
  60%  { transform: scale(3.2); opacity: 0; }
  100% { transform: scale(3.2); opacity: 0; }
}
.pulse {
  position: relative;
  display: inline-block;
  flex: none;
  width: .6em;
  height: .6em;
  border-radius: 50%;
  background: currentColor;
}
.pulse::before,
.pulse::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: currentColor;
  opacity: 0;
  animation: pulse 2s var(--ease-out) var(--pulse-count, infinite);
}
.pulse::after { display: none; }

/* the dot itself gives one small thump as each halo leaves it, so the beat has a source */
@keyframes pulse-dot {
  0%, 30%, 100% { scale: 1; }
  8%  { scale: 1.2; }
}
.pulse { animation: pulse-dot 2s var(--ease-out) var(--pulse-count, infinite); }

/* pulse-double: a second halo half a beat behind, like sonar. For the one thing that is live. */
.pulse-double::after { display: block; animation-delay: .5s; }

/* pulse-count: beat a few times, then rest for good. For "new" or "updated", which are not live. */
.pulse-count { --pulse-count: 3; }

@media (prefers-reduced-motion: reduce) {
  /* no beat: a still halo keeps the dot reading as a status, not a bullet */
  .pulse, .pulse::before, .pulse::after { animation: none; }
  .pulse::before { opacity: .25; transform: scale(1.9); }
}
