Add promo components and screenshot images
- Add LiveActivityAnimation, LiveActivityCard, BackgroundStill components - Update Root.tsx composition - Add widget and voting screenshot images Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BIN
feels-promo/public/timeline_dark_medium_voting.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
feels-promo/public/timeline_light_small_voting.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
feels-promo/public/voting_dark_large.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
feels-promo/public/voting_light_large.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
feels-promo/public/voting_light_medium.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
feels-promo/public/voting_light_small.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
52
feels-promo/src/BackgroundStill.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { AbsoluteFill, Img, staticFile } from "remotion";
|
||||||
|
|
||||||
|
// Static version of the tiled background (no animation)
|
||||||
|
export const BackgroundStill: React.FC = () => {
|
||||||
|
const width = 1080;
|
||||||
|
const height = 1920;
|
||||||
|
|
||||||
|
const iconSize = 80;
|
||||||
|
const gap = 40;
|
||||||
|
const cellSize = iconSize + gap;
|
||||||
|
|
||||||
|
const cols = Math.ceil(width / cellSize) + 4;
|
||||||
|
const rows = Math.ceil(height / cellSize) + 4;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbsoluteFill
|
||||||
|
style={{
|
||||||
|
background: "linear-gradient(180deg, #f59e0b 0%, #ef4444 100%)",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: -cellSize * 2,
|
||||||
|
left: -cellSize * 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{[...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>
|
||||||
|
);
|
||||||
|
};
|
||||||
280
feels-promo/src/LiveActivityAnimation.tsx
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
AbsoluteFill,
|
||||||
|
interpolate,
|
||||||
|
useCurrentFrame,
|
||||||
|
useVideoConfig,
|
||||||
|
spring,
|
||||||
|
Easing,
|
||||||
|
} from "remotion";
|
||||||
|
|
||||||
|
// Mood colors matching the app
|
||||||
|
const MOOD_COLORS = {
|
||||||
|
horrible: "#F44336",
|
||||||
|
bad: "#FF9800",
|
||||||
|
average: "#FFC107",
|
||||||
|
good: "#8BC34A",
|
||||||
|
great: "#4CAF50",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get mood based on progress
|
||||||
|
const getMoodForProgress = (progress: number): { name: string; color: string } => {
|
||||||
|
if (progress < 0.2) return { name: "Horrible", color: MOOD_COLORS.horrible };
|
||||||
|
if (progress < 0.4) return { name: "Bad", color: MOOD_COLORS.bad };
|
||||||
|
if (progress < 0.6) return { name: "Average", color: MOOD_COLORS.average };
|
||||||
|
if (progress < 0.8) return { name: "Good", color: MOOD_COLORS.good };
|
||||||
|
return { name: "Great", color: MOOD_COLORS.great };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Flame SVG icon
|
||||||
|
const FlameIcon: React.FC<{ size: number; color: string }> = ({ size, color }) => (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill={color}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M12 23C16.1421 23 19.5 19.6421 19.5 15.5C19.5 14.1183 19.1425 12.8052 18.5 11.6447C18.5 11.6447 18 12.5 17 12.5C17 12.5 18 9.5 16 6C14.5 7.5 13.5 8 12.5 8C12.5 8 13.5 5 12 2C10.5 4 9.5 5 8 6.5C6.5 8 5 10 5 12.5C5 12.5 4.5 12 4 11.5C4 11.5 3.5 13 3.5 14.5C3.5 19.1944 7.30558 23 12 23ZM12 20C9.79086 20 8 18.2091 8 16C8 15.3504 8.15822 14.7369 8.43721 14.1967C8.43721 14.1967 9 15 10 15C10 15 9 13 10 11C10.75 11.75 11.25 12 11.75 12C11.75 12 11.25 10.5 12 9C12.75 10 13.25 10.5 14 11.25C14.75 12 15.5 13 15.5 14.5C15.5 14.5 16 14 16.25 13.75C16.25 13.75 16.5 14.5 16.5 15.25C16.5 17.8734 14.5 20 12 20Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LiveActivityAnimation: React.FC = () => {
|
||||||
|
const frame = useCurrentFrame();
|
||||||
|
const { fps, durationInFrames, width, height } = useVideoConfig();
|
||||||
|
|
||||||
|
const targetStreak = 365;
|
||||||
|
|
||||||
|
// Animation timing
|
||||||
|
const animationStartFrame = fps * 1; // Start after 1 second
|
||||||
|
const animationEndFrame = durationInFrames - fps * 1; // End 1 second before end
|
||||||
|
const animationDuration = animationEndFrame - animationStartFrame;
|
||||||
|
|
||||||
|
// Calculate current streak with easing
|
||||||
|
const rawProgress = interpolate(
|
||||||
|
frame,
|
||||||
|
[animationStartFrame, animationEndFrame],
|
||||||
|
[0, 1],
|
||||||
|
{
|
||||||
|
extrapolateLeft: "clamp",
|
||||||
|
extrapolateRight: "clamp",
|
||||||
|
easing: Easing.out(Easing.cubic),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentStreak = Math.round(rawProgress * targetStreak);
|
||||||
|
const progressPercent = currentStreak / targetStreak;
|
||||||
|
const mood = getMoodForProgress(progressPercent);
|
||||||
|
|
||||||
|
// Spring animation for the flame icon
|
||||||
|
const flameScale = spring({
|
||||||
|
frame: frame % 15, // Pulse every 15 frames
|
||||||
|
fps,
|
||||||
|
config: {
|
||||||
|
damping: 10,
|
||||||
|
stiffness: 200,
|
||||||
|
mass: 0.5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const flameScaleValue = interpolate(flameScale, [0, 1], [1, 1.1]);
|
||||||
|
|
||||||
|
// Fade in animation
|
||||||
|
const fadeIn = interpolate(frame, [0, fps * 0.5], [0, 1], {
|
||||||
|
extrapolateRight: "clamp",
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbsoluteFill
|
||||||
|
style={{
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif",
|
||||||
|
opacity: fadeIn,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Live Activity Card */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: width * 0.9,
|
||||||
|
backgroundColor: "rgba(44, 44, 46, 0.95)",
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 24,
|
||||||
|
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Streak Indicator */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ transform: `scale(${flameScaleValue})` }}>
|
||||||
|
<FlameIcon size={64} color="#FF9500" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 56,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: "#FFFFFF",
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentStreak}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
day streak
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 100,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Status Section */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 12,
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentStreak > 0 ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Mood Color Circle */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: mood.color,
|
||||||
|
boxShadow: `0 0 20px ${mood.color}80`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Today's mood
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "#FFFFFF",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{mood.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "#FFFFFF",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Start your streak!
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tap to log your mood
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Bar Section */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Progress Bar */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 12,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
borderRadius: 6,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: `${progressPercent * 100}%`,
|
||||||
|
height: "100%",
|
||||||
|
background: `linear-gradient(90deg, ${MOOD_COLORS.horrible}, ${MOOD_COLORS.bad}, ${MOOD_COLORS.average}, ${MOOD_COLORS.good}, ${MOOD_COLORS.great})`,
|
||||||
|
borderRadius: 6,
|
||||||
|
transition: "width 0.1s ease-out",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Label */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
fontSize: 18,
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>0</span>
|
||||||
|
<span style={{ color: "#FFFFFF", fontWeight: 600 }}>
|
||||||
|
{currentStreak} / {targetStreak} days
|
||||||
|
</span>
|
||||||
|
<span>{targetStreak}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</AbsoluteFill>
|
||||||
|
);
|
||||||
|
};
|
||||||
261
feels-promo/src/LiveActivityCard.tsx
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
import { interpolate, useCurrentFrame, useVideoConfig, spring, Easing } from "remotion";
|
||||||
|
|
||||||
|
// Mood colors matching the app
|
||||||
|
const MOOD_COLORS = {
|
||||||
|
horrible: "#F44336",
|
||||||
|
bad: "#FF9800",
|
||||||
|
average: "#FFC107",
|
||||||
|
good: "#8BC34A",
|
||||||
|
great: "#4CAF50",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get mood based on progress
|
||||||
|
const getMoodForProgress = (progress: number): { name: string; color: string } => {
|
||||||
|
if (progress < 0.2) return { name: "Horrible", color: MOOD_COLORS.horrible };
|
||||||
|
if (progress < 0.4) return { name: "Bad", color: MOOD_COLORS.bad };
|
||||||
|
if (progress < 0.6) return { name: "Average", color: MOOD_COLORS.average };
|
||||||
|
if (progress < 0.8) return { name: "Good", color: MOOD_COLORS.good };
|
||||||
|
return { name: "Great", color: MOOD_COLORS.great };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Flame SVG icon
|
||||||
|
const FlameIcon: React.FC<{ size: number; color: string }> = ({ size, color }) => (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill={color}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M12 23C16.1421 23 19.5 19.6421 19.5 15.5C19.5 14.1183 19.1425 12.8052 18.5 11.6447C18.5 11.6447 18 12.5 17 12.5C17 12.5 18 9.5 16 6C14.5 7.5 13.5 8 12.5 8C12.5 8 13.5 5 12 2C10.5 4 9.5 5 8 6.5C6.5 8 5 10 5 12.5C5 12.5 4.5 12 4 11.5C4 11.5 3.5 13 3.5 14.5C3.5 19.1944 7.30558 23 12 23ZM12 20C9.79086 20 8 18.2091 8 16C8 15.3504 8.15822 14.7369 8.43721 14.1967C8.43721 14.1967 9 15 10 15C10 15 9 13 10 11C10.75 11.75 11.25 12 11.75 12C11.75 12 11.25 10.5 12 9C12.75 10 13.25 10.5 14 11.25C14.75 12 15.5 13 15.5 14.5C15.5 14.5 16 14 16.25 13.75C16.25 13.75 16.5 14.5 16.5 15.25C16.5 17.8734 14.5 20 12 20Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface LiveActivityCardProps {
|
||||||
|
width: number;
|
||||||
|
targetStreak?: number;
|
||||||
|
animationSpeed?: number; // multiplier for animation speed
|
||||||
|
showProgressBar?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LiveActivityCard: React.FC<LiveActivityCardProps> = ({
|
||||||
|
width,
|
||||||
|
targetStreak = 365,
|
||||||
|
animationSpeed = 1,
|
||||||
|
showProgressBar = false,
|
||||||
|
}) => {
|
||||||
|
const frame = useCurrentFrame();
|
||||||
|
const { fps, durationInFrames } = useVideoConfig();
|
||||||
|
|
||||||
|
// Animation timing - use full scene duration
|
||||||
|
const animationStartFrame = Math.round(fps * 0.3); // Start after 0.3 seconds
|
||||||
|
const animationEndFrame = durationInFrames - Math.round(fps * 0.2);
|
||||||
|
|
||||||
|
// Calculate current streak with easing
|
||||||
|
const rawProgress = interpolate(
|
||||||
|
frame * animationSpeed,
|
||||||
|
[animationStartFrame, animationEndFrame],
|
||||||
|
[0, 1],
|
||||||
|
{
|
||||||
|
extrapolateLeft: "clamp",
|
||||||
|
extrapolateRight: "clamp",
|
||||||
|
easing: Easing.out(Easing.cubic),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentStreak = Math.round(rawProgress * targetStreak);
|
||||||
|
const progressPercent = currentStreak / targetStreak;
|
||||||
|
const mood = getMoodForProgress(progressPercent);
|
||||||
|
|
||||||
|
// Spring animation for the flame icon
|
||||||
|
const flameScale = spring({
|
||||||
|
frame: frame % 15,
|
||||||
|
fps,
|
||||||
|
config: {
|
||||||
|
damping: 10,
|
||||||
|
stiffness: 200,
|
||||||
|
mass: 0.5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const flameScaleValue = interpolate(flameScale, [0, 1], [1, 1.1]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: width,
|
||||||
|
backgroundColor: "rgba(44, 44, 46, 0.95)",
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 24,
|
||||||
|
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4)",
|
||||||
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Streak Indicator */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ transform: `scale(${flameScaleValue})` }}>
|
||||||
|
<FlameIcon size={64} color="#FF9500" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 56,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: "#FFFFFF",
|
||||||
|
lineHeight: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentStreak}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
day streak
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 1,
|
||||||
|
height: 100,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Status Section */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 12,
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentStreak > 0 ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Mood Color Circle */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: mood.color,
|
||||||
|
boxShadow: `0 0 20px ${mood.color}80`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Today's mood
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "#FFFFFF",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{mood.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "#FFFFFF",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Start your streak!
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tap to log your mood
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Bar Section */}
|
||||||
|
{showProgressBar && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 24,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 12,
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||||
|
borderRadius: 6,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: `${progressPercent * 100}%`,
|
||||||
|
height: "100%",
|
||||||
|
background: `linear-gradient(90deg, ${MOOD_COLORS.horrible}, ${MOOD_COLORS.bad}, ${MOOD_COLORS.average}, ${MOOD_COLORS.good}, ${MOOD_COLORS.great})`,
|
||||||
|
borderRadius: 6,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
fontSize: 18,
|
||||||
|
color: "rgba(255, 255, 255, 0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>0</span>
|
||||||
|
<span style={{ color: "#FFFFFF", fontWeight: 600 }}>
|
||||||
|
{currentStreak} / {targetStreak} days
|
||||||
|
</span>
|
||||||
|
<span>{targetStreak}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Composition } from "remotion";
|
import { Composition, Still } from "remotion";
|
||||||
import { FeelsPromoV1 } from "./FeelsPromo";
|
import { FeelsPromoV1 } from "./FeelsPromo";
|
||||||
import { ConceptASelfAwareness } from "./ConceptA-SelfAwareness";
|
import { ConceptASelfAwareness } from "./ConceptA-SelfAwareness";
|
||||||
import { ConceptBNoJournalJournal } from "./ConceptB-NoJournalJournal";
|
import { ConceptBNoJournalJournal } from "./ConceptB-NoJournalJournal";
|
||||||
@@ -13,6 +13,9 @@ import { ConceptIRetroArcade } from "./ConceptI-RetroArcade";
|
|||||||
import { ConceptJConspiracy } from "./ConceptJ-Conspiracy";
|
import { ConceptJConspiracy } from "./ConceptJ-Conspiracy";
|
||||||
import { ConceptKSportsCenter } from "./ConceptK-SportsCenter";
|
import { ConceptKSportsCenter } from "./ConceptK-SportsCenter";
|
||||||
import { ConceptLMusical } from "./ConceptL-Musical";
|
import { ConceptLMusical } from "./ConceptL-Musical";
|
||||||
|
// Utility animations
|
||||||
|
import { LiveActivityAnimation } from "./LiveActivityAnimation";
|
||||||
|
import { BackgroundStill } from "./BackgroundStill";
|
||||||
|
|
||||||
export const RemotionRoot: React.FC = () => {
|
export const RemotionRoot: React.FC = () => {
|
||||||
const fps = 30;
|
const fps = 30;
|
||||||
@@ -52,11 +55,11 @@ export const RemotionRoot: React.FC = () => {
|
|||||||
height={1920}
|
height={1920}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Concept B: The No-Journal Journal (20s) */}
|
{/* Concept B: The No-Journal Journal (15s) */}
|
||||||
<Composition
|
<Composition
|
||||||
id="ConceptB-NoJournalJournal"
|
id="ConceptB-NoJournalJournal"
|
||||||
component={ConceptBNoJournalJournal}
|
component={ConceptBNoJournalJournal}
|
||||||
durationInFrames={Math.round(20 * fps)}
|
durationInFrames={Math.round(15 * fps)}
|
||||||
fps={fps}
|
fps={fps}
|
||||||
width={1080}
|
width={1080}
|
||||||
height={1920}
|
height={1920}
|
||||||
@@ -102,11 +105,11 @@ export const RemotionRoot: React.FC = () => {
|
|||||||
height={1920}
|
height={1920}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Concept G: The Streak Effect (20s) */}
|
{/* Concept G: The Streak Effect (12s) */}
|
||||||
<Composition
|
<Composition
|
||||||
id="ConceptG-StreakEffect"
|
id="ConceptG-StreakEffect"
|
||||||
component={ConceptGStreakEffect}
|
component={ConceptGStreakEffect}
|
||||||
durationInFrames={Math.round(20 * fps)}
|
durationInFrames={Math.round(12 * fps)}
|
||||||
fps={fps}
|
fps={fps}
|
||||||
width={1080}
|
width={1080}
|
||||||
height={1920}
|
height={1920}
|
||||||
@@ -165,6 +168,28 @@ export const RemotionRoot: React.FC = () => {
|
|||||||
width={1080}
|
width={1080}
|
||||||
height={1920}
|
height={1920}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* ═══════════════════════════════════════════════════════════════
|
||||||
|
UTILITY ANIMATIONS
|
||||||
|
═══════════════════════════════════════════════════════════════ */}
|
||||||
|
|
||||||
|
{/* Live Activity Preview - Streak 0 to 365 animation (12s) */}
|
||||||
|
<Composition
|
||||||
|
id="LiveActivityAnimation"
|
||||||
|
component={LiveActivityAnimation}
|
||||||
|
durationInFrames={Math.round(12 * fps)}
|
||||||
|
fps={fps}
|
||||||
|
width={1080}
|
||||||
|
height={1920}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Background Still for export */}
|
||||||
|
<Still
|
||||||
|
id="BackgroundStill"
|
||||||
|
component={BackgroundStill}
|
||||||
|
width={1080}
|
||||||
|
height={1920}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
screens/ai_dark.png
Normal file
|
After Width: | Height: | Size: 391 KiB |
BIN
screens/aj_light.png
Normal file
|
After Width: | Height: | Size: 378 KiB |
BIN
screens/insights_dark.png
Normal file
|
After Width: | Height: | Size: 338 KiB |
BIN
screens/insights_light.png
Normal file
|
After Width: | Height: | Size: 369 KiB |
BIN
screens/timeline_dark_large_voting.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
screens/timeline_dark_medium_voting.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
screens/timeline_light_large_voting.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
screens/timeline_light_medium_voting.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
screens/voting_dark_large.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
screens/voting_header.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
screens/voting_light_large.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
screens/voting_light_medium.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
screens/voting_light_small.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
screens/watch.png
Normal file
|
After Width: | Height: | Size: 428 KiB |
BIN
screens/watch_voting_light.png
Normal file
|
After Width: | Height: | Size: 76 KiB |