{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "product-launch-trailer", "title": "Product Launch Trailer", "description": "Cinematic Product Hunt teaser: pulsing logo, zoom-through transition, 3D bento fly-over, and a confetti version drop.", "dependencies": [ "remotion" ], "files": [ { "path": "registry/remocn/product-launch-trailer/index.tsx", "content": "\"use client\";\n\nimport {\n AbsoluteFill,\n Easing,\n interpolate,\n random,\n Sequence,\n spring,\n useCurrentFrame,\n useVideoConfig,\n} from \"remotion\";\n\nexport interface ProductLaunchTrailerProps {\n logoLabel?: string;\n productName?: string;\n versionLabel?: string;\n accentPeach?: string;\n accentLavender?: string;\n accentMint?: string;\n background?: string;\n speed?: number;\n className?: string;\n}\n\nconst FONT_FAMILY =\n \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\nconst MONO_FAMILY =\n \"var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, monospace\";\n\n// Soft \"Tactile\" easing — used for every camera move and reveal.\nconst SOFT = Easing.bezier(0.16, 1, 0.3, 1);\n\n// Sequence anchors (frames). All later math is derived from these so the\n// trailer is easy to retime in one place.\nconst T = {\n pulseStart: 0,\n pulseEnd: 50,\n zoomStart: 38,\n zoomEnd: 78,\n shellStart: 70,\n flyStart: 110,\n flyEnd: 200,\n outroStart: 190,\n outroEnd: 240,\n};\n\nexport function ProductLaunchTrailer({\n logoLabel = \"R\",\n productName = \"Remocn\",\n versionLabel = \"v1.0 is live\",\n accentPeach = \"#FFB38E\",\n accentLavender = \"#D4B3FF\",\n accentMint = \"#A1EEBD\",\n background = \"#141318\",\n speed = 1,\n className,\n}: ProductLaunchTrailerProps) {\n return (\n \n {/* TODO(audio): TransitionWhoosh @ frame ~38, SuccessChime @ frame ~200 */}\n\n \n \n \n\n \n \n \n\n \n \n \n \n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Phase 1 */\n/* -------------------------------------------------------------------------- */\n\nfunction PulseLogo({\n label,\n name,\n accentLavender,\n background,\n speed,\n}: {\n label: string;\n name: string;\n accentLavender: string;\n background: string;\n speed: number;\n}) {\n const frame = useCurrentFrame() * speed;\n const localDuration = T.zoomEnd - T.pulseStart;\n\n // Soft pulse — sin wave on a slow period, modulating both glow and scale.\n const pulse = (Math.sin(frame / 6) + 1) / 2; // 0..1\n\n // After pulseEnd we hand off to the zoom transition: scale up + fade out.\n const zoomLocal = Math.max(0, frame - (T.zoomStart - T.pulseStart));\n const zoomDuration = T.zoomEnd - T.zoomStart;\n const zoom = interpolate(zoomLocal, [0, zoomDuration], [1, 14], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n easing: Easing.bezier(0.7, 0, 0.84, 0),\n });\n const fade = interpolate(zoomLocal, [zoomDuration * 0.5, zoomDuration], [1, 0], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n\n const baseScale = 0.96 + pulse * 0.04;\n const glowAlpha = 0.35 + pulse * 0.4;\n\n const labelOpacity = interpolate(frame, [4, 18], [0, 1], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n\n return (\n \n \n {/* Logo squircle */}\n \n {label}\n \n\n {/* Product name */}\n \n {name}\n \n \n {/* swallow lint: localDuration kept for reference */}\n {localDuration}\n \n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Phase 2 */\n/* -------------------------------------------------------------------------- */\n\nfunction AppShellFlyover({\n accentPeach,\n accentLavender,\n accentMint,\n background,\n speed,\n flyStartLocal,\n flyEndLocal,\n outroStartLocal,\n}: {\n accentPeach: string;\n accentLavender: string;\n accentMint: string;\n background: string;\n speed: number;\n flyStartLocal: number;\n flyEndLocal: number;\n outroStartLocal: number;\n}) {\n const frame = useCurrentFrame() * speed;\n\n // Shell fade-in — comes in right after the zoom punches through.\n const enter = interpolate(frame, [0, 14], [0, 1], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n easing: SOFT,\n });\n\n // 3D camera fly-over: rx, ry, tz, scale all interpolated with the soft bezier.\n const flyT = interpolate(frame, [flyStartLocal, flyEndLocal], [0, 1], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n easing: SOFT,\n });\n // Outro pull-back blends the camera away.\n const outroT = interpolate(\n frame,\n [outroStartLocal, outroStartLocal + 30],\n [0, 1],\n {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n easing: SOFT,\n },\n );\n\n const rx = interpolate(flyT, [0, 1], [0, -8]) * (1 - outroT);\n const ry = interpolate(flyT, [0, 1], [-6, 6]) * (1 - outroT);\n const tz =\n interpolate(flyT, [0, 1], [-220, 80]) * (1 - outroT) +\n outroT * -360;\n const scaleBoost =\n interpolate(flyT, [0, 1], [0.92, 1.04]) * (1 - outroT) + outroT * 0.84;\n\n // Drop-shadow blur grows when the camera hovers above the cards (tz > 0).\n const shadowBlur = Math.max(20, 28 + tz * 0.4);\n const shadowOpacity = 0.35 + Math.max(0, tz / 180) * 0.35;\n\n return (\n \n \n {/* Faint border to suggest the app frame */}\n \n\n {/* Glass code block on the left */}\n \n \n \n\n {/* Bento grid on the right */}\n \n \n \n \n \n );\n}\n\nfunction FauxCodeBlock({\n accentLavender,\n accentMint,\n}: {\n accentLavender: string;\n accentMint: string;\n}) {\n const frame = useCurrentFrame();\n const lines = [\n { t: \"import { Trailer } from 'remocn'\", c: \"#c4b5fd\" },\n { t: \"\", c: \"\" },\n { t: \"export const Scene = () => (\", c: \"#e4e4e7\" },\n { t: \" \", c: \"#e4e4e7\" },\n { t: \")\", c: \"#e4e4e7\" },\n ];\n\n return (\n \n {/* Title bar */}\n \n {[\"#FFB38E\", \"#FCD34D\", accentMint].map((c) => (\n \n ))}\n \n scene.tsx\n \n \n\n {/* Code lines, staggered in */}\n
\n {lines.map((line, i) => {\n const local = frame - 14 - i * 5;\n const o = interpolate(local, [0, 10], [0, 1], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n const x = interpolate(local, [0, 10], [12, 0], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n return (\n \n \n {i + 1}\n \n \n {line.t || \" \"}\n \n
\n );\n })}\n \n \n );\n}\n\nfunction FauxBentoGrid({\n accentPeach,\n accentLavender,\n accentMint,\n speed,\n}: {\n accentPeach: string;\n accentLavender: string;\n accentMint: string;\n speed: number;\n}) {\n const frame = useCurrentFrame() * speed;\n const { fps } = useVideoConfig();\n\n const cards: Array<{ title: string; body: string; tint: string; span: 1 | 2 }> = [\n { title: \"Animations\", body: \"Spring-tuned\", tint: accentLavender, span: 2 },\n { title: \"Transitions\", body: \"Cinematic\", tint: accentPeach, span: 1 },\n { title: \"Backgrounds\", body: \"Volumetric\", tint: accentMint, span: 1 },\n { title: \"Compositions\", body: \"Production-ready\", tint: accentLavender, span: 2 },\n ];\n\n return (\n \n {cards.map((c, i) => {\n const progress = spring({\n frame: frame - 30 - i * 8,\n fps,\n config: { damping: 14, stiffness: 110, mass: 0.7 },\n durationInFrames: 28,\n });\n return (\n \n \n {c.title}\n \n \n {c.body}\n \n \n );\n })}\n \n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Phase 3 */\n/* -------------------------------------------------------------------------- */\n\nfunction Outro({\n versionLabel,\n accentMint,\n accentPeach,\n accentLavender,\n speed,\n}: {\n versionLabel: string;\n accentMint: string;\n accentPeach: string;\n accentLavender: string;\n speed: number;\n}) {\n const frame = useCurrentFrame() * speed;\n const { fps } = useVideoConfig();\n\n // Heading drops in with a soft spring.\n const drop = spring({\n frame: frame - 6,\n fps,\n config: { damping: 14, stiffness: 110, mass: 0.7 },\n durationInFrames: 28,\n });\n const headingY = interpolate(drop, [0, 1], [-40, 0]);\n const headingOpacity = interpolate(drop, [0, 1], [0, 1]);\n\n // Confetti — lightweight inline burst (no SuccessConfetti import to avoid\n // its full-frame background).\n const particles = Array.from({ length: 70 }, (_, i) => {\n const angle = random(`pl:${i}:a`) * Math.PI * 2;\n const v = 9 + random(`pl:${i}:v`) * 9;\n const sz = 5 + random(`pl:${i}:s`) * 7;\n const f = Math.max(0, frame - 14 - Math.floor(random(`pl:${i}:d`) * 6));\n const cx = 640 + Math.cos(angle) * v * f;\n const cy = 280 + Math.sin(angle) * v * f + 0.4 * f * f;\n const colors = [accentMint, accentPeach, accentLavender, \"#FCD34D\"];\n const color = colors[Math.floor(random(`pl:${i}:c`) * colors.length)];\n const opacity = interpolate(f, [0, 4, 38, 50], [0, 1, 1, 0], {\n extrapolateLeft: \"clamp\",\n extrapolateRight: \"clamp\",\n });\n return { cx, cy, sz, color, opacity, key: i };\n });\n\n return (\n \n {/* Confetti layer */}\n \n {particles.map((p) => (\n \n ))}\n \n\n {/* Heading */}\n \n \n {versionLabel}\n \n \n \n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------------- */\n\nfunction toHex(alpha: number) {\n const clamped = Math.max(0, Math.min(1, alpha));\n return Math.round(clamped * 255)\n .toString(16)\n .padStart(2, \"0\");\n}\n", "type": "registry:component", "target": "components/remocn/product-launch-trailer.tsx" } ], "type": "registry:component" }