diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c26dbbc..11c12cb 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -21,7 +21,20 @@ "Bash( comm -23 /tmp/code_keys.txt /tmp/xcstrings_keys.txt)", "Bash( comm -13 /tmp/code_keys.txt /tmp/xcstrings_keys.txt)", "Bash(xargs cat:*)", - "Bash(xcrun simctl:*)" + "Bash(xcrun simctl:*)", + "Bash(curl:*)", + "Bash(open:*)", + "Bash(npx skills:*)", + "Bash(npx create-video@latest:*)", + "Bash(npm install:*)", + "Bash(npm run build:*)", + "Bash(npx remotion render:*)", + "Bash(ffmpeg:*)", + "Bash(sips:*)", + "Bash(unzip:*)", + "Bash(plutil:*)", + "Bash(done)", + "Bash(for:*)" ], "ask": [ "Bash(git commit:*)", diff --git a/.claude/skills/remotion-best-practices b/.claude/skills/remotion-best-practices new file mode 120000 index 0000000..4b38dbf --- /dev/null +++ b/.claude/skills/remotion-best-practices @@ -0,0 +1 @@ +../../.agents/skills/remotion-best-practices \ No newline at end of file diff --git a/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist b/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist index 218c6d9..cb40cc7 100644 --- a/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Feels.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,12 +12,12 @@ Feels (macOS).xcscheme_^#shared#^_ orderHint - 3 + 2 Feels Watch App.xcscheme_^#shared#^_ orderHint - 2 + 3 FeelsWidgetExtension.xcscheme_^#shared#^_ diff --git a/feels-promo/.claude/settings.local.json b/feels-promo/.claude/settings.local.json new file mode 100644 index 0000000..0a37a01 --- /dev/null +++ b/feels-promo/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "WebSearch" + ] + } +} diff --git a/feels-promo/STORYBOARD-V2-CONCEPTS.md b/feels-promo/STORYBOARD-V2-CONCEPTS.md new file mode 100644 index 0000000..9ba3665 --- /dev/null +++ b/feels-promo/STORYBOARD-V2-CONCEPTS.md @@ -0,0 +1,270 @@ +# Feels Promo Video - Future Concepts + +This document outlines additional promotional video concepts to target different user segments and marketing channels. + +--- + +## Concept A: "30 Seconds to Self-Awareness" + +**Duration**: 30 seconds +**Target**: New Year's resolution crowd, mental health awareness campaigns +**Tone**: Motivational, aspirational + +### Scene Flow + +``` +[0-5s] HOOK: "What if understanding yourself took 5 seconds a day?" +[5-12s] DEMO: Quick-fire mood logging from widget, watch, app +[12-18s] PAYOFF: Calendar filling with colors → Year View heatmap +[18-25s] INSIGHTS: AI insight card appearing with personalized message +[25-30s] CTA: App icon + "Start today. Understand tomorrow." +``` + +### Key Visuals +- Time-lapse of mood entries accumulating +- Split-screen: logging (left) → patterns emerging (right) +- Focus on the "aha moment" when patterns become visible + +--- + +## Concept B: "The No-Journal Journal" + +**Duration**: 20 seconds +**Target**: People who've tried and abandoned journaling apps +**Tone**: Relatable, humorous, practical + +### Scene Flow + +``` +[0-4s] PROBLEM: Text overlay "Journaling is hard" → crossed out +[4-8s] SOLUTION: "Feels is easy" → one tap → done animation +[8-14s] COMPARISON: Journal app (complex) vs Feels (simple) +[14-18s] BENEFIT: Same insights, fraction of effort +[18-20s] CTA: "Finally, tracking that sticks" +``` + +### Key Visuals +- Side-by-side comparison (typing vs tapping) +- Exaggerated "effort meter" going from high to low +- Calendar streak growing effortlessly + +--- + +## Concept C: "Your Year in Feelings" + +**Duration**: 15 seconds +**Target**: Social media (Instagram Reels, TikTok) +**Tone**: Aesthetic, shareable, FOMO-inducing + +### Scene Flow + +``` +[0-3s] HOOK: Beautiful Year View heatmap filling in +[3-8s] ZOOM: Into specific days, showing mood details +[8-12s] SHARE: Year-in-review card being generated +[12-15s] CTA: "What will YOUR year look like?" +``` + +### Key Visuals +- Satisfying animation of colors appearing +- Premium, aesthetic design emphasis +- The shareable card as social proof + +--- + +## Concept D: "Always There" + +**Duration**: 25 seconds +**Target**: Apple ecosystem users +**Tone**: Seamless, integrated, premium + +### Scene Flow + +``` +[0-5s] SCENE 1: Morning - iPhone widget tap while coffee +[5-10s] SCENE 2: Midday - Apple Watch glance at work +[10-15s] SCENE 3: Evening - Lock Screen Live Activity +[15-20s] SCENE 4: Night - Quick note before bed +[20-25s] CTA: "Your mood tracker, wherever you are" +``` + +### Key Visuals +- Lifestyle shots (hands, devices in context) +- Seamless device transitions +- Emphasis on Apple ecosystem integration + +--- + +## Concept E: "Make It Yours" + +**Duration**: 20 seconds +**Target**: Customization-focused users, Gen Z +**Tone**: Expressive, creative, personal + +### Scene Flow + +``` +[0-5s] RAPID MONTAGE: Different themes flashing by +[5-10s] FOCUS: One theme in detail, showing variations +[10-15s] ICONS: Different mood icon packs cycling +[15-18s] WIDGETS: Custom widgets on home screen +[18-20s] CTA: "12 themes. Unlimited you." +``` + +### Key Visuals +- Quick cuts between dramatically different looks +- Color explosions during theme transitions +- Focus on personality expression + +--- + +## Concept F: "Privacy First" + +**Duration**: 15 seconds +**Target**: Privacy-conscious users +**Tone**: Trustworthy, secure, Apple-aligned + +### Scene Flow + +``` +[0-3s] TEXT: "Your feelings are personal" +[3-8s] VISUAL: Data flowing into iCloud (encrypted visual) +[8-12s] HEALTH: Apple Health integration badge +[12-15s] CTA: "Private. Secure. Yours." +``` + +### Key Visuals +- Shield/lock iconography +- iCloud and HealthKit logos +- No server, no tracking messaging + +--- + +## Concept G: "The Streak Effect" + +**Duration**: 20 seconds +**Target**: Gamification lovers, habit builders +**Tone**: Motivational, game-like, rewarding + +### Scene Flow + +``` +[0-5s] HOOK: Streak counter at 0 → building up +[5-12s] BUILD: Calendar days filling, streak growing +[12-16s] CELEBRATE: Milestone celebration animation +[16-20s] CTA: "How long can you go?" +``` + +### Key Visuals +- Streak number incrementing dramatically +- Celebration particles/confetti +- Progress bar filling satisfaction + +--- + +## Seasonal Variants + +### New Year (January) +**Hook**: "New year, new understanding of yourself" +**Visual**: Year View starting fresh, first entry being made +**CTA**: "Start your journey January 1st" + +### Mental Health Month (May) +**Hook**: "Small check-ins. Big self-awareness." +**Visual**: Gentle, soft colors, supportive messaging +**CTA**: "Your mental wellness companion" + +### Back to School (August) +**Hook**: "Track your journey through the semester" +**Visual**: Student-relatable scenarios +**CTA**: "The simplest self-care routine" + +### Holiday Season (December) +**Hook**: "Reflect on your year. All of it." +**Visual**: Year in review, sharing with loved ones +**CTA**: "See how far you've come" + +--- + +## Platform-Specific Cuts + +### App Store Preview (30s max) +- Full feature showcase +- High production value +- Sound on expected + +### Instagram Reels (15-30s) +- Vertical format +- Hook in first 2 seconds +- Text overlays (sound off friendly) +- Trending audio compatible + +### TikTok (15-60s) +- Casual, authentic feel +- Quick cuts +- Trend-adaptable structure + +### YouTube Pre-Roll (6s) +- Ultra-condensed hook +- Single feature focus +- Immediate brand recognition + +### Facebook/Meta (15s) +- Sound off friendly +- Clear text overlays +- Emotional appeal + +--- + +## A/B Testing Priorities + +### Test 1: Hook Variations +- A: "Track your mood in one tap" +- B: "Your mood. Your journey. Your way." +- C: "The simplest mood tracker" + +### Test 2: Feature Focus +- A: Lead with Widgets +- B: Lead with Insights +- C: Lead with Year View + +### Test 3: Visual Style +- A: Lifestyle (hands, real contexts) +- B: Product (device mockups) +- C: Abstract (colors, animations) + +### Test 4: CTA Messaging +- A: "Download now" +- B: "Start your journey" +- C: "Try free for 30 days" + +--- + +## Production Checklist + +### Pre-Production +- [ ] Script final approval +- [ ] Shot list created +- [ ] Assets gathered (screenshots, icons) +- [ ] Music licensed + +### Production +- [ ] Remotion scenes built +- [ ] All animations tuned +- [ ] Preview reviewed on mobile + +### Post-Production +- [ ] Color grading consistent +- [ ] Audio levels balanced +- [ ] Captions/text readable at small sizes +- [ ] Export in all required formats + +### Delivery +- [ ] App Store (1080x1920, H.264) +- [ ] Instagram (1080x1920, <30s) +- [ ] TikTok (1080x1920, with watermark) +- [ ] YouTube (1080x1920 or 1920x1080) + +--- + +*Last Updated: January 2026* diff --git a/feels-promo/STORYBOARD.md b/feels-promo/STORYBOARD.md new file mode 100644 index 0000000..633399b --- /dev/null +++ b/feels-promo/STORYBOARD.md @@ -0,0 +1,266 @@ +# Feels Promo Video Storyboard + +**Version**: V1 - App Store Feature Video +**Duration**: ~25 seconds (7 scenes + outro) +**Resolution**: 1080x1920 (9:16 Portrait) +**FPS**: 30 + +--- + +## Video Concept + +**Tagline**: *Track your mood. Understand yourself.* + +**Target Audience**: People seeking a simple, beautiful way to track their emotional wellbeing without the complexity of journaling apps. + +**Key Differentiators to Highlight**: +1. One-tap simplicity (vs. complex journaling) +2. Beautiful widgets & Apple Watch integration +3. AI-powered insights (Apple Intelligence) +4. Deep customization (themes, icons, colors) +5. Privacy-first with iCloud sync + +--- + +## Scene Breakdown + +### Scene 1: Hero (0:00-3:30) +**Title**: "Your mood. Your journey. Your way." + +| Element | Details | +|---------|---------| +| **Background** | Dark forest green gradient (#1a472a → #2d5a3d) with tiled app icons | +| **Animation** | Title springs in from top; phone slides in from right | +| **Phone Screen** | Day View showing mood list with colorful entries | +| **Layout** | Title top-left, phone large on right (extends off-screen) | +| **Emotion** | Empowering, personal, inviting | + +**Screenshot Required**: `screen1-day.png` - Day view with several mood entries + +--- + +### Scene 2: Widget & Watch (3:30-7:00) +**Title**: "Tap. Logged. Done." +**Subtitle**: "Never miss a day" + +| Element | Details | +|---------|---------| +| **Background** | Golden yellow gradient (#c4a000 → #d4b400) | +| **Animation** | Title springs in; widget scales up; watch scales up with delay | +| **Widget** | Large square widget showing mood selection | +| **Watch** | Apple Watch with Feels complication | +| **Labels** | "One-tap widgets" under widget, "Wrist ready" under watch | +| **Emotion** | Quick, effortless, accessible | + +**Screenshots Required**: +- `screen2-widget.png` - Large widget with 5 mood options +- `screen2-watch.png` - Watch face with Feels complication + +--- + +### Scene 3: Journal (7:00-10:30) +**Title**: "Reflect & Record" +**Subtitle**: "Add notes & photos to remember why" + +| Element | Details | +|---------|---------| +| **Background** | Medium green gradient (#3d7a4a → #4a8f5a) | +| **Animation** | Title springs in; phone scales up from center | +| **Phone Screen** | Note editor with text and photo attachment | +| **Layout** | Title top, phone centered with slight tilt | +| **Emotion** | Thoughtful, meaningful, personal | + +**Screenshot Required**: `screen3-journal.png` - Note editor with example entry + +--- + +### Scene 4: Insights (10:30-14:00) +**Title**: "Beautiful Insights" +**Badge**: "Powered by Apple AI" + +| Element | Details | +|---------|---------| +| **Background** | Blue gradient (#2563eb → #3b82f6) | +| **Animation** | Title springs in centered; phone scales up; AI badge pops in | +| **Phone Screen** | Insights view showing month/year analysis | +| **Badge** | White pill with sparkle emoji, appears at bottom | +| **Emotion** | Intelligent, modern, trustworthy | + +**Screenshot Required**: `screen4-insights.png` - Insights view with AI badge visible + +--- + +### Scene 5: Privacy (14:00-17:30) +**Title**: "Private & Secure" +**Subtitle**: "Syncs with Apple Health - Locked to you" + +| Element | Details | +|---------|---------| +| **Background** | Teal green gradient (#059669 → #10b981) | +| **Animation** | Title centered; shield emoji scales in; phone slides from right | +| **Phone Screen** | Settings or HealthKit integration screen | +| **Shield** | Large shield emoji (left side) | +| **Emotion** | Safe, trustworthy, Apple ecosystem | + +**Screenshot Required**: `screen5-privacy.png` - Privacy/HealthKit settings + +--- + +### Scene 6: Themes (17:30-21:00) +**Title**: "Complete Customization" +**Subtitle**: "Your Style" +**Detail**: "12 Thoughtful Themes" + +| Element | Details | +|---------|---------| +| **Background** | Purple gradient (#7c3aed → #8b5cf6) | +| **Animation** | Title right-aligned springs in; phone slides from left | +| **Phone Screen** | Theme picker showing color palette options | +| **Layout** | Title on right, phone on left (extends off-screen) | +| **Emotion** | Personal, expressive, premium | + +**Screenshot Required**: `screen6-themes.png` - Theme/customization picker + +--- + +### Scene 7: Notifications (21:00-24:30) +**Title**: "Guidance that gets you" + +| Element | Details | +|---------|---------| +| **Background** | Cyan gradient (#0891b2 → #06b6d4) | +| **Animation** | Title springs in; phone scales up from right | +| **Phone Screen** | Lock screen with Feels notification or Live Activity | +| **Layout** | Title top-left, phone large on right | +| **Emotion** | Supportive, timely, helpful | + +**Screenshot Required**: `screen7-notifications.png` - Notification or Live Activity + +--- + +### Outro (24:30-27:00) +**Text**: "Feels" + "Track your mood. Understand yourself." + +| Element | Details | +|---------|---------| +| **Background** | Default purple gradient with tiled icons | +| **Animation** | App icon scales up with glow; text fades in | +| **Glow** | Pulsing white radial glow behind icon | +| **Emotion** | Memorable, brand moment, call to action | + +**Asset Required**: `app-icon.png` - App icon (1024x1024) + +--- + +## Required Assets Checklist + +### Static Assets +- [ ] `app-icon.png` - App icon (1024x1024) +- [ ] `phone.png` - iPhone frame overlay +- [ ] `watch-frame.png` - Apple Watch frame overlay + +### Screenshots (capture from app) +- [ ] `screen1-day.png` - Day view with mood entries +- [ ] `screen2-widget.png` - Large widget mockup +- [ ] `screen2-watch.png` - Watch face/complication +- [ ] `screen3-journal.png` - Note editor with photo +- [ ] `screen4-insights.png` - Insights view +- [ ] `screen5-privacy.png` - Privacy/HealthKit settings +- [ ] `screen6-themes.png` - Theme customization +- [ ] `screen7-notifications.png` - Notification/Live Activity + +--- + +## Color Palette by Scene + +| Scene | Primary Color | Hex | +|-------|--------------|-----| +| 1. Hero | Forest Green | #1a472a | +| 2. Widget | Golden Yellow | #c4a000 | +| 3. Journal | Medium Green | #3d7a4a | +| 4. Insights | Royal Blue | #2563eb | +| 5. Privacy | Teal | #059669 | +| 6. Themes | Purple | #7c3aed | +| 7. Notifications | Cyan | #0891b2 | +| Outro | Purple | #667eea | + +--- + +## Animation Timing Reference + +| Animation | Duration | Easing | +|-----------|----------|--------| +| Title entrance | ~0.5s | Spring (damping: 200) | +| Phone entrance | ~0.8s | Spring (damping: 12, stiffness: 80) | +| Badge/icon pop | ~0.4s | Spring (damping: 10-15, stiffness: 100) | +| Scene transition | 0.6s | Linear fade | + +--- + +## Future Video Concepts + +### V2: "Year in Pixels" Focus +Highlight the Year View heatmap visualization - emphasize seeing a full year of emotions at a glance. + +**Key Scene**: Animated heatmap filling in with colors over time. + +### V3: "Streak Challenge" +Gamification angle - show streak building, Live Activity on lock screen, celebrating milestones. + +**Key Scene**: Streak counter incrementing with celebration animations. + +### V4: "Personality Packs" +Customization deep-dive - showcase different icon packs, themes, and color combinations. + +**Key Scene**: Rapid montage of different visual styles. + +### V5: "Share Your Year" +Social sharing feature - show the shareable year review card being generated and shared. + +**Key Scene**: Year card animation rendering, share sheet appearing. + +--- + +## Technical Notes + +### Remotion Commands +```bash +# Start preview +npm run dev + +# Render video +npx remotion render FeelsPromoV1 out/feels-promo-v1.mp4 + +# Render specific frame range (for testing) +npx remotion render FeelsPromoV1 out/test.mp4 --frames=0-30 +``` + +### Screenshot Capture Tips +1. Use iPhone 15 Pro Max simulator for highest quality +2. Enable "Show Touch" in simulator for interaction demos +3. Capture at 3x scale for maximum resolution +4. Remove status bar in post-processing if needed + +--- + +## Messaging Framework + +### Primary Message +"Track your mood in seconds, understand your patterns over time." + +### Supporting Messages +1. **Simplicity**: "One tap. That's all it takes." +2. **Insight**: "See your emotional journey unfold." +3. **Privacy**: "Your feelings, your data, your control." +4. **Style**: "Make it yours with 12 beautiful themes." +5. **Ecosystem**: "Works seamlessly with Apple Watch and widgets." + +### Emotional Arc +1. **Hook** (Scene 1): Personal, empowering +2. **Features** (Scenes 2-6): Practical, impressive +3. **Trust** (Scene 5-6): Secure, personalized +4. **CTA** (Outro): Memorable, action-driving + +--- + +*Last Updated: January 2026* diff --git a/feels-promo/src/ConceptA-SelfAwareness.tsx b/feels-promo/src/ConceptA-SelfAwareness.tsx new file mode 100644 index 0000000..ef0a8b6 --- /dev/null +++ b/feels-promo/src/ConceptA-SelfAwareness.tsx @@ -0,0 +1,674 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, +} from "remotion"; + +// Shared tiled background component +const TiledIconBackground: React.FC<{ color?: string }> = ({ + color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", +}) => { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + + const iconSize = 80; + const gap = 40; + const cellSize = iconSize + gap; + + const cols = Math.ceil(width / cellSize) + 4; + const rows = Math.ceil(height / cellSize) + 4; + + const offsetX = (frame * 0.3) % cellSize; + const offsetY = (frame * 0.2) % cellSize; + + return ( + +
+ {[...Array(rows)].map((_, row) => + [...Array(cols)].map((_, col) => { + const staggerX = row % 2 === 0 ? 0 : cellSize / 2; + return ( + + ); + }) + )} +
+
+ ); +}; + +// Phone frame component +const PhoneFrame: React.FC<{ + mediaSrc: string; + width?: number; + rotation?: number; + style?: React.CSSProperties; +}> = ({ mediaSrc, width = 460, rotation = 0, style }) => { + const aspectRatio = 2760 / 1350; + const height = width * aspectRatio; + + return ( +
+
+ +
+ +
+ ); +}; + +// Scene 1: Hook - "What if understanding yourself took 5 seconds a day?" +const HookScene: 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 textY = interpolate(textProgress, [0, 1], [50, 0]); + + const secondLineProgress = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + const secondLineOpacity = interpolate(secondLineProgress, [0, 1], [0, 1]); + + return ( + + + +
+
+ What if understanding yourself +
+
+ took 5 seconds a day? +
+
+
+ ); +}; + +// Scene 2: Demo - Quick-fire mood logging +const DemoScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const widget1Progress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const widget1Scale = interpolate(widget1Progress, [0, 1], [0.5, 1]); + + const widget2Progress = spring({ + frame: frame - 20, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const widget2Scale = interpolate(widget2Progress, [0, 1], [0.5, 1]); + + const widget3Progress = spring({ + frame: frame - 40, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const widget3Scale = interpolate(widget3Progress, [0, 1], [0.5, 1]); + + return ( + + + + {/* Title */} +
+
+ Log anywhere, anytime +
+
+ + {/* Three devices */} +
+ {/* Widget */} +
+ +
+ Widget +
+
+ + {/* Phone */} +
+ +
+ App +
+
+ + {/* Watch */} +
+
+
+ +
+ +
+
+ Watch +
+
+
+
+ ); +}; + +// Scene 3: Payoff - Calendar filling with colors +const PayoffScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const phoneProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const phoneScale = interpolate(phoneProgress, [0, 1], [0.8, 1]); + + // Simulate cells filling in + const fillProgress = interpolate(frame, [0, fps * 4], [0, 1], { + extrapolateRight: "clamp", + }); + + return ( + + + + {/* Title */} +
+
+ Watch patterns +
+ emerge +
+
+ + {/* Phone with year view */} +
+ +
+ + {/* Progress indicator */} +
+
+
+
+
+ {Math.round(fillProgress * 365)} days tracked +
+
+ + ); +}; + +// Scene 4: AI Insights +const InsightsScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const cardProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const cardScale = interpolate(cardProgress, [0, 1], [0.8, 1]); + const cardOpacity = interpolate(cardProgress, [0, 1], [0, 1]); + + const sparkleProgress = spring({ + frame: frame - 20, + fps, + config: { damping: 10, stiffness: 100 }, + }); + + return ( + + + + {/* Title */} +
+
+ AI understands you +
+
+ + {/* Insight card */} +
+
+
+ + ✨ + + + This Month + +
+
+ "You tend to feel better on weekends. Consider bringing more of that + weekend energy into your weekdays." +
+
+
+
+ ); +}; + +// Scene 5: CTA +const CTAScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoScale = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + const textOpacity = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + + const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]); + + return ( + + + +
+ +
+ +
+ Feels +
+
+ Start today. Understand tomorrow. +
+
+ + ); +}; + +// Main composition +export const ConceptASelfAwareness: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptB-NoJournalJournal.tsx b/feels-promo/src/ConceptB-NoJournalJournal.tsx new file mode 100644 index 0000000..3f6712d --- /dev/null +++ b/feels-promo/src/ConceptB-NoJournalJournal.tsx @@ -0,0 +1,589 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, +} from "remotion"; + +// Shared tiled background component +const TiledIconBackground: React.FC<{ color?: string }> = ({ + color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", +}) => { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + + const iconSize = 80; + const gap = 40; + const cellSize = iconSize + gap; + + const cols = Math.ceil(width / cellSize) + 4; + const rows = Math.ceil(height / cellSize) + 4; + + const offsetX = (frame * 0.3) % cellSize; + const offsetY = (frame * 0.2) % cellSize; + + return ( + +
+ {[...Array(rows)].map((_, row) => + [...Array(cols)].map((_, col) => { + const staggerX = row % 2 === 0 ? 0 : cellSize / 2; + return ( + + ); + }) + )} +
+
+ ); +}; + +// Scene 1: Problem - "Journaling is hard" +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 strikeProgress = interpolate(frame, [fps * 1.5, fps * 2], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + return ( + + + +
+
+
+ Journaling is hard +
+ {/* Strike-through line */} +
+
+
+ + ); +}; + +// Scene 2: Solution - "Feels is easy" +const SolutionScene: 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 textScale = interpolate(textProgress, [0, 1], [0.8, 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, + fps, + config: { damping: 200 }, + }); + + return ( + + + +
+
+ Same insights. +
+ Fraction of effort. +
+
+ Finally, tracking that sticks. +
+
+
+ ); +}; + +// Scene 5: CTA +const CTAScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoScale = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]); + + return ( + + + +
+ +
+ +
+ Feels +
+
+ The no-journal journal +
+
+ + ); +}; + +// Main composition - 20 seconds total +export const ConceptBNoJournalJournal: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptC-YearInFeelings.tsx b/feels-promo/src/ConceptC-YearInFeelings.tsx new file mode 100644 index 0000000..4e96b1b --- /dev/null +++ b/feels-promo/src/ConceptC-YearInFeelings.tsx @@ -0,0 +1,410 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, +} from "remotion"; + +// Shared tiled background component +const TiledIconBackground: React.FC<{ color?: string }> = ({ + color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", +}) => { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + + const iconSize = 80; + const gap = 40; + const cellSize = iconSize + gap; + + const cols = Math.ceil(width / cellSize) + 4; + const rows = Math.ceil(height / cellSize) + 4; + + const offsetX = (frame * 0.3) % cellSize; + const offsetY = (frame * 0.2) % cellSize; + + return ( + +
+ {[...Array(rows)].map((_, row) => + [...Array(cols)].map((_, col) => { + const staggerX = row % 2 === 0 ? 0 : cellSize / 2; + return ( + + ); + }) + )} +
+
+ ); +}; + +// Mood colors for the heatmap +const MOOD_COLORS = ["#ef4444", "#f97316", "#fbbf24", "#22c55e", "#10b981"]; + +// Scene 1: Heatmap filling in +const HeatmapScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + // Generate a consistent grid of mood values + const gridCols = 12; + const gridRows = 31; + + const fillProgress = interpolate(frame, [0, fps * 6], [0, 1], { + extrapolateRight: "clamp", + }); + + const totalCells = gridCols * gridRows; + const filledCells = Math.floor(fillProgress * totalCells); + + // Title animation + const titleOpacity = interpolate(frame, [0, fps * 0.5], [0, 1], { + extrapolateRight: "clamp", + }); + + return ( + + + + {/* Title */} +
+
+ Your Year in Feelings +
+
+ + {/* Heatmap grid */} +
+ {[...Array(totalCells)].map((_, i) => { + const isFilled = i < filledCells; + const moodIndex = Math.floor(Math.random() * 5); + const color = isFilled ? MOOD_COLORS[moodIndex] : "rgba(255,255,255,0.1)"; + + // Stagger the scale animation + const cellDelay = (i / totalCells) * fps * 5; + const cellProgress = spring({ + frame: frame - cellDelay, + fps, + config: { damping: 15, stiffness: 150 }, + }); + + return ( +
+ ); + })} +
+ + {/* Year label */} +
+
+ 2025 +
+
+ + ); +}; + +// Scene 2: Share card appearing +const ShareScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const cardProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const cardScale = interpolate(cardProgress, [0, 1], [0.7, 1]); + const cardOpacity = interpolate(cardProgress, [0, 1], [0, 1]); + + const shareProgress = spring({ + frame: frame - 30, + fps, + config: { damping: 10, stiffness: 100 }, + }); + + return ( + + + + {/* Share card */} +
+
+
+ 2025 +
+
+ Year in Review +
+ + {/* Mini heatmap */} +
+ {[...Array(60)].map((_, i) => ( +
+ ))} +
+ + {/* Stats */} +
+
+
+ 312 +
+
Days Tracked
+
+
+
😊
+
Top Mood
+
+
+ + {/* Feels branding */} +
+ ifeel +
+
+
+ + {/* Share button */} +
+
+ 📤 + + Share Your Year + +
+
+ + ); +}; + +// Scene 3: CTA +const CTAScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const textProgress = spring({ frame, fps, config: { damping: 200 } }); + + return ( + + + +
+ +
+ What will YOUR year look like? +
+
+
+ ); +}; + +// Main composition - 15 seconds total +export const ConceptCYearInFeelings: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptD-AlwaysThere.tsx b/feels-promo/src/ConceptD-AlwaysThere.tsx new file mode 100644 index 0000000..c724aee --- /dev/null +++ b/feels-promo/src/ConceptD-AlwaysThere.tsx @@ -0,0 +1,645 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, +} from "remotion"; + +// Shared tiled background component +const TiledIconBackground: React.FC<{ color?: string }> = ({ + color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", +}) => { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + + const iconSize = 80; + const gap = 40; + const cellSize = iconSize + gap; + + const cols = Math.ceil(width / cellSize) + 4; + const rows = Math.ceil(height / cellSize) + 4; + + const offsetX = (frame * 0.3) % cellSize; + const offsetY = (frame * 0.2) % cellSize; + + return ( + +
+ {[...Array(rows)].map((_, row) => + [...Array(cols)].map((_, col) => { + const staggerX = row % 2 === 0 ? 0 : cellSize / 2; + return ( + + ); + }) + )} +
+
+ ); +}; + +// Phone frame component +const PhoneFrame: React.FC<{ + mediaSrc: string; + width?: number; + rotation?: number; + style?: React.CSSProperties; +}> = ({ mediaSrc, width = 460, rotation = 0, style }) => { + const aspectRatio = 2760 / 1350; + const height = width * aspectRatio; + + return ( +
+
+ +
+ +
+ ); +}; + +// Scene 1: Morning - Widget tap while coffee +const MorningScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const widgetProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const widgetScale = interpolate(widgetProgress, [0, 1], [0.8, 1]); + + const labelProgress = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + + return ( + + + + {/* Time label */} +
+
+ 7:30 AM +
+
+ Morning coffee +
+
+ + {/* Widget */} +
+ +
+ + {/* Tap indicator */} +
+ + + One tap from home screen + +
+
+ ); +}; + +// Scene 2: Midday - Apple Watch at work +const MiddayScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const watchProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const watchScale = interpolate(watchProgress, [0, 1], [0.8, 1]); + + const labelProgress = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + + return ( + + + + {/* Time label */} +
+
+ 12:45 PM +
+
+ Lunch break +
+
+ + {/* Watch */} +
+
+
+ +
+ +
+
+ + {/* Label */} +
+ + + Quick glance from your wrist + +
+
+ ); +}; + +// Scene 3: Evening - Lock Screen Live Activity +const EveningScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const phoneProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const phoneScale = interpolate(phoneProgress, [0, 1], [0.8, 1]); + + const labelProgress = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + + return ( + + + + {/* Time label */} +
+
+ 6:30 PM +
+
+ Evening wind-down +
+
+ + {/* Phone with lock screen */} +
+ +
+ + {/* Label */} +
+ + 🔔 Gentle reminder on your Lock Screen + +
+
+ ); +}; + +// Scene 4: Night - Quick note before bed +const NightScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const phoneProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const phoneScale = interpolate(phoneProgress, [0, 1], [0.8, 1]); + + const labelProgress = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + + return ( + + + + {/* Time label */} +
+
+ 10:15 PM +
+
+ Before sleep +
+
+ + {/* Phone with journal */} +
+ +
+ + {/* Label */} +
+ 🌙 + + Add a note to remember the day + +
+
+ ); +}; + +// Scene 5: CTA +const CTAScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoScale = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + const textOpacity = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + + const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]); + + return ( + + + +
+ +
+ +
+ Feels +
+
+ Your mood tracker, wherever you are +
+
+ + ); +}; + +// Main composition - 25 seconds total +export const ConceptDAlwaysThere: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptE-MakeItYours.tsx b/feels-promo/src/ConceptE-MakeItYours.tsx new file mode 100644 index 0000000..037e6c8 --- /dev/null +++ b/feels-promo/src/ConceptE-MakeItYours.tsx @@ -0,0 +1,664 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, +} from "remotion"; + +// Theme colors for montage +const THEMES = [ + { name: "Forest", colors: ["#1a472a", "#2d5a3d"] }, + { name: "Ocean", colors: ["#1e3a5f", "#2563eb"] }, + { name: "Sunset", colors: ["#f59e0b", "#ef4444"] }, + { name: "Lavender", colors: ["#7c3aed", "#a855f7"] }, + { name: "Rose", colors: ["#e11d48", "#f43f5e"] }, + { name: "Mint", colors: ["#059669", "#10b981"] }, + { name: "Neon", colors: ["#0ea5e9", "#06b6d4"] }, + { name: "Charcoal", colors: ["#1f2937", "#374151"] }, +]; + +// Shared tiled background component +const TiledIconBackground: React.FC<{ color?: string }> = ({ + color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", +}) => { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + + const iconSize = 80; + const gap = 40; + const cellSize = iconSize + gap; + + const cols = Math.ceil(width / cellSize) + 4; + const rows = Math.ceil(height / cellSize) + 4; + + const offsetX = (frame * 0.3) % cellSize; + const offsetY = (frame * 0.2) % cellSize; + + return ( + +
+ {[...Array(rows)].map((_, row) => + [...Array(cols)].map((_, col) => { + const staggerX = row % 2 === 0 ? 0 : cellSize / 2; + return ( + + ); + }) + )} +
+
+ ); +}; + +// Phone frame component +const PhoneFrame: React.FC<{ + mediaSrc: string; + width?: number; + rotation?: number; + style?: React.CSSProperties; +}> = ({ mediaSrc, width = 460, rotation = 0, style }) => { + const aspectRatio = 2760 / 1350; + const height = width * aspectRatio; + + return ( +
+
+ +
+ +
+ ); +}; + +// Scene 1: Rapid theme montage +const ThemeMontageScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + // Cycle through themes every 0.4 seconds + const themeDuration = Math.round(0.4 * fps); + const currentThemeIndex = Math.floor(frame / themeDuration) % THEMES.length; + const theme = THEMES[currentThemeIndex]; + + const titleProgress = spring({ frame, fps, config: { damping: 200 } }); + + // Calculate progress within current theme for animation + const themeProgress = (frame % themeDuration) / themeDuration; + + return ( + + + + {/* Title */} +
+
+ Make it yours +
+
+ + {/* Theme name */} +
+
+ {theme.name} +
+
+ + {/* Mood icons row */} +
+ {["😢", "😕", "😐", "😊", "😄"].map((emoji, i) => ( +
+ {emoji} +
+ ))} +
+
+ ); +}; + +// Scene 2: Theme picker detail +const ThemeDetailScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const phoneProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + const phoneScale = interpolate(phoneProgress, [0, 1], [0.8, 1]); + + return ( + + + + {/* Title */} +
+
+ 12 thoughtful themes +
+
+ Light, dark, and everything in between +
+
+ + {/* Phone with theme picker */} +
+ +
+
+ ); +}; + +// Scene 3: Icon packs +const IconPacksScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const titleProgress = spring({ frame, fps, config: { damping: 200 } }); + + // Cycle through icon sets + const iconSets = [ + ["😢", "😕", "😐", "😊", "😄"], + ["💔", "😤", "😑", "🙂", "😁"], + ["🌧️", "☁️", "⛅", "🌤️", "☀️"], + ["1", "2", "3", "4", "5"], + ]; + const setDuration = Math.round(0.8 * fps); + const currentSetIndex = Math.floor(frame / setDuration) % iconSets.length; + const iconSet = iconSets[currentSetIndex]; + + const setProgress = (frame % setDuration) / setDuration; + + return ( + + + + {/* Title */} +
+
+ Choose your icons +
+
+ + {/* Icon set display */} +
+
+ {iconSet.map((icon, i) => ( +
+ {icon} +
+ ))} +
+
+ + {/* Label */} +
+
+ Multiple icon packs to match your style +
+
+
+ ); +}; + +// Scene 4: Widgets +const WidgetsScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const widget1Progress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + const widget2Progress = spring({ + frame: frame - 10, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + return ( + + + + {/* Title */} +
+
+ Custom widgets +
+
+ + {/* Widgets */} +
+ {/* Large widget */} +
+ +
+ + {/* Small widgets */} +
+
+ 😊 +
+
+ + 🔥 15 day streak + +
+
+
+ + {/* Label */} +
+
+ Small, medium, and large sizes +
+
+
+ ); +}; + +// Scene 5: CTA +const CTAScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoScale = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]); + + return ( + + + +
+ +
+ +
+ Feels +
+
+ 12 themes. Unlimited you. +
+
+ + ); +}; + +// Main composition - 20 seconds total +export const ConceptEMakeItYours: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptF-PrivacyFirst.tsx b/feels-promo/src/ConceptF-PrivacyFirst.tsx new file mode 100644 index 0000000..8961c5b --- /dev/null +++ b/feels-promo/src/ConceptF-PrivacyFirst.tsx @@ -0,0 +1,549 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, +} from "remotion"; + +// Shared tiled background component +const TiledIconBackground: React.FC<{ color?: string }> = ({ + color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", +}) => { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + + const iconSize = 80; + const gap = 40; + const cellSize = iconSize + gap; + + const cols = Math.ceil(width / cellSize) + 4; + const rows = Math.ceil(height / cellSize) + 4; + + const offsetX = (frame * 0.3) % cellSize; + const offsetY = (frame * 0.2) % cellSize; + + return ( + +
+ {[...Array(rows)].map((_, row) => + [...Array(cols)].map((_, col) => { + const staggerX = row % 2 === 0 ? 0 : cellSize / 2; + return ( + + ); + }) + )} +
+
+ ); +}; + +// Scene 1: Statement - "Your feelings are personal" +const StatementScene: 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 textScale = interpolate(textProgress, [0, 1], [0.9, 1]); + + const lockProgress = spring({ + frame: frame - 20, + fps, + config: { damping: 10, stiffness: 100 }, + }); + + return ( + + + +
+ {/* Lock icon */} +
+ 🔒 +
+ +
+ Your feelings +
+ are personal +
+
+
+ ); +}; + +// Scene 2: iCloud visualization +const iCloudScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + // Data flowing animation + const flowProgress = interpolate(frame, [0, fps * 4], [0, 1], { + extrapolateRight: "clamp", + }); + + const titleProgress = spring({ frame, fps, config: { damping: 200 } }); + + // Create particles for data flow + const particles = [...Array(12)].map((_, i) => { + const delay = i * 5; + const particleProgress = interpolate( + frame - delay, + [0, fps * 1.5], + [0, 1], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" } + ); + return { + x: interpolate(particleProgress, [0, 1], [0, 300]), + y: interpolate(particleProgress, [0, 0.5, 1], [0, -30, 0]) + i * 25, + opacity: interpolate(particleProgress, [0, 0.2, 0.8, 1], [0, 1, 1, 0]), + scale: interpolate(particleProgress, [0, 0.5, 1], [0.5, 1, 0.5]), + }; + }); + + return ( + + + + {/* Title */} +
+
+ Encrypted & synced +
+
+ + {/* Data flow visualization */} +
+ {/* Phone icon */} +
+ 📱 + {/* Particles emanating */} + {particles.map((p, i) => ( +
+ ))} +
+ + {/* Arrow */} +
+ → +
+ + {/* iCloud icon */} +
+ ☁️ + + iCloud + +
+
+ + {/* Labels */} +
+
+ 🔐 + + End-to-end encrypted + +
+
+ 🔄 + + Syncs across devices + +
+
+ + ); +}; + +// Scene 3: Apple Health +const HealthScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const badgeProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + const titleProgress = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + + return ( + + + + {/* Title */} +
+
+ Works with Apple Health +
+
+ + {/* Health badge */} +
+
+ {/* Apple Health icon approximation */} +
+ ❤️ +
+
+ Apple Health +
+
+ Your mood data syncs with State of Mind +
+
+
+
+ ); +}; + +// Scene 4: CTA +const CTAScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoScale = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + const textProgress = spring({ + frame: frame - 10, + fps, + config: { damping: 200 }, + }); + + const glowIntensity = interpolate(Math.sin(frame * 0.1), [-1, 1], [0.3, 0.6]); + + return ( + + + +
+ +
+ {/* Trust badges */} +
+ 🔒 + ☁️ + ❤️ +
+ + +
+ Feels +
+
+ Private. Secure. Yours. +
+
+ + ); +}; + +// Main composition - 15 seconds total +export const ConceptFPrivacyFirst: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptG-StreakEffect.tsx b/feels-promo/src/ConceptG-StreakEffect.tsx new file mode 100644 index 0000000..2e88250 --- /dev/null +++ b/feels-promo/src/ConceptG-StreakEffect.tsx @@ -0,0 +1,571 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, + Easing, +} from "remotion"; + +// Shared tiled background component +const TiledIconBackground: React.FC<{ color?: string }> = ({ + color = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)", +}) => { + const frame = useCurrentFrame(); + const { width, height } = useVideoConfig(); + + const iconSize = 80; + const gap = 40; + const cellSize = iconSize + gap; + + const cols = Math.ceil(width / cellSize) + 4; + const rows = Math.ceil(height / cellSize) + 4; + + const offsetX = (frame * 0.3) % cellSize; + const offsetY = (frame * 0.2) % cellSize; + + return ( + +
+ {[...Array(rows)].map((_, row) => + [...Array(cols)].map((_, col) => { + const staggerX = row % 2 === 0 ? 0 : cellSize / 2; + return ( + + ); + }) + )} +
+
+ ); +}; + +// Confetti particle component +const Confetti: React.FC<{ count: number; startFrame: number }> = ({ + count, + startFrame, +}) => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + const localFrame = frame - startFrame; + if (localFrame < 0) return null; + + const colors = ["#fbbf24", "#ef4444", "#10b981", "#3b82f6", "#8b5cf6", "#ec4899"]; + + return ( + <> + {[...Array(count)].map((_, i) => { + const seed = i * 1234.5678; + const startX = (Math.sin(seed) * 0.5 + 0.5) * width; + const startY = -50; + const endX = startX + (Math.sin(seed * 2) * 200); + const endY = height + 100; + + const progress = interpolate(localFrame, [0, fps * 3], [0, 1], { + extrapolateRight: "clamp", + easing: Easing.out(Easing.quad), + }); + + const x = interpolate(progress, [0, 1], [startX, endX]); + const y = interpolate(progress, [0, 1], [startY, endY]); + const rotation = localFrame * (5 + (i % 10)); + const opacity = interpolate(progress, [0, 0.7, 1], [1, 1, 0]); + const scale = 0.5 + (i % 5) * 0.2; + + return ( +
+ ); + })} + + ); +}; + +// Scene 1: Streak counter building up +const StreakBuildScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + // Count up from 0 to target + const targetStreak = 30; + const countProgress = interpolate(frame, [fps * 0.5, fps * 4], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }); + const currentCount = Math.round(countProgress * targetStreak); + + const titleProgress = spring({ frame, fps, config: { damping: 200 } }); + + // Fire emoji scale on each increment + const fireScale = interpolate( + Math.sin(frame * 0.5), + [-1, 1], + [0.9, 1.1] + ); + + return ( + + + + {/* Title */} +
+
+ Build your streak +
+
+ + {/* Streak counter */} +
+
+ 🔥 +
+
+ {currentCount} +
+
+ day streak +
+
+ + {/* Progress bar */} +
+
+
+
+
+ + ); +}; + +// Scene 2: Calendar filling +const CalendarScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const titleProgress = spring({ frame, fps, config: { damping: 200 } }); + + // Calendar grid + const days = 35; + const cols = 7; + const fillProgress = interpolate(frame, [0, fps * 4], [0, 1], { + extrapolateRight: "clamp", + }); + const filledDays = Math.floor(fillProgress * 30); + + const MOOD_COLORS = ["#10b981", "#22c55e", "#fbbf24", "#10b981", "#22c55e"]; + + return ( + + + + {/* Title */} +
+
+ Watch it grow +
+
+ + {/* Calendar grid */} +
+ {/* Day labels */} + {["S", "M", "T", "W", "T", "F", "S"].map((day, i) => ( +
+ {day} +
+ ))} + + {/* Days */} + {[...Array(days)].map((_, i) => { + const isFilled = i < filledDays; + const cellProgress = spring({ + frame: frame - i * 2, + fps, + config: { damping: 15, stiffness: 150 }, + }); + + return ( +
+ {i + 1} +
+ ); + })} +
+
+ ); +}; + +// Scene 3: Milestone celebration +const CelebrationScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const badgeProgress = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + const textProgress = spring({ + frame: frame - 15, + fps, + config: { damping: 200 }, + }); + + return ( + + + + {/* Confetti */} + + + {/* Achievement badge */} +
+
+
🏆
+
+ 30 Day Streak! +
+
+ You're on fire! +
+
+
+ + {/* Subtitle */} +
+
+ Celebrate every milestone +
+
+
+ ); +}; + +// Scene 4: CTA +const CTAScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoScale = spring({ + frame, + fps, + 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]); + + // Pulsing fire + const fireScale = interpolate(Math.sin(frame * 0.15), [-1, 1], [0.9, 1.1]); + + return ( + + + +
+ +
+
+ 🔥 +
+ + +
+ Feels +
+
+ How long can you go? +
+
+ + ); +}; + +// Main composition - 20 seconds total +export const ConceptGStreakEffect: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptH-MoodHeist.tsx b/feels-promo/src/ConceptH-MoodHeist.tsx new file mode 100644 index 0000000..50ba20a --- /dev/null +++ b/feels-promo/src/ConceptH-MoodHeist.tsx @@ -0,0 +1,788 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, + Easing, +} from "remotion"; + +// Cinematic letterbox bars +const LetterBox: React.FC = () => ( + <> +
+
+ +); + +// Scan lines for that heist movie feel +const ScanLines: React.FC<{ opacity?: number }> = ({ opacity = 0.1 }) => { + const frame = useCurrentFrame(); + const offset = (frame * 2) % 4; + + return ( +
+ ); +}; + +// Glitch effect text +const GlitchText: React.FC<{ + children: string; + style?: React.CSSProperties; +}> = ({ children, style }) => { + const frame = useCurrentFrame(); + const glitchOffset = Math.sin(frame * 0.5) * 2; + const shouldGlitch = frame % 30 < 3; + + return ( +
+ {shouldGlitch && ( + <> + + {children} + + + {children} + + + )} + {children} +
+ ); +}; + +// Scene 1: The Setup - "They took something from you" +const SetupScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const fadeIn = interpolate(frame, [0, fps * 0.5], [0, 1], { + extrapolateRight: "clamp", + }); + + const textReveal = interpolate(frame, [fps * 0.5, fps * 2], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + const typewriterLength = Math.floor(textReveal * 28); + const fullText = "They took something from you"; + const displayText = fullText.slice(0, typewriterLength); + + // Flicker effect + const flicker = frame % 60 < 2 ? 0.3 : 1; + + return ( + + + + + {/* Dramatic spotlight */} +
+ + {/* Main text */} +
+ + {displayText} + + + {/* Blinking cursor */} + {typewriterLength < fullText.length && ( + + _ + + )} +
+ + {/* Bottom text */} +
+
+ YOUR EMOTIONS +
+
+ + ); +}; + +// Scene 2: The Crew - Mood emojis as heist team +const CrewScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const crew = [ + { emoji: "😊", name: "THE OPTIMIST", color: "#10b981", role: "INFILTRATION" }, + { emoji: "😤", name: "THE MUSCLE", color: "#ef4444", role: "FIREPOWER" }, + { emoji: "🤔", name: "THE BRAINS", color: "#3b82f6", role: "STRATEGY" }, + { emoji: "😌", name: "THE COOL", color: "#8b5cf6", role: "EXTRACTION" }, + ]; + + return ( + + + + + {/* Title */} +
+
+ ASSEMBLING THE CREW +
+
+ + {/* Crew grid */} +
+ {crew.map((member, i) => { + const delay = i * 8; + const memberProgress = spring({ + frame: frame - delay, + fps, + config: { damping: 12, stiffness: 100 }, + }); + + return ( +
+
+ {member.emoji} +
+
+ {member.name} +
+
+ {member.role} +
+
+ ); + })} +
+
+ ); +}; + +// Scene 3: The Plan - Blueprint style +const PlanScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Grid lines drawing animation + const gridProgress = interpolate(frame, [0, fps * 2], [0, 1], { + extrapolateRight: "clamp", + }); + + return ( + + + + + {/* Blueprint grid */} + + {/* Vertical lines */} + {[...Array(20)].map((_, i) => ( + + ))} + {/* Horizontal lines */} + {[...Array(30)].map((_, i) => ( + + ))} + + + {/* Phone blueprint */} +
+
+ {/* Target markers */} +
+
+ TARGET +
+
+ + {/* Entry point */} +
+ ENTRY POINT +
+
+
+ + {/* Title */} +
+
+ THE PLAN +
+
+ + {/* Steps */} +
+ {["DOWNLOAD", "TAP MOOD", "REPEAT"].map((step, i) => ( +
+
+ {i + 1} +
+
+ {step} +
+
+ ))} +
+
+ ); +}; + +// Scene 4: The Execution - Dramatic mood selection +const ExecutionScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const moods = ["😢", "😕", "😐", "🙂", "😊"]; + const selectedIndex = 4; // Great mood + + // Dramatic slow reveal + const revealProgress = interpolate(frame, [0, fps * 2], [0, 1], { + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }); + + // Finger approaching + const fingerProgress = interpolate(frame, [fps * 2, fps * 3], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + // Selection flash + const selectFlash = frame > fps * 3 && frame < fps * 3.5; + + return ( + + + + + {/* Dramatic lighting */} + {selectFlash && ( +
+ )} + + {/* Mood buttons */} +
+ {moods.map((mood, i) => { + const isSelected = i === selectedIndex && frame > fps * 3; + const buttonProgress = interpolate( + revealProgress, + [i * 0.15, i * 0.15 + 0.3], + [0, 1], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" } + ); + + return ( +
+ {mood} +
+ ); + })} +
+ + {/* Approaching finger/cursor */} +
+ 👆 +
+ + {/* "ACQUIRED" text */} + {frame > fps * 3.2 && ( +
+ + MOOD ACQUIRED + +
+ )} + + ); +}; + +// Scene 5: The Getaway - Success celebration +const GetawayScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoProgress = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + // Vault door opening effect + const vaultOpen = interpolate(frame, [0, fps], [0, 1], { + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }); + + return ( + + + + + {/* Vault doors */} +
+
+ + {/* Bright light behind vault */} +
+ + {/* App icon revealed */} +
+ + +
+ Feels +
+ +
+ TAKE BACK YOUR EMOTIONS +
+
+ + ); +}; + +// Main composition - 25 seconds total +export const ConceptHMoodHeist: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptI-RetroArcade.tsx b/feels-promo/src/ConceptI-RetroArcade.tsx new file mode 100644 index 0000000..ad5af2f --- /dev/null +++ b/feels-promo/src/ConceptI-RetroArcade.tsx @@ -0,0 +1,833 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, + Easing, +} from "remotion"; + +// Pixel font style helper +const pixelText: React.CSSProperties = { + fontFamily: "'Press Start 2P', 'Courier New', monospace", + imageRendering: "pixelated", + textShadow: "4px 4px 0 #000", +}; + +// CRT screen effect +const CRTEffect: React.FC = () => { + const frame = useCurrentFrame(); + + return ( + <> + {/* Scan lines */} +
+ {/* Screen flicker */} +
0.9 ? 1 : 0, + pointerEvents: "none", + zIndex: 101, + }} + /> + {/* Vignette */} +
+ + ); +}; + +// Pixel art mood character +const MoodSprite: React.FC<{ + mood: number; + x: number; + y: number; + size?: number; + bounce?: boolean; +}> = ({ mood, x, y, size = 80, bounce = true }) => { + const frame = useCurrentFrame(); + const colors = ["#ef4444", "#f97316", "#fbbf24", "#22c55e", "#10b981"]; + const faces = [":(", ":/", ":|", ":)", ":D"]; + + const bounceY = bounce ? Math.sin(frame * 0.3) * 5 : 0; + + return ( +
+ + {faces[mood]} + +
+ ); +}; + +// Scene 1: Insert Coin +const InsertCoinScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const blink = frame % 30 < 20; + + const titleDrop = spring({ + frame, + fps, + config: { damping: 8, stiffness: 100 }, + }); + + return ( + + + + {/* Starfield background */} + {[...Array(50)].map((_, i) => { + const x = (i * 137.5) % 100; + const y = (i * 73.3 + frame * 0.5) % 100; + const size = (i % 3) + 1; + return ( +
+ ); + })} + + {/* Title */} +
+
+ FEELS +
+
+ THE GAME +
+
+ + {/* Mood characters parade */} +
+ {[0, 1, 2, 3, 4].map((mood, i) => { + const delay = i * 5; + const appearProgress = spring({ + frame: frame - delay - 15, + fps, + config: { damping: 12 }, + }); + return ( +
+ +
+ ); + })} +
+ + {/* Insert Coin */} +
+
+ INSERT COIN +
+
+ + {/* Credits */} +
+
+ © 2025 FEELS CORP +
+
+ + ); +}; + +// Scene 2: Gameplay - Catching moods +const GameplayScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + // Player position (bottom of screen) + const playerX = width / 2 - 50 + Math.sin(frame * 0.1) * 200; + + // Falling moods + const fallingMoods = [ + { mood: 4, startX: 200, delay: 0 }, + { mood: 3, startX: 500, delay: 20 }, + { mood: 2, startX: 800, delay: 40 }, + { mood: 4, startX: 350, delay: 60 }, + { mood: 3, startX: 650, delay: 80 }, + ]; + + // Score counter + const score = Math.floor(interpolate(frame, [0, fps * 4], [0, 9999], { + extrapolateRight: "clamp", + })); + + return ( + + + + {/* HUD */} +
+
+ SCORE: {score.toString().padStart(5, "0")} +
+
+ ♥♥♥ +
+
+ STREAK: 7 +
+
+ + {/* Falling moods */} + {fallingMoods.map((item, i) => { + const fallProgress = interpolate( + frame - item.delay, + [0, fps * 2], + [0, 1], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" } + ); + const y = interpolate(fallProgress, [0, 1], [-100, height - 300]); + + return ( + + ); + })} + + {/* Player basket */} +
+
+ 🧺 +
+
+ + {/* Ground */} +
+ {/* Ground pattern */} + {[...Array(20)].map((_, i) => ( +
+ ))} +
+ + {/* "CATCH THE GOOD VIBES" */} +
+
+ CATCH THE GOOD VIBES! +
+
+ + ); +}; + +// Scene 3: Power Up - Streak bonus +const PowerUpScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const powerUpPulse = Math.sin(frame * 0.3) * 0.2 + 1; + const rotateSpeed = frame * 3; + + const streakNumber = Math.floor( + interpolate(frame, [0, fps * 2], [7, 30], { + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }) + ); + + return ( + + + + {/* Explosion rays */} +
+ {[...Array(12)].map((_, i) => ( +
+ ))} +
+ + {/* Power up text */} +
+
+ POWER UP! +
+
+ + {/* Giant streak number */} +
+
+ {streakNumber} +
+
+ DAY STREAK! +
+
+ + {/* Bonus items floating */} + {["🔥", "⭐", "💎", "🏆"].map((emoji, i) => { + const angle = (frame * 2 + i * 90) * (Math.PI / 180); + const radius = 300; + const x = Math.cos(angle) * radius; + const y = Math.sin(angle) * radius * 0.5; + + return ( +
+ {emoji} +
+ ); + })} + + {/* XP bonus */} +
+
+ +500 XP BONUS! +
+
+ + ); +}; + +// Scene 4: High Score - Leaderboard +const HighScoreScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const scores = [ + { rank: 1, name: "YOU", score: 99999, isPlayer: true }, + { rank: 2, name: "PRO", score: 88420, isPlayer: false }, + { rank: 3, name: "ACE", score: 77350, isPlayer: false }, + { rank: 4, name: "ZEN", score: 66100, isPlayer: false }, + { rank: 5, name: "JOY", score: 55000, isPlayer: false }, + ]; + + return ( + + + + {/* Title */} +
+
+ HIGH SCORES +
+
+ + {/* Leaderboard */} +
+ {scores.map((entry, i) => { + const delay = i * 8; + const rowProgress = spring({ + frame: frame - delay, + fps, + config: { damping: 12 }, + }); + + return ( +
+
+ {entry.rank}. +
+
+ {entry.name} +
+
+ {entry.score.toLocaleString()} +
+ {entry.isPlayer && ( + 👑 + )} +
+ ); + })} +
+ + {/* New high score flash */} +
+
+ ★ NEW HIGH SCORE ★ +
+
+
+ ); +}; + +// Scene 5: Game Over - CTA +const GameOverScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoProgress = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + const blink = frame % 40 < 30; + + return ( + + + + {/* Pixelated app icon */} +
+ +
+ + {/* Title */} +
+
+ FEELS +
+
+ LEVEL UP YOUR MOOD +
+
+ + {/* Press Start */} +
+
+ PRESS START +
+
+ + {/* Download prompt */} +
+
+ DOWNLOAD NOW ON APP STORE +
+
+
+ ); +}; + +// Main composition - 20 seconds total +export const ConceptIRetroArcade: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptJ-Conspiracy.tsx b/feels-promo/src/ConceptJ-Conspiracy.tsx new file mode 100644 index 0000000..0d01819 --- /dev/null +++ b/feels-promo/src/ConceptJ-Conspiracy.tsx @@ -0,0 +1,609 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, + Easing, +} from "remotion"; + +// Film grain effect +const FilmGrain: React.FC<{ intensity?: number }> = ({ intensity = 0.15 }) => { + const frame = useCurrentFrame(); + + return ( +
+ ); +}; + +// Redacted text bar +const Redacted: React.FC<{ width: number }> = ({ width }) => ( + +); + +// Glitch/static overlay +const StaticOverlay: React.FC<{ active: boolean }> = ({ active }) => { + const frame = useCurrentFrame(); + + if (!active) return null; + + return ( +
+ ); +}; + +// Document stamp +const ClassifiedStamp: React.FC<{ delay?: number }> = ({ delay = 0 }) => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const stampProgress = spring({ + frame: frame - delay, + fps, + config: { damping: 8, stiffness: 200 }, + }); + + return ( +
+
+ CLASSIFIED +
+
+ ); +}; + +// Scene 1: The Hook - "They don't want you to know" +const HookScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const staticActive = frame < fps * 0.5 || (frame > fps * 2 && frame < fps * 2.3); + + const textOpacity = interpolate(frame, [fps * 0.5, fps * 1], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + const zoomIn = interpolate(frame, [0, fps * 4], [1, 1.1], { + extrapolateRight: "clamp", + }); + + return ( + + + + + {/* Dark vignette */} +
+ + {/* Ominous text */} +
+
+ WHAT IF EVERYTHING YOU KNEW +
+
+ WAS A LIE? +
+
+ + {/* Timestamp */} +
+ DOC-7X-{frame.toString().padStart(4, "0")} +
+ + ); +}; + +// Scene 2: The Evidence - Redacted documents +const EvidenceScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const docSlide = interpolate(frame, [0, fps], [100, 0], { + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }); + + return ( + + + + {/* Cork board background */} +
+ + {/* Document */} +
+ {/* Header */} +
+ INTERNAL MEMO - RESTRICTED ACCESS +
+ + {/* Body text with redactions */} +
+ Subject: Emotional Awareness Initiative +
+
+ The has determined that widespread + of emotional patterns could lead to + self-improvement. This is + to our interests. +
+
+ Recommendation: Continue suppression of + tracking tools. +
+ + {/* Classified stamp */} + +
+ + {/* Red strings connecting */} + + + + + + {/* Narration text */} +
+
+ "They don't want you to understand yourself." +
+
+ + ); +}; + +// Scene 3: The Truth - Revelation +const TruthScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const revealProgress = interpolate(frame, [0, fps * 2], [0, 1], { + extrapolateRight: "clamp", + }); + + const glitchActive = frame % 45 < 3; + + return ( + + + + + {/* Dramatic light rays */} +
+ + {/* The truth revealed */} +
+
+ THE TRUTH IS +
+ +
+ YOUR EMOTIONS +
+ +
+ MATTER +
+
+ + {/* Bottom text */} +
+
+ And there's an app that knows it. +
+
+ + ); +}; + +// Scene 4: The Solution - Feels reveal +const SolutionScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoProgress = spring({ + frame, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + // Typing effect for tagline + const tagline = "Track your truth."; + const typedLength = Math.floor( + interpolate(frame, [fps * 1.5, fps * 3], [0, tagline.length], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }) + ); + + return ( + + + + {/* Spotlight effect */} +
+ + {/* App icon */} +
+ +
+ + {/* App name */} +
+
+ Feels +
+ + {/* Typed tagline */} +
+ {tagline.slice(0, typedLength)} + | +
+
+ + {/* "Wake up" text */} +
+
+ WAKE UP. DOWNLOAD NOW. +
+
+ + {/* File number */} +
+ CASE CLOSED +
+ + ); +}; + +// Main composition - 20 seconds total +export const ConceptJConspiracy: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptK-SportsCenter.tsx b/feels-promo/src/ConceptK-SportsCenter.tsx new file mode 100644 index 0000000..56ed2ac --- /dev/null +++ b/feels-promo/src/ConceptK-SportsCenter.tsx @@ -0,0 +1,701 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, + Easing, +} from "remotion"; + +// ESPN-style colors +const ESPN_RED = "#cc0000"; +const ESPN_DARK = "#1a1a1a"; +const ESPN_YELLOW = "#ffc629"; + +// Breaking news ticker +const NewsTicker: React.FC = () => { + const frame = useCurrentFrame(); + const { width } = useVideoConfig(); + + const headlines = [ + "BREAKING: Local user hits 30-day streak, experts baffled", + "MOOD ALERT: Wednesday showing signs of improvement league-wide", + "TRADE DEADLINE: Bad vibes traded for good vibes in blockbuster deal", + "INJURY REPORT: Monday motivation OUT indefinitely", + "STATS: Average mood up 15% since app download", + ]; + + const tickerText = headlines.join(" • "); + const tickerWidth = tickerText.length * 14; + const offset = (frame * 3) % (tickerWidth + width); + + return ( +
+
+ + FEELS CENTER + +
+
+ {tickerText} +
+
+ ); +}; + +// Score bug / mood tracker +const MoodScoreBug: React.FC<{ mood: string; score: number; streak: number }> = ({ + mood, + score, + streak, +}) => { + return ( +
+
+
TODAY
+
{mood}
+
+
+
SCORE
+
+ {score} +
+
+
+
STREAK
+
+ {streak}🔥 +
+
+
+ ); +}; + +// Scene 1: Opening - "Welcome to Feels Center" +const OpeningScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoSlam = spring({ + frame, + fps, + config: { damping: 8, stiffness: 150 }, + }); + + const textReveal = interpolate(frame, [fps * 0.5, fps * 1.5], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + return ( + + {/* Dramatic spotlight */} +
+ + {/* Logo slam */} +
+
+ FEELS +
+
+ CENTER +
+
+ + {/* Subtitle */} +
+
+ YOUR EMOTIONAL HIGHLIGHTS +
+
+ + {/* ESPN-style corner graphics */} +
+ LIVE + +
+ + + + ); +}; + +// Scene 2: Play of the Day - Mood selection replay +const PlayOfDayScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const replayProgress = interpolate(frame, [fps * 0.5, fps * 3], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + // Slow-mo finger movement + const fingerX = interpolate(replayProgress, [0, 0.8, 1], [0, 200, 200]); + const fingerY = interpolate(replayProgress, [0, 0.8, 0.9, 1], [100, 0, -20, 0]); + + // Impact flash + const impactFlash = replayProgress > 0.85 && replayProgress < 0.95; + + return ( + + {/* "PLAY OF THE DAY" banner */} +
+
+ + 🏆 PLAY OF THE DAY + +
+
+ + {/* Replay frame */} +
+ {/* Mood buttons */} +
+ {["😢", "😕", "😐", "🙂", "😊"].map((emoji, i) => { + const isTarget = i === 4; + const isSelected = isTarget && replayProgress > 0.85; + + return ( +
+ {emoji} +
+ ); + })} +
+ + {/* Slow-mo finger */} +
+ 👆 +
+ + {/* Impact lines */} + {impactFlash && ( + <> + {[...Array(8)].map((_, i) => ( +
+ ))} + + )} +
+ + {/* Slow-mo indicator */} +
+
+ SLOW-MO REPLAY +
+
🎬
+
+ + {/* Commentary */} +
0.9 ? 1 : 0, + }} + > +
+ "WHAT A SELECTION! ABSOLUTELY CLINICAL!" +
+
+ + + + ); +}; + +// Scene 3: Stats breakdown +const StatsScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const stats = [ + { label: "DAYS TRACKED", value: 127, color: "#10b981" }, + { label: "CURRENT STREAK", value: 23, color: ESPN_YELLOW }, + { label: "BEST STREAK", value: 45, color: ESPN_RED }, + { label: "MOOD AVERAGE", value: "4.2", color: "#3b82f6" }, + ]; + + return ( + + {/* Stats title */} +
+
+ 📊 SEASON STATS +
+
+ + {/* Stats grid */} +
+ {stats.map((stat, i) => { + const delay = i * 8; + const statProgress = spring({ + frame: frame - delay, + fps, + config: { damping: 12 }, + }); + + const countUp = Math.floor( + interpolate(statProgress, [0, 1], [0, typeof stat.value === "number" ? stat.value : parseFloat(stat.value)]) + ); + + return ( +
+
+ {stat.label} +
+
+ {typeof stat.value === "number" ? countUp : stat.value} +
+
+ ); + })} +
+ + + +
+ ); +}; + +// Scene 4: CTA - "Download to join the league" +const CTAScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const logoProgress = spring({ + frame, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + return ( + + {/* Dramatic background */} +
+ + {/* App icon */} +
+ +
+ + {/* CTA text */} +
+
+ Feels +
+
+ JOIN THE LEAGUE +
+
+ + {/* ESPN-style "Download" button */} +
+
+ + DOWNLOAD NOW + +
+
+ + {/* "THIS HAS BEEN" outro */} +
+
+ This has been FEELS CENTER. Track responsibly. +
+
+ + ); +}; + +// Main composition - 20 seconds total +export const ConceptKSportsCenter: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/ConceptL-Musical.tsx b/feels-promo/src/ConceptL-Musical.tsx new file mode 100644 index 0000000..f24e1c5 --- /dev/null +++ b/feels-promo/src/ConceptL-Musical.tsx @@ -0,0 +1,724 @@ +import { + AbsoluteFill, + Img, + staticFile, + useCurrentFrame, + useVideoConfig, + interpolate, + spring, + Sequence, + Easing, +} from "remotion"; + +// Stage curtain component +const Curtain: React.FC<{ side: "left" | "right"; open: number }> = ({ + side, + open, +}) => { + const offset = interpolate(open, [0, 1], [0, 100]); + + return ( +
+ {/* Curtain folds */} + {[...Array(8)].map((_, i) => ( +
+ ))} +
+ ); +}; + +// Spotlight effect +const Spotlight: React.FC<{ + x: number; + y: number; + size?: number; + color?: string; + intensity?: number; +}> = ({ x, y, size = 400, color = "#fff", intensity = 0.6 }) => ( +
+); + +// Dancing emoji character +const DancingEmoji: React.FC<{ + emoji: string; + x: number; + y: number; + size?: number; + delay?: number; + danceStyle?: "bounce" | "spin" | "sway"; +}> = ({ emoji, x, y, size = 100, delay = 0, danceStyle = "bounce" }) => { + const frame = useCurrentFrame(); + const localFrame = frame - delay; + + let transform = ""; + + switch (danceStyle) { + case "bounce": + const bounceY = Math.abs(Math.sin(localFrame * 0.2)) * 30; + transform = `translateY(${-bounceY}px)`; + break; + case "spin": + transform = `rotate(${localFrame * 5}deg)`; + break; + case "sway": + const swayX = Math.sin(localFrame * 0.15) * 20; + const swayRotate = Math.sin(localFrame * 0.15) * 10; + transform = `translateX(${swayX}px) rotate(${swayRotate}deg)`; + break; + } + + return ( +
+ {emoji} +
+ ); +}; + +// Stage lights at top +const StageLights: React.FC = () => { + const frame = useCurrentFrame(); + + const colors = ["#ff6b6b", "#feca57", "#48dbfb", "#ff9ff3", "#1dd1a1"]; + + return ( +
+ {colors.map((color, i) => { + const pulse = Math.sin(frame * 0.1 + i) * 0.3 + 0.7; + return ( +
+
+
+ ); + })} +
+ ); +}; + +// Scene 1: Curtain Rise - "Welcome to the show" +const CurtainRiseScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps } = useVideoConfig(); + + const curtainOpen = interpolate(frame, [fps * 1, fps * 3], [0, 1], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + easing: Easing.out(Easing.cubic), + }); + + const titleProgress = spring({ + frame: frame - fps * 2, + fps, + config: { damping: 12, stiffness: 80 }, + }); + + return ( + + {/* Stage floor */} +
+ + {/* Spotlight */} + + + {/* Title card */} +
+
+ Welcome to +
+
+ FEELS +
+
+ The Musical +
+
+ + + + + + ); +}; + +// Scene 2: "The Feelings Song" - Dancing mood emojis +const FeelingSongScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + const emojis = [ + { emoji: "😢", color: "#3b82f6", name: "SADNESS" }, + { emoji: "😤", color: "#ef4444", name: "ANGER" }, + { emoji: "😊", color: "#10b981", name: "JOY" }, + { emoji: "😰", color: "#8b5cf6", name: "ANXIETY" }, + { emoji: "🥰", color: "#ec4899", name: "LOVE" }, + ]; + + // Choreographed positions + const getPosition = (index: number, progress: number) => { + const centerX = width / 2; + const baseY = height * 0.5; + + // Dance formation - V shape to line to circle + const phase = Math.floor(progress * 3) % 3; + + if (phase === 0) { + // V formation + const vX = centerX + (index - 2) * 150; + const vY = baseY + Math.abs(index - 2) * 80; + return { x: vX - 50, y: vY }; + } else if (phase === 1) { + // Line formation + const lineX = centerX + (index - 2) * 180; + return { x: lineX - 50, y: baseY }; + } else { + // Circle formation + const angle = (index / 5) * Math.PI * 2 + progress * 2; + const radius = 250; + return { + x: centerX + Math.cos(angle) * radius - 50, + y: baseY + Math.sin(angle) * radius * 0.5, + }; + } + }; + + const danceProgress = interpolate(frame, [0, fps * 5], [0, 1], { + extrapolateRight: "clamp", + }); + + return ( + + {/* Stage floor with reflection */} +
+ + {/* Multiple spotlights */} + {emojis.map((e, i) => { + const pos = getPosition(i, danceProgress); + return ( + + ); + })} + + {/* "EVERYBODY FEEL!" banner */} +
+
+ ♪ EVERYBODY FEEL! ♪ +
+
+ + {/* Dancing emojis */} + {emojis.map((e, i) => { + const pos = getPosition(i, danceProgress); + const delay = i * 3; + + return ( + + ); + })} + + {/* Musical notes floating */} + {["♪", "♫", "♬", "♩"].map((note, i) => { + const noteX = (frame * 2 + i * 200) % (width + 100) - 50; + const noteY = 200 + Math.sin(frame * 0.1 + i) * 100; + + return ( +
+ {note} +
+ ); + })} + + + + ); +}; + +// Scene 3: Solo number - Joy takes center stage +const SoloNumberScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + const spotlightPulse = Math.sin(frame * 0.1) * 0.2 + 0.8; + + // Joy's dramatic entrance + const joyEnter = spring({ + frame, + fps, + config: { damping: 12, stiffness: 60 }, + }); + + const joyY = interpolate(joyEnter, [0, 1], [height, height * 0.4]); + + // Star burst behind Joy + const starRotation = frame * 0.5; + + return ( + + {/* Dramatic backdrop */} +
+ + {/* Star burst */} +
+ {[...Array(12)].map((_, i) => ( +
+ ))} +
+ + {/* Giant spotlight on Joy */} + + + {/* Joy - center stage */} +
+ 😊 +
+ + {/* Lyric text */} +
+
+ "One tap is all it takes..." +
+
+ "To know how your heart feels today!" +
+
+ + + + ); +}; + +// Scene 4: Finale - All together + app reveal +const FinaleScene: React.FC = () => { + const frame = useCurrentFrame(); + const { fps, width, height } = useVideoConfig(); + + const emojis = ["😢", "😕", "😐", "🙂", "😊"]; + + const logoProgress = spring({ + frame: frame - fps * 1, + fps, + config: { damping: 10, stiffness: 80 }, + }); + + // Confetti + const confettiColors = ["#ff6b6b", "#feca57", "#48dbfb", "#ff9ff3", "#1dd1a1"]; + + return ( + + {/* Grand finale lighting */} +
+ + {/* Line of emojis at top */} +
+ {emojis.map((emoji, i) => { + const delay = i * 3; + const emojiProgress = spring({ + frame: frame - delay, + fps, + config: { damping: 12 }, + }); + + return ( +
+ {emoji} +
+ ); + })} +
+ + {/* App icon center stage */} +
+ +
+ + {/* App name */} +
+
+ Feels +
+
+ ♪ Download Today ♪ +
+
+ + {/* Confetti */} + {[...Array(40)].map((_, i) => { + const seed = i * 137.5; + const x = (Math.sin(seed) * 0.5 + 0.5) * width; + const fallProgress = interpolate( + frame, + [fps * 1 + i * 2, fps * 5], + [0, 1], + { extrapolateLeft: "clamp", extrapolateRight: "clamp" } + ); + const y = interpolate(fallProgress, [0, 1], [-50, height + 50]); + + return ( +
+ ); + })} + + + + ); +}; + +// Main composition - 25 seconds total +export const ConceptLMusical: React.FC = () => { + const { fps } = useVideoConfig(); + + return ( + + + + + + + + + + + + + + + + + + ); +}; diff --git a/feels-promo/src/Root.tsx b/feels-promo/src/Root.tsx index 86ab3a0..7eff48b 100644 --- a/feels-promo/src/Root.tsx +++ b/feels-promo/src/Root.tsx @@ -1,23 +1,166 @@ import { Composition } from "remotion"; import { FeelsPromoV1 } from "./FeelsPromo"; +import { ConceptASelfAwareness } from "./ConceptA-SelfAwareness"; +import { ConceptBNoJournalJournal } from "./ConceptB-NoJournalJournal"; +import { ConceptCYearInFeelings } from "./ConceptC-YearInFeelings"; +import { ConceptDAlwaysThere } from "./ConceptD-AlwaysThere"; +import { ConceptEMakeItYours } from "./ConceptE-MakeItYours"; +import { ConceptFPrivacyFirst } from "./ConceptF-PrivacyFirst"; +import { ConceptGStreakEffect } from "./ConceptG-StreakEffect"; +// Wild concepts +import { ConceptHMoodHeist } from "./ConceptH-MoodHeist"; +import { ConceptIRetroArcade } from "./ConceptI-RetroArcade"; +import { ConceptJConspiracy } from "./ConceptJ-Conspiracy"; +import { ConceptKSportsCenter } from "./ConceptK-SportsCenter"; +import { ConceptLMusical } from "./ConceptL-Musical"; export const RemotionRoot: React.FC = () => { const fps = 30; - const sceneDuration = 3.5 * fps; // 3.5 seconds per scene - const transitionDuration = Math.round(0.6 * fps); // 0.6 second transitions - const outroDuration = Math.round(2.5 * fps); - // Calculate total duration accounting for transition overlaps - // 7 scenes + outro - 7 transitions - const totalDuration = + // V1 calculations + const sceneDuration = 3.5 * fps; + const transitionDuration = Math.round(0.6 * fps); + const outroDuration = Math.round(2.5 * fps); + const v1TotalDuration = sceneDuration * 7 + outroDuration - transitionDuration * 7; return ( <> + {/* ═══════════════════════════════════════════════════════════════ + ORIGINAL PROMO + ═══════════════════════════════════════════════════════════════ */} + + {/* ═══════════════════════════════════════════════════════════════ + STANDARD CONCEPTS (A-G) + ═══════════════════════════════════════════════════════════════ */} + + {/* Concept A: 30 Seconds to Self-Awareness */} + + + {/* Concept B: The No-Journal Journal (20s) */} + + + {/* Concept C: Your Year in Feelings (15s) */} + + + {/* Concept D: Always There (25s) */} + + + {/* Concept E: Make It Yours (20s) */} + + + {/* Concept F: Privacy First (15s) */} + + + {/* Concept G: The Streak Effect (20s) */} + + + {/* ═══════════════════════════════════════════════════════════════ + WILD CONCEPTS (H-L) - OFF THE WALL CREATIVE + ═══════════════════════════════════════════════════════════════ */} + + {/* Concept H: The Mood Heist (25s) - Ocean's Eleven style thriller */} + + + {/* Concept I: Retro Arcade Feels (20s) - 8-bit video game */} + + + {/* Concept J: The Feels Conspiracy (20s) - Dark documentary thriller */} + + + {/* Concept K: Sports Center Emotions (20s) - ESPN broadcast parody */} + + + {/* Concept L: Feelings The Musical (25s) - Broadway musical number */} +