An ambient loop, not a state flip. There's no user-triggered start or end here, just a passive "something's happening" cue that runs for as long as the component is mounted. That's why it reaches for withRepeat/withSequence with withTiming and an in-out quad easing instead of a spring, continuous ambient motion reads better as a smooth, breathing curve than a bouncy one.
One shared value drives both scale and opacity. Each dot animates a single progress value from 0 to 1 and back, then derives scale (0.7 to 1) and opacity (0.35 to 1) from it via interpolate. Two separate shared values running identical loops look synced at a glance but drift out of phase, one shared value keeps them exactly in step.
Each dot owns its own delay. Dot i starts its loop i * 120ms after mount via withDelay, so the three dots pulse in a wave instead of in unison. That delay is set up once inside a mount-only effect, if it re-ran on every render the stagger would reset and the dots would briefly sync up.
No reverse flag on the repeat. The sequence already round-trips (0 to 1 to 0), adding reverse: true on top would replay it backwards and desync the easing into a stutter.
Theme-aware by default, overridable when it matters. With no color prop, useColorScheme() picks a neutral tone appropriate to light or dark mode; passing color bypasses that entirely.