diff --git a/feels-promo/public/ai_dark.png b/feels-promo/public/ai_dark.png new file mode 100644 index 0000000..5aaddf3 Binary files /dev/null and b/feels-promo/public/ai_dark.png differ diff --git a/feels-promo/public/vote_dark_medium_notvoted.png b/feels-promo/public/vote_dark_medium_notvoted.png new file mode 100644 index 0000000..d10494b Binary files /dev/null and b/feels-promo/public/vote_dark_medium_notvoted.png differ diff --git a/feels-promo/public/vote_light_small_notvoted.png b/feels-promo/public/vote_light_small_notvoted.png new file mode 100644 index 0000000..896669b Binary files /dev/null and b/feels-promo/public/vote_light_small_notvoted.png differ diff --git a/feels-promo/public/voting_header.png b/feels-promo/public/voting_header.png new file mode 100644 index 0000000..1dcfb31 Binary files /dev/null and b/feels-promo/public/voting_header.png differ diff --git a/feels-promo/public/watch.png b/feels-promo/public/watch.png new file mode 100644 index 0000000..30117e7 Binary files /dev/null and b/feels-promo/public/watch.png differ diff --git a/feels-promo/src/ConceptB-NoJournalJournal.tsx b/feels-promo/src/ConceptB-NoJournalJournal.tsx index 3f6712d..7f51c08 100644 --- a/feels-promo/src/ConceptB-NoJournalJournal.tsx +++ b/feels-promo/src/ConceptB-NoJournalJournal.tsx @@ -10,13 +10,13 @@ import { } from "remotion"; // Shared tiled background component -const TiledIconBackground: React.FC<{ color?: string }> = ({ +const TiledIconBackground: React.FC<{ color?: string; iconSize?: number }> = ({ color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", + iconSize = 96, }) => { const frame = useCurrentFrame(); const { width, height } = useVideoConfig(); - const iconSize = 80; const gap = 40; const cellSize = iconSize + gap; @@ -66,22 +66,26 @@ const TiledIconBackground: React.FC<{ color?: string }> = ({ ); }; -// Scene 1: Problem - "Journaling is hard" +// Scene 1: "Journaling takes time... but what if it doesn't?" const ProblemScene: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); - const textProgress = spring({ frame, fps, config: { damping: 200 } }); - const textOpacity = interpolate(textProgress, [0, 1], [0, 1]); + const line1Progress = spring({ frame, fps, config: { damping: 200 } }); + const line1Opacity = interpolate(line1Progress, [0, 1], [0, 1]); - const strikeProgress = interpolate(frame, [fps * 1.5, fps * 2], [0, 1], { + const line2Progress = spring({ + frame: frame - fps * 1.5, + fps, + config: { damping: 200 }, + }); + const line2Opacity = interpolate(line2Progress, [0, 1], [0, 1], { extrapolateLeft: "clamp", - extrapolateRight: "clamp", }); return ( - +
{ padding: 60, }} > -
+
+ Journaling takes time. +
+ +
+ But what if it doesn't? +
+
+ + ); +}; + +// Scene 2: Single tap voting - Widget, Watch, In-App +const SingleTapScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width } = useVideoConfig(); + + // Title animations (matching other scenes) + const line1Progress = spring({ frame, fps, config: { damping: 200 } }); + const line1Opacity = interpolate(line1Progress, [0, 1], [0, 1]); + + const line2Progress = spring({ + frame: frame - fps * 0.5, + fps, + config: { damping: 200 }, + }); + const line2Opacity = interpolate(line2Progress, [0, 1], [0, 1], { + extrapolateLeft: "clamp", + }); + + // Staggered label animations only + const watchLabelProgress = spring({ + frame: frame - fps * 0.8, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + const inAppLabelProgress = spring({ + frame: frame - fps * 1.2, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + const widgetLabelProgress = spring({ + frame: frame - fps * 1.6, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + + const watchSize = width * 0.27; + const widgetSize = width * 0.33; + + return ( + + + +
+ {/* Two-line title at top */} +
- Journaling is hard + One tap is
- {/* Strike-through line */}
+ all it takes. +
+
+ + {/* Three device rows - full width, image at 33%, label at 66% */} +
+ {/* Watch row */} +
+ {/* Image at 33% */} +
+
+ + +
+
+ {/* Label at 66% */} +
+ Watch +
+
+ + {/* In-App row */} +
+ {/* Image at 33% */} +
+ +
+ {/* Label at 66% */} +
+ In-App +
+
+ + {/* Widget row */} +
+ {/* Image at 33% */} +
+ +
+ {/* Label at 66% */} +
+ Widget +
+
+
+
+
+ ); +}; + +// Scene 3: Year view insights +const InsightsScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + const line1Progress = spring({ frame, fps, config: { damping: 200 } }); + const line1Opacity = interpolate(line1Progress, [0, 1], [0, 1]); + + const line2Progress = spring({ + frame: frame - fps * 0.8, + fps, + config: { damping: 200 }, + }); + const line2Opacity = interpolate(line2Progress, [0, 1], [0, 1], { + extrapolateLeft: "clamp", + }); + + const imageProgress = spring({ + frame: frame - fps * 0.3, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + const phoneHeight = height * 0.8; + + return ( + + + + {/* Text - positioned on left */} +
+
+ All the insight. +
+
+ Fraction of the time. +
+
+ + {/* Phone with year screenshot - positioned on right, 50% off-screen */} +
+
+ {/* Phone frame as size reference */} + + {/* Screenshot positioned to align notch */} + + {/* Phone frame on top */} +
@@ -130,352 +541,132 @@ const ProblemScene: React.FC = () => { ); }; -// Scene 2: Solution - "Feels is easy" -const SolutionScene: React.FC = () => { +// Scene 4: AI Insights - Privacy focused +const PrivacyScene: React.FC = () => { const frame = useCurrentFrame(); - const { fps } = useVideoConfig(); + const { fps, width, height } = useVideoConfig(); - const textProgress = spring({ frame, fps, config: { damping: 200 } }); - const textOpacity = interpolate(textProgress, [0, 1], [0, 1]); - const textScale = interpolate(textProgress, [0, 1], [0.8, 1]); + const line1Progress = spring({ frame, fps, config: { damping: 200 } }); + const line1Opacity = interpolate(line1Progress, [0, 1], [0, 1]); - const tapProgress = spring({ - frame: frame - 20, - fps, - config: { damping: 15, stiffness: 100 }, - }); - - const checkProgress = spring({ - frame: frame - 40, - fps, - config: { damping: 10, stiffness: 100 }, - }); - - return ( - - - -
-
- Feels is easy -
- - {/* Tap animation sequence */} -
-
- 😊 -
-
- → -
-
- ✅ -
-
- Done! -
-
-
-
- ); -}; - -// Scene 3: Comparison - Complex vs Simple -const ComparisonScene: React.FC = () => { - const frame = useCurrentFrame(); - const { fps } = useVideoConfig(); - - const leftProgress = spring({ - frame, - fps, - config: { damping: 12, stiffness: 80 }, - }); - - const rightProgress = spring({ - frame: frame - 15, - fps, - config: { damping: 12, stiffness: 80 }, - }); - - const vsProgress = spring({ - frame: frame - 30, - fps, - config: { damping: 10, stiffness: 100 }, - }); - - return ( - - - -
- {/* Left: Other apps */} -
-
-
-
-
-
-
-
- Other apps: 5+ minutes -
-
- - {/* VS */} -
- VS -
- - {/* Right: Feels */} -
-
- {["😢", "😕", "😐", "😊", "😄"].map((emoji, i) => ( -
- {emoji} -
- ))} -
-
- Feels: 5 seconds -
-
-
- - ); -}; - -// Scene 4: Benefit -const BenefitScene: React.FC = () => { - const frame = useCurrentFrame(); - const { fps } = useVideoConfig(); - - const textProgress = spring({ frame, fps, config: { damping: 200 } }); - const textOpacity = interpolate(textProgress, [0, 1], [0, 1]); - - const subProgress = spring({ - frame: frame - 15, + const line2Progress = spring({ + frame: frame - fps * 0.8, fps, config: { damping: 200 }, }); + const line2Opacity = interpolate(line2Progress, [0, 1], [0, 1], { + extrapolateLeft: "clamp", + }); + + const imageProgress = spring({ + frame: frame - fps * 0.3, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + const glowIntensity = interpolate(Math.sin(frame * 0.08), [-1, 1], [0.2, 0.5]); + + const phoneWidth = width * 0.8; return ( - + + {/* Text - positioned at top */}
- Same insights. -
- Fraction of effort. + Insights that
- Finally, tracking that sticks. + belong to you. +
+
+ + {/* Phone with AI screenshot - positioned at bottom, 30% cut off */} +
+
+ {/* Phone frame as size reference */} + + {/* Screenshot positioned to align notch */} + + {/* Phone frame on top */} +
@@ -493,6 +684,12 @@ const CTAScene: React.FC = () => { config: { damping: 10, stiffness: 80 }, }); + const textProgress = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]); return ( @@ -502,13 +699,13 @@ const CTAScene: React.FC = () => { alignItems: "center", }} > - +
{
Feels
- The no-journal journal + One tap. +
+ All the feels.
); }; -// Main composition - 20 seconds total +// Main composition - 15 seconds total (5 scenes x 3 seconds each) export const ConceptBNoJournalJournal: React.FC = () => { const { fps } = useVideoConfig(); + const sceneDuration = Math.round(3 * fps); return ( - + - - + + - - + + - - + + - +