# App Demo Template

Screen recording style: Problem → Solution (app screens) → Download CTA

## Structure (20 seconds)

| Scene | Frames | Duration | Content |
|-------|--------|----------|---------|
| Problem | 0-90 | 3s | Pain point text |
| Intro | 90-150 | 2s | "Introducing [App]" |
| Demo | 150-450 | 10s | App screenshots with highlights |
| Features | 450-540 | 3s | Feature bullets |
| CTA | 540-600 | 2s | Download badges |

## Implementation

```tsx
import {AbsoluteFill, Sequence, Img, interpolate, useCurrentFrame, spring, useVideoConfig} from 'remotion';

interface AppDemoProps {
  problemText: string;
  appName: string;
  tagline: string;
  screens: string[]; // Array of screenshot URLs
  features: string[];
  appStoreUrl?: string;
  playStoreUrl?: string;
  brandColor: string;
  logoUrl: string;
}

export const AppDemo: React.FC<AppDemoProps> = ({
  problemText,
  appName,
  tagline,
  screens,
  features,
  brandColor,
  logoUrl,
}) => {
  return (
    <AbsoluteFill style={{backgroundColor: '#0f172a'}}>
      <Sequence from={0} durationInFrames={90}>
        <ProblemScene problemText={problemText} />
      </Sequence>

      <Sequence from={90} durationInFrames={60}>
        <IntroScene appName={appName} tagline={tagline} logoUrl={logoUrl} brandColor={brandColor} />
      </Sequence>

      <Sequence from={150} durationInFrames={300}>
        <DemoScene screens={screens} brandColor={brandColor} />
      </Sequence>

      <Sequence from={450} durationInFrames={90}>
        <FeaturesScene features={features} brandColor={brandColor} />
      </Sequence>

      <Sequence from={540} durationInFrames={60}>
        <DownloadCTA appName={appName} brandColor={brandColor} />
      </Sequence>
    </AbsoluteFill>
  );
};

const ProblemScene: React.FC<{problemText: string}> = ({problemText}) => {
  const frame = useCurrentFrame();
  const opacity = interpolate(frame, [0, 20], [0, 1], {extrapolateRight: 'clamp'});
  
  // Shake effect for emphasis
  const shake = frame < 60 ? Math.sin(frame * 0.5) * 2 : 0;

  return (
    <AbsoluteFill style={{
      justifyContent: 'center',
      alignItems: 'center',
      padding: 80,
    }}>
      <div style={{
        opacity,
        transform: `translateX(${shake}px)`,
        textAlign: 'center',
      }}>
        <div style={{fontSize: 80, marginBottom: 30}}>😩</div>
        <h1 style={{
          fontSize: 52,
          fontWeight: 700,
          color: 'white',
          lineHeight: 1.3,
        }}>
          {problemText}
        </h1>
      </div>
    </AbsoluteFill>
  );
};

const IntroScene: React.FC<{
  appName: string;
  tagline: string;
  logoUrl: string;
  brandColor: string;
}> = ({appName, tagline, logoUrl, brandColor}) => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  
  const scale = spring({frame, fps, config: {damping: 12}});

  return (
    <AbsoluteFill style={{
      justifyContent: 'center',
      alignItems: 'center',
    }}>
      <div style={{transform: `scale(${scale})`, textAlign: 'center'}}>
        <Img src={logoUrl} style={{height: 100, marginBottom: 30}} />
        <h1 style={{
          fontSize: 64,
          fontWeight: 800,
          color: 'white',
          marginBottom: 15,
        }}>
          {appName}
        </h1>
        <p style={{
          fontSize: 32,
          color: brandColor,
        }}>
          {tagline}
        </p>
      </div>
    </AbsoluteFill>
  );
};

const DemoScene: React.FC<{screens: string[]; brandColor: string}> = ({screens, brandColor}) => {
  const frame = useCurrentFrame();
  
  // Calculate which screen to show
  const framesPerScreen = 300 / screens.length;
  const currentScreenIndex = Math.min(
    Math.floor(frame / framesPerScreen),
    screens.length - 1
  );
  
  const screenFrame = frame % framesPerScreen;
  const opacity = interpolate(screenFrame, [0, 15], [0, 1], {extrapolateRight: 'clamp'});
  const y = interpolate(screenFrame, [0, 20], [50, 0], {extrapolateRight: 'clamp'});

  return (
    <AbsoluteFill style={{
      justifyContent: 'center',
      alignItems: 'center',
    }}>
      {/* Phone frame */}
      <div style={{
        position: 'relative',
        width: 380,
        height: 780,
        backgroundColor: '#1e293b',
        borderRadius: 50,
        padding: 12,
        boxShadow: `0 0 60px ${brandColor}40`,
      }}>
        {/* Screen */}
        <div style={{
          width: '100%',
          height: '100%',
          borderRadius: 40,
          overflow: 'hidden',
          backgroundColor: '#000',
        }}>
          <Img 
            src={screens[currentScreenIndex]}
            style={{
              width: '100%',
              height: '100%',
              objectFit: 'cover',
              opacity,
              transform: `translateY(${y}px)`,
            }}
          />
        </div>
        
        {/* Notch */}
        <div style={{
          position: 'absolute',
          top: 20,
          left: '50%',
          transform: 'translateX(-50%)',
          width: 150,
          height: 30,
          backgroundColor: '#0f172a',
          borderRadius: 20,
        }} />
      </div>
      
      {/* Screen indicator dots */}
      <div style={{
        position: 'absolute',
        bottom: 100,
        display: 'flex',
        gap: 12,
      }}>
        {screens.map((_, i) => (
          <div
            key={i}
            style={{
              width: 12,
              height: 12,
              borderRadius: 6,
              backgroundColor: i === currentScreenIndex ? brandColor : 'rgba(255,255,255,0.3)',
              transition: 'background-color 0.2s',
            }}
          />
        ))}
      </div>
    </AbsoluteFill>
  );
};

const FeaturesScene: React.FC<{features: string[]; brandColor: string}> = ({features, brandColor}) => {
  const frame = useCurrentFrame();

  return (
    <AbsoluteFill style={{
      justifyContent: 'center',
      alignItems: 'flex-start',
      padding: 80,
    }}>
      {features.map((feature, i) => {
        const delay = i * 20;
        const opacity = interpolate(frame - delay, [0, 15], [0, 1], {extrapolateRight: 'clamp'});
        const x = interpolate(frame - delay, [0, 15], [-30, 0], {extrapolateRight: 'clamp'});
        
        return (
          <div
            key={i}
            style={{
              opacity,
              transform: `translateX(${x}px)`,
              display: 'flex',
              alignItems: 'center',
              gap: 20,
              marginBottom: 30,
            }}
          >
            <div style={{
              width: 50,
              height: 50,
              borderRadius: 25,
              backgroundColor: brandColor,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontSize: 24,
            }}>
              ✓
            </div>
            <span style={{
              fontSize: 36,
              fontWeight: 600,
              color: 'white',
            }}>
              {feature}
            </span>
          </div>
        );
      })}
    </AbsoluteFill>
  );
};

const DownloadCTA: React.FC<{appName: string; brandColor: string}> = ({appName, brandColor}) => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  
  const scale = spring({frame, fps, config: {damping: 10}});

  return (
    <AbsoluteFill style={{
      justifyContent: 'center',
      alignItems: 'center',
    }}>
      <div style={{transform: `scale(${scale})`, textAlign: 'center'}}>
        <h2 style={{
          fontSize: 48,
          fontWeight: 700,
          color: 'white',
          marginBottom: 40,
        }}>
          Download {appName} Today
        </h2>
        
        {/* Store badges */}
        <div style={{display: 'flex', gap: 20, justifyContent: 'center'}}>
          <div style={{
            padding: '15px 30px',
            backgroundColor: 'white',
            borderRadius: 12,
            color: '#000',
            fontSize: 24,
            fontWeight: 600,
          }}>
            App Store
          </div>
          <div style={{
            padding: '15px 30px',
            backgroundColor: 'white',
            borderRadius: 12,
            color: '#000',
            fontSize: 24,
            fontWeight: 600,
          }}>
            Google Play
          </div>
        </div>
      </div>
    </AbsoluteFill>
  );
};
```

## Composition Registration

```tsx
<Composition
  id="AppDemo"
  component={AppDemo}
  durationInFrames={600}
  fps={30}
  width={1080}
  height={1920}
  defaultProps={{
    problemText: 'Tired of losing track of your tasks?',
    appName: 'TaskFlow',
    tagline: 'Your productivity, simplified',
    screens: [
      staticFile('screen1.png'),
      staticFile('screen2.png'),
      staticFile('screen3.png'),
    ],
    features: [
      'Smart task organization',
      'Calendar sync',
      'Team collaboration',
    ],
    brandColor: '#6366f1',
    logoUrl: staticFile('logo.png'),
  }}
/>
```
