About this animation
A studio landing page with a tilting mascot and expanding capability card, with configurable artwork, copy, colors, and fonts.
Why this one
The character lands with squash and spring recovery rather than behaving like a static hero image. Its contact shadow follows the gesture in the opposite direction, while the headline and character travel at 60% of the page's scroll speed. The orange surface gently straightens as the visitor scrolls.
How it works
- The container measures its own layout. A centered page stays at most 480 points wide; short containers scroll a minimum 650-point composition instead of shrinking the copy.
- An entrance progress reveals individually clipped headline lines and raises the orange panel. The mascot falls for 360 ms, compresses for 90 ms, then recovers with an explicit mass-1 spring.
- A single two-axis tilt value drives perspective, position, and the opposing contact shadow. The pan region covers only the mascot, keeping buttons outside the gesture detector.
- Scroll offset drives parallax and panel rotation. Scroll impulses feed a damped angular velocity on top of the seal's nine-second rotation.
- A single disclosure progress drives the card height, staggered rows, and chevron. Each button has an independent pressure value.
- The demo calls the same imperative methods as an embedding app and cancels its timers on direct touch.
Install
npx expo install @shopify/react-native-skia expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsConfigure Reanimated and Gesture Handler for your app, and place the screen under GestureHandlerRootView. For Skia on web, provide CanvasKit according to your installed Skia version. No additional native module or development build is required beyond the SDK 58 Expo Go modules.
Usage
import { MascotStudioLanding } from './mascot-studio-landing';
<MascotStudioLanding
mascot={require('./assets/character.webp')}
brandScript="Your studio"
brandCaption="independent"
onCta={() => openProjectForm()}
/>Load your fonts before mounting. The component imports no demo assets, theme, or fonts. Supply badgeFontSource to render the circular lettering.
const landing = useRef<MascotStudioLandingRef>(null);
<MascotStudioLanding
ref={landing}
mascot={require('./assets/character.webp')}
fontFamily="YourSans"
mediumFontFamily="YourSansMedium"
boldFontFamily="YourSansBold"
heavyFontFamily="YourSansDisplay"
scriptFontFamily="YourEditorialItalic"
badgeFontSource={require('./assets/interface-medium.ttf')}
colors={{ accent: '#D8552D', highlight: '#F7774B' }}
capabilities={['Character design', 'Animated films']}
onCapability={(label, index) => showService(index)}
/>
landing.current?.drop();
landing.current?.toggleCapabilities();
landing.current?.scrollTo(420);
landing.current?.setTilt(0.6, -0.2);Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
mascot | ImageSource | number | Required | Transparent local character image. |
mascotAspectRatio | number | 764 / 1030 | Source width divided by height. |
brandScript, brandCaption | string | Studio, independent | Brand lockup and introduction name. |
headline | string[] | ANIMATED / VIDEO / PRODUCTION | Independently revealed headline lines. Designed for three concise lines. |
introLead | string | We're | Introduction prefix. |
intro | string | Studio introduction | Body copy in a flowing text block. |
badgeText | string | OUR CAPABILITIES • repeated | Circular lettering. |
cardTitle | string | Animation & Motion graphics | Disclosure heading and accessible toggle label. |
capabilities | string[] | Four production services | Expandable list. Keep labels concise. |
stats | { value: string; label: string } | 12+ / CHARACTERS | Lower editorial card. |
footerTitle, ctaLabel | string | Let's make something, Start a project | Footer invitation and action. |
colors | Partial<Palette> | Warm dark/orange/white | top, topDeep, accent, card, ink, foreground, onAccent, highlight. Override foregrounds together with surfaces for light themes. |
fontFamily | string | System font | Body copy. |
mediumFontFamily, boldFontFamily, heavyFontFamily | string | System font | Interface and display weights. |
scriptFontFamily | string | System font | Brand and footer editorial face. |
badgeFontSource | DataSourceParam | None | Skia font for circular lettering; absent font leaves the ring without lettering. |
menuLabel | string | Open studio menu | Menu accessibility label. |
socialLabels | [string, string] | Send, Play | Labels for decorative social symbols. |
mascotLabel | string | Studio mascot description | Image accessibility label. |
onMenu, onCta | () => void | None | Host navigation actions. |
onCapability | (label, index) => void | None | Service selection callback. |
onInteraction | () => void | None | Direct-touch notification for autoplay cancellation. |
style, testID | StyleProp<ViewStyle>, string | None, mascot-studio-landing | Host container and stable control identifiers. |
Ref
drop() replays the entrance without changing disclosure state. The component plays its entrance once on mount; container measurements and resizing do not restart it. The demo starts its autoplay choreography without replaying that initial entrance. toggleCapabilities() opens or closes the list. scrollTo(y) accepts a nonnegative point offset. setTilt(x, y) clamps each axis to −1…1 and springs to the target; use (0, 0) to reset.
Compatibility
Designed for Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Gesture Handler 3.3, and Skia 2.14.0. Supports iOS, Android, and web. The piece is a full-screen scrolling page but can be embedded in a 300×400 container. Tablet and landscape layouts retain a centered mobile column. The host owns safe-area insets.
The included 764×1030 mascot is demonstration artwork and is not imported by the reusable component. Display width is capped at 250 points to preserve approximately 3× source density.
Accessibility
Buttons have at least 48-point touch targets, disclosure state, and stable test identifiers. Collapsed capability rows are hidden from accessibility and touch. All visible copy and accessible labels are replaceable. Dynamic type remains enabled; use concise copy and review enlarged text in the host app. Reduced motion removes the fall, squash, perspective gesture, breathing, seal rotation, and parallax, using 200 ms fades for entry and disclosure. The optional tilt is decorative and never required to access content.
Icons: Lucide (ISC). The menu's three unequal lines follow the specified studio composition.




