diff --git a/apps/videos/src/components/ShieldIcon.tsx b/apps/videos/src/components/ShieldIcon.tsx index 576cb499..f4a80be0 100644 --- a/apps/videos/src/components/ShieldIcon.tsx +++ b/apps/videos/src/components/ShieldIcon.tsx @@ -40,11 +40,19 @@ export const ShieldIcon: React.FC<{ strokeDasharray={strokeDasharray} strokeDashoffset={strokeDashoffset} /> + + + + + + + 0 ? "url(#shield-fill-grad)" : "none"} + fillOpacity={fillOpacity > 0 ? fillOpacity * 12 : 0} strokeDasharray={strokeDasharray} strokeDashoffset={strokeDashoffset} strokeLinecap="round" diff --git a/apps/videos/src/compositions/features/AiMagicReel.tsx b/apps/videos/src/compositions/features/AiMagicReel.tsx index cf38b68c..5fe440da 100644 --- a/apps/videos/src/compositions/features/AiMagicReel.tsx +++ b/apps/videos/src/compositions/features/AiMagicReel.tsx @@ -1,11 +1,13 @@ import type React from "react"; import { AbsoluteFill, + Img, interpolate, interpolateColors, random, Sequence, spring, + staticFile, useCurrentFrame, useVideoConfig, } from "remotion"; @@ -133,19 +135,8 @@ const SegmentBackgroundRemoval: React.FC = () => { easing: EASE.smooth, }); - // Geometric pattern background - const patternBg = ` - linear-gradient(45deg, rgba(245,158,11,0.15) 25%, transparent 25%), - linear-gradient(-45deg, rgba(245,158,11,0.15) 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, rgba(245,158,11,0.1) 75%), - linear-gradient(-45deg, transparent 75%, rgba(245,158,11,0.1) 75%) - `; - - // Silhouette: a head-and-shoulders shape via gradient - const silhouetteGradient = ` - radial-gradient(ellipse 90px 100px at 50% 30%, hsl(25, 65%, 65%) 0%, hsl(25, 65%, 65%) 70%, transparent 71%), - radial-gradient(ellipse 130px 200px at 50% 70%, hsl(30, 55%, 55%) 0%, hsl(30, 55%, 55%) 70%, transparent 71%) - `; + // Checkerboard background for transparency effect + const checkerBg = CHECKERBOARD_BG; // Scan line particle trail const particles = Array.from({ length: 5 }, (_, i) => { @@ -187,28 +178,19 @@ const SegmentBackgroundRemoval: React.FC = () => { boxShadow: "0 8px 24px rgba(0,0,0,0.3)", }} > - {/* "Before" layer: photo with geometric background */} -
- {/* Silhouette subject */} -
-
+ /> - {/* "After" layer: checkerboard + subject only */} + {/* "After" layer: checkerboard + portrait (revealed via clip) */}
{ style={{ position: "absolute", inset: 0, - background: CHECKERBOARD_BG, + background: checkerBg, }} /> - {/* Subject only */} -
@@ -246,7 +230,7 @@ const SegmentBackgroundRemoval: React.FC = () => { width: 2, height: "100%", background: COLOR.accent, - boxShadow: `0 0 20px 10px rgba(245,158,11,0.3)`, + boxShadow: "0 0 20px 10px rgba(245,158,11,0.3)", zIndex: 10, }} /> @@ -260,7 +244,7 @@ const SegmentBackgroundRemoval: React.FC = () => { key={`scan-p-${i}`} style={{ position: "absolute", - left: photoX + p.x - photoX, + left: p.x, top: p.y, width: 3, height: 3, @@ -358,26 +342,38 @@ const SegmentColorization: React.FC = () => { transform: `scale(${holdScale})`, }} > - {/* Grayscale layer (on top) */} -
- {/* Color layer (revealed underneath via clip) */} + {/* Color layer (revealed via clip, using portrait as color version) */}
+ > + +
{/* Color particles */} {colorParticles.map( @@ -411,11 +407,11 @@ const SegmentColorization: React.FC = () => { const PIXEL_GRID_SIZE = 8; const PIXEL_GAP = 0.5; -/** Generate deterministic colors for the pixel grid */ +/** Generate deterministic colors for the pixel grid (blues and greens from landscape) */ const pixelColors: string[] = Array.from({ length: PIXEL_GRID_SIZE * PIXEL_GRID_SIZE }, (_, i) => { - const hue = random(`pixel-hue-${i}`) * 60 + 15; // warm hues - const sat = 40 + random(`pixel-sat-${i}`) * 30; - const lit = 40 + random(`pixel-lit-${i}`) * 30; + const hue = random(`pixel-hue-${i}`) * 80 + 180; // blues and greens (180-260) + const sat = 35 + random(`pixel-sat-${i}`) * 35; + const lit = 35 + random(`pixel-lit-${i}`) * 30; return `hsl(${hue}, ${sat}%, ${lit}%)`; }); @@ -518,14 +514,17 @@ const SegmentSuperResolution: React.FC = () => { ))}
- {/* Smooth "resolved" version */} + {/* Smooth "resolved" version - actual landscape photo */} {showSmooth && ( -
{ }); // Color shift from sepia to warm - const bgFrom = interpolateColors(frame, [80, 95], ["hsl(35, 40%, 45%)", "hsl(30, 55%, 50%)"]); - const bgTo = interpolateColors(frame, [80, 95], ["hsl(40, 35%, 55%)", "hsl(45, 50%, 58%)"]); + const _bgFrom = interpolateColors(frame, [80, 95], ["hsl(35, 40%, 45%)", "hsl(30, 55%, 50%)"]); + const _bgTo = interpolateColors(frame, [80, 95], ["hsl(40, 35%, 55%)", "hsl(45, 50%, 58%)"]); return ( @@ -653,15 +652,18 @@ const SegmentRestoration: React.FC = () => { borderRadius: 8, overflow: "hidden", boxShadow: "0 8px 24px rgba(0,0,0,0.3)", - filter: `sepia(${sepiaAmount}) contrast(${contrastAmount})`, + filter: `sepia(${sepiaAmount * 0.8}) contrast(${contrastAmount})`, }} > - {/* Base photo gradient */} -
diff --git a/apps/videos/src/compositions/features/PipelineFlow.tsx b/apps/videos/src/compositions/features/PipelineFlow.tsx index 2e509ce7..1c3ca8bd 100644 --- a/apps/videos/src/compositions/features/PipelineFlow.tsx +++ b/apps/videos/src/compositions/features/PipelineFlow.tsx @@ -3,17 +3,18 @@ import { evolvePath } from "@remotion/paths"; import type React from "react"; import { AbsoluteFill, + Img, interpolate, random, Sequence, spring, + staticFile, useCurrentFrame, useVideoConfig, } from "remotion"; import { ClipReveal } from "@/components/ClipReveal"; import { Counter } from "@/components/Counter"; import { GrainOverlay } from "@/components/GrainOverlay"; -import { PhotoPlaceholder } from "@/components/PhotoPlaceholder"; import { COLOR } from "@/lib/colors"; import { FONT, TEXT } from "@/lib/fonts"; import { EASE, SPRING } from "@/lib/motion"; @@ -113,10 +114,10 @@ const Station: React.FC<{ border: `2px solid ${station.color}${glowing || pulseGlow ? "cc" : "66"}`, boxShadow: glowOpacity > 0 - ? `0 0 20px ${station.color}${Math.round(glowOpacity * 255) + ? `0 4px 20px rgba(0,0,0,0.4), 0 0 20px ${station.color}${Math.round(glowOpacity * 255) .toString(16) .padStart(2, "0")}` - : "none", + : "0 4px 20px rgba(0,0,0,0.4)", transform: `scale(${scale})`, display: "flex", flexDirection: "column", @@ -386,7 +387,17 @@ const SingleFlow: React.FC = () => { zIndex: frame >= 108 ? 0 : 5, }} > - + {/* Watermark stamp */} {stampOpacity > 0 && ( { bottom: 2, right: 3, fontFamily: FONT.body, - fontSize: 5, + fontSize: 8, fontWeight: 700, color: `rgba(255,255,255,${stampOpacity})`, whiteSpace: "nowrap", @@ -489,9 +500,10 @@ const BatchThumb: React.FC<{ if (localFrame < 0) return null; - const hue = random(`thumb-hue-${index}`) * 360; const size = 24 + random(`thumb-size-${index}`) * 8; const yOffset = (random(`thumb-y-${index}`) - 0.5) * 30; + const objPosX = Math.round(random(`thumb-opx-${index}`) * 100); + const objPosY = Math.round(random(`thumb-opy-${index}`) * 100); // Fast traversal: cover pipeline in ~28 frames per thumb const thumbX = interpolate(localFrame, [0, 28], [THUMB_START_X, FOLDER_X + 10], { @@ -517,7 +529,17 @@ const BatchThumb: React.FC<{ zIndex: 4, }} > - +
); }; @@ -601,13 +623,10 @@ export const PipelineFlow: React.FC = () => { {/* Act 1 stations (visible throughout until Act 2 takes over) */} - {frame < 40 && ( - <> - {STATIONS.map((s, i) => ( - - ))} - - )} + {frame < 40 && + STATIONS.map((s, i) => ( + + ))} {/* Act 2: Single Image Flow (frame 40-170) */} @@ -640,13 +659,11 @@ export const PipelineFlow: React.FC = () => { {/* Act 3 stations when not covered by SingleFlow or BatchStream */} - {frame >= 170 && frame < 180 && ( - <> - {STATIONS.map((s, i) => ( - - ))} - - )} + {frame >= 170 && + frame < 180 && + STATIONS.map((s, i) => ( + + ))} {/* Act 4: Result (frame 340-390) */} {frame >= 340 && ( diff --git a/apps/videos/src/compositions/hero/OneCommand.tsx b/apps/videos/src/compositions/hero/OneCommand.tsx index ef818200..6d0018d1 100644 --- a/apps/videos/src/compositions/hero/OneCommand.tsx +++ b/apps/videos/src/compositions/hero/OneCommand.tsx @@ -1,13 +1,14 @@ import type React from "react"; import { AbsoluteFill, + Img, interpolate, interpolateColors, spring, + staticFile, useCurrentFrame, useVideoConfig, } from "remotion"; -import { AppMockup } from "@/components/AppMockup"; import { ClipReveal } from "@/components/ClipReveal"; import { GrainOverlay } from "@/components/GrainOverlay"; import { TerminalWindow } from "@/components/TerminalWindow"; @@ -189,7 +190,7 @@ export const OneCommand: React.FC = () => { /* ================================================================ */ /* Amber glow behind browser */ - const glowOpacity = interpolate(frame, [320, 340], [0, 0.05], { + const glowOpacity = interpolate(frame, [320, 340], [0, 0.12], { extrapolateLeft: "clamp", extrapolateRight: "clamp", }); @@ -304,16 +305,26 @@ export const OneCommand: React.FC = () => { )}
- {/* App mockup (appears during morph) */} + {/* Real app screenshot (appears during morph) */} {showAppMockup && (
- +
)} diff --git a/apps/videos/src/compositions/hero/PrivacyPromise.tsx b/apps/videos/src/compositions/hero/PrivacyPromise.tsx index e729c637..1d253e6b 100644 --- a/apps/videos/src/compositions/hero/PrivacyPromise.tsx +++ b/apps/videos/src/compositions/hero/PrivacyPromise.tsx @@ -1,8 +1,15 @@ import type React from "react"; -import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion"; +import { + AbsoluteFill, + Img, + interpolate, + spring, + staticFile, + useCurrentFrame, + useVideoConfig, +} from "remotion"; import { ClipReveal } from "@/components/ClipReveal"; import { GrainOverlay } from "@/components/GrainOverlay"; -import { PhotoPlaceholder } from "@/components/PhotoPlaceholder"; import { ShieldIcon } from "@/components/ShieldIcon"; import { TEXT } from "@/lib/fonts"; import { EASE, SPRING } from "@/lib/motion"; @@ -12,9 +19,23 @@ import { EASE, SPRING } from "@/lib/motion"; /* ------------------------------------------------------------------ */ const PHOTOS = [ - { hue: 210, w: 180, h: 135, fromX: -300, fromY: -250, delay: 0 }, - { hue: 150, w: 160, h: 120, fromX: 320, fromY: 280, delay: 4 }, - { hue: 280, w: 150, h: 115, fromX: 280, fromY: -260, delay: 8 }, + { + src: "screenshots/sample-photo-portrait.jpg", + w: 180, + h: 135, + fromX: -300, + fromY: -250, + delay: 0, + }, + { + src: "screenshots/sample-photo-landscape.jpg", + w: 160, + h: 120, + fromX: 320, + fromY: 280, + delay: 4, + }, + { src: "screenshots/sample-photo-bw.jpg", w: 150, h: 115, fromX: 280, fromY: -260, delay: 8 }, ] as const; /* ------------------------------------------------------------------ */ @@ -23,8 +44,8 @@ const PHOTOS = [ const PadlockIcon: React.FC<{ opacity: number }> = ({ opacity }) => ( { }); return ( - { }); /* Shield outline */ - const shieldDrawProgress = interpolate(frame, [135, 160], [0, 1], { + const _shieldDrawProgress = interpolate(frame, [135, 160], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: EASE.enter, @@ -559,14 +558,14 @@ export const CloudVsLocal: React.FC = () => { return { ...badge, scale: s }; }); - /* Left side red tint escalation */ - const escalatedLeftTint = interpolate(frame, [160, 320], [0.03, 0.15], { + /* Left side red tint escalation (starts at 5%, escalates to 20% by Act 4) */ + const escalatedLeftTint = interpolate(frame, [160, 320], [0.05, 0.2], { extrapolateLeft: "clamp", extrapolateRight: "clamp", }); /* Shake animation (frame 300-320) */ - const shakeX = frame >= 300 && frame <= 320 ? Math.sin(frame * 2.5) * 2 : 0; + const shakeX = frame >= 300 && frame <= 320 ? Math.sin(frame * 2.5) * 5 : 0; /* Right: extra photos arrive calmly */ const rightExtraPhotos = Array.from({ length: 4 }, (_, i) => { @@ -593,13 +592,13 @@ export const CloudVsLocal: React.FC = () => { /* ================================================================ */ /* Left: deeper red tint */ - const peakLeftTint = interpolate(frame, [320, 360], [0.15, 0.18], { + const peakLeftTint = interpolate(frame, [320, 360], [0.2, 0.25], { extrapolateLeft: "clamp", extrapolateRight: "clamp", }); /* Left vibrate */ - const vibrateX = frame >= 320 && frame < 420 ? Math.sin(frame * 3) * 1.5 : 0; + const vibrateX = frame >= 320 && frame < 420 ? Math.sin(frame * 3) * 3 : 0; /* Right glow */ const rightGlowOpacity = interpolate(frame, [360, 400], [0, 0.12], { @@ -736,12 +735,14 @@ export const CloudVsLocal: React.FC = () => { {/* Photo thumbnail (Act 2) */} {frame >= 40 && frame < 85 && ( - { opacity: Math.min(scatterProgress * 2, 1), }} > - +
))} @@ -794,13 +795,15 @@ export const CloudVsLocal: React.FC = () => { const copyX = interpolate(scatterProgress, [0, 1], [400, server.x]); const copyY = interpolate(scatterProgress, [0, 1], [160, server.y - 30]); return ( - { const py = interpolate(photo.progress, [0, 1], [750, 160]); if (photo.progress <= 0) return null; return ( - { opacity: Math.min(server.scale * 2, 1), }} > - +
))} @@ -1132,12 +1138,14 @@ export const CloudVsLocal: React.FC = () => { {/* First photo (Act 2) */} {frame >= 40 && frame < 80 && ( - { key={`rphoto-${photo.index}`} style={{ position: "absolute", left: px, top: py }} > - + {photo.checkScale > 0.1 && (