Skip to content
Native Animation
Challenge Pot Card animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add challenge-pot-card

Challenge Pot Card

A fitness challenge pot with arriving avatars and a balance-to-entry flight, with configurable challenges, colors, labels, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A fitness challenge pot with arriving avatars and a balance-to-entry flight, with configurable challenges, colors, labels, and fonts.

Why this one

A static prize amount gives no sense of the people behind it. Here each arrival has a visible cause and effect: an avatar flies into the stack, the participant count increases, and the pot gains one stake. Your own entry follows a coin flight and gains a green avatar ring. All financial changes are local display simulations; the host owns payment and persistence.

How it works

  1. Artwork mounts eagerly at its final view size with memory caching. All cards wait for image display (or a load error), then enter with a 260 ms UI-thread fade/translation staggered by 70 ms. Blur intensity and shadows stay fixed. Exercise loops, live events, and demo autoplay start after this entrance; onReady lets hosts synchronize their own events. The bundled 477×316 artwork already covers the maximum 150 pt width at 3× density.
  2. Challenge data initializes a transaction snapshot with previous and next amounts, counts, and avatar initials.
  3. A single arrival shared value drives the rolling characters, avatar flight, and green light along the glass edge. React updates only for transactions, layout, navigation, and the minute clock.
  4. Each character has an invisible measurement copy and two clipped text layers. Unchanged characters stay still; changed columns end at their exact destination. Punctuation uses its actual glyph width.
  5. Tapping a card expands its existing surface with a nearly critical spring and lowers the other cards. A downward swipe or the back button closes it.
  6. Joining reserves the available balance, sends a coin into the pot, then commits the balance and calls onJoin once. Repeated entries are ignored. A reset cancels the pending flight and restores initial data.
  7. The demo owns its autoplay schedule: arrival at 0.55 seconds after readiness, second arrival at 2.75 seconds, expand at 3.9 seconds, join at 4.7 seconds, return at 7.2 seconds, reset at 9.5 seconds. Direct input stops autoplay.

Install

npx expo install expo-image expo-blur expo-linear-gradient @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-worklets

Copy ChallengePotCard.tsx, icons.ts, and index.ts. Keep the application's root inside GestureHandlerRootView. Load any supplied fonts in the host. For Skia on web, initialize CanvasKit with the host's existing Skia web setup before mounting. The demo artwork is local and separate from the reusable component; its transparent margins have been cropped. Icons: Lucide (ISC).

Usage

import { ChallengePotCard } from './challenge-pot-card';

<ChallengePotCard
  challenges={[
    {
      id: 'daily-reps',
      title: '100 Push-ups · 7 days',
      image: require('./exercise.png'),
      pot: 1840,
      progress: 0.6,
      participants: 42,
      stake: 25,
      endsAt: Date.now() + 32 * 3600000,
      avatars: ['MK', 'JR', 'SP'],
    },
  ]}
  balance={120}
  onJoin={(id) => saveChallengeEntry(id)}
/>

With external live events and personality controls:

const card = useRef<ChallengePotCardRef>(null);

<ChallengePotCard
  ref={card}
  challenges={challenges}
  balance={120}
  liveInterval={0}
  arrivalDuration={580}
  blurIntensity={48}
  fontFamily="YourInterfaceFont"
  monoFontFamily="YourNumberFont"
  labels={{ heading: 'Your challenges' }}
  theme={{ accent: '#8BD94B', button: '#2F7BFF' }}
/>

// Call from an event handler or subscription:
card.current?.simulateJoiner('daily-reps');
card.current?.expand('daily-reps');
card.current?.join('daily-reps');

Props

PropTypeDefaultPurpose
challengesreadonly Challenge[]requiredID, title, image, initial pot, completion fraction, participant count, stake, end timestamp, avatar initials
balancenumberrequiredInitial available funds
onJoin(id: string) => void—Local entry has settled; host handles persistence
themePartial<ChallengePotTheme>dark paletteBackground, card, glass, border, text, muted, accent, button, button text, live, amber, avatar colors and avatar text
labelsPartial<ChallengePotLabels>American EnglishAll structural copy and four navigation labels
brandReactNode—Optional host lockup
fontFamily / headingFontFamily / monoFontFamilystringsystemInterface, heading and numeral fonts
formatMoney(value: number) => stringwhole US dollarsCurrency and locale formatting
formatRemaining(minutes: number) => stringdays, hours, minutesCountdown formatting, refreshed every minute
liveIntervalnumber2600Simulated live arrivals; 0 disables them
joinerInitialsreadonly string[]AL, MK, JR, SPIncoming avatar initials
arrivalDurationnumber580Avatar and coin flight duration, minimum 250 ms
blurTintBlurTintdarkGlass tint; pair light with a light palette
blurIntensitynumber48Glass blur intensity
onReady() => void—Artwork has settled and the initial card entrance has finished; a failed image does not block the interface
onInteraction() => void—Stop host autoplay on input
onTabPress(index: number) => void—Host navigation; this component does not create routes
announcement(title, joined) => stringEnglish arrival messageAccessible live announcement
style / testIDStyleProp<ViewStyle> / string— / challenge-pot-cardContainer styling and stable control IDs

The ref exposes join(id), simulateJoiner(id), expand(id), collapse(), and reset(). Unknown IDs, expired challenges, duplicate local entries, and entries without sufficient funds are ignored. Incoming arrivals during a pending local coin flight are ignored to keep the transaction callback atomic. Initial financial values are snapshots: after replacing them, call reset() or remount. Use finite nonnegative amounts, unique IDs, and completion fractions from 0 to 1.

Layout and safe areas

Android SDK 31+ uses BlurTargetView and dimezisBlurViewSdk31Plus; older Android versions retain the tinted material fallback.

The root fills its allocated container and centers cards at a maximum width of 520 pt. Short layouts use compact cards and vertical scrolling; expanded content remains scrollable in a 300×400 container or landscape. Artwork never exceeds 150 pt wide. The host owns safe-area insets. The demo leaves the upper-right viewer close-button area empty. At larger accessibility text sizes, scroll to reach the lower content.

Compatibility

Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, and Skia 2.14.0. Uses modules bundled in the project's Expo Go setup. Supports iOS, Android, and web. No browser capture or server is created by this delivery.

Accessibility

Tap targets are at least 48 pt. Buttons and cards expose accessible roles, labels, activation actions, and stable test IDs. Joining and live arrivals announce changes. Reduced motion removes coin flights, avatar arcs, edge sweeps, and exercise loops, replacing arrivals with 200 ms fades. A tap or back button provides an alternative to the downward close gesture.