About this animation
A flashlight search that reveals a reward card, with configurable scene image, offer copy, accent color, and fonts.
How it works
- The component measures its own container and keeps the full-bleed scene fixed while all positions are derived from its width and height.
- A Skia radial gradient creates the soft-edged flashlight. Reanimated shared values drive its center, velocity-responsive radius, warm proximity pulse, and continuous reveal progress.
- A pan gesture springs the focus toward the pointer. Remaining within 70 points of the hidden reward for 400 ms triggers discovery.
- The same reveal progress expands the clear radius, flies the hidden token toward center, materializes a blurred reward card, and emits a brief warm spark burst.
- The forwarded ref exposes deterministic reveal, reset, and guided-tour controls without adding demo-only behavior to the component.
Why this one
Typical flashlight reveals stop at exposing an image. This one turns discovery into a complete product moment: velocity-sensitive light, dwell-based intent, a real hidden object that transforms into an actionable reward, reduced-motion behavior, and a curved autoplay tour designed to land the payoff within three seconds.
Install
npx expo install @shopify/react-native-skia expo-blur expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsWrap the app with GestureHandlerRootView. Skia web projects must initialize CanvasKit before this component is loaded. Icons: Lucide (ISC).
Usage
import FlashlightReveal from './FlashlightReveal';
export function RewardScreen() {
return <FlashlightReveal sceneSource={require('./cafe-scene.jpg')} />;
}const revealRef = useRef<FlashlightRevealRef>(null);
<FlashlightReveal
ref={revealRef}
sceneSource={require('./cafe-scene.jpg')}
title="Find tonight's surprise"
rewardTitle="A dessert on the house"
rewardCode="NIGHT-8K2P"
accentColor="#FB923C"
focusRadius={132}
onAddToWallet={saveReward}
/>;The ref exposes reveal(), reset(), and playTour().
Props
| Prop | Type | Default | Description |
|---|---|---|---|
sceneSource | ImageSourcePropType | Required | Full-bleed artwork shown beneath the flashlight layer. |
title | string | 'Find your mystery reward' | Search headline. |
hint | string | 'Drag to explore' | Gesture hint. |
brandName | string | 'Ember & Oat' | Brand name shown on the hidden token. |
rewardLabel | string | 'Free pastry' | Short reward shown on the hidden token. |
rewardTitle | string | 'Free pastry with any drink' | Reward headline shown after discovery. |
rewardCode | string | 'EMBER-7Q4K' | Reward redemption code. |
validThrough | string | 'Valid through 10/31' | Reward expiration copy. |
walletLabel | string | 'Add to wallet' | Primary action label. |
revealLabel | string | 'Reveal' | Alternative search action label. |
resetLabel | string | 'Try again' | Reset action label. |
darknessColor | string | 'rgba(5,4,3,0.94)' | Color outside the focus. |
accentColor | string | '#FB923C' | Warm accent used for focus light and reward details. |
focusRadius | number | 120 | Base flashlight radius in points. |
fontFamily | string | undefined | Body font family. |
mediumFontFamily | string | undefined | Medium body font family. |
titleFontFamily | string | undefined | Editorial title font family. |
monoFontFamily | string | undefined | Monospaced reward code font family. |
onReveal | () => void | undefined | Called when the reward finishes being triggered. |
onReset | () => void | undefined | Called when the reward is reset. |
onAddToWallet | () => void | undefined | Called when the wallet action is pressed. |
accessibilityLabel | string | 'Explore the scene to find a mystery reward' | Screen-reader label for the search surface. |
style | StyleProp<ViewStyle> | undefined | Optional style for the outer container. |
testID | string | undefined | Test identifier applied to the outer container. |
Compatibility
- Expo SDK 58
- React Native 0.88.0
- Reanimated 4.7.1 and Worklets 0.13.0
- Skia 2.14.0
- iOS, Android, and web: supported
- Expo Go: supported by the SDK 58 bundled modules; use a development build after adding native dependencies to another Expo project
Accessibility
The search surface and all actions have explicit roles and labels, the reveal button provides a touch alternative to dragging, and every action has at least a 44-point hit area. Reduced Motion removes the moving focus and spark choreography, replacing discovery with a short fade to the reward card.
Customization notes
The flashlight follows the gesture. The optional tour keeps its motion and scheduled hold synchronized.




