Component

Mobile Carousel Hero

Full-section hero with a 3D coverflow carousel of app screens inside a device frame. Screens auto-advance on a timer, pause on hover, and can be clicked to jump to any position. Supports dark and light themes. Responsive — peek cards visible on all screen sizes.

Preview

This component uses Framer Motion animations and requires real images — open the interactive preview to see it in action.

Open interactive preview →

How it works

The carousel uses an unbounded tick integer as its state rather than a circular index. Each 3D slot is keyed by tick + offset — a stable key that never jumps, so Framer Motion always transitions each card exactly one position at a time, producing true coverflow depth without the cross-screen sweep artifact.

The center slot (offset 0) is rendered in the 3D layer at opacity: 0 so the handoff between the 3D layer and the device viewport is seamless — same spring, same physics. Depth is communicated via brightness() filter (never opacity) so cards remain fully opaque and don't bleed through each other.

Coverflow depth tiers

OffsetX positionrotateYBrightnessBlur
0 (active)0 — inside device100% (full)0px
±1±190px±52°62%1px
±2±350px±64°36%3px
±3±490px±72°22%6px

Sanity fields

Available as mobileCarouselHero in the Page builder. The Screens field uses a grid layout in Studio — drag-select multiple portrait images at once for bulk upload. Portrait ratio (9:19) works best.

PropTypeDefaultDescription
headlinestringMain headline. Rendered in Cal Sans 600. Required.
eyebrowstringSmall uppercase label above the headline in Electric blue.
subheadtextSupporting copy below the headline.
screensimage[]App screen images. Min 1. Bulk upload via drag-and-drop in Studio. Stores dimensions + LQIP automatically.
themestring"dark""dark" or "light". Controls section background, text colors, and device frame.

Interaction model

  • Auto-advances every 4.5 s when idle
  • Hovering a peek card pauses the timer; mouse-leave resumes it
  • Clicking a peek card advances it to center and pauses the timer
  • Dot indicators show active screen; click any dot for shortest-path navigation
  • Respects prefers-reduced-motion — skips spring animation, uses simple fade

Usage

tsx
import { MobileCarouselHero } from '@/components/sections/mobile-carousel-hero'

<MobileCarouselHero
  block={{
    _type:    'mobileCarouselHero',
    headline: 'Your photography, always in your pocket',
    eyebrow:  'Mobile-First Experience',
    subhead:  'Browse collections, save favourites, share the work.',
    theme:    'dark',
    screens:  [
      { _type: 'image', asset: { _id: '…', url: '…', metadata: { lqip: '…', dimensions: { width: 600, height: 1300, aspectRatio: 0.46 } } } },
      // add as many screens as needed
    ],
  }}
/>
tsx
// Via Sanity BlockRenderer — no extra code needed.
// Add to a Page document in Studio:
// Sections → Add item → Mobile Carousel Hero