add marketing movies

This commit is contained in:
Trey t
2026-01-26 18:03:51 -06:00
parent 77c28516fa
commit 2828e5eef7
19 changed files with 8467 additions and 10 deletions

View File

@@ -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:*)",

View File

@@ -0,0 +1 @@
../../.agents/skills/remotion-best-practices

View File

@@ -12,12 +12,12 @@
<key>Feels (macOS).xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
<integer>2</integer>
</dict>
<key>Feels Watch App.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
<integer>3</integer>
</dict>
<key>FeelsWidgetExtension.xcscheme_^#shared#^_</key>
<dict>

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"WebSearch"
]
}
}

View File

@@ -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*

266
feels-promo/STORYBOARD.md Normal file
View File

@@ -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*

View File

@@ -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 (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// 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 (
<div
style={{
position: "relative",
width,
height,
transform: rotation ? `rotate(${rotation}deg)` : undefined,
filter: "drop-shadow(0 40px 80px rgba(0,0,0,0.5))",
...style,
}}
>
<div
style={{
position: "absolute",
top: "2.5%",
left: "5.33%",
right: "5.33%",
bottom: "2.5%",
borderRadius: width * 0.083,
overflow: "hidden",
backgroundColor: "#000",
}}
>
<Img
src={staticFile(mediaSrc)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("phone.png")}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
/>
</div>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #2d5a87 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
lineHeight: 1.2,
transform: `translateY(${-textY}px)`,
opacity: textOpacity,
}}
>
What if understanding yourself
</div>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
lineHeight: 1.2,
marginTop: 20,
opacity: secondLineOpacity,
}}
>
took 5 seconds a day?
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 80,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 56,
fontWeight: 700,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Log anywhere, anytime
</div>
</div>
{/* Three devices */}
<div
style={{
position: "absolute",
bottom: 100,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
alignItems: "flex-end",
gap: 40,
}}
>
{/* Widget */}
<div
style={{
transform: `scale(${widget1Scale})`,
filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.4))",
}}
>
<Img
src={staticFile("screen2-widget.png")}
style={{
width: 280,
height: 280,
borderRadius: 30,
}}
/>
<div
style={{
fontSize: 24,
fontWeight: 600,
color: "white",
textAlign: "center",
marginTop: 16,
}}
>
Widget
</div>
</div>
{/* Phone */}
<div
style={{
transform: `scale(${widget2Scale})`,
}}
>
<PhoneFrame mediaSrc="screen1-day.png" width={320} />
<div
style={{
fontSize: 24,
fontWeight: 600,
color: "white",
textAlign: "center",
marginTop: 16,
}}
>
App
</div>
</div>
{/* Watch */}
<div
style={{
transform: `scale(${widget3Scale})`,
filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.4))",
}}
>
<div style={{ position: "relative" }}>
<div
style={{
position: "absolute",
top: "22%",
left: "15%",
width: "70%",
height: "42%",
borderRadius: 12,
overflow: "hidden",
zIndex: 1,
}}
>
<Img
src={staticFile("screen2-watch.png")}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("watch-frame.png")}
style={{
width: 180,
height: 288,
position: "relative",
zIndex: 2,
}}
/>
</div>
<div
style={{
fontSize: 24,
fontWeight: 600,
color: "white",
textAlign: "center",
marginTop: 16,
}}
>
Watch
</div>
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #8b5cf6 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 60,
right: 60,
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
lineHeight: 1.1,
}}
>
Watch patterns
<br />
emerge
</div>
</div>
{/* Phone with year view */}
<div
style={{
position: "absolute",
left: "50%",
bottom: -150,
transform: `translateX(-50%) scale(${phoneScale})`,
}}
>
<PhoneFrame mediaSrc="screen1-day.png" width={600} />
</div>
{/* Progress indicator */}
<div
style={{
position: "absolute",
bottom: 120,
left: 60,
right: 60,
}}
>
<div
style={{
height: 8,
borderRadius: 4,
backgroundColor: "rgba(255,255,255,0.3)",
overflow: "hidden",
}}
>
<div
style={{
height: "100%",
width: `${fillProgress * 100}%`,
backgroundColor: "white",
borderRadius: 4,
}}
/>
</div>
<div
style={{
fontSize: 24,
color: "rgba(255,255,255,0.8)",
marginTop: 12,
textAlign: "center",
}}
>
{Math.round(fillProgress * 365)} days tracked
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #2563eb 0%, #3b82f6 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
AI understands you
</div>
</div>
{/* Insight card */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${cardScale})`,
opacity: cardOpacity,
}}
>
<div
style={{
background: "rgba(255,255,255,0.95)",
borderRadius: 30,
padding: 50,
width: 800,
boxShadow: "0 30px 60px rgba(0,0,0,0.3)",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: 20, marginBottom: 30 }}>
<span
style={{
fontSize: 48,
transform: `scale(${interpolate(sparkleProgress, [0, 1], [0, 1])})`,
}}
>
</span>
<span
style={{
fontSize: 32,
fontWeight: 600,
color: "#1a1a1a",
}}
>
This Month
</span>
</div>
<div
style={{
fontSize: 36,
color: "#374151",
lineHeight: 1.5,
}}
>
"You tend to feel better on weekends. Consider bringing more of that
weekend energy into your weekdays."
</div>
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #2d5a87 100%)" />
<div
style={{
position: "absolute",
width: 500,
height: 500,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 200,
height: 200,
borderRadius: 200 * 0.22,
marginBottom: 40,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 80,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 36,
fontWeight: 500,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 30,
opacity: interpolate(textOpacity, [0, 1], [0, 1]),
}}
>
Start today. Understand tomorrow.
</div>
</div>
</AbsoluteFill>
);
};
// Main composition
export const ConceptASelfAwareness: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<HookScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(7 * fps)}>
<DemoScene />
</Sequence>
<Sequence from={Math.round(12 * fps)} durationInFrames={Math.round(6 * fps)}>
<PayoffScene />
</Sequence>
<Sequence from={Math.round(18 * fps)} durationInFrames={Math.round(7 * fps)}>
<InsightsScene />
</Sequence>
<Sequence from={Math.round(25 * fps)} durationInFrames={Math.round(5 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #991b1b 0%, #b91c1c 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<div style={{ position: "relative" }}>
<div
style={{
fontSize: 96,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: textOpacity,
}}
>
Journaling is hard
</div>
{/* Strike-through line */}
<div
style={{
position: "absolute",
top: "50%",
left: 0,
height: 12,
backgroundColor: "#fbbf24",
width: `${strikeProgress * 100}%`,
transform: "translateY(-50%) rotate(-2deg)",
borderRadius: 6,
}}
/>
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<div
style={{
fontSize: 96,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: textOpacity,
transform: `scale(${textScale})`,
marginBottom: 60,
}}
>
Feels is easy
</div>
{/* Tap animation sequence */}
<div
style={{
display: "flex",
alignItems: "center",
gap: 30,
opacity: interpolate(tapProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
width: 100,
height: 100,
borderRadius: 25,
backgroundColor: "#fbbf24",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 48,
boxShadow: "0 10px 30px rgba(0,0,0,0.3)",
}}
>
😊
</div>
<div
style={{
fontSize: 64,
color: "white",
}}
>
</div>
<div
style={{
fontSize: 72,
transform: `scale(${interpolate(checkProgress, [0, 1], [0, 1])})`,
}}
>
</div>
<div
style={{
fontSize: 48,
fontWeight: 600,
color: "white",
opacity: interpolate(checkProgress, [0, 1], [0, 1]),
}}
>
Done!
</div>
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #374151 0%, #4b5563 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
padding: 40,
gap: 40,
}}
>
{/* Left: Other apps */}
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
opacity: interpolate(leftProgress, [0, 1], [0, 1]),
transform: `translateX(${interpolate(leftProgress, [0, 1], [-50, 0])}px)`,
}}
>
<div
style={{
backgroundColor: "rgba(0,0,0,0.4)",
borderRadius: 30,
padding: 40,
width: 400,
}}
>
<div
style={{
height: 30,
backgroundColor: "rgba(255,255,255,0.3)",
borderRadius: 8,
marginBottom: 20,
}}
/>
<div
style={{
height: 30,
backgroundColor: "rgba(255,255,255,0.2)",
borderRadius: 8,
marginBottom: 20,
width: "80%",
}}
/>
<div
style={{
height: 100,
backgroundColor: "rgba(255,255,255,0.15)",
borderRadius: 12,
marginBottom: 20,
}}
/>
<div
style={{
height: 30,
backgroundColor: "rgba(255,255,255,0.2)",
borderRadius: 8,
width: "60%",
}}
/>
</div>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "#ef4444",
marginTop: 30,
}}
>
Other apps: 5+ minutes
</div>
</div>
{/* VS */}
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
transform: `scale(${interpolate(vsProgress, [0, 1], [0, 1])})`,
}}
>
VS
</div>
{/* Right: Feels */}
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
opacity: interpolate(rightProgress, [0, 1], [0, 1]),
transform: `translateX(${interpolate(rightProgress, [0, 1], [50, 0])}px)`,
}}
>
<div
style={{
backgroundColor: "#059669",
borderRadius: 30,
padding: 40,
width: 400,
display: "flex",
justifyContent: "center",
gap: 20,
}}
>
{["😢", "😕", "😐", "😊", "😄"].map((emoji, i) => (
<div
key={i}
style={{
width: 60,
height: 60,
borderRadius: 15,
backgroundColor: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 32,
}}
>
{emoji}
</div>
))}
</div>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "#10b981",
marginTop: 30,
}}
>
Feels: 5 seconds
</div>
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #8b5cf6 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: textOpacity,
lineHeight: 1.2,
}}
>
Same insights.
<br />
Fraction of effort.
</div>
<div
style={{
fontSize: 42,
fontWeight: 500,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
marginTop: 40,
opacity: interpolate(subProgress, [0, 1], [0, 1]),
}}
>
Finally, tracking that sticks.
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
marginBottom: 30,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 36,
fontWeight: 600,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
opacity: 0.9,
}}
>
The no-journal journal
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptBNoJournalJournal: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(4 * fps)}>
<ProblemScene />
</Sequence>
<Sequence from={Math.round(4 * fps)} durationInFrames={Math.round(4 * fps)}>
<SolutionScene />
</Sequence>
<Sequence from={Math.round(8 * fps)} durationInFrames={Math.round(5 * fps)}>
<ComparisonScene />
</Sequence>
<Sequence from={Math.round(13 * fps)} durationInFrames={Math.round(4 * fps)}>
<BenefitScene />
</Sequence>
<Sequence from={Math.round(17 * fps)} durationInFrames={Math.round(3 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e1b4b 0%, #312e81 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 80,
left: 0,
right: 0,
textAlign: "center",
opacity: titleOpacity,
}}
>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Your Year in Feelings
</div>
</div>
{/* Heatmap grid */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "grid",
gridTemplateColumns: `repeat(${gridCols}, 60px)`,
gap: 6,
padding: 30,
backgroundColor: "rgba(0,0,0,0.3)",
borderRadius: 30,
}}
>
{[...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 (
<div
key={i}
style={{
width: 60,
height: 16,
borderRadius: 4,
backgroundColor: isFilled ? color : "rgba(255,255,255,0.1)",
transform: `scaleX(${isFilled ? interpolate(cellProgress, [0, 1], [0, 1]) : 1})`,
transformOrigin: "left",
}}
/>
);
})}
</div>
{/* Year label */}
<div
style={{
position: "absolute",
bottom: 100,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 120,
fontWeight: 800,
color: "rgba(255,255,255,0.1)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
2025
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #a855f7 100%)" />
{/* Share card */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${cardScale})`,
opacity: cardOpacity,
}}
>
<div
style={{
backgroundColor: "white",
borderRadius: 40,
padding: 60,
width: 700,
boxShadow: "0 40px 80px rgba(0,0,0,0.4)",
}}
>
<div
style={{
fontSize: 48,
fontWeight: 800,
color: "#1a1a1a",
marginBottom: 20,
}}
>
2025
</div>
<div
style={{
fontSize: 28,
color: "#6b7280",
marginBottom: 40,
}}
>
Year in Review
</div>
{/* Mini heatmap */}
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(12, 1fr)",
gap: 4,
marginBottom: 40,
}}
>
{[...Array(60)].map((_, i) => (
<div
key={i}
style={{
height: 12,
borderRadius: 3,
backgroundColor: MOOD_COLORS[Math.floor(Math.random() * 5)],
}}
/>
))}
</div>
{/* Stats */}
<div style={{ display: "flex", justifyContent: "space-around" }}>
<div style={{ textAlign: "center" }}>
<div style={{ fontSize: 48, fontWeight: 700, color: "#10b981" }}>
312
</div>
<div style={{ fontSize: 20, color: "#6b7280" }}>Days Tracked</div>
</div>
<div style={{ textAlign: "center" }}>
<div style={{ fontSize: 48 }}>😊</div>
<div style={{ fontSize: 20, color: "#6b7280" }}>Top Mood</div>
</div>
</div>
{/* Feels branding */}
<div
style={{
marginTop: 40,
textAlign: "center",
fontSize: 24,
color: "#9ca3af",
}}
>
ifeel
</div>
</div>
</div>
{/* Share button */}
<div
style={{
position: "absolute",
bottom: 150,
left: "50%",
transform: `translateX(-50%) scale(${interpolate(shareProgress, [0, 1], [0, 1])})`,
}}
>
<div
style={{
backgroundColor: "white",
borderRadius: 50,
padding: "20px 50px",
display: "flex",
alignItems: "center",
gap: 15,
boxShadow: "0 10px 30px rgba(0,0,0,0.3)",
}}
>
<span style={{ fontSize: 32 }}>📤</span>
<span
style={{
fontSize: 28,
fontWeight: 600,
color: "#1a1a1a",
}}
>
Share Your Year
</span>
</div>
</div>
</AbsoluteFill>
);
};
// Scene 3: CTA
const CTAScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const textProgress = spring({ frame, fps, config: { damping: 200 } });
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e1b4b 0%, #312e81 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 160,
height: 160,
borderRadius: 160 * 0.22,
marginBottom: 40,
filter: "drop-shadow(0 20px 40px rgba(0,0,0,0.4))",
transform: `scale(${interpolate(textProgress, [0, 1], [0.5, 1])})`,
}}
/>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
What will YOUR year look like?
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 15 seconds total
export const ConceptCYearInFeelings: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(7 * fps)}>
<HeatmapScene />
</Sequence>
<Sequence from={Math.round(7 * fps)} durationInFrames={Math.round(5 * fps)}>
<ShareScene />
</Sequence>
<Sequence from={Math.round(12 * fps)} durationInFrames={Math.round(3 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// 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 (
<div
style={{
position: "relative",
width,
height,
transform: rotation ? `rotate(${rotation}deg)` : undefined,
filter: "drop-shadow(0 40px 80px rgba(0,0,0,0.5))",
...style,
}}
>
<div
style={{
position: "absolute",
top: "2.5%",
left: "5.33%",
right: "5.33%",
bottom: "2.5%",
borderRadius: width * 0.083,
overflow: "hidden",
backgroundColor: "#000",
}}
>
<Img
src={staticFile(mediaSrc)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("phone.png")}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
/>
</div>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #f59e0b 0%, #fbbf24 100%)" />
{/* Time label */}
<div
style={{
position: "absolute",
top: 80,
left: 60,
opacity: interpolate(labelProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "rgba(255,255,255,0.8)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
7:30 AM
</div>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.2)",
}}
>
Morning coffee
</div>
</div>
{/* Widget */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${widgetScale})`,
filter: "drop-shadow(0 30px 60px rgba(0,0,0,0.3))",
}}
>
<Img
src={staticFile("screen2-widget.png")}
style={{
width: 500,
height: 500,
borderRadius: 50,
}}
/>
</div>
{/* Tap indicator */}
<div
style={{
position: "absolute",
bottom: 200,
left: "50%",
transform: "translateX(-50%)",
display: "flex",
alignItems: "center",
gap: 15,
}}
>
<span style={{ fontSize: 40 }}></span>
<span
style={{
fontSize: 32,
color: "white",
fontWeight: 600,
}}
>
One tap from home screen
</span>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #3b82f6 0%, #60a5fa 100%)" />
{/* Time label */}
<div
style={{
position: "absolute",
top: 80,
right: 60,
textAlign: "right",
opacity: interpolate(labelProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "rgba(255,255,255,0.8)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
12:45 PM
</div>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.2)",
}}
>
Lunch break
</div>
</div>
{/* Watch */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${watchScale})`,
filter: "drop-shadow(0 30px 60px rgba(0,0,0,0.3))",
}}
>
<div style={{ position: "relative" }}>
<div
style={{
position: "absolute",
top: "22%",
left: "15%",
width: "70%",
height: "42%",
borderRadius: 25,
overflow: "hidden",
zIndex: 1,
}}
>
<Img
src={staticFile("screen2-watch.png")}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("watch-frame.png")}
style={{
width: 400,
height: 640,
position: "relative",
zIndex: 2,
}}
/>
</div>
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 200,
left: "50%",
transform: "translateX(-50%)",
display: "flex",
alignItems: "center",
gap: 15,
}}
>
<span style={{ fontSize: 40 }}></span>
<span
style={{
fontSize: 32,
color: "white",
fontWeight: 600,
}}
>
Quick glance from your wrist
</span>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #8b5cf6 0%, #a78bfa 100%)" />
{/* Time label */}
<div
style={{
position: "absolute",
top: 80,
left: 60,
opacity: interpolate(labelProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "rgba(255,255,255,0.8)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
6:30 PM
</div>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.2)",
}}
>
Evening wind-down
</div>
</div>
{/* Phone with lock screen */}
<div
style={{
position: "absolute",
right: -50,
bottom: -150,
transform: `scale(${phoneScale})`,
}}
>
<PhoneFrame mediaSrc="screen7-notifications.png" width={550} rotation={-5} />
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 200,
left: 60,
}}
>
<span
style={{
fontSize: 32,
color: "white",
fontWeight: 600,
}}
>
🔔 Gentle reminder on your Lock Screen
</span>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #1e293b 100%)" />
{/* Time label */}
<div
style={{
position: "absolute",
top: 80,
right: 60,
textAlign: "right",
opacity: interpolate(labelProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 32,
fontWeight: 600,
color: "rgba(255,255,255,0.8)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
10:15 PM
</div>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.2)",
}}
>
Before sleep
</div>
</div>
{/* Phone with journal */}
<div
style={{
position: "absolute",
left: "50%",
bottom: -150,
transform: `translateX(-50%) scale(${phoneScale})`,
}}
>
<PhoneFrame mediaSrc="screen3-journal.png" width={550} />
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 200,
left: "50%",
transform: "translateX(-50%)",
display: "flex",
alignItems: "center",
gap: 15,
}}
>
<span style={{ fontSize: 40 }}>🌙</span>
<span
style={{
fontSize: 32,
color: "white",
fontWeight: 600,
}}
>
Add a note to remember the day
</span>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
marginBottom: 40,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 36,
fontWeight: 600,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
opacity: interpolate(textOpacity, [0, 1], [0, 1]),
}}
>
Your mood tracker, wherever you are
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 25 seconds total
export const ConceptDAlwaysThere: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<MorningScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(5 * fps)}>
<MiddayScene />
</Sequence>
<Sequence from={Math.round(10 * fps)} durationInFrames={Math.round(5 * fps)}>
<EveningScene />
</Sequence>
<Sequence from={Math.round(15 * fps)} durationInFrames={Math.round(5 * fps)}>
<NightScene />
</Sequence>
<Sequence from={Math.round(20 * fps)} durationInFrames={Math.round(5 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// 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 (
<div
style={{
position: "relative",
width,
height,
transform: rotation ? `rotate(${rotation}deg)` : undefined,
filter: "drop-shadow(0 40px 80px rgba(0,0,0,0.5))",
...style,
}}
>
<div
style={{
position: "absolute",
top: "2.5%",
left: "5.33%",
right: "5.33%",
bottom: "2.5%",
borderRadius: width * 0.083,
overflow: "hidden",
backgroundColor: "#000",
}}
>
<Img
src={staticFile(mediaSrc)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
</div>
<Img
src={staticFile("phone.png")}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
/>
</div>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<AbsoluteFill
style={{
background: `linear-gradient(180deg, ${theme.colors[0]} 0%, ${theme.colors[1]} 100%)`,
transition: "background 0.1s ease-out",
}}
/>
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Make it yours
</div>
</div>
{/* Theme name */}
<div
style={{
position: "absolute",
top: "45%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(themeProgress, [0, 0.5, 1], [0.8, 1, 0.8])})`,
opacity: interpolate(themeProgress, [0, 0.3, 0.7, 1], [0, 1, 1, 0]),
}}
>
<div
style={{
fontSize: 120,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 60px rgba(0,0,0,0.5)",
}}
>
{theme.name}
</div>
</div>
{/* Mood icons row */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
gap: 30,
}}
>
{["😢", "😕", "😐", "😊", "😄"].map((emoji, i) => (
<div
key={i}
style={{
width: 80,
height: 80,
borderRadius: 20,
backgroundColor: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 48,
}}
>
{emoji}
</div>
))}
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1f2937 0%, #374151 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 60,
right: 60,
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
12 thoughtful themes
</div>
<div
style={{
fontSize: 32,
fontWeight: 500,
color: "rgba(255,255,255,0.7)",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 15,
}}
>
Light, dark, and everything in between
</div>
</div>
{/* Phone with theme picker */}
<div
style={{
position: "absolute",
left: "50%",
bottom: -150,
transform: `translateX(-50%) scale(${phoneScale})`,
}}
>
<PhoneFrame mediaSrc="screen6-themes.png" width={550} />
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #a855f7 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Choose your icons
</div>
</div>
{/* Icon set display */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(setProgress, [0, 0.5, 1], [0.9, 1, 0.9])})`,
opacity: interpolate(setProgress, [0, 0.2, 0.8, 1], [0, 1, 1, 0]),
}}
>
<div
style={{
display: "flex",
gap: 30,
backgroundColor: "rgba(0,0,0,0.3)",
padding: 50,
borderRadius: 40,
}}
>
{iconSet.map((icon, i) => (
<div
key={i}
style={{
width: 120,
height: 120,
borderRadius: 30,
backgroundColor: `hsl(${i * 30 + 0}, 70%, 50%)`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 64,
color: "white",
fontWeight: 700,
}}
>
{icon}
</div>
))}
</div>
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 32,
color: "rgba(255,255,255,0.8)",
fontWeight: 500,
}}
>
Multiple icon packs to match your style
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Custom widgets
</div>
</div>
{/* Widgets */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
gap: 40,
alignItems: "center",
}}
>
{/* Large widget */}
<div
style={{
transform: `scale(${interpolate(widget1Progress, [0, 1], [0.5, 1])})`,
opacity: interpolate(widget1Progress, [0, 1], [0, 1]),
}}
>
<Img
src={staticFile("screen2-widget.png")}
style={{
width: 400,
height: 400,
borderRadius: 40,
filter: "drop-shadow(0 30px 60px rgba(0,0,0,0.4))",
}}
/>
</div>
{/* Small widgets */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: 20,
transform: `scale(${interpolate(widget2Progress, [0, 1], [0.5, 1])})`,
opacity: interpolate(widget2Progress, [0, 1], [0, 1]),
}}
>
<div
style={{
width: 200,
height: 200,
borderRadius: 30,
backgroundColor: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 80,
}}
>
😊
</div>
<div
style={{
width: 200,
height: 90,
borderRadius: 20,
backgroundColor: "rgba(255,255,255,0.2)",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "0 20px",
}}
>
<span style={{ fontSize: 28, color: "white", fontWeight: 600 }}>
🔥 15 day streak
</span>
</div>
</div>
</div>
{/* Label */}
<div
style={{
position: "absolute",
bottom: 150,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 32,
color: "rgba(255,255,255,0.8)",
fontWeight: 500,
}}
>
Small, medium, and large sizes
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #a855f7 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
marginBottom: 40,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 72,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 42,
fontWeight: 700,
color: "#fbbf24",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
}}
>
12 themes. Unlimited you.
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptEMakeItYours: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<ThemeMontageScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(4 * fps)}>
<ThemeDetailScene />
</Sequence>
<Sequence from={Math.round(9 * fps)} durationInFrames={Math.round(4 * fps)}>
<IconPacksScene />
</Sequence>
<Sequence from={Math.round(13 * fps)} durationInFrames={Math.round(4 * fps)}>
<WidgetsScene />
</Sequence>
<Sequence from={Math.round(17 * fps)} durationInFrames={Math.round(3 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #1e293b 100%)" />
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
padding: 60,
}}
>
{/* Lock icon */}
<div
style={{
fontSize: 120,
marginBottom: 40,
transform: `scale(${interpolate(lockProgress, [0, 1], [0, 1])})`,
}}
>
🔒
</div>
<div
style={{
fontSize: 80,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textAlign: "center",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
opacity: textOpacity,
transform: `scale(${textScale})`,
lineHeight: 1.2,
}}
>
Your feelings
<br />
are personal
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Encrypted & synced
</div>
</div>
{/* Data flow visualization */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
alignItems: "center",
gap: 80,
}}
>
{/* Phone icon */}
<div
style={{
width: 200,
height: 350,
backgroundColor: "rgba(255,255,255,0.15)",
borderRadius: 40,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 100,
position: "relative",
}}
>
📱
{/* Particles emanating */}
{particles.map((p, i) => (
<div
key={i}
style={{
position: "absolute",
right: -p.x - 50,
top: p.y + 100,
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: "rgba(255,255,255,0.8)",
opacity: p.opacity,
transform: `scale(${p.scale})`,
}}
/>
))}
</div>
{/* Arrow */}
<div
style={{
fontSize: 80,
color: "white",
opacity: 0.5,
}}
>
</div>
{/* iCloud icon */}
<div
style={{
width: 250,
height: 250,
backgroundColor: "rgba(255,255,255,0.2)",
borderRadius: 50,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 15,
}}
>
<span style={{ fontSize: 80 }}></span>
<span
style={{
fontSize: 28,
fontWeight: 600,
color: "white",
}}
>
iCloud
</span>
</div>
</div>
{/* Labels */}
<div
style={{
position: "absolute",
bottom: 150,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
gap: 60,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
backgroundColor: "rgba(0,0,0,0.3)",
padding: "15px 25px",
borderRadius: 30,
}}
>
<span style={{ fontSize: 28 }}>🔐</span>
<span style={{ fontSize: 24, color: "white", fontWeight: 500 }}>
End-to-end encrypted
</span>
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
backgroundColor: "rgba(0,0,0,0.3)",
padding: "15px 25px",
borderRadius: 30,
}}
>
<span style={{ fontSize: 28 }}>🔄</span>
<span style={{ fontSize: 24, color: "white", fontWeight: 500 }}>
Syncs across devices
</span>
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #dc2626 0%, #ef4444 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Works with Apple Health
</div>
</div>
{/* Health badge */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(badgeProgress, [0, 1], [0.5, 1])})`,
opacity: interpolate(badgeProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
backgroundColor: "white",
borderRadius: 60,
padding: 60,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 30,
boxShadow: "0 30px 60px rgba(0,0,0,0.3)",
}}
>
{/* Apple Health icon approximation */}
<div
style={{
width: 140,
height: 140,
borderRadius: 35,
background: "linear-gradient(180deg, #ff6b6b 0%, #ff4757 100%)",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<span style={{ fontSize: 80 }}></span>
</div>
<div
style={{
fontSize: 36,
fontWeight: 700,
color: "#1a1a1a",
}}
>
Apple Health
</div>
<div
style={{
fontSize: 24,
color: "#6b7280",
textAlign: "center",
maxWidth: 400,
}}
>
Your mood data syncs with State of Mind
</div>
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #1e3a5f 0%, #1e293b 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
{/* Trust badges */}
<div
style={{
display: "flex",
gap: 30,
marginBottom: 50,
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
<span style={{ fontSize: 60 }}>🔒</span>
<span style={{ fontSize: 60 }}></span>
<span style={{ fontSize: 60 }}></span>
</div>
<Img
src={staticFile("app-icon.png")}
style={{
width: 160,
height: 160,
borderRadius: 160 * 0.22,
marginBottom: 30,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 42,
fontWeight: 700,
color: "#10b981",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
Private. Secure. Yours.
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 15 seconds total
export const ConceptFPrivacyFirst: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(3.5 * fps)}>
<StatementScene />
</Sequence>
<Sequence from={Math.round(3.5 * fps)} durationInFrames={Math.round(4.5 * fps)}>
<iCloudScene />
</Sequence>
<Sequence from={Math.round(8 * fps)} durationInFrames={Math.round(4 * fps)}>
<HealthScene />
</Sequence>
<Sequence from={Math.round(12 * fps)} durationInFrames={Math.round(3 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<AbsoluteFill
style={{
background: color,
overflow: "hidden",
}}
>
<div
style={{
position: "absolute",
top: -cellSize * 2,
left: -cellSize * 2,
transform: `translate(${offsetX}px, ${offsetY}px)`,
}}
>
{[...Array(rows)].map((_, row) =>
[...Array(cols)].map((_, col) => {
const staggerX = row % 2 === 0 ? 0 : cellSize / 2;
return (
<Img
key={`${row}-${col}`}
src={staticFile("app-icon.png")}
style={{
position: "absolute",
width: iconSize,
height: iconSize,
left: col * cellSize + staggerX,
top: row * cellSize,
opacity: 0.08,
borderRadius: iconSize * 0.22,
}}
/>
);
})
)}
</div>
</AbsoluteFill>
);
};
// 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 (
<div
key={i}
style={{
position: "absolute",
left: x,
top: y,
width: 20,
height: 20,
backgroundColor: colors[i % colors.length],
borderRadius: i % 2 === 0 ? "50%" : 4,
transform: `rotate(${rotation}deg) scale(${scale})`,
opacity,
}}
/>
);
})}
</>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #f59e0b 0%, #ef4444 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Build your streak
</div>
</div>
{/* Streak counter */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
textAlign: "center",
}}
>
<div
style={{
fontSize: 120,
transform: `scale(${fireScale})`,
marginBottom: 20,
}}
>
🔥
</div>
<div
style={{
fontSize: 200,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 60px rgba(0,0,0,0.3)",
lineHeight: 1,
}}
>
{currentCount}
</div>
<div
style={{
fontSize: 48,
fontWeight: 600,
color: "rgba(255,255,255,0.9)",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 10,
}}
>
day streak
</div>
</div>
{/* Progress bar */}
<div
style={{
position: "absolute",
bottom: 200,
left: 100,
right: 100,
}}
>
<div
style={{
height: 20,
borderRadius: 10,
backgroundColor: "rgba(255,255,255,0.3)",
overflow: "hidden",
}}
>
<div
style={{
height: "100%",
width: `${countProgress * 100}%`,
backgroundColor: "white",
borderRadius: 10,
}}
/>
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #059669 0%, #10b981 100%)" />
{/* Title */}
<div
style={{
position: "absolute",
top: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 56,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Watch it grow
</div>
</div>
{/* Calendar grid */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "grid",
gridTemplateColumns: `repeat(${cols}, 100px)`,
gap: 15,
padding: 40,
backgroundColor: "rgba(0,0,0,0.2)",
borderRadius: 30,
}}
>
{/* Day labels */}
{["S", "M", "T", "W", "T", "F", "S"].map((day, i) => (
<div
key={`label-${i}`}
style={{
fontSize: 24,
fontWeight: 600,
color: "rgba(255,255,255,0.6)",
textAlign: "center",
marginBottom: 10,
}}
>
{day}
</div>
))}
{/* Days */}
{[...Array(days)].map((_, i) => {
const isFilled = i < filledDays;
const cellProgress = spring({
frame: frame - i * 2,
fps,
config: { damping: 15, stiffness: 150 },
});
return (
<div
key={i}
style={{
width: 100,
height: 100,
borderRadius: 20,
backgroundColor: isFilled
? MOOD_COLORS[i % MOOD_COLORS.length]
: "rgba(255,255,255,0.1)",
transform: `scale(${isFilled ? interpolate(cellProgress, [0, 1], [0.5, 1]) : 1})`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 32,
fontWeight: 600,
color: isFilled ? "white" : "rgba(255,255,255,0.3)",
}}
>
{i + 1}
</div>
);
})}
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ overflow: "hidden" }}>
<TiledIconBackground color="linear-gradient(180deg, #7c3aed 0%, #a855f7 100%)" />
{/* Confetti */}
<Confetti count={50} startFrame={10} />
{/* Achievement badge */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(badgeProgress, [0, 1], [0, 1])})`,
}}
>
<div
style={{
backgroundColor: "white",
borderRadius: 60,
padding: 60,
textAlign: "center",
boxShadow: "0 30px 60px rgba(0,0,0,0.3)",
}}
>
<div style={{ fontSize: 100, marginBottom: 20 }}>🏆</div>
<div
style={{
fontSize: 48,
fontWeight: 800,
color: "#1a1a1a",
marginBottom: 15,
}}
>
30 Day Streak!
</div>
<div
style={{
fontSize: 28,
color: "#6b7280",
}}
>
You're on fire!
</div>
</div>
</div>
{/* Subtitle */}
<div
style={{
position: "absolute",
bottom: 150,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
<div
style={{
fontSize: 36,
fontWeight: 600,
color: "white",
}}
>
Celebrate every milestone
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill
style={{
justifyContent: "center",
alignItems: "center",
}}
>
<TiledIconBackground color="linear-gradient(180deg, #f59e0b 0%, #ef4444 100%)" />
<div
style={{
position: "absolute",
width: 400,
height: 400,
borderRadius: "50%",
background: `radial-gradient(circle, rgba(255,255,255,${glowIntensity}) 0%, transparent 70%)`,
transform: `scale(${interpolate(logoScale, [0, 1], [0.3, 1])})`,
}}
/>
<div
style={{
textAlign: "center",
transform: `scale(${interpolate(logoScale, [0, 1], [0.5, 1])})`,
}}
>
<div
style={{
fontSize: 100,
marginBottom: 30,
transform: `scale(${fireScale})`,
}}
>
🔥
</div>
<Img
src={staticFile("app-icon.png")}
style={{
width: 160,
height: 160,
borderRadius: 160 * 0.22,
marginBottom: 30,
filter: `drop-shadow(0 0 60px rgba(255,255,255,${glowIntensity}))`,
}}
/>
<div
style={{
fontSize: 64,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 4px 30px rgba(0,0,0,0.3)",
}}
>
Feels
</div>
<div
style={{
fontSize: 42,
fontWeight: 700,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
marginTop: 20,
opacity: interpolate(textProgress, [0, 1], [0, 1]),
}}
>
How long can you go?
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptGStreakEffect: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<StreakBuildScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(6 * fps)}>
<CalendarScene />
</Sequence>
<Sequence from={Math.round(11 * fps)} durationInFrames={Math.round(5 * fps)}>
<CelebrationScene />
</Sequence>
<Sequence from={Math.round(16 * fps)} durationInFrames={Math.round(4 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -0,0 +1,788 @@
import {
AbsoluteFill,
Img,
staticFile,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
Sequence,
Easing,
} from "remotion";
// Cinematic letterbox bars
const LetterBox: React.FC = () => (
<>
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 120,
background: "black",
zIndex: 100,
}}
/>
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 120,
background: "black",
zIndex: 100,
}}
/>
</>
);
// 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 (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,${opacity}) 2px,
rgba(0,0,0,${opacity}) 4px
)`,
transform: `translateY(${offset}px)`,
pointerEvents: "none",
zIndex: 50,
}}
/>
);
};
// 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 (
<div style={{ position: "relative", ...style }}>
{shouldGlitch && (
<>
<span
style={{
position: "absolute",
left: glitchOffset,
color: "#ff0000",
opacity: 0.8,
clipPath: "inset(10% 0 60% 0)",
}}
>
{children}
</span>
<span
style={{
position: "absolute",
left: -glitchOffset,
color: "#00ffff",
opacity: 0.8,
clipPath: "inset(60% 0 10% 0)",
}}
>
{children}
</span>
</>
)}
<span style={{ position: "relative" }}>{children}</span>
</div>
);
};
// 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 (
<AbsoluteFill
style={{
backgroundColor: "#0a0a0a",
opacity: fadeIn * flicker,
}}
>
<ScanLines opacity={0.15} />
<LetterBox />
{/* Dramatic spotlight */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
width: 800,
height: 800,
transform: "translate(-50%, -50%)",
background:
"radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%)",
}}
/>
{/* Main text */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
textAlign: "center",
}}
>
<GlitchText
style={{
fontSize: 72,
fontWeight: 300,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
letterSpacing: 8,
textTransform: "uppercase",
}}
>
{displayText}
</GlitchText>
{/* Blinking cursor */}
{typewriterLength < fullText.length && (
<span
style={{
opacity: frame % 30 < 15 ? 1 : 0,
color: "#ef4444",
fontSize: 72,
fontWeight: 300,
}}
>
_
</span>
)}
</div>
{/* Bottom text */}
<div
style={{
position: "absolute",
bottom: 180,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 3, fps * 4], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 24,
color: "#ef4444",
fontFamily: "monospace",
letterSpacing: 4,
}}
>
YOUR EMOTIONS
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<ScanLines opacity={0.1} />
<LetterBox />
{/* Title */}
<div
style={{
position: "absolute",
top: 180,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 32,
color: "#fbbf24",
fontFamily: "monospace",
letterSpacing: 8,
opacity: interpolate(frame, [0, fps * 0.5], [0, 1]),
}}
>
ASSEMBLING THE CREW
</div>
</div>
{/* Crew grid */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: 60,
}}
>
{crew.map((member, i) => {
const delay = i * 8;
const memberProgress = spring({
frame: frame - delay,
fps,
config: { damping: 12, stiffness: 100 },
});
return (
<div
key={member.name}
style={{
textAlign: "center",
opacity: interpolate(memberProgress, [0, 1], [0, 1]),
transform: `translateY(${interpolate(memberProgress, [0, 1], [50, 0])}px)`,
}}
>
<div
style={{
fontSize: 100,
marginBottom: 15,
filter: `drop-shadow(0 0 20px ${member.color})`,
}}
>
{member.emoji}
</div>
<div
style={{
fontSize: 20,
color: member.color,
fontFamily: "monospace",
fontWeight: 700,
letterSpacing: 2,
}}
>
{member.name}
</div>
<div
style={{
fontSize: 14,
color: "#666",
fontFamily: "monospace",
marginTop: 5,
}}
>
{member.role}
</div>
</div>
);
})}
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a1628" }}>
<ScanLines opacity={0.05} />
<LetterBox />
{/* Blueprint grid */}
<svg
style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }}
>
{/* Vertical lines */}
{[...Array(20)].map((_, i) => (
<line
key={`v-${i}`}
x1={(i * width) / 20}
y1={0}
x2={(i * width) / 20}
y2={height * gridProgress}
stroke="#1e40af"
strokeWidth={0.5}
opacity={0.3}
/>
))}
{/* Horizontal lines */}
{[...Array(30)].map((_, i) => (
<line
key={`h-${i}`}
x1={0}
y1={(i * height) / 30}
x2={width * gridProgress}
y2={(i * height) / 30}
stroke="#1e40af"
strokeWidth={0.5}
opacity={0.3}
/>
))}
</svg>
{/* Phone blueprint */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
opacity: interpolate(frame, [fps, fps * 2], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
width: 300,
height: 600,
border: "2px solid #3b82f6",
borderRadius: 40,
position: "relative",
}}
>
{/* Target markers */}
<div
style={{
position: "absolute",
top: "30%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 100,
height: 100,
border: "2px dashed #ef4444",
borderRadius: "50%",
}}
>
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
fontSize: 14,
color: "#ef4444",
fontFamily: "monospace",
}}
>
TARGET
</div>
</div>
{/* Entry point */}
<div
style={{
position: "absolute",
bottom: 80,
left: "50%",
transform: "translateX(-50%)",
fontSize: 12,
color: "#10b981",
fontFamily: "monospace",
}}
>
ENTRY POINT
</div>
</div>
</div>
{/* Title */}
<div
style={{
position: "absolute",
top: 180,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 32,
color: "#3b82f6",
fontFamily: "monospace",
letterSpacing: 8,
}}
>
THE PLAN
</div>
</div>
{/* Steps */}
<div
style={{
position: "absolute",
bottom: 200,
left: 60,
right: 60,
display: "flex",
justifyContent: "space-around",
}}
>
{["DOWNLOAD", "TAP MOOD", "REPEAT"].map((step, i) => (
<div
key={step}
style={{
opacity: interpolate(
frame,
[fps * 2 + i * 10, fps * 2.5 + i * 10],
[0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
),
}}
>
<div
style={{
fontSize: 48,
color: "#fbbf24",
fontFamily: "monospace",
fontWeight: 700,
}}
>
{i + 1}
</div>
<div
style={{
fontSize: 16,
color: "#94a3b8",
fontFamily: "monospace",
}}
>
{step}
</div>
</div>
))}
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<ScanLines opacity={0.1} />
<LetterBox />
{/* Dramatic lighting */}
{selectFlash && (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(16, 185, 129, 0.3)",
zIndex: 10,
}}
/>
)}
{/* Mood buttons */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
gap: 30,
}}
>
{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 (
<div
key={i}
style={{
width: 120,
height: 120,
borderRadius: 30,
backgroundColor: isSelected ? "#10b981" : "rgba(255,255,255,0.1)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 60,
opacity: buttonProgress,
transform: `scale(${isSelected ? 1.2 : 1}) translateY(${interpolate(buttonProgress, [0, 1], [50, 0])}px)`,
boxShadow: isSelected ? "0 0 60px #10b981" : "none",
transition: "all 0.3s",
}}
>
{mood}
</div>
);
})}
</div>
{/* Approaching finger/cursor */}
<div
style={{
position: "absolute",
top: "60%",
left: `${50 + 20 - fingerProgress * 8}%`,
fontSize: 80,
transform: `translateX(-50%) rotate(-30deg)`,
opacity: fingerProgress < 1 ? fingerProgress : 0,
}}
>
👆
</div>
{/* "ACQUIRED" text */}
{frame > fps * 3.2 && (
<div
style={{
position: "absolute",
bottom: 250,
left: 0,
right: 0,
textAlign: "center",
}}
>
<GlitchText
style={{
fontSize: 48,
color: "#10b981",
fontFamily: "monospace",
letterSpacing: 16,
fontWeight: 700,
}}
>
MOOD ACQUIRED
</GlitchText>
</div>
)}
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<ScanLines opacity={0.05} />
<LetterBox />
{/* Vault doors */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
width: "50%",
height: "100%",
backgroundColor: "#1a1a1a",
transform: `translateX(${-vaultOpen * 100}%)`,
borderRight: "4px solid #333",
zIndex: 20,
}}
/>
<div
style={{
position: "absolute",
top: 0,
right: 0,
width: "50%",
height: "100%",
backgroundColor: "#1a1a1a",
transform: `translateX(${vaultOpen * 100}%)`,
borderLeft: "4px solid #333",
zIndex: 20,
}}
/>
{/* Bright light behind vault */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 600,
height: 600,
background: `radial-gradient(circle, rgba(251,191,36,${vaultOpen * 0.5}) 0%, transparent 70%)`,
}}
/>
{/* App icon revealed */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
opacity: vaultOpen,
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 200,
height: 200,
borderRadius: 200 * 0.22,
marginBottom: 40,
filter: `drop-shadow(0 0 60px rgba(251,191,36,0.8))`,
}}
/>
<div
style={{
fontSize: 80,
fontWeight: 800,
color: "white",
fontFamily: "system-ui, -apple-system, sans-serif",
textShadow: "0 0 40px rgba(255,255,255,0.5)",
}}
>
Feels
</div>
<div
style={{
fontSize: 28,
color: "#fbbf24",
fontFamily: "monospace",
marginTop: 20,
letterSpacing: 4,
opacity: interpolate(frame, [fps * 2, fps * 2.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
TAKE BACK YOUR EMOTIONS
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 25 seconds total
export const ConceptHMoodHeist: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<SetupScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(5 * fps)}>
<CrewScene />
</Sequence>
<Sequence from={Math.round(10 * fps)} durationInFrames={Math.round(5 * fps)}>
<PlanScene />
</Sequence>
<Sequence from={Math.round(15 * fps)} durationInFrames={Math.round(5 * fps)}>
<ExecutionScene />
</Sequence>
<Sequence from={Math.round(20 * fps)} durationInFrames={Math.round(5 * fps)}>
<GetawayScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.3) 2px,
rgba(0,0,0,0.3) 4px
)`,
pointerEvents: "none",
zIndex: 100,
}}
/>
{/* Screen flicker */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(255,255,255,0.02)",
opacity: Math.sin(frame * 0.5) > 0.9 ? 1 : 0,
pointerEvents: "none",
zIndex: 101,
}}
/>
{/* Vignette */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background:
"radial-gradient(ellipse at center, transparent 50%, rgba(0,0,0,0.5) 100%)",
pointerEvents: "none",
zIndex: 99,
}}
/>
</>
);
};
// 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 (
<div
style={{
position: "absolute",
left: x,
top: y + bounceY,
width: size,
height: size,
backgroundColor: colors[mood],
borderRadius: 8,
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "4px solid #000",
boxShadow: "4px 4px 0 #000",
}}
>
<span
style={{
fontSize: size * 0.4,
...pixelText,
color: "#000",
textShadow: "none",
}}
>
{faces[mood]}
</span>
</div>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#1a1a2e" }}>
<CRTEffect />
{/* 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 (
<div
key={i}
style={{
position: "absolute",
left: `${x}%`,
top: `${y}%`,
width: size * 2,
height: size * 2,
backgroundColor: "#fff",
opacity: 0.5 + (i % 5) * 0.1,
}}
/>
);
})}
{/* Title */}
<div
style={{
position: "absolute",
top: 200,
left: 0,
right: 0,
textAlign: "center",
transform: `translateY(${interpolate(titleDrop, [0, 1], [-200, 0])}px)`,
}}
>
<div
style={{
fontSize: 80,
color: "#fbbf24",
...pixelText,
marginBottom: 20,
}}
>
FEELS
</div>
<div
style={{
fontSize: 28,
color: "#10b981",
...pixelText,
}}
>
THE GAME
</div>
</div>
{/* Mood characters parade */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "flex",
gap: 20,
}}
>
{[0, 1, 2, 3, 4].map((mood, i) => {
const delay = i * 5;
const appearProgress = spring({
frame: frame - delay - 15,
fps,
config: { damping: 12 },
});
return (
<div
key={mood}
style={{
transform: `scale(${interpolate(appearProgress, [0, 1], [0, 1])})`,
}}
>
<MoodSprite mood={mood} x={0} y={0} size={100} />
</div>
);
})}
</div>
{/* Insert Coin */}
<div
style={{
position: "absolute",
bottom: 300,
left: 0,
right: 0,
textAlign: "center",
opacity: blink ? 1 : 0,
}}
>
<div
style={{
fontSize: 24,
color: "#fff",
...pixelText,
}}
>
INSERT COIN
</div>
</div>
{/* Credits */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 16,
color: "#666",
...pixelText,
}}
>
© 2025 FEELS CORP
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#16213e" }}>
<CRTEffect />
{/* HUD */}
<div
style={{
position: "absolute",
top: 40,
left: 40,
right: 40,
display: "flex",
justifyContent: "space-between",
}}
>
<div style={{ fontSize: 20, color: "#fff", ...pixelText }}>
SCORE: {score.toString().padStart(5, "0")}
</div>
<div style={{ fontSize: 20, color: "#ef4444", ...pixelText }}>
</div>
<div style={{ fontSize: 20, color: "#fbbf24", ...pixelText }}>
STREAK: 7
</div>
</div>
{/* 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 (
<MoodSprite
key={i}
mood={item.mood}
x={item.startX}
y={y}
size={80}
bounce={false}
/>
);
})}
{/* Player basket */}
<div
style={{
position: "absolute",
left: playerX,
bottom: 150,
width: 100,
height: 80,
backgroundColor: "#3b82f6",
border: "4px solid #000",
borderRadius: "0 0 20px 20px",
boxShadow: "4px 4px 0 #000",
}}
>
<div
style={{
position: "absolute",
top: -30,
left: "50%",
transform: "translateX(-50%)",
fontSize: 40,
}}
>
🧺
</div>
</div>
{/* Ground */}
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 100,
backgroundColor: "#2d3748",
borderTop: "4px solid #4a5568",
}}
>
{/* Ground pattern */}
{[...Array(20)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
left: i * 60,
top: 20,
width: 40,
height: 20,
backgroundColor: "#3d4a5c",
}}
/>
))}
</div>
{/* "CATCH THE GOOD VIBES" */}
<div
style={{
position: "absolute",
top: 150,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 28,
color: "#10b981",
...pixelText,
}}
>
CATCH THE GOOD VIBES!
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0f0f23" }}>
<CRTEffect />
{/* Explosion rays */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) rotate(${rotateSpeed}deg)`,
}}
>
{[...Array(12)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
width: 20,
height: 600,
backgroundColor: i % 2 === 0 ? "#fbbf24" : "#f97316",
transform: `rotate(${i * 30}deg)`,
transformOrigin: "center center",
opacity: 0.3,
}}
/>
))}
</div>
{/* Power up text */}
<div
style={{
position: "absolute",
top: 250,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 48,
color: "#fbbf24",
...pixelText,
transform: `scale(${powerUpPulse})`,
}}
>
POWER UP!
</div>
</div>
{/* Giant streak number */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${powerUpPulse})`,
textAlign: "center",
}}
>
<div
style={{
fontSize: 200,
color: "#fff",
...pixelText,
}}
>
{streakNumber}
</div>
<div
style={{
fontSize: 32,
color: "#10b981",
...pixelText,
}}
>
DAY STREAK!
</div>
</div>
{/* 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 (
<div
key={i}
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(calc(-50% + ${x}px), calc(-50% + ${y}px))`,
fontSize: 60,
}}
>
{emoji}
</div>
);
})}
{/* XP bonus */}
<div
style={{
position: "absolute",
bottom: 300,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 24,
color: "#a78bfa",
...pixelText,
}}
>
+500 XP BONUS!
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#1a1a2e" }}>
<CRTEffect />
{/* Title */}
<div
style={{
position: "absolute",
top: 150,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 48,
color: "#fbbf24",
...pixelText,
}}
>
HIGH SCORES
</div>
</div>
{/* Leaderboard */}
<div
style={{
position: "absolute",
top: 350,
left: "50%",
transform: "translateX(-50%)",
}}
>
{scores.map((entry, i) => {
const delay = i * 8;
const rowProgress = spring({
frame: frame - delay,
fps,
config: { damping: 12 },
});
return (
<div
key={i}
style={{
display: "flex",
gap: 40,
marginBottom: 30,
opacity: interpolate(rowProgress, [0, 1], [0, 1]),
transform: `translateX(${interpolate(rowProgress, [0, 1], [-100, 0])}px)`,
}}
>
<div
style={{
fontSize: 28,
color: entry.rank === 1 ? "#fbbf24" : "#666",
...pixelText,
width: 60,
}}
>
{entry.rank}.
</div>
<div
style={{
fontSize: 28,
color: entry.isPlayer ? "#10b981" : "#fff",
...pixelText,
width: 150,
}}
>
{entry.name}
</div>
<div
style={{
fontSize: 28,
color: entry.isPlayer ? "#10b981" : "#fff",
...pixelText,
}}
>
{entry.score.toLocaleString()}
</div>
{entry.isPlayer && (
<span style={{ fontSize: 28, marginLeft: 10 }}>👑</span>
)}
</div>
);
})}
</div>
{/* New high score flash */}
<div
style={{
position: "absolute",
bottom: 250,
left: 0,
right: 0,
textAlign: "center",
opacity: frame % 20 < 15 ? 1 : 0,
}}
>
<div
style={{
fontSize: 32,
color: "#ef4444",
...pixelText,
}}
>
NEW HIGH SCORE
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<CRTEffect />
{/* Pixelated app icon */}
<div
style={{
position: "absolute",
top: "40%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
border: "6px solid #fff",
boxShadow: "8px 8px 0 #000",
imageRendering: "pixelated",
}}
/>
</div>
{/* Title */}
<div
style={{
position: "absolute",
top: "60%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 64,
color: "#10b981",
...pixelText,
marginBottom: 20,
}}
>
FEELS
</div>
<div
style={{
fontSize: 24,
color: "#fbbf24",
...pixelText,
}}
>
LEVEL UP YOUR MOOD
</div>
</div>
{/* Press Start */}
<div
style={{
position: "absolute",
bottom: 300,
left: 0,
right: 0,
textAlign: "center",
opacity: blink ? 1 : 0,
}}
>
<div
style={{
fontSize: 24,
color: "#fff",
...pixelText,
}}
>
PRESS START
</div>
</div>
{/* Download prompt */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 16,
color: "#666",
...pixelText,
}}
>
DOWNLOAD NOW ON APP STORE
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptIRetroArcade: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(4 * fps)}>
<InsertCoinScene />
</Sequence>
<Sequence from={Math.round(4 * fps)} durationInFrames={Math.round(5 * fps)}>
<GameplayScene />
</Sequence>
<Sequence from={Math.round(9 * fps)} durationInFrames={Math.round(4 * fps)}>
<PowerUpScene />
</Sequence>
<Sequence from={Math.round(13 * fps)} durationInFrames={Math.round(3 * fps)}>
<HighScoreScene />
</Sequence>
<Sequence from={Math.round(16 * fps)} durationInFrames={Math.round(4 * fps)}>
<GameOverScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
opacity: intensity,
background: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' seed='${frame}' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E")`,
pointerEvents: "none",
zIndex: 200,
}}
/>
);
};
// Redacted text bar
const Redacted: React.FC<{ width: number }> = ({ width }) => (
<span
style={{
display: "inline-block",
width,
height: "1em",
backgroundColor: "#000",
marginLeft: 8,
marginRight: 8,
verticalAlign: "middle",
}}
/>
);
// Glitch/static overlay
const StaticOverlay: React.FC<{ active: boolean }> = ({ active }) => {
const frame = useCurrentFrame();
if (!active) return null;
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "#fff",
opacity: 0.1 + Math.random() * 0.2,
zIndex: 150,
}}
/>
);
};
// 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 (
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) rotate(-15deg) scale(${interpolate(stampProgress, [0, 1], [3, 1])})`,
opacity: interpolate(stampProgress, [0, 0.5, 1], [0, 1, 1]),
border: "8px solid #ef4444",
padding: "20px 40px",
borderRadius: 8,
}}
>
<div
style={{
fontSize: 48,
fontWeight: 900,
color: "#ef4444",
fontFamily: "Impact, sans-serif",
letterSpacing: 8,
}}
>
CLASSIFIED
</div>
</div>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<FilmGrain intensity={0.2} />
<StaticOverlay active={staticActive} />
{/* Dark vignette */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background:
"radial-gradient(ellipse at center, transparent 30%, rgba(0,0,0,0.8) 100%)",
}}
/>
{/* Ominous text */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${zoomIn})`,
textAlign: "center",
opacity: textOpacity,
}}
>
<div
style={{
fontSize: 28,
color: "#666",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginBottom: 40,
letterSpacing: 4,
}}
>
WHAT IF EVERYTHING YOU KNEW
</div>
<div
style={{
fontSize: 72,
color: "#fff",
fontWeight: 900,
fontFamily: "system-ui, sans-serif",
letterSpacing: 2,
textTransform: "uppercase",
}}
>
WAS A LIE?
</div>
</div>
{/* Timestamp */}
<div
style={{
position: "absolute",
bottom: 100,
right: 60,
fontSize: 14,
color: "#ef4444",
fontFamily: "monospace",
}}
>
DOC-7X-{frame.toString().padStart(4, "0")}
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#1a1a1a" }}>
<FilmGrain intensity={0.15} />
{/* Cork board background */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "#8B4513",
opacity: 0.3,
}}
/>
{/* Document */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) translateY(${docSlide}px) rotate(-2deg)`,
width: 700,
backgroundColor: "#f5f5dc",
padding: 60,
boxShadow: "0 20px 60px rgba(0,0,0,0.5)",
}}
>
{/* Header */}
<div
style={{
fontSize: 14,
color: "#333",
fontFamily: "Courier New, monospace",
marginBottom: 20,
borderBottom: "1px solid #999",
paddingBottom: 10,
}}
>
INTERNAL MEMO - RESTRICTED ACCESS
</div>
{/* Body text with redactions */}
<div
style={{
fontSize: 18,
color: "#222",
fontFamily: "Georgia, serif",
lineHeight: 2,
}}
>
Subject: Emotional Awareness Initiative
<br />
<br />
The <Redacted width={120} /> has determined that widespread
<Redacted width={180} /> of emotional patterns could lead to
<Redacted width={80} /> self-improvement. This is
<Redacted width={100} /> to our interests.
<br />
<br />
Recommendation: Continue suppression of <Redacted width={150} />
tracking tools.
</div>
{/* Classified stamp */}
<ClassifiedStamp delay={fps} />
</div>
{/* Red strings connecting */}
<svg
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
>
<line
x1="100"
y1="200"
x2="400"
y2="400"
stroke="#ef4444"
strokeWidth="2"
opacity="0.6"
/>
<line
x1="900"
y1="300"
x2="650"
y2="500"
stroke="#ef4444"
strokeWidth="2"
opacity="0.6"
/>
</svg>
{/* Narration text */}
<div
style={{
position: "absolute",
bottom: 150,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 2, fps * 2.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 24,
color: "#fff",
fontFamily: "Georgia, serif",
fontStyle: "italic",
}}
>
"They don't want you to understand yourself."
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#000" }}>
<FilmGrain intensity={0.25} />
<StaticOverlay active={glitchActive} />
{/* Dramatic light rays */}
<div
style={{
position: "absolute",
top: 0,
left: "50%",
width: 400,
height: "100%",
background:
"linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.05) 50%, transparent 100%)",
transform: "translateX(-50%)",
}}
/>
{/* The truth revealed */}
<div
style={{
position: "absolute",
top: "30%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 24,
color: "#ef4444",
fontFamily: "monospace",
letterSpacing: 8,
marginBottom: 40,
opacity: interpolate(revealProgress, [0, 0.3], [0, 1]),
}}
>
THE TRUTH IS
</div>
<div
style={{
fontSize: 64,
color: "#fff",
fontWeight: 900,
fontFamily: "system-ui, sans-serif",
opacity: interpolate(revealProgress, [0.3, 0.6], [0, 1]),
}}
>
YOUR EMOTIONS
</div>
<div
style={{
fontSize: 64,
color: "#10b981",
fontWeight: 900,
fontFamily: "system-ui, sans-serif",
marginTop: 20,
opacity: interpolate(revealProgress, [0.6, 1], [0, 1]),
}}
>
MATTER
</div>
</div>
{/* Bottom text */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 3, fps * 3.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 28,
color: "#666",
fontFamily: "Georgia, serif",
fontStyle: "italic",
}}
>
And there's an app that knows it.
</div>
</div>
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
<FilmGrain intensity={0.1} />
{/* Spotlight effect */}
<div
style={{
position: "absolute",
top: "40%",
left: "50%",
width: 600,
height: 600,
transform: "translate(-50%, -50%)",
background:
"radial-gradient(circle, rgba(16,185,129,0.2) 0%, transparent 70%)",
}}
/>
{/* App icon */}
<div
style={{
position: "absolute",
top: "35%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 200,
height: 200,
borderRadius: 200 * 0.22,
boxShadow: "0 0 100px rgba(16,185,129,0.5)",
}}
/>
</div>
{/* App name */}
<div
style={{
position: "absolute",
top: "58%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 80,
fontWeight: 800,
color: "#fff",
fontFamily: "system-ui, sans-serif",
opacity: interpolate(logoProgress, [0, 1], [0, 1]),
}}
>
Feels
</div>
{/* Typed tagline */}
<div
style={{
fontSize: 32,
color: "#10b981",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginTop: 30,
minHeight: 50,
}}
>
{tagline.slice(0, typedLength)}
<span style={{ opacity: frame % 30 < 15 ? 1 : 0 }}>|</span>
</div>
</div>
{/* "Wake up" text */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 3.5, fps * 4], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 20,
color: "#ef4444",
fontFamily: "monospace",
letterSpacing: 8,
}}
>
WAKE UP. DOWNLOAD NOW.
</div>
</div>
{/* File number */}
<div
style={{
position: "absolute",
bottom: 100,
right: 60,
fontSize: 12,
color: "#333",
fontFamily: "monospace",
}}
>
CASE CLOSED
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptJConspiracy: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(4.5 * fps)}>
<HookScene />
</Sequence>
<Sequence from={Math.round(4.5 * fps)} durationInFrames={Math.round(5 * fps)}>
<EvidenceScene />
</Sequence>
<Sequence from={Math.round(9.5 * fps)} durationInFrames={Math.round(5 * fps)}>
<TruthScene />
</Sequence>
<Sequence from={Math.round(14.5 * fps)} durationInFrames={Math.round(5.5 * fps)}>
<SolutionScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 60,
backgroundColor: ESPN_RED,
display: "flex",
alignItems: "center",
overflow: "hidden",
zIndex: 100,
}}
>
<div
style={{
position: "absolute",
left: 0,
width: 200,
height: "100%",
backgroundColor: ESPN_DARK,
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 2,
}}
>
<span
style={{
fontSize: 18,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
}}
>
FEELS CENTER
</span>
</div>
<div
style={{
position: "absolute",
left: 200,
whiteSpace: "nowrap",
fontSize: 20,
color: "#fff",
fontWeight: 600,
fontFamily: "system-ui, sans-serif",
transform: `translateX(${width - offset}px)`,
}}
>
{tickerText}
</div>
</div>
);
};
// Score bug / mood tracker
const MoodScoreBug: React.FC<{ mood: string; score: number; streak: number }> = ({
mood,
score,
streak,
}) => {
return (
<div
style={{
position: "absolute",
top: 40,
left: 40,
backgroundColor: "rgba(0,0,0,0.8)",
borderLeft: `4px solid ${ESPN_RED}`,
padding: "15px 25px",
display: "flex",
gap: 30,
alignItems: "center",
}}
>
<div>
<div style={{ fontSize: 14, color: "#888", fontWeight: 600 }}>TODAY</div>
<div style={{ fontSize: 36, color: "#fff" }}>{mood}</div>
</div>
<div style={{ borderLeft: "1px solid #444", paddingLeft: 30 }}>
<div style={{ fontSize: 14, color: "#888", fontWeight: 600 }}>SCORE</div>
<div style={{ fontSize: 36, color: ESPN_YELLOW, fontWeight: 700 }}>
{score}
</div>
</div>
<div style={{ borderLeft: "1px solid #444", paddingLeft: 30 }}>
<div style={{ fontSize: 14, color: "#888", fontWeight: 600 }}>STREAK</div>
<div style={{ fontSize: 36, color: "#10b981", fontWeight: 700 }}>
{streak}🔥
</div>
</div>
</div>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: ESPN_DARK }}>
{/* Dramatic spotlight */}
<div
style={{
position: "absolute",
top: 0,
left: "50%",
width: 800,
height: "100%",
background: `linear-gradient(90deg, transparent 0%, rgba(204,0,0,0.1) 50%, transparent 100%)`,
transform: "translateX(-50%)",
}}
/>
{/* Logo slam */}
<div
style={{
position: "absolute",
top: "35%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoSlam, [0, 1], [3, 1])})`,
textAlign: "center",
}}
>
<div
style={{
fontSize: 120,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
textShadow: `0 0 60px ${ESPN_RED}`,
}}
>
FEELS
</div>
<div
style={{
fontSize: 48,
fontWeight: 700,
color: ESPN_RED,
fontFamily: "system-ui, sans-serif",
letterSpacing: 16,
}}
>
CENTER
</div>
</div>
{/* Subtitle */}
<div
style={{
position: "absolute",
top: "60%",
left: 0,
right: 0,
textAlign: "center",
opacity: textReveal,
}}
>
<div
style={{
fontSize: 28,
color: "#888",
fontFamily: "system-ui, sans-serif",
fontWeight: 600,
}}
>
YOUR EMOTIONAL HIGHLIGHTS
</div>
</div>
{/* ESPN-style corner graphics */}
<div
style={{
position: "absolute",
bottom: 100,
right: 40,
fontSize: 16,
color: "#444",
fontFamily: "monospace",
}}
>
LIVE
<span
style={{
display: "inline-block",
width: 10,
height: 10,
backgroundColor: ESPN_RED,
borderRadius: "50%",
marginLeft: 10,
animation: "blink 1s infinite",
}}
/>
</div>
<NewsTicker />
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: ESPN_DARK }}>
{/* "PLAY OF THE DAY" banner */}
<div
style={{
position: "absolute",
top: 40,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
display: "inline-block",
backgroundColor: ESPN_YELLOW,
padding: "10px 40px",
transform: "skewX(-10deg)",
}}
>
<span
style={{
fontSize: 32,
fontWeight: 900,
color: "#000",
fontFamily: "system-ui, sans-serif",
transform: "skewX(10deg)",
display: "inline-block",
}}
>
🏆 PLAY OF THE DAY
</span>
</div>
</div>
{/* Replay frame */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
border: "4px solid #333",
borderRadius: 20,
padding: 40,
backgroundColor: "rgba(0,0,0,0.5)",
}}
>
{/* Mood buttons */}
<div style={{ display: "flex", gap: 20 }}>
{["😢", "😕", "😐", "🙂", "😊"].map((emoji, i) => {
const isTarget = i === 4;
const isSelected = isTarget && replayProgress > 0.85;
return (
<div
key={i}
style={{
width: 100,
height: 100,
borderRadius: 25,
backgroundColor: isSelected ? "#10b981" : "rgba(255,255,255,0.1)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 50,
boxShadow: isSelected ? `0 0 40px #10b981` : "none",
transform: isSelected ? "scale(1.1)" : "scale(1)",
}}
>
{emoji}
</div>
);
})}
</div>
{/* Slow-mo finger */}
<div
style={{
position: "absolute",
left: 40 + fingerX,
bottom: -50 + fingerY,
fontSize: 60,
transform: "rotate(-30deg)",
opacity: replayProgress < 0.9 ? 1 : 0,
}}
>
👆
</div>
{/* Impact lines */}
{impactFlash && (
<>
{[...Array(8)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
right: 0,
top: "50%",
width: 60,
height: 4,
backgroundColor: "#fff",
transform: `rotate(${i * 45}deg)`,
transformOrigin: "right center",
}}
/>
))}
</>
)}
</div>
{/* Slow-mo indicator */}
<div
style={{
position: "absolute",
bottom: 150,
left: 40,
display: "flex",
alignItems: "center",
gap: 10,
}}
>
<div
style={{
fontSize: 18,
color: ESPN_YELLOW,
fontWeight: 700,
}}
>
SLOW-MO REPLAY
</div>
<div style={{ fontSize: 24 }}>🎬</div>
</div>
{/* Commentary */}
<div
style={{
position: "absolute",
bottom: 200,
left: 0,
right: 0,
textAlign: "center",
opacity: replayProgress > 0.9 ? 1 : 0,
}}
>
<div
style={{
fontSize: 36,
color: "#fff",
fontWeight: 700,
fontFamily: "system-ui, sans-serif",
}}
>
"WHAT A SELECTION! ABSOLUTELY CLINICAL!"
</div>
</div>
<NewsTicker />
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: ESPN_DARK }}>
{/* Stats title */}
<div
style={{
position: "absolute",
top: 80,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 36,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
}}
>
📊 SEASON STATS
</div>
</div>
{/* Stats grid */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: 40,
}}
>
{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 (
<div
key={stat.label}
style={{
backgroundColor: "rgba(255,255,255,0.05)",
borderLeft: `4px solid ${stat.color}`,
padding: "30px 50px",
opacity: interpolate(statProgress, [0, 1], [0, 1]),
transform: `translateY(${interpolate(statProgress, [0, 1], [30, 0])}px)`,
}}
>
<div
style={{
fontSize: 16,
color: "#888",
fontWeight: 600,
marginBottom: 10,
}}
>
{stat.label}
</div>
<div
style={{
fontSize: 64,
color: stat.color,
fontWeight: 900,
fontFamily: "system-ui, sans-serif",
}}
>
{typeof stat.value === "number" ? countUp : stat.value}
</div>
</div>
);
})}
</div>
<MoodScoreBug mood="😊" score={85} streak={23} />
<NewsTicker />
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: ESPN_DARK }}>
{/* Dramatic background */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `radial-gradient(ellipse at center, rgba(204,0,0,0.2) 0%, transparent 70%)`,
}}
/>
{/* App icon */}
<div
style={{
position: "absolute",
top: "35%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 180,
height: 180,
borderRadius: 180 * 0.22,
border: `4px solid ${ESPN_RED}`,
boxShadow: `0 0 60px ${ESPN_RED}`,
}}
/>
</div>
{/* CTA text */}
<div
style={{
position: "absolute",
top: "58%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 72,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
}}
>
Feels
</div>
<div
style={{
fontSize: 32,
color: ESPN_YELLOW,
fontWeight: 700,
marginTop: 20,
opacity: interpolate(frame, [fps, fps * 1.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
JOIN THE LEAGUE
</div>
</div>
{/* ESPN-style "Download" button */}
<div
style={{
position: "absolute",
bottom: 200,
left: "50%",
transform: `translateX(-50%) scale(${interpolate(frame, [fps * 2, fps * 2.5], [0.8, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})})`,
}}
>
<div
style={{
backgroundColor: ESPN_RED,
padding: "20px 60px",
borderRadius: 8,
transform: "skewX(-5deg)",
}}
>
<span
style={{
fontSize: 28,
fontWeight: 900,
color: "#fff",
fontFamily: "system-ui, sans-serif",
transform: "skewX(5deg)",
display: "inline-block",
}}
>
DOWNLOAD NOW
</span>
</div>
</div>
{/* "THIS HAS BEEN" outro */}
<div
style={{
position: "absolute",
bottom: 100,
left: 0,
right: 0,
textAlign: "center",
opacity: interpolate(frame, [fps * 3, fps * 3.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
<div
style={{
fontSize: 18,
color: "#666",
fontFamily: "system-ui, sans-serif",
}}
>
This has been FEELS CENTER. Track responsibly.
</div>
</div>
</AbsoluteFill>
);
};
// Main composition - 20 seconds total
export const ConceptKSportsCenter: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(4 * fps)}>
<OpeningScene />
</Sequence>
<Sequence from={Math.round(4 * fps)} durationInFrames={Math.round(6 * fps)}>
<PlayOfDayScene />
</Sequence>
<Sequence from={Math.round(10 * fps)} durationInFrames={Math.round(5 * fps)}>
<StatsScene />
</Sequence>
<Sequence from={Math.round(15 * fps)} durationInFrames={Math.round(5 * fps)}>
<CTAScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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 (
<div
style={{
position: "absolute",
top: 0,
[side]: 0,
width: "55%",
height: "100%",
background: "linear-gradient(180deg, #8B0000 0%, #4a0000 100%)",
transform: `translateX(${side === "left" ? -offset : offset}%)`,
zIndex: 50,
boxShadow:
side === "left"
? "10px 0 30px rgba(0,0,0,0.5)"
: "-10px 0 30px rgba(0,0,0,0.5)",
}}
>
{/* Curtain folds */}
{[...Array(8)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
top: 0,
left: `${i * 12.5}%`,
width: "12.5%",
height: "100%",
background: `linear-gradient(90deg, rgba(0,0,0,0.2) 0%, transparent 50%, rgba(0,0,0,0.1) 100%)`,
}}
/>
))}
</div>
);
};
// 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 }) => (
<div
style={{
position: "absolute",
left: x - size / 2,
top: y - size / 2,
width: size,
height: size,
background: `radial-gradient(circle, ${color}${Math.floor(intensity * 255).toString(16).padStart(2, "0")} 0%, transparent 70%)`,
pointerEvents: "none",
}}
/>
);
// 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 (
<div
style={{
position: "absolute",
left: x,
top: y,
fontSize: size,
transform,
filter: "drop-shadow(0 10px 20px rgba(0,0,0,0.5))",
}}
>
{emoji}
</div>
);
};
// Stage lights at top
const StageLights: React.FC = () => {
const frame = useCurrentFrame();
const colors = ["#ff6b6b", "#feca57", "#48dbfb", "#ff9ff3", "#1dd1a1"];
return (
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 80,
backgroundColor: "#1a1a1a",
display: "flex",
justifyContent: "space-around",
alignItems: "center",
zIndex: 60,
}}
>
{colors.map((color, i) => {
const pulse = Math.sin(frame * 0.1 + i) * 0.3 + 0.7;
return (
<div
key={i}
style={{
width: 40,
height: 50,
backgroundColor: "#333",
borderRadius: "50% 50% 0 0",
display: "flex",
alignItems: "flex-end",
justifyContent: "center",
paddingBottom: 5,
}}
>
<div
style={{
width: 30,
height: 30,
borderRadius: "50%",
backgroundColor: color,
opacity: pulse,
boxShadow: `0 0 30px ${color}, 0 20px 60px ${color}`,
}}
/>
</div>
);
})}
</div>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
{/* Stage floor */}
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 400,
background: "linear-gradient(180deg, #2d2d2d 0%, #1a1a1a 100%)",
}}
/>
{/* Spotlight */}
<Spotlight x={540} y={600} size={600} intensity={0.4} />
{/* Title card */}
<div
style={{
position: "absolute",
top: "45%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(titleProgress, [0, 1], [0.5, 1])})`,
textAlign: "center",
opacity: interpolate(titleProgress, [0, 1], [0, 1]),
zIndex: 40,
}}
>
<div
style={{
fontSize: 32,
color: "#feca57",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginBottom: 20,
}}
>
Welcome to
</div>
<div
style={{
fontSize: 100,
fontWeight: 900,
color: "#fff",
fontFamily: "Georgia, serif",
textShadow: "0 0 60px rgba(255,200,100,0.5)",
letterSpacing: 8,
}}
>
FEELS
</div>
<div
style={{
fontSize: 48,
color: "#ff6b6b",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginTop: 10,
}}
>
The Musical
</div>
</div>
<StageLights />
<Curtain side="left" open={curtainOpen} />
<Curtain side="right" open={curtainOpen} />
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#1a1a1a" }}>
{/* Stage floor with reflection */}
<div
style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 500,
background:
"linear-gradient(180deg, #2d2d2d 0%, #1a1a1a 50%, #0a0a0a 100%)",
}}
/>
{/* Multiple spotlights */}
{emojis.map((e, i) => {
const pos = getPosition(i, danceProgress);
return (
<Spotlight
key={i}
x={pos.x + 50}
y={pos.y + 50}
size={300}
color={e.color}
intensity={0.3}
/>
);
})}
{/* "EVERYBODY FEEL!" banner */}
<div
style={{
position: "absolute",
top: 150,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 48,
fontWeight: 900,
color: "#feca57",
fontFamily: "Georgia, serif",
textShadow: "0 0 30px rgba(254,202,87,0.5)",
transform: `scale(${1 + Math.sin(frame * 0.2) * 0.05})`,
}}
>
EVERYBODY FEEL!
</div>
</div>
{/* Dancing emojis */}
{emojis.map((e, i) => {
const pos = getPosition(i, danceProgress);
const delay = i * 3;
return (
<DancingEmoji
key={i}
emoji={e.emoji}
x={pos.x}
y={pos.y}
size={100}
delay={delay}
danceStyle={i % 3 === 0 ? "bounce" : i % 3 === 1 ? "sway" : "spin"}
/>
);
})}
{/* 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 (
<div
key={i}
style={{
position: "absolute",
left: noteX,
top: noteY,
fontSize: 60,
color: "#feca57",
opacity: 0.6,
transform: `rotate(${Math.sin(frame * 0.1 + i) * 20}deg)`,
}}
>
{note}
</div>
);
})}
<StageLights />
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
{/* Dramatic backdrop */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background:
"radial-gradient(ellipse at center, #1a472a 0%, #0a0a0a 70%)",
}}
/>
{/* Star burst */}
<div
style={{
position: "absolute",
top: joyY - 100,
left: width / 2,
transform: `translate(-50%, -50%) rotate(${starRotation}deg)`,
}}
>
{[...Array(12)].map((_, i) => (
<div
key={i}
style={{
position: "absolute",
width: 8,
height: 300,
background: `linear-gradient(180deg, #feca57 0%, transparent 100%)`,
transform: `rotate(${i * 30}deg)`,
transformOrigin: "center bottom",
opacity: 0.3,
}}
/>
))}
</div>
{/* Giant spotlight on Joy */}
<Spotlight
x={width / 2}
y={joyY}
size={800}
color="#10b981"
intensity={spotlightPulse * 0.5}
/>
{/* Joy - center stage */}
<div
style={{
position: "absolute",
left: width / 2 - 100,
top: joyY - 100,
fontSize: 200,
filter: "drop-shadow(0 20px 40px rgba(16,185,129,0.5))",
transform: `scale(${1 + Math.sin(frame * 0.15) * 0.1})`,
}}
>
😊
</div>
{/* Lyric text */}
<div
style={{
position: "absolute",
bottom: 300,
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 40,
color: "#fff",
fontFamily: "Georgia, serif",
fontStyle: "italic",
opacity: interpolate(frame, [fps, fps * 1.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
"One tap is all it takes..."
</div>
<div
style={{
fontSize: 40,
color: "#10b981",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginTop: 20,
opacity: interpolate(frame, [fps * 2, fps * 2.5], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
"To know how your heart feels today!"
</div>
</div>
<StageLights />
</AbsoluteFill>
);
};
// 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 (
<AbsoluteFill style={{ backgroundColor: "#0a0a0a" }}>
{/* Grand finale lighting */}
<div
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `
radial-gradient(ellipse at 50% 30%, rgba(254,202,87,0.3) 0%, transparent 50%),
radial-gradient(ellipse at 30% 70%, rgba(236,72,153,0.2) 0%, transparent 40%),
radial-gradient(ellipse at 70% 70%, rgba(72,219,251,0.2) 0%, transparent 40%)
`,
}}
/>
{/* Line of emojis at top */}
<div
style={{
position: "absolute",
top: 200,
left: 0,
right: 0,
display: "flex",
justifyContent: "center",
gap: 40,
}}
>
{emojis.map((emoji, i) => {
const delay = i * 3;
const emojiProgress = spring({
frame: frame - delay,
fps,
config: { damping: 12 },
});
return (
<div
key={i}
style={{
fontSize: 80,
transform: `translateY(${interpolate(emojiProgress, [0, 1], [100, 0])}px) scale(${interpolate(emojiProgress, [0, 1], [0.5, 1])})`,
opacity: interpolate(emojiProgress, [0, 1], [0, 1]),
}}
>
{emoji}
</div>
);
})}
</div>
{/* App icon center stage */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
transform: `translate(-50%, -50%) scale(${interpolate(logoProgress, [0, 1], [0, 1])})`,
textAlign: "center",
}}
>
<Img
src={staticFile("app-icon.png")}
style={{
width: 200,
height: 200,
borderRadius: 200 * 0.22,
boxShadow: "0 0 100px rgba(254,202,87,0.5)",
}}
/>
</div>
{/* App name */}
<div
style={{
position: "absolute",
top: "68%",
left: 0,
right: 0,
textAlign: "center",
}}
>
<div
style={{
fontSize: 80,
fontWeight: 900,
color: "#fff",
fontFamily: "Georgia, serif",
textShadow: "0 0 40px rgba(255,255,255,0.5)",
opacity: interpolate(logoProgress, [0, 1], [0, 1]),
}}
>
Feels
</div>
<div
style={{
fontSize: 32,
color: "#feca57",
fontFamily: "Georgia, serif",
fontStyle: "italic",
marginTop: 20,
opacity: interpolate(frame, [fps * 2.5, fps * 3], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
}),
}}
>
Download Today
</div>
</div>
{/* 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 (
<div
key={i}
style={{
position: "absolute",
left: x,
top: y,
width: 15,
height: 15,
backgroundColor: confettiColors[i % confettiColors.length],
borderRadius: i % 2 === 0 ? "50%" : 2,
transform: `rotate(${frame * (3 + (i % 5))}deg)`,
opacity: interpolate(fallProgress, [0, 0.8, 1], [1, 1, 0]),
}}
/>
);
})}
<StageLights />
</AbsoluteFill>
);
};
// Main composition - 25 seconds total
export const ConceptLMusical: React.FC = () => {
const { fps } = useVideoConfig();
return (
<AbsoluteFill>
<Sequence from={0} durationInFrames={Math.round(5 * fps)}>
<CurtainRiseScene />
</Sequence>
<Sequence from={Math.round(5 * fps)} durationInFrames={Math.round(7 * fps)}>
<FeelingSongScene />
</Sequence>
<Sequence from={Math.round(12 * fps)} durationInFrames={Math.round(6 * fps)}>
<SoloNumberScene />
</Sequence>
<Sequence from={Math.round(18 * fps)} durationInFrames={Math.round(7 * fps)}>
<FinaleScene />
</Sequence>
</AbsoluteFill>
);
};

View File

@@ -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
═══════════════════════════════════════════════════════════════ */}
<Composition
id="FeelsPromoV1"
component={FeelsPromoV1}
durationInFrames={totalDuration}
durationInFrames={v1TotalDuration}
fps={fps}
width={1080}
height={1920}
/>
{/* ═══════════════════════════════════════════════════════════════
STANDARD CONCEPTS (A-G)
═══════════════════════════════════════════════════════════════ */}
{/* Concept A: 30 Seconds to Self-Awareness */}
<Composition
id="ConceptA-SelfAwareness"
component={ConceptASelfAwareness}
durationInFrames={Math.round(30 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept B: The No-Journal Journal (20s) */}
<Composition
id="ConceptB-NoJournalJournal"
component={ConceptBNoJournalJournal}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept C: Your Year in Feelings (15s) */}
<Composition
id="ConceptC-YearInFeelings"
component={ConceptCYearInFeelings}
durationInFrames={Math.round(15 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept D: Always There (25s) */}
<Composition
id="ConceptD-AlwaysThere"
component={ConceptDAlwaysThere}
durationInFrames={Math.round(25 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept E: Make It Yours (20s) */}
<Composition
id="ConceptE-MakeItYours"
component={ConceptEMakeItYours}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept F: Privacy First (15s) */}
<Composition
id="ConceptF-PrivacyFirst"
component={ConceptFPrivacyFirst}
durationInFrames={Math.round(15 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept G: The Streak Effect (20s) */}
<Composition
id="ConceptG-StreakEffect"
component={ConceptGStreakEffect}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* ═══════════════════════════════════════════════════════════════
WILD CONCEPTS (H-L) - OFF THE WALL CREATIVE
═══════════════════════════════════════════════════════════════ */}
{/* Concept H: The Mood Heist (25s) - Ocean's Eleven style thriller */}
<Composition
id="ConceptH-MoodHeist"
component={ConceptHMoodHeist}
durationInFrames={Math.round(25 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept I: Retro Arcade Feels (20s) - 8-bit video game */}
<Composition
id="ConceptI-RetroArcade"
component={ConceptIRetroArcade}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept J: The Feels Conspiracy (20s) - Dark documentary thriller */}
<Composition
id="ConceptJ-Conspiracy"
component={ConceptJConspiracy}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept K: Sports Center Emotions (20s) - ESPN broadcast parody */}
<Composition
id="ConceptK-SportsCenter"
component={ConceptKSportsCenter}
durationInFrames={Math.round(20 * fps)}
fps={fps}
width={1080}
height={1920}
/>
{/* Concept L: Feelings The Musical (25s) - Broadway musical number */}
<Composition
id="ConceptL-Musical"
component={ConceptLMusical}
durationInFrames={Math.round(25 * fps)}
fps={fps}
width={1080}
height={1920}