fix(videos): replace gradient mockups with real photos in compositions

Use real screenshots and sample photos from apps/videos/public/screenshots/
instead of CSS-gradient PhotoPlaceholder components:

- PipelineFlow: landscape photo for thumbnails with object-position variation
- AiMagicReel: portrait photo for bg removal, B&W photo for colorization,
  landscape for super resolution and restoration segments
- PrivacyPromise: real portrait/landscape/B&W photos in shield, larger padlock
- OneCommand: real app-full.png screenshot instead of wireframe AppMockup,
  increased amber glow to 12%
- CloudVsLocal: real portrait photos for all thumbnails, larger server icons
  (36px), amplified shake (5px) and vibrate (3px), escalated red tint overlay
- ShieldIcon: increased stroke width to 3.5px, added gradient fill
This commit is contained in:
SnapOtter
2026-05-08 18:48:22 +08:00
parent fa352f837b
commit a44214cc6d
6 changed files with 205 additions and 125 deletions
+10 -2
View File
@@ -40,11 +40,19 @@ export const ShieldIcon: React.FC<{
strokeDasharray={strokeDasharray}
strokeDashoffset={strokeDashoffset}
/>
<defs>
<linearGradient id="shield-fill-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="rgba(255,255,255,0.12)" />
<stop offset="100%" stopColor="rgba(255,255,255,0.02)" />
</linearGradient>
</defs>
<path
d={SHIELD_PATH}
stroke="white"
strokeWidth={2.5}
fill={`rgba(255,255,255,${fillOpacity})`}
strokeWidth={3.5}
fill={fillOpacity > 0 ? "url(#shield-fill-grad)" : "none"}
fillOpacity={fillOpacity > 0 ? fillOpacity * 12 : 0}
strokeDasharray={strokeDasharray}
strokeDashoffset={strokeDashoffset}
strokeLinecap="round"
@@ -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 */}
<div
{/* "Before" layer: real portrait photo */}
<Img
src={staticFile("screenshots/sample-photo-portrait.jpg")}
style={{
position: "absolute",
inset: 0,
backgroundImage: patternBg,
backgroundSize: "24px 24px",
backgroundColor: "hsl(30, 20%, 35%)",
width: "100%",
height: "100%",
objectFit: "cover",
}}
>
{/* Silhouette subject */}
<div
style={{
position: "absolute",
inset: 0,
backgroundImage: silhouetteGradient,
backgroundRepeat: "no-repeat",
}}
/>
</div>
/>
{/* "After" layer: checkerboard + subject only */}
{/* "After" layer: checkerboard + portrait (revealed via clip) */}
<div
style={{
position: "absolute",
@@ -222,16 +204,18 @@ const SegmentBackgroundRemoval: React.FC = () => {
style={{
position: "absolute",
inset: 0,
background: CHECKERBOARD_BG,
background: checkerBg,
}}
/>
{/* Subject only */}
<div
{/* Subject portrait on top of checkerboard */}
<Img
src={staticFile("screenshots/sample-photo-portrait.jpg")}
style={{
position: "absolute",
inset: 0,
backgroundImage: silhouetteGradient,
backgroundRepeat: "no-repeat",
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
@@ -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) */}
<div
{/* B&W layer (on top, real B&W photo) */}
<Img
src={staticFile("screenshots/sample-photo-bw.jpg")}
style={{
position: "absolute",
inset: 0,
background: `linear-gradient(135deg, hsl(30, 60%, 65%) 0%, hsl(200, 50%, 55%) 50%, hsl(340, 55%, 60%) 100%)`,
filter: "grayscale(1)",
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
{/* Color layer (revealed underneath via clip) */}
{/* Color layer (revealed via clip, using portrait as color version) */}
<div
style={{
position: "absolute",
inset: 0,
background: `linear-gradient(135deg, hsl(30, 72%, 65%) 0%, hsl(200, 60%, 55%) 50%, hsl(340, 66%, 60%) 100%)`,
filter: "saturate(1.2)",
clipPath: `inset(0 ${100 - waveProgress}% 0 0)`,
}}
/>
>
<Img
src={staticFile("screenshots/sample-photo-portrait.jpg")}
style={{
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
objectFit: "cover",
filter: "saturate(1.2)",
}}
/>
</div>
{/* 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 = () => {
))}
</div>
{/* Smooth "resolved" version */}
{/* Smooth "resolved" version - actual landscape photo */}
{showSmooth && (
<div
<Img
src={staticFile("screenshots/sample-photo-landscape.jpg")}
style={{
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
objectFit: "cover",
borderRadius: 8,
background: `linear-gradient(135deg, hsl(25, 55%, 50%) 0%, hsl(40, 50%, 55%) 50%, hsl(30, 60%, 45%) 100%)`,
opacity: smoothOpacity,
border: "1.5px solid rgba(255,255,255,0.15)",
boxShadow: "0 8px 24px rgba(0,0,0,0.3)",
@@ -630,8 +629,8 @@ const SegmentRestoration: React.FC = () => {
});
// 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 (
<AbsoluteFill style={{ background: COLOR.dark }}>
@@ -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 */}
<div
{/* Base photo - real landscape */}
<Img
src={staticFile("screenshots/sample-photo-landscape.jpg")}
style={{
position: "absolute",
inset: 0,
background: `linear-gradient(135deg, ${bgFrom} 0%, ${bgTo} 100%)`,
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
@@ -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,
}}
>
<PhotoPlaceholder width={thumbSize} height={thumbSize} hue={30} />
<Img
src={staticFile("screenshots/sample-photo-landscape.jpg")}
style={{
width: thumbSize,
height: thumbSize,
objectFit: "cover",
borderRadius: 6,
border: "1.5px solid rgba(255,255,255,0.15)",
boxShadow: "0 4px 20px rgba(0,0,0,0.4)",
}}
/>
{/* Watermark stamp */}
{stampOpacity > 0 && (
<span
@@ -395,7 +406,7 @@ const SingleFlow: React.FC = () => {
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,
}}
>
<PhotoPlaceholder width={size} height={size} hue={hue} />
<Img
src={staticFile("screenshots/sample-photo-landscape.jpg")}
style={{
width: size,
height: size,
objectFit: "cover",
objectPosition: `${objPosX}% ${objPosY}%`,
borderRadius: 6,
border: "1px solid rgba(255,255,255,0.1)",
}}
/>
</div>
);
};
@@ -601,13 +623,10 @@ export const PipelineFlow: React.FC = () => {
</Sequence>
{/* Act 1 stations (visible throughout until Act 2 takes over) */}
{frame < 40 && (
<>
{STATIONS.map((s, i) => (
<Station key={s.id} station={s} index={i} glowing={false} pulseGlow={false} />
))}
</>
)}
{frame < 40 &&
STATIONS.map((s, i) => (
<Station key={s.id} station={s} index={i} glowing={false} pulseGlow={false} />
))}
{/* Act 2: Single Image Flow (frame 40-170) */}
<Sequence from={40} durationInFrames={130}>
@@ -640,13 +659,11 @@ export const PipelineFlow: React.FC = () => {
</Sequence>
{/* Act 3 stations when not covered by SingleFlow or BatchStream */}
{frame >= 170 && frame < 180 && (
<>
{STATIONS.map((s, i) => (
<Station key={s.id} station={s} index={i} glowing={false} pulseGlow={false} />
))}
</>
)}
{frame >= 170 &&
frame < 180 &&
STATIONS.map((s, i) => (
<Station key={s.id} station={s} index={i} glowing={false} pulseGlow={false} />
))}
{/* Act 4: Result (frame 340-390) */}
{frame >= 340 && (
@@ -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 = () => {
)}
</div>
{/* App mockup (appears during morph) */}
{/* Real app screenshot (appears during morph) */}
{showAppMockup && (
<div
style={{
position: "absolute",
inset: 0,
opacity: appMockupOpacity,
overflow: "hidden",
borderRadius: "0 0 12px 12px",
}}
>
<AppMockup startFrame={260} />
<Img
src={staticFile("screenshots/app-full.png")}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
objectPosition: "top center",
}}
/>
</div>
)}
</TerminalWindow>
@@ -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 }) => (
<svg
width={24}
height={24}
width={36}
height={36}
viewBox="0 0 24 24"
style={{ opacity }}
role="img"
@@ -190,13 +211,17 @@ export const PrivacyPromise: React.FC = () => {
});
return (
<PhotoPlaceholder
key={photo.hue}
width={photo.w}
height={photo.h}
hue={photo.hue}
<Img
key={photo.src}
src={staticFile(photo.src)}
style={{
position: "absolute",
width: photo.w,
height: photo.h,
objectFit: "cover",
borderRadius: 8,
border: "2px solid rgba(255,255,255,0.2)",
boxShadow: "0 8px 24px rgba(0,0,0,0.3)",
left: `calc(50% + ${exitX}px - ${photo.w / 2}px)`,
top: `calc(50% + ${exitY}px - ${photo.h / 2}px)`,
transform: `scale(${entrySpring}) rotate(${rotation}deg)`,
@@ -12,7 +12,6 @@ import {
} from "remotion";
import { ClipReveal } from "@/components/ClipReveal";
import { GrainOverlay } from "@/components/GrainOverlay";
import { PhotoPlaceholder } from "@/components/PhotoPlaceholder";
import { ShieldIcon } from "@/components/ShieldIcon";
import { COLOR } from "@/lib/colors";
import { FONT, TEXT } from "@/lib/fonts";
@@ -35,9 +34,9 @@ const CLOUD_SVG_PATH = "M6 19a5 5 0 0 1-1-9.9A7 7 0 0 1 19.07 11 4.5 4.5 0 0 1 1
const LOCK_SVG_PATH =
"M5 11V7a5 5 0 0 1 10 0v4M3 11h14a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-6a2 2 0 0 1 2-2Z";
const SERVER_SVG_PATH = "M2 2h16v6H2zM2 10h16v6H2zM6 5h.01M6 13h.01";
const _SERVER_SVG_PATH = "M2 2h16v6H2zM2 10h16v6H2zM6 5h.01M6 13h.01";
const EYE_SVG_PATH =
const _EYE_SVG_PATH =
"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z";
/* ------------------------------------------------------------------ */
@@ -487,7 +486,7 @@ export const CloudVsLocal: React.FC = () => {
});
/* 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 && (
<PhotoPlaceholder
width={48}
height={48}
hue={30}
<Img
src={staticFile("screenshots/sample-photo-portrait.jpg")}
style={{
position: "absolute",
width: 48,
height: 48,
objectFit: "cover",
borderRadius: 6,
left: leftPhotoX - 24,
top: leftPhotoY - 24,
opacity: leftPhotoOpacity,
@@ -784,7 +785,7 @@ export const CloudVsLocal: React.FC = () => {
opacity: Math.min(scatterProgress * 2, 1),
}}
>
<ServerIcon size={24} />
<ServerIcon size={36} />
</div>
))}
@@ -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 (
<PhotoPlaceholder
<Img
key={`copy-${i}`}
width={28}
height={28}
hue={30 + i * 20}
src={staticFile("screenshots/sample-photo-portrait.jpg")}
style={{
position: "absolute",
width: 28,
height: 28,
objectFit: "cover",
borderRadius: 4,
left: copyX - 14,
top: copyY - 14,
opacity: scatterProgress,
@@ -865,13 +868,16 @@ export const CloudVsLocal: React.FC = () => {
const py = interpolate(photo.progress, [0, 1], [750, 160]);
if (photo.progress <= 0) return null;
return (
<PhotoPlaceholder
<Img
key={`extra-${photo.index}`}
width={36}
height={36}
hue={photo.hue}
src={staticFile("screenshots/sample-photo-portrait.jpg")}
style={{
position: "absolute",
width: 36,
height: 36,
objectFit: "cover",
objectPosition: `${photo.index * 25}% center`,
borderRadius: 6,
left: px - 18,
top: py - 18,
opacity: photo.progress,
@@ -893,7 +899,7 @@ export const CloudVsLocal: React.FC = () => {
opacity: Math.min(server.scale * 2, 1),
}}
>
<ServerIcon size={22} />
<ServerIcon size={36} />
</div>
))}
@@ -1132,12 +1138,14 @@ export const CloudVsLocal: React.FC = () => {
{/* First photo (Act 2) */}
{frame >= 40 && frame < 80 && (
<PhotoPlaceholder
width={48}
height={48}
hue={140}
<Img
src={staticFile("screenshots/sample-photo-portrait.jpg")}
style={{
position: "absolute",
width: 48,
height: 48,
objectFit: "cover",
borderRadius: 6,
left: rightPhotoX - DIVIDER_X - 24,
top: rightPhotoY - 24,
opacity: rightPhotoOpacity,
@@ -1250,7 +1258,16 @@ export const CloudVsLocal: React.FC = () => {
key={`rphoto-${photo.index}`}
style={{ position: "absolute", left: px, top: py }}
>
<PhotoPlaceholder width={32} height={32} hue={photo.hue} />
<Img
src={staticFile("screenshots/sample-photo-portrait.jpg")}
style={{
width: 32,
height: 32,
objectFit: "cover",
objectPosition: `${photo.index * 25}% center`,
borderRadius: 6,
}}
/>
{photo.checkScale > 0.1 && (
<div style={{ position: "absolute", right: -8, bottom: -8 }}>
<Checkmark size={14} color={COLOR.safe} scale={photo.checkScale} />