/*
 * Pull-to-refresh. One offset — --pull, written by the gesture — drives the
 * content's travel and everything the indicator does, so the spinner cannot
 * drift out of step with the list it belongs to.
 */
.ptr {
  position: relative;
  /*
   * Vertical still belongs to the page: the component claims a gesture only at
   * scroll top, and pan-y is what lets every other one through untouched.
   */
  touch-action: pan-y;
  /*
   * Load-bearing, not hygiene: left on, the browser starts a native selection
   * drag on a team name and fires pointercancel mid-pull.
   */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.ptr__content {
  transform: translateY(var(--pull, 0px));
  transition: transform var(--spring-duration) var(--spring);
}

/*
 * Parked above the list and revealed by the pull rather than pushed down by it,
 * so the indicator and the content move as one piece.
 */
.ptr__indicator {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  display: grid;
  place-items: center;
  height: var(--pull, 0px);
  overflow: hidden;
  /* Fades in over the first half of the travel — visible well before the pull
     is armed, so there is something to aim at. */
  opacity: clamp(0, calc(var(--pull, 0px) / 32px), 1);
}

/*
 * A ring, not a glyph: it is the one place in the app that spins, and a
 * three-quarter arc reads as motion at any angle. --accent, because a refresh
 * in flight is the app acting on a request.
 */
.ptr__spinner {
  width: 20px;
  height: 20px;
  border: 2px solid var(--accent-soft);
  border-top-color: var(--accent);
  border-radius: var(--radius-full);
  /* Winds up with the pull, so the ring is already turning under the finger. */
  transform: rotate(calc(var(--pull, 0px) * 4));
}

/*
 * While the finger is down the list tracks it 1:1 — no transition, or it lags
 * the thumb by a whole spring. Everything else springs home.
 */
.ptr[data-state="pulling"] .ptr__content,
.ptr[data-state="armed"] .ptr__content {
  transition: none;
}

/* Armed. The ring fills out to say a release will commit — the affordance swaps
   at the crossing, alongside the haptic, not on release. */
.ptr[data-state="armed"] .ptr__spinner {
  border-color: var(--accent);
  border-top-color: var(--accent);
}

.ptr[data-state="refreshing"] .ptr__spinner {
  /* The wind-up hands over to a free spin; the ring never stops turning
     between the two. */
  animation: ptr-spin 700ms linear infinite;
  transform: none;
}

@keyframes ptr-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ptr[data-state="refreshing"] .ptr__spinner {
    animation: none;
    /* Still has to say "working" — it holds the armed ring instead of turning. */
    border-color: var(--accent);
  }
}
