diff --git a/feels-promo/public/timeline_dark_medium_voting.png b/feels-promo/public/timeline_dark_medium_voting.png new file mode 100644 index 0000000..f6d62cd Binary files /dev/null and b/feels-promo/public/timeline_dark_medium_voting.png differ diff --git a/feels-promo/public/timeline_light_small_voting.png b/feels-promo/public/timeline_light_small_voting.png new file mode 100644 index 0000000..153ed63 Binary files /dev/null and b/feels-promo/public/timeline_light_small_voting.png differ diff --git a/feels-promo/public/voting_dark_large.png b/feels-promo/public/voting_dark_large.png new file mode 100644 index 0000000..3693d16 Binary files /dev/null and b/feels-promo/public/voting_dark_large.png differ diff --git a/feels-promo/public/voting_light_large.png b/feels-promo/public/voting_light_large.png new file mode 100644 index 0000000..318d861 Binary files /dev/null and b/feels-promo/public/voting_light_large.png differ diff --git a/feels-promo/public/voting_light_medium.png b/feels-promo/public/voting_light_medium.png new file mode 100644 index 0000000..eb00496 Binary files /dev/null and b/feels-promo/public/voting_light_medium.png differ diff --git a/feels-promo/public/voting_light_small.png b/feels-promo/public/voting_light_small.png new file mode 100644 index 0000000..153ed63 Binary files /dev/null and b/feels-promo/public/voting_light_small.png differ diff --git a/feels-promo/src/BackgroundStill.tsx b/feels-promo/src/BackgroundStill.tsx new file mode 100644 index 0000000..faca032 --- /dev/null +++ b/feels-promo/src/BackgroundStill.tsx @@ -0,0 +1,52 @@ +import { AbsoluteFill, Img, staticFile } from "remotion"; + +// Static version of the tiled background (no animation) +export const BackgroundStill: React.FC = () => { + const width = 1080; + const height = 1920; + + const iconSize = 80; + const gap = 40; + const cellSize = iconSize + gap; + + const cols = Math.ceil(width / cellSize) + 4; + const rows = Math.ceil(height / cellSize) + 4; + + return ( + +
+ {[...Array(rows)].map((_, row) => + [...Array(cols)].map((_, col) => { + const staggerX = row % 2 === 0 ? 0 : cellSize / 2; + return ( + + ); + }) + )} +
+
+ ); +}; diff --git a/feels-promo/src/LiveActivityAnimation.tsx b/feels-promo/src/LiveActivityAnimation.tsx new file mode 100644 index 0000000..9584160 --- /dev/null +++ b/feels-promo/src/LiveActivityAnimation.tsx @@ -0,0 +1,280 @@ +import React from "react"; +import { + AbsoluteFill, + interpolate, + useCurrentFrame, + useVideoConfig, + spring, + Easing, +} from "remotion"; + +// Mood colors matching the app +const MOOD_COLORS = { + horrible: "#F44336", + bad: "#FF9800", + average: "#FFC107", + good: "#8BC34A", + great: "#4CAF50", +}; + +// Get mood based on progress +const getMoodForProgress = (progress: number): { name: string; color: string } => { + if (progress < 0.2) return { name: "Horrible", color: MOOD_COLORS.horrible }; + if (progress < 0.4) return { name: "Bad", color: MOOD_COLORS.bad }; + if (progress < 0.6) return { name: "Average", color: MOOD_COLORS.average }; + if (progress < 0.8) return { name: "Good", color: MOOD_COLORS.good }; + return { name: "Great", color: MOOD_COLORS.great }; +}; + +// Flame SVG icon +const FlameIcon: React.FC<{ size: number; color: string }> = ({ size, color }) => ( + + + +); + +export const LiveActivityAnimation: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, durationInFrames, width, height } = useVideoConfig(); + + const targetStreak = 365; + + // Animation timing + const animationStartFrame = fps * 1; // Start after 1 second + const animationEndFrame = durationInFrames - fps * 1; // End 1 second before end + const animationDuration = animationEndFrame - animationStartFrame; + + // Calculate current streak with easing + const rawProgress = interpolate( + frame, + [animationStartFrame, animationEndFrame], + [0, 1], + { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + } + ); + + const currentStreak = Math.round(rawProgress * targetStreak); + const progressPercent = currentStreak / targetStreak; + const mood = getMoodForProgress(progressPercent); + + // Spring animation for the flame icon + const flameScale = spring({ + frame: frame % 15, // Pulse every 15 frames + fps, + config: { + damping: 10, + stiffness: 200, + mass: 0.5, + }, + }); + + const flameScaleValue = interpolate(flameScale, [0, 1], [1, 1.1]); + + // Fade in animation + const fadeIn = interpolate(frame, [0, fps * 0.5], [0, 1], { + extrapolateRight: "clamp", + }); + + return ( + + {/* Live Activity Card */} +
+
+ {/* Streak Indicator */} +
+
+ +
+
+ {currentStreak} +
+
+ day streak +
+
+ + {/* Divider */} +
+ + {/* Status Section */} +
+ {currentStreak > 0 ? ( + <> +
+ {/* Mood Color Circle */} +
+
+
+ Today's mood +
+
+ {mood.name} +
+
+
+ + ) : ( +
+
+ Start your streak! +
+
+ Tap to log your mood +
+
+ )} +
+
+ + {/* Progress Bar Section */} +
+ {/* Progress Bar */} +
+
+
+ + {/* Progress Label */} +
+ 0 + + {currentStreak} / {targetStreak} days + + {targetStreak} +
+
+
+ + + ); +}; diff --git a/feels-promo/src/LiveActivityCard.tsx b/feels-promo/src/LiveActivityCard.tsx new file mode 100644 index 0000000..f11d9f9 --- /dev/null +++ b/feels-promo/src/LiveActivityCard.tsx @@ -0,0 +1,261 @@ +import { interpolate, useCurrentFrame, useVideoConfig, spring, Easing } from "remotion"; + +// Mood colors matching the app +const MOOD_COLORS = { + horrible: "#F44336", + bad: "#FF9800", + average: "#FFC107", + good: "#8BC34A", + great: "#4CAF50", +}; + +// Get mood based on progress +const getMoodForProgress = (progress: number): { name: string; color: string } => { + if (progress < 0.2) return { name: "Horrible", color: MOOD_COLORS.horrible }; + if (progress < 0.4) return { name: "Bad", color: MOOD_COLORS.bad }; + if (progress < 0.6) return { name: "Average", color: MOOD_COLORS.average }; + if (progress < 0.8) return { name: "Good", color: MOOD_COLORS.good }; + return { name: "Great", color: MOOD_COLORS.great }; +}; + +// Flame SVG icon +const FlameIcon: React.FC<{ size: number; color: string }> = ({ size, color }) => ( + + + +); + +interface LiveActivityCardProps { + width: number; + targetStreak?: number; + animationSpeed?: number; // multiplier for animation speed + showProgressBar?: boolean; +} + +export const LiveActivityCard: React.FC = ({ + width, + targetStreak = 365, + animationSpeed = 1, + showProgressBar = false, +}) => { + const frame = useCurrentFrame(); + const { fps, durationInFrames } = useVideoConfig(); + + // Animation timing - use full scene duration + const animationStartFrame = Math.round(fps * 0.3); // Start after 0.3 seconds + const animationEndFrame = durationInFrames - Math.round(fps * 0.2); + + // Calculate current streak with easing + const rawProgress = interpolate( + frame * animationSpeed, + [animationStartFrame, animationEndFrame], + [0, 1], + { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + } + ); + + const currentStreak = Math.round(rawProgress * targetStreak); + const progressPercent = currentStreak / targetStreak; + const mood = getMoodForProgress(progressPercent); + + // Spring animation for the flame icon + const flameScale = spring({ + frame: frame % 15, + fps, + config: { + damping: 10, + stiffness: 200, + mass: 0.5, + }, + }); + + const flameScaleValue = interpolate(flameScale, [0, 1], [1, 1.1]); + + return ( +
+
+ {/* Streak Indicator */} +
+
+ +
+
+ {currentStreak} +
+
+ day streak +
+
+ + {/* Divider */} +
+ + {/* Status Section */} +
+ {currentStreak > 0 ? ( +
+ {/* Mood Color Circle */} +
+
+
+ Today's mood +
+
+ {mood.name} +
+
+
+ ) : ( +
+
+ Start your streak! +
+
+ Tap to log your mood +
+
+ )} +
+
+ + {/* Progress Bar Section */} + {showProgressBar && ( +
+
+
+
+
+ 0 + + {currentStreak} / {targetStreak} days + + {targetStreak} +
+
+ )} +
+ ); +}; diff --git a/feels-promo/src/Root.tsx b/feels-promo/src/Root.tsx index 7eff48b..23dc1ee 100644 --- a/feels-promo/src/Root.tsx +++ b/feels-promo/src/Root.tsx @@ -1,4 +1,4 @@ -import { Composition } from "remotion"; +import { Composition, Still } from "remotion"; import { FeelsPromoV1 } from "./FeelsPromo"; import { ConceptASelfAwareness } from "./ConceptA-SelfAwareness"; import { ConceptBNoJournalJournal } from "./ConceptB-NoJournalJournal"; @@ -13,6 +13,9 @@ import { ConceptIRetroArcade } from "./ConceptI-RetroArcade"; import { ConceptJConspiracy } from "./ConceptJ-Conspiracy"; import { ConceptKSportsCenter } from "./ConceptK-SportsCenter"; import { ConceptLMusical } from "./ConceptL-Musical"; +// Utility animations +import { LiveActivityAnimation } from "./LiveActivityAnimation"; +import { BackgroundStill } from "./BackgroundStill"; export const RemotionRoot: React.FC = () => { const fps = 30; @@ -52,11 +55,11 @@ export const RemotionRoot: React.FC = () => { height={1920} /> - {/* Concept B: The No-Journal Journal (20s) */} + {/* Concept B: The No-Journal Journal (15s) */} { height={1920} /> - {/* Concept G: The Streak Effect (20s) */} + {/* Concept G: The Streak Effect (12s) */} { width={1080} height={1920} /> + + {/* ═══════════════════════════════════════════════════════════════ + UTILITY ANIMATIONS + ═══════════════════════════════════════════════════════════════ */} + + {/* Live Activity Preview - Streak 0 to 365 animation (12s) */} + + + {/* Background Still for export */} + ); }; diff --git a/screens/ai_dark.png b/screens/ai_dark.png new file mode 100644 index 0000000..5aaddf3 Binary files /dev/null and b/screens/ai_dark.png differ diff --git a/screens/aj_light.png b/screens/aj_light.png new file mode 100644 index 0000000..5b2b211 Binary files /dev/null and b/screens/aj_light.png differ diff --git a/screens/insights_dark.png b/screens/insights_dark.png new file mode 100644 index 0000000..0bd7ede Binary files /dev/null and b/screens/insights_dark.png differ diff --git a/screens/insights_light.png b/screens/insights_light.png new file mode 100644 index 0000000..195618b Binary files /dev/null and b/screens/insights_light.png differ diff --git a/screens/timeline_dark_large_voting.png b/screens/timeline_dark_large_voting.png new file mode 100644 index 0000000..5bd7323 Binary files /dev/null and b/screens/timeline_dark_large_voting.png differ diff --git a/screens/timeline_dark_medium_voting.png b/screens/timeline_dark_medium_voting.png new file mode 100644 index 0000000..f6d62cd Binary files /dev/null and b/screens/timeline_dark_medium_voting.png differ diff --git a/screens/timeline_light_large_voting.png b/screens/timeline_light_large_voting.png new file mode 100644 index 0000000..07f0d50 Binary files /dev/null and b/screens/timeline_light_large_voting.png differ diff --git a/screens/timeline_light_medium_voting.png b/screens/timeline_light_medium_voting.png new file mode 100644 index 0000000..114867d Binary files /dev/null and b/screens/timeline_light_medium_voting.png differ diff --git a/screens/voting_dark_large.png b/screens/voting_dark_large.png new file mode 100644 index 0000000..3693d16 Binary files /dev/null and b/screens/voting_dark_large.png differ diff --git a/screens/voting_header.png b/screens/voting_header.png new file mode 100644 index 0000000..1dcfb31 Binary files /dev/null and b/screens/voting_header.png differ diff --git a/screens/voting_light_large.png b/screens/voting_light_large.png new file mode 100644 index 0000000..318d861 Binary files /dev/null and b/screens/voting_light_large.png differ diff --git a/screens/voting_light_medium.png b/screens/voting_light_medium.png new file mode 100644 index 0000000..eb00496 Binary files /dev/null and b/screens/voting_light_medium.png differ diff --git a/screens/voting_light_small.png b/screens/voting_light_small.png new file mode 100644 index 0000000..153ed63 Binary files /dev/null and b/screens/voting_light_small.png differ diff --git a/screens/watch.png b/screens/watch.png new file mode 100644 index 0000000..30117e7 Binary files /dev/null and b/screens/watch.png differ diff --git a/screens/watch_voting_light.png b/screens/watch_voting_light.png new file mode 100644 index 0000000..2d1a0ec Binary files /dev/null and b/screens/watch_voting_light.png differ