← Catalog
Edit Props (live)
IMAGE
config
config
config
config
config
config
config
config
config
Source
288 lines// App Store End Card — the closing hold of an app-preview video.
// App icon → name → tagline → ★ rating → "Download on the App Store"
// badge, each springing/fading in, then held. Designed portrait for the
// App Store (e.g. iPhone 6.9" 1290×2796) but resolution-independent.
//
// Drop a real icon via the `appIcon` IMAGE port; with no icon it renders
// a branded gradient tile with a health pulse mark so the card still reads.
import {
AbsoluteFill,
Img,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from 'remotion';
import { loadFont as loadInter } from '@remotion/google-fonts/Inter';
import { loadFont as loadSpaceGrotesk } from '@remotion/google-fonts/SpaceGrotesk';
const { fontFamily: SPACE_GROTESK } = loadSpaceGrotesk();
const { fontFamily: INTER } = loadInter();
export type AppStoreEndCardProps = {
appIcon: string;
appName: string;
tagline: string;
ctaText: string;
ratingText: string;
accentColor: string;
accentColor2: string;
backgroundColor: string;
textColor: string;
nameFontSize: number;
};
// Apple logo silhouette (viewBox 0 0 384 512).
const APPLE_PATH =
'M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C32.3 141.2-8.4 184.8-8.4 273.1c0 26.1 4.8 53.1 14.4 80.9 12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.8-89.9-61.8-91.8zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z';
function rise(frame: number, start: number, end: number) {
return interpolate(frame, [start, end], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
}
export default function AppStoreEndCard({
appIcon = '',
appName = 'BasedHealth',
tagline = 'AI food scanning & workout coaching',
ctaText = 'Download on the App Store',
ratingText = '4.9',
accentColor = '#C44FE0',
accentColor2 = '#3B4ED9',
backgroundColor = '#060608',
textColor = '#FFFFFF',
nameFontSize = 150,
}: Partial<AppStoreEndCardProps>) {
const frame = useCurrentFrame();
const { fps, width } = useVideoConfig();
// Accept any non-empty src (http(s), data:, staticFile path…).
const hasIcon = typeof appIcon === 'string' && appIcon.trim().length > 0;
// Icon size scales to the frame width so the card looks right at any res.
const iconSize = Math.round(width * 0.24);
const iconRadius = Math.round(iconSize * 0.225); // iOS-ish squircle radius
const iconSpring = spring({
frame,
fps,
config: { damping: 13, stiffness: 150, mass: 0.7 },
});
const iconFloat = Math.sin(frame / 16) * (width * 0.004);
const nameIn = rise(frame, 8, 24);
const taglineIn = rise(frame, 16, 32);
const starsIn = rise(frame, 24, 40);
const ctaIn = rise(frame, 34, 52);
const ctaPulse = 1 + Math.sin(Math.max(0, frame - 52) / 14) * 0.012;
const stars = '★★★★★';
return (
<AbsoluteFill
style={{
backgroundColor,
overflow: 'hidden',
fontFamily: INTER,
}}
>
{/* Brand gradient glow — echoes the app icon (blue ↘ magenta) */}
<AbsoluteFill
style={{
background: `radial-gradient(46% 38% at 18% 100%, ${accentColor2}3D 0%, transparent 66%), radial-gradient(52% 40% at 84% 100%, ${accentColor}42 0%, transparent 66%)`,
}}
/>
{/* Soft glow behind the icon so it pops on black */}
<AbsoluteFill
style={{
background: `radial-gradient(32% 21% at 50% 33%, ${accentColor}22 0%, transparent 70%)`,
}}
/>
<AbsoluteFill
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: `0 ${Math.round(width * 0.08)}px`,
transform: 'translateY(-3%)',
}}
>
{/* ── App icon ── */}
<div
style={{
width: iconSize,
height: iconSize,
borderRadius: iconRadius,
transform: `scale(${iconSpring}) translateY(${iconFloat}px)`,
overflow: 'hidden',
boxShadow: `0 ${iconSize * 0.06}px ${
iconSize * 0.18
}px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.06) inset`,
background: `linear-gradient(150deg, ${accentColor2} 0%, #7B3FD9 52%, ${accentColor} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{hasIcon ? (
<Img
src={appIcon as string}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
) : (
<svg
width={iconSize * 0.62}
height={iconSize * 0.62}
viewBox='0 0 200 200'
fill='none'
>
<polyline
points='22,108 70,108 92,52 120,156 146,96 168,108 182,108'
stroke='#FFFFFF'
strokeWidth={16}
strokeLinecap='round'
strokeLinejoin='round'
opacity={0.96}
/>
</svg>
)}
</div>
{/* ── App name ── */}
<div
style={{
marginTop: Math.round(width * 0.06),
fontFamily: SPACE_GROTESK,
fontSize: nameFontSize,
fontWeight: 700,
color: textColor,
letterSpacing: '-0.03em',
lineHeight: 1,
textAlign: 'center',
opacity: nameIn,
transform: `translateY(${(1 - nameIn) * 38}px)`,
}}
>
{appName}
</div>
{/* ── Tagline ── */}
<div
style={{
marginTop: Math.round(width * 0.028),
fontSize: Math.round(nameFontSize * 0.34),
fontWeight: 500,
color: textColor,
opacity: taglineIn * 0.82,
transform: `translateY(${(1 - taglineIn) * 30}px)`,
textAlign: 'center',
letterSpacing: '0.01em',
maxWidth: '88%',
}}
>
{tagline}
</div>
{/* ── Rating ── */}
{ratingText ? (
<div
style={{
marginTop: Math.round(width * 0.045),
display: 'flex',
alignItems: 'center',
gap: Math.round(width * 0.018),
opacity: starsIn,
transform: `translateY(${(1 - starsIn) * 24}px)`,
}}
>
<span
style={{
color: accentColor,
fontSize: Math.round(nameFontSize * 0.3),
letterSpacing: '0.08em',
}}
>
{stars}
</span>
<span
style={{
color: textColor,
opacity: 0.85,
fontSize: Math.round(nameFontSize * 0.26),
fontWeight: 600,
}}
>
{ratingText}
</span>
</div>
) : null}
{/* ── App Store badge ── */}
<div
style={{
marginTop: Math.round(width * 0.075),
opacity: ctaIn,
transform: `translateY(${(1 - ctaIn) * 28}px) scale(${ctaPulse})`,
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: Math.round(width * 0.022),
background: '#000000',
border: '1.5px solid rgba(255,255,255,0.22)',
borderRadius: Math.round(width * 0.026),
padding: `${Math.round(width * 0.026)}px ${Math.round(
width * 0.05
)}px`,
}}
>
<svg
width={Math.round(nameFontSize * 0.5)}
height={Math.round(nameFontSize * 0.5)}
viewBox='0 0 384 512'
fill='#FFFFFF'
>
<path d={APPLE_PATH} />
</svg>
<div
style={{
display: 'flex',
flexDirection: 'column',
lineHeight: 1.05,
}}
>
<span
style={{
color: '#FFFFFF',
fontSize: Math.round(nameFontSize * 0.2),
fontWeight: 500,
opacity: 0.9,
}}
>
Download on the
</span>
<span
style={{
color: '#FFFFFF',
fontSize: Math.round(nameFontSize * 0.4),
fontWeight: 600,
letterSpacing: '-0.01em',
}}
>
{ctaText.replace(/^Download on the\s*/i, '') || 'App Store'}
</span>
</div>
</div>
</div>
</AbsoluteFill>
</AbsoluteFill>
);
}
Schema (Zod)
import { z } from 'zod';
export const Schema = z.object({
appIcon: z
.string()
.default('https://www.wireflow.ai/brand/basedhealth-icon.png')
.describe(
'$port:image App icon (square PNG). Falls back to a branded tile.'
),
appName: z.string().default('BasedHealth').describe('App name'),
tagline: z
.string()
.default('AI food scanning & workout coaching')
.describe('One-line tagline'),
ctaText: z
.string()
.default('Download on the App Store')
.describe('Call to action / store badge label'),
ratingText: z
.string()
.default('4.9')
.describe('Rating shown next to the stars (blank to hide)'),
accentColor: z
.string()
.default('#C44FE0')
.describe('$style:color Accent / brand color (stars, glow)'),
accentColor2: z
.string()
.default('#3B4ED9')
.describe('$style:color Secondary brand color (gradient glow)'),
backgroundColor: z
.string()
.default('#060608')
.describe('$style:color Background color'),
textColor: z.string().default('#FFFFFF').describe('$style:color Text color'),
nameFontSize: z
.number()
.default(150)
.describe('$style:size App name font size (other type scales off this)'),
});
App Store End Card
app_store_end_cardClosing hold for an app-preview video: app icon, name, tagline, ★ rating and a "Download on the App Store" badge. Portrait, springs in then holds. Drop a real icon via the IMAGE port.
Workflow Inputs (1)
- App icon (square PNG). Falls back to a branded tile.
appIconIMAGE
Config Fields (9)
appNamectaTexttaglinetextColorratingTextaccentColoraccentColor2nameFontSizebackgroundColor
Meta
- Updated
- 7/10/2026