small fix, don't worry about it

This commit is contained in:
Tomas Dvorak
2026-04-10 12:06:01 +02:00
parent 954a1a1080
commit c6a99c7e21
214 changed files with 40234 additions and 2825 deletions
+25
View File
@@ -0,0 +1,25 @@
{
"name": "video",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "remotion studio src/index.ts",
"render": "sh -c 'mkdir -p ../dist/video && remotion render src/index.ts TrackeepTrailer16x9 ../dist/video/trackeep-trailer.mp4'",
"render:vertical": "sh -c 'mkdir -p ../dist/video && remotion render src/index.ts TrackeepTrailer9x16 ../dist/video/trackeep-trailer-vertical.mp4'",
"render:poster": "sh -c 'mkdir -p ../dist/video && remotion still src/index.ts TrackeepPoster16x9 ../dist/video/trackeep-poster.png'"
},
"dependencies": {
"@fontsource/inter": "^5.2.8",
"@remotion/cli": "4.0.434",
"@tabler/icons-react": "^3.39.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"remotion": "4.0.434"
},
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"typescript": "~5.9.3"
}
}
+2
View File
@@ -0,0 +1,2 @@
Replace `trailer-bed.mp3` with your final music bed to keep the timing unchanged.
The current file is a generated placeholder pulse track aligned to the 120 BPM edit grid.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

+14
View File
@@ -0,0 +1,14 @@
import {Backdrop} from './components/Backdrop';
import {EndCard} from './components/EndCard';
import {endCard} from './data/trailerData';
import {TrailerConfigProvider} from './lib/trailer-config';
export const Poster = () => {
return (
<TrailerConfigProvider layout="landscape" effects="full">
<Backdrop>
<EndCard frame={30} title={endCard.title} subtitle={endCard.subtitle} />
</Backdrop>
</TrailerConfigProvider>
);
};
+45
View File
@@ -0,0 +1,45 @@
import {Composition} from 'remotion';
import {HEIGHT, FPS, TRAILER_DURATION, WIDTH} from './data/trailerData';
import {Poster} from './Poster';
import {Trailer} from './Trailer';
import {VerticalTrailer} from './VerticalTrailer';
export const RemotionRoot = () => {
return (
<>
<Composition
id="TrackeepTrailer16x9"
component={Trailer}
durationInFrames={TRAILER_DURATION}
fps={FPS}
width={WIDTH}
height={HEIGHT}
/>
<Composition
id="TrackeepTrailer16x9Preview"
component={Trailer}
defaultProps={{effects: 'lite', includeAudio: false}}
durationInFrames={TRAILER_DURATION}
fps={FPS}
width={WIDTH}
height={HEIGHT}
/>
<Composition
id="TrackeepTrailer9x16"
component={VerticalTrailer}
durationInFrames={TRAILER_DURATION}
fps={FPS}
width={1080}
height={1920}
/>
<Composition
id="TrackeepPoster16x9"
component={Poster}
durationInFrames={1}
fps={FPS}
width={WIDTH}
height={HEIGHT}
/>
</>
);
};
+48
View File
@@ -0,0 +1,48 @@
import type {ComponentType} from 'react';
import {AbsoluteFill, Sequence} from 'remotion';
import {sceneTimings} from './data/trailerData';
import type {SceneTiming} from './data/types';
import {TrailerConfigProvider, type TrailerEffects} from './lib/trailer-config';
import {TrailerAudio} from './TrailerAudio';
import {AnalyticsScene} from './scenes/AnalyticsScene';
import {ColdOpenScene} from './scenes/ColdOpenScene';
import {DashboardFlybyScene} from './scenes/DashboardFlybyScene';
import {EndCardScene} from './scenes/EndCardScene';
import {MetricsActivationScene} from './scenes/MetricsActivationScene';
import {MontageScene} from './scenes/MontageScene';
import {SearchAiScene} from './scenes/SearchAiScene';
import {TimeTrackingScene} from './scenes/TimeTrackingScene';
interface TrailerProps {
effects?: TrailerEffects;
includeAudio?: boolean;
}
export const Trailer = ({effects = 'full', includeAudio = true}: TrailerProps) => {
const sceneMap: Record<SceneTiming['id'], ComponentType> = {
'cold-open': ColdOpenScene,
'dashboard-flyby': DashboardFlybyScene,
'metric-activation': MetricsActivationScene,
'time-tracking': TimeTrackingScene,
analytics: AnalyticsScene,
'search-ai': SearchAiScene,
montage: MontageScene,
'end-card': EndCardScene
} as const;
return (
<TrailerConfigProvider layout="landscape" effects={effects}>
<AbsoluteFill>
{includeAudio ? <TrailerAudio /> : null}
{sceneTimings.map((scene) => {
const SceneComponent = sceneMap[scene.id];
return (
<Sequence key={scene.id} from={scene.from} durationInFrames={scene.duration}>
<SceneComponent />
</Sequence>
);
})}
</AbsoluteFill>
</TrailerConfigProvider>
);
};
+16
View File
@@ -0,0 +1,16 @@
import {Audio, interpolate, staticFile, useCurrentFrame} from 'remotion';
import {TRAILER_DURATION} from './data/trailerData';
export const TrailerAudio = () => {
const frame = useCurrentFrame();
const fadeIn = interpolate(frame, [0, 24], [0, 0.48], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
const fadeOut = interpolate(frame, [TRAILER_DURATION - 36, TRAILER_DURATION], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
return <Audio src={staticFile('audio/trailer-bed.mp3')} volume={fadeIn * fadeOut} />;
};
+48
View File
@@ -0,0 +1,48 @@
import type {ComponentType} from 'react';
import {AbsoluteFill, Sequence} from 'remotion';
import {sceneTimings} from './data/trailerData';
import type {SceneTiming} from './data/types';
import {TrailerConfigProvider, type TrailerEffects} from './lib/trailer-config';
import {TrailerAudio} from './TrailerAudio';
import {PortraitAnalyticsScene} from './scenes/portrait/PortraitAnalyticsScene';
import {PortraitColdOpenScene} from './scenes/portrait/PortraitColdOpenScene';
import {PortraitDashboardScene} from './scenes/portrait/PortraitDashboardScene';
import {PortraitMetricsScene} from './scenes/portrait/PortraitMetricsScene';
import {PortraitMontageScene} from './scenes/portrait/PortraitMontageScene';
import {PortraitSearchAiScene} from './scenes/portrait/PortraitSearchAiScene';
import {PortraitTimeTrackingScene} from './scenes/portrait/PortraitTimeTrackingScene';
import {EndCardScene} from './scenes/EndCardScene';
interface VerticalTrailerProps {
effects?: TrailerEffects;
includeAudio?: boolean;
}
export const VerticalTrailer = ({effects = 'full', includeAudio = true}: VerticalTrailerProps) => {
const sceneMap: Record<SceneTiming['id'], ComponentType> = {
'cold-open': PortraitColdOpenScene,
'dashboard-flyby': PortraitDashboardScene,
'metric-activation': PortraitMetricsScene,
'time-tracking': PortraitTimeTrackingScene,
analytics: PortraitAnalyticsScene,
'search-ai': PortraitSearchAiScene,
montage: PortraitMontageScene,
'end-card': EndCardScene
};
return (
<TrailerConfigProvider layout="portrait" effects={effects}>
<AbsoluteFill>
{includeAudio ? <TrailerAudio /> : null}
{sceneTimings.map((scene) => {
const SceneComponent = sceneMap[scene.id];
return (
<Sequence key={scene.id} from={scene.from} durationInFrames={scene.duration}>
<SceneComponent />
</Sequence>
);
})}
</AbsoluteFill>
</TrailerConfigProvider>
);
};
+59
View File
@@ -0,0 +1,59 @@
import type {CSSProperties, ReactNode} from 'react';
import {useTrailerConfig} from '../lib/trailer-config';
import {trackeepTheme} from '../theme/trackeep';
import {HeaderShell} from './HeaderShell';
import {SidebarShell} from './SidebarShell';
interface AppChromeProps {
frame: number;
activeNav: string;
sidebarItems: string[];
searchText?: string;
searchDelay?: number;
children: ReactNode;
style?: CSSProperties;
contentStyle?: CSSProperties;
width?: number;
height?: number;
showSidebar?: boolean;
}
export const AppChrome = ({
frame,
activeNav,
sidebarItems,
searchText,
searchDelay = 0,
children,
style,
contentStyle,
width = 1520,
height = 860,
showSidebar = true
}: AppChromeProps) => {
const {effects} = useTrailerConfig();
const isLite = effects === 'lite';
return (
<div
style={{
width,
height,
borderRadius: 28,
overflow: 'hidden',
border: `1px solid ${trackeepTheme.colors.border}`,
background: 'rgba(9,11,16,0.82)',
boxShadow: isLite ? '0 12px 38px rgba(0, 0, 0, 0.24)' : trackeepTheme.shadow.soft,
display: 'flex',
backdropFilter: isLite ? 'none' : 'blur(22px)',
...style
}}
>
{showSidebar ? <SidebarShell frame={frame} items={sidebarItems} active={activeNav} /> : null}
<div style={{flex: 1, display: 'flex', flexDirection: 'column', background: 'rgba(10,12,18,0.55)'}}>
<HeaderShell frame={frame} searchText={searchText} searchDelay={searchDelay} />
<div style={{flex: 1, padding: '28px 28px 30px', position: 'relative', ...contentStyle}}>{children}</div>
</div>
</div>
);
};
+71
View File
@@ -0,0 +1,71 @@
import type {ReactNode} from 'react';
import {AbsoluteFill, interpolate, useCurrentFrame, useVideoConfig} from 'remotion';
import {useTrailerConfig} from '../lib/trailer-config';
import {trackeepTheme} from '../theme/trackeep';
interface BackdropProps {
children: ReactNode;
}
export const Backdrop = ({children}: BackdropProps) => {
const {effects, layout} = useTrailerConfig();
const frame = useCurrentFrame();
const {durationInFrames} = useVideoConfig();
const isLite = effects === 'lite';
const introFade = interpolate(frame, [0, 5, 14], [0.92, 0.52, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
const outroFade = interpolate(frame, [durationInFrames - 18, durationInFrames - 7, durationInFrames - 1], [0, 0.4, 0.86], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
return (
<AbsoluteFill
style={{
background: layout === 'portrait'
? `radial-gradient(circle at 50% 9%, rgba(57,185,255,0.2) 0%, rgba(57,185,255,0) 30%), radial-gradient(circle at 14% 88%, rgba(57,185,255,0.08) 0%, rgba(57,185,255,0) 30%), linear-gradient(180deg, #07090d 0%, ${trackeepTheme.colors.backgroundDeep} 34%, #06080d 100%)`
: `radial-gradient(circle at 78% 9%, rgba(57,185,255,0.18) 0%, rgba(57,185,255,0) 30%), radial-gradient(circle at 16% 86%, rgba(57,185,255,0.08) 0%, rgba(57,185,255,0) 30%), linear-gradient(180deg, #07090d 0%, ${trackeepTheme.colors.backgroundDeep} 36%, #06080d 100%)`
}}
>
<AbsoluteFill
style={{
backgroundImage: 'linear-gradient(rgba(255,255,255,0.028) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.028) 1px, transparent 1px)',
backgroundSize: isLite ? '92px 92px' : '72px 72px',
opacity: isLite ? 0.14 : 0.22,
maskImage: 'linear-gradient(180deg, rgba(0,0,0,0.92), rgba(0,0,0,0.36))'
}}
/>
<AbsoluteFill
style={{
backgroundImage: 'radial-gradient(circle at 20% 20%, rgba(255,255,255,0.05) 0, rgba(255,255,255,0.05) 1px, transparent 1px)',
backgroundSize: '28px 28px',
opacity: isLite ? 0.02 : 0.08,
mixBlendMode: 'screen'
}}
/>
{children}
<AbsoluteFill
style={{
background: 'radial-gradient(circle at 50% 44%, rgba(0,0,0,0) 40%, rgba(0,0,0,0.54) 100%)',
opacity: isLite ? 0.58 : 0.72,
pointerEvents: 'none'
}}
/>
<AbsoluteFill
style={{
background: 'linear-gradient(180deg, rgba(2,4,7,0.56) 0%, rgba(2,4,7,0.1) 25%, rgba(2,4,7,0.08) 65%, rgba(2,4,7,0.72) 100%)',
pointerEvents: 'none'
}}
/>
<AbsoluteFill
style={{
background: '#020305',
opacity: introFade + outroFade,
pointerEvents: 'none'
}}
/>
</AbsoluteFill>
);
};
+56
View File
@@ -0,0 +1,56 @@
import {interpolate, useVideoConfig} from 'remotion';
import {springIn} from '../lib/motion';
import {trackeepTheme} from '../theme/trackeep';
interface ChartBarsProps {
frame: number;
delay?: number;
values: Array<{label: string; value: number}>;
}
export const ChartBars = ({frame, delay = 0, values}: ChartBarsProps) => {
const {fps} = useVideoConfig();
const entrance = springIn(frame, fps, delay, 26);
const maxValue = Math.max(...values.map((entry) => entry.value));
return (
<div
style={{
borderRadius: 22,
border: `1px solid ${trackeepTheme.colors.border}`,
background: 'rgba(255,255,255,0.02)',
padding: '22px 24px',
display: 'flex',
flexDirection: 'column',
gap: 18,
opacity: entrance,
transform: `translate3d(0, ${interpolate(entrance, [0, 1], [26, 0])}px, 0)`
}}
>
<div style={{fontSize: 26, fontWeight: 700, color: trackeepTheme.colors.text, letterSpacing: '-0.04em'}}>Weekly Activity</div>
<div style={{display: 'flex', alignItems: 'flex-end', gap: 16, height: 180}}>
{values.map((entry, index) => {
const progress = springIn(frame, fps, delay + 10 + index * 4, 24);
const height = interpolate(progress, [0, 1], [0, (entry.value / maxValue) * 128]);
return (
<div key={entry.label + index} style={{flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10}}>
<div style={{fontSize: 16, color: trackeepTheme.colors.textMuted}}>{entry.value}</div>
<div
style={{
width: '100%',
maxWidth: 48,
height,
borderRadius: 18,
background: `linear-gradient(180deg, ${trackeepTheme.colors.primary} 0%, rgba(57,185,255,0.25) 100%)`,
boxShadow: `0 0 22px ${trackeepTheme.colors.primarySoft}`
}}
/>
<div style={{fontSize: 18, fontWeight: 600, color: trackeepTheme.colors.textMuted}}>{entry.label}</div>
</div>
);
})}
</div>
</div>
);
};
+121
View File
@@ -0,0 +1,121 @@
import type {CSSProperties, ReactNode} from 'react';
import {Img, interpolate, staticFile} from 'remotion';
import {useTrailerConfig} from '../lib/trailer-config';
import {trackeepTheme} from '../theme/trackeep';
interface MotionRange {
from: number;
to: number;
}
interface DemoSurfaceProps {
frame: number;
src: string;
width?: number;
height?: number;
style?: CSSProperties;
children?: ReactNode;
rotateX?: MotionRange;
rotateY?: MotionRange;
rotateZ?: MotionRange;
scale?: MotionRange;
translateX?: MotionRange;
translateY?: MotionRange;
imageScale?: MotionRange;
imageX?: MotionRange;
imageY?: MotionRange;
}
const resolveMotion = (frame: number, motion: MotionRange | undefined, fallback: number) => {
if (!motion) {
return fallback;
}
return interpolate(frame, [0, 120], [motion.from, motion.to], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
};
export const DemoSurface = ({
frame,
src,
width = 1520,
height = 855,
style,
children,
rotateX,
rotateY,
rotateZ,
scale,
translateX,
translateY,
imageScale,
imageX,
imageY
}: DemoSurfaceProps) => {
const {effects} = useTrailerConfig();
const isLite = effects === 'lite';
const resolvedRotateX = resolveMotion(frame, rotateX, 0);
const resolvedRotateY = resolveMotion(frame, rotateY, 0);
const resolvedRotateZ = resolveMotion(frame, rotateZ, 0);
const resolvedScale = resolveMotion(frame, scale, 1);
const resolvedTranslateX = resolveMotion(frame, translateX, 0);
const resolvedTranslateY = resolveMotion(frame, translateY, 0);
const resolvedImageScale = resolveMotion(frame, imageScale, 1);
const resolvedImageX = resolveMotion(frame, imageX, 0);
const resolvedImageY = resolveMotion(frame, imageY, 0);
return (
<div
style={{
position: 'absolute',
width,
height,
borderRadius: 32,
overflow: 'hidden',
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
background: trackeepTheme.colors.backgroundDeep,
boxShadow: isLite
? '0 24px 70px rgba(0, 0, 0, 0.34), 0 0 0 1px rgba(255,255,255,0.03)'
: '0 46px 140px rgba(0, 0, 0, 0.58), 0 22px 62px rgba(0, 0, 0, 0.42), 0 0 0 1px rgba(255,255,255,0.04)',
transform: `perspective(2200px) rotateX(${resolvedRotateX}deg) rotateY(${resolvedRotateY}deg) rotateZ(${resolvedRotateZ}deg) translate3d(${resolvedTranslateX}px, ${resolvedTranslateY}px, 0) scale(${resolvedScale})`,
transformOrigin: 'center center',
...style
}}
>
<Img
src={staticFile(src)}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
transform: `translate3d(${resolvedImageX}px, ${resolvedImageY}px, 0) scale(${resolvedImageScale})`
}}
/>
<div
style={{
position: 'absolute',
inset: 0,
background: 'linear-gradient(180deg, rgba(5,7,10,0.14) 0%, rgba(5,7,10,0) 28%, rgba(5,7,10,0.3) 100%)'
}}
/>
<div
style={{
position: 'absolute',
inset: 0,
background: 'linear-gradient(90deg, rgba(4,6,9,0.34) 0%, rgba(4,6,9,0) 22%, rgba(4,6,9,0) 78%, rgba(4,6,9,0.38) 100%)'
}}
/>
<div
style={{
position: 'absolute',
inset: 0,
boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.04), inset 0 -64px 82px rgba(0,0,0,0.2)',
pointerEvents: 'none'
}}
/>
{children}
</div>
);
};
+118
View File
@@ -0,0 +1,118 @@
import trackeepLogo from '../../../frontend/public/trackeep.svg';
import {Img, interpolate, useVideoConfig} from 'remotion';
import {springIn} from '../lib/motion';
import {useTrailerConfig} from '../lib/trailer-config';
import {trackeepTheme} from '../theme/trackeep';
interface EndCardProps {
frame: number;
title: string;
subtitle: string;
}
export const EndCard = ({frame, title, subtitle}: EndCardProps) => {
const {fps, durationInFrames} = useVideoConfig();
const {effects} = useTrailerConfig();
const isLite = effects === 'lite';
const entrance = springIn(frame, fps, 0, 24);
const logoEntrance = springIn(frame, fps, 4, 22);
const titleEntrance = springIn(frame, fps, 8, 20);
const underline = springIn(frame, fps, 12, 20);
const subtitleEntrance = springIn(frame, fps, 16, 20);
const outroFade = interpolate(frame, [durationInFrames - 18, durationInFrames - 7, durationInFrames - 1], [0, 0.3, 0.8], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
return (
<div
style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
gap: 30,
opacity: entrance,
transform: `translate3d(0, ${interpolate(entrance, [0, 1], [26, 0])}px, 0)`
}}
>
<div
style={{
position: 'absolute',
left: '50%',
top: '50%',
width: 820,
height: 480,
borderRadius: 999,
transform: `translate(-50%, -48%) scale(${interpolate(logoEntrance, [0, 1], [0.9, 1.05])})`,
background: 'radial-gradient(circle, rgba(57,185,255,0.16) 0%, rgba(57,185,255,0.02) 42%, rgba(57,185,255,0) 75%)',
opacity: logoEntrance
}}
/>
<div
style={{
width: 154,
height: 154,
borderRadius: 40,
background: 'rgba(57,185,255,0.08)',
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
opacity: logoEntrance,
transform: `scale(${interpolate(logoEntrance, [0, 1], [0.92, 1])})`,
boxShadow: isLite ? '0 0 30px rgba(57,185,255,0.12)' : `0 0 100px ${trackeepTheme.colors.primarySoft}, 0 28px 80px rgba(0,0,0,0.34)`
}}
>
<Img src={trackeepLogo} style={{width: 86, height: 86}} />
</div>
<div style={{fontSize: 16, fontWeight: 700, letterSpacing: '0.2em', textTransform: 'uppercase', color: trackeepTheme.colors.primary, opacity: subtitleEntrance}}>
Plan Track Learn
</div>
<div
style={{
fontSize: 124,
fontWeight: 700,
letterSpacing: '-0.08em',
lineHeight: 0.9,
color: trackeepTheme.colors.text,
opacity: titleEntrance,
transform: `translate3d(0, ${interpolate(titleEntrance, [0, 1], [18, 0])}px, 0)`
}}
>
{title}
</div>
<div
style={{
width: interpolate(underline, [0, 1], [0, 320]),
height: 6,
borderRadius: trackeepTheme.radius.pill,
background: `linear-gradient(90deg, ${trackeepTheme.colors.primary} 0%, rgba(57,185,255,0) 100%)`
}}
/>
<div
style={{
width: 980,
textAlign: 'center',
fontSize: 30,
lineHeight: 1.34,
color: trackeepTheme.colors.textMuted,
letterSpacing: '-0.03em',
opacity: subtitleEntrance
}}
>
{subtitle}
</div>
<div
style={{
position: 'absolute',
inset: 0,
background: '#020305',
opacity: outroFade
}}
/>
</div>
);
};
+106
View File
@@ -0,0 +1,106 @@
import {IconChevronDown, IconLink, IconMenu2, IconUpload} from '@tabler/icons-react';
import {interpolate, useVideoConfig} from 'remotion';
import {springIn} from '../lib/motion';
import {trackeepTheme} from '../theme/trackeep';
import {SearchField} from './SearchField';
interface HeaderShellProps {
frame: number;
searchText?: string;
searchDelay?: number;
}
const controlButtonStyle = {
width: 40,
height: 40,
borderRadius: 10,
border: `1px solid ${trackeepTheme.colors.border}`,
background: 'rgba(255,255,255,0.02)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
} as const;
export const HeaderShell = ({frame, searchText, searchDelay = 0}: HeaderShellProps) => {
const {fps} = useVideoConfig();
const entrance = springIn(frame, fps, 6, 22);
return (
<div
style={{
height: 78,
borderBottom: `1px solid ${trackeepTheme.colors.border}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '14px 18px 14px 22px',
opacity: entrance,
transform: `translate3d(0, ${interpolate(entrance, [0, 1], [-14, 0])}px, 0)`
}}
>
<div style={{display: 'flex', alignItems: 'center', gap: 16}}>
<div style={{width: 34, height: 34, display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
<IconMenu2 size={22} stroke={1.9} color={trackeepTheme.colors.text} />
</div>
<SearchField frame={frame} delay={searchDelay} query={searchText} />
</div>
<div style={{display: 'flex', alignItems: 'center', gap: 10}}>
<div
style={{
height: 44,
padding: '0 18px',
borderRadius: 10,
background: trackeepTheme.colors.primary,
color: '#03131d',
display: 'flex',
alignItems: 'center',
gap: 10,
fontSize: 18,
fontWeight: 700,
boxShadow: `0 0 28px ${trackeepTheme.colors.primarySoft}`
}}
>
<IconUpload size={16} stroke={2} />
Import a document
</div>
<div style={controlButtonStyle}>
<IconLink size={16} stroke={1.9} color={trackeepTheme.colors.textMuted} />
</div>
<div style={controlButtonStyle}>
<IconChevronDown size={16} stroke={1.9} color={trackeepTheme.colors.textMuted} />
</div>
<div
style={{
height: 40,
padding: '0 12px',
borderRadius: 10,
border: `1px solid ${trackeepTheme.colors.border}`,
background: 'rgba(255,255,255,0.02)',
display: 'flex',
alignItems: 'center',
gap: 8
}}
>
<div
style={{
width: 26,
height: 26,
borderRadius: 13,
background: trackeepTheme.colors.primary,
color: '#03131d',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 12,
fontWeight: 800,
letterSpacing: '-0.03em'
}}
>
DU
</div>
<IconChevronDown size={14} stroke={1.9} color={trackeepTheme.colors.textMuted} />
</div>
</div>
</div>
);
};
+61
View File
@@ -0,0 +1,61 @@
import {interpolate} from 'remotion';
import {trackeepTheme} from '../theme/trackeep';
interface HighlightBoxProps {
x: number;
y: number;
width: number;
height: number;
progress: number;
label: string;
align?: 'top-left' | 'top-right' | 'bottom-left';
}
export const HighlightBox = ({x, y, width, height, progress, label, align = 'top-left'}: HighlightBoxProps) => {
const badgeLeft = align === 'top-right' ? width - 12 : 16;
const badgeTop = align === 'bottom-left' ? height + 12 : -18;
const badgeTransform = align === 'top-right' ? 'translateX(-100%)' : 'none';
const translateY = interpolate(progress, [0, 1], [18, 0]);
const scale = interpolate(progress, [0, 1], [0.96, 1]);
return (
<div
style={{
position: 'absolute',
left: x,
top: y,
width,
height,
borderRadius: 24,
opacity: progress,
transform: `translate3d(0, ${translateY}px, 0) scale(${scale})`,
border: `1px solid rgba(57, 185, 255, 0.92)`,
boxShadow: `inset 0 0 0 1px rgba(255,255,255,0.06), 0 0 0 1px rgba(57,185,255,0.1), 0 0 42px ${trackeepTheme.colors.primarySoft}`,
background: 'linear-gradient(180deg, rgba(57,185,255,0.12) 0%, rgba(57,185,255,0.02) 100%)',
pointerEvents: 'none'
}}
>
<div
style={{
position: 'absolute',
left: badgeLeft,
top: badgeTop,
transform: badgeTransform,
padding: '8px 12px',
borderRadius: trackeepTheme.radius.pill,
background: 'rgba(8, 14, 22, 0.88)',
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
color: trackeepTheme.colors.text,
fontSize: 14,
fontWeight: 700,
letterSpacing: '0.08em',
textTransform: 'uppercase',
whiteSpace: 'nowrap',
boxShadow: '0 10px 26px rgba(0, 0, 0, 0.26)'
}}
>
{label}
</div>
</div>
);
};
+72
View File
@@ -0,0 +1,72 @@
import type {ComponentType} from 'react';
import {
IconActivity,
IconBookmark,
IconBrandGithub,
IconChecklist,
IconClock,
IconDatabase,
IconFileText,
IconFlame,
IconFolder,
IconHome,
IconMessageCircle,
IconNotes,
IconSchool,
IconSearch,
IconSparkles,
IconVideo
} from '@tabler/icons-react';
import type {ShowcaseIcon} from '../data/types';
import {useTrailerConfig} from '../lib/trailer-config';
import {trackeepTheme} from '../theme/trackeep';
const iconMap: Record<ShowcaseIcon, ComponentType<{size?: number; stroke?: number; color?: string}>> = {
home: IconHome,
documents: IconFileText,
bookmarks: IconBookmark,
tasks: IconChecklist,
notes: IconNotes,
videos: IconVideo,
learning: IconSchool,
time: IconClock,
productivity: IconActivity,
storage: IconDatabase,
analytics: IconActivity,
github: IconBrandGithub,
search: IconSearch,
ai: IconSparkles,
messages: IconMessageCircle,
files: IconFolder,
habit: IconFlame
};
interface IconBadgeProps {
icon: ShowcaseIcon;
size?: number;
accent?: string;
}
export const IconBadge = ({icon, size = 16, accent = trackeepTheme.colors.primary}: IconBadgeProps) => {
const {effects} = useTrailerConfig();
const isLite = effects === 'lite';
const Icon = iconMap[icon];
return (
<div
style={{
width: size + 14,
height: size + 14,
borderRadius: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(255,255,255,0.03)',
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
boxShadow: isLite ? '0 0 0 1px rgba(255,255,255,0.02)' : `0 0 0 1px rgba(255,255,255,0.02), 0 0 18px ${trackeepTheme.colors.primarySoft}`
}}
>
<Icon size={size} stroke={1.8} color={accent} />
</div>
);
};
+96
View File
@@ -0,0 +1,96 @@
import type {CSSProperties} from 'react';
import {interpolate, useVideoConfig} from 'remotion';
import type {HeadlineBeat} from '../data/types';
import {springIn} from '../lib/motion';
import {trackeepTheme} from '../theme/trackeep';
interface KineticHeadlineProps {
frame: number;
beat: HeadlineBeat;
style?: CSSProperties;
titleSize?: number;
subtitleSize?: number;
kickerSize?: number;
maxWidth?: number | string;
}
export const KineticHeadline = ({
frame,
beat,
style,
titleSize,
subtitleSize = 26,
kickerSize = 16,
maxWidth
}: KineticHeadlineProps) => {
const {fps} = useVideoConfig();
const entrance = springIn(frame, fps, 0, 34);
const align = beat.align ?? 'left';
const centered = align === 'center';
const resolvedTitleSize = titleSize ?? (centered ? 94 : 84);
const resolvedMaxWidth = maxWidth ?? (centered ? '100%' : 640);
return (
<div
style={{
position: 'absolute',
zIndex: 2,
display: 'flex',
flexDirection: 'column',
gap: 14,
width: centered ? 1120 : 760,
textAlign: align,
opacity: entrance,
transform: `translate3d(0, ${interpolate(entrance, [0, 1], [36, 0])}px, 0)`,
...style
}}
>
{beat.kicker ? (
<div
style={{
fontSize: kickerSize,
fontWeight: 700,
letterSpacing: '0.18em',
textTransform: 'uppercase',
color: trackeepTheme.colors.primary
}}
>
{beat.kicker}
</div>
) : null}
<div
style={{
fontSize: resolvedTitleSize,
lineHeight: 0.96,
letterSpacing: '-0.05em',
fontWeight: 700,
color: trackeepTheme.colors.text,
maxWidth: resolvedMaxWidth
}}
>
{beat.title}
</div>
<div
style={{
width: centered ? 260 : 180,
height: 4,
borderRadius: trackeepTheme.radius.pill,
background: `linear-gradient(90deg, ${trackeepTheme.colors.primary} 0%, rgba(57,185,255,0) 100%)`,
alignSelf: centered ? 'center' : 'flex-start'
}}
/>
{beat.subtitle ? (
<div
style={{
fontSize: subtitleSize,
lineHeight: 1.35,
color: trackeepTheme.colors.textMuted,
maxWidth: centered ? 760 : 620
}}
>
{beat.subtitle}
</div>
) : null}
</div>
);
};
+48
View File
@@ -0,0 +1,48 @@
import {interpolate, useVideoConfig} from 'remotion';
import type {ShowcaseIcon} from '../data/types';
import {springIn} from '../lib/motion';
import {useTrailerConfig} from '../lib/trailer-config';
import {trackeepTheme} from '../theme/trackeep';
import {IconBadge} from './IconBadge';
interface MetricTileProps {
frame: number;
delay?: number;
label: string;
value: string;
icon: ShowcaseIcon;
compact?: boolean;
}
export const MetricTile = ({frame, delay = 0, label, value, icon, compact = false}: MetricTileProps) => {
const {fps} = useVideoConfig();
const {effects} = useTrailerConfig();
const isLite = effects === 'lite';
const entrance = springIn(frame, fps, delay, 22);
return (
<div
style={{
borderRadius: compact ? 16 : 18,
border: `1px solid ${trackeepTheme.colors.border}`,
background: 'linear-gradient(180deg, rgba(255,255,255,0.018), rgba(255,255,255,0.01))',
padding: compact ? '16px 18px' : '20px 22px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
gap: compact ? 10 : 16,
minHeight: compact ? 104 : 148,
boxShadow: isLite ? '0 8px 16px rgba(0, 0, 0, 0.14)' : trackeepTheme.shadow.card,
opacity: entrance,
transform: `translate3d(0, ${interpolate(entrance, [0, 1], [24, 0])}px, 0) scale(${interpolate(entrance, [0, 1], [0.96, 1])})`,
filter: isLite ? 'none' : `blur(${interpolate(entrance, [0, 1], [8, 0])}px)`
}}
>
<IconBadge icon={icon} size={compact ? 14 : 16} />
<div style={{display: 'flex', flexDirection: 'column', gap: 6}}>
<div style={{fontSize: compact ? 32 : 48, letterSpacing: '-0.05em', fontWeight: 700, color: trackeepTheme.colors.text}}>{value}</div>
<div style={{fontSize: compact ? 17 : 21, color: trackeepTheme.colors.textMuted, fontWeight: 500}}>{label}</div>
</div>
</div>
);
};
+70
View File
@@ -0,0 +1,70 @@
import {interpolate, useVideoConfig} from 'remotion';
import type {ProgressBeat} from '../data/types';
import {easedProgress, springIn} from '../lib/motion';
import {useTrailerConfig} from '../lib/trailer-config';
import {trackeepTheme} from '../theme/trackeep';
import {IconBadge} from './IconBadge';
interface ProgressModuleProps {
frame: number;
delay?: number;
beat: ProgressBeat;
}
export const ProgressModule = ({frame, delay = 0, beat}: ProgressModuleProps) => {
const {fps} = useVideoConfig();
const {effects} = useTrailerConfig();
const isLite = effects === 'lite';
const entrance = springIn(frame, fps, delay, 28);
const fill = easedProgress(frame, delay + 14, 48) * beat.progress;
return (
<div
style={{
borderRadius: 22,
border: `1px solid ${trackeepTheme.colors.border}`,
background: 'rgba(255,255,255,0.02)',
padding: '22px 24px',
display: 'flex',
flexDirection: 'column',
gap: 18,
opacity: entrance,
transform: `translate3d(0, ${interpolate(entrance, [0, 1], [30, 0])}px, 0)`
}}
>
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
<div style={{display: 'flex', alignItems: 'center', gap: 14}}>
<IconBadge icon={beat.icon} />
<div>
<div style={{fontSize: 28, fontWeight: 700, letterSpacing: '-0.04em', color: trackeepTheme.colors.text}}>
{beat.label}
</div>
{beat.caption ? (
<div style={{fontSize: 18, color: trackeepTheme.colors.textMuted, marginTop: 4}}>{beat.caption}</div>
) : null}
</div>
</div>
<div style={{fontSize: 30, fontWeight: 700, color: trackeepTheme.colors.text}}>{beat.valueLabel}</div>
</div>
<div
style={{
width: '100%',
height: 12,
borderRadius: trackeepTheme.radius.pill,
overflow: 'hidden',
background: 'rgba(255,255,255,0.08)'
}}
>
<div
style={{
width: `${fill * 100}%`,
height: '100%',
borderRadius: trackeepTheme.radius.pill,
background: `linear-gradient(90deg, ${trackeepTheme.colors.primary} 0%, rgba(57,185,255,0.52) 100%)`,
boxShadow: isLite ? 'none' : `0 0 20px ${trackeepTheme.colors.primaryGlow}`
}}
/>
</div>
</div>
);
};
+88
View File
@@ -0,0 +1,88 @@
import {interpolate, staticFile} from 'remotion';
import type {RoutePreviewBeat} from '../data/types';
import {IconBadge} from './IconBadge';
import {trackeepTheme} from '../theme/trackeep';
interface RoutePreviewCardProps {
beat: RoutePreviewBeat;
progress: number;
width: number;
height: number;
x: number;
y: number;
emphasis?: boolean;
}
export const RoutePreviewCard = ({beat, progress, width, height, x, y, emphasis = false}: RoutePreviewCardProps) => {
const translateY = interpolate(progress, [0, 1], [22, 0]);
const scale = interpolate(progress, [0, 1], [0.93, 1]);
return (
<div
style={{
position: 'absolute',
left: x,
top: y,
width,
height,
borderRadius: emphasis ? 30 : 26,
overflow: 'hidden',
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
background: 'rgba(7, 10, 15, 0.9)',
opacity: progress,
transform: `translate3d(0, ${translateY}px, 0) scale(${scale})`,
boxShadow: emphasis ? '0 36px 120px rgba(0, 0, 0, 0.52)' : '0 24px 70px rgba(0, 0, 0, 0.38)'
}}
>
<div
style={{
position: 'relative',
height: Math.round(height * 0.72),
backgroundImage: `url(${staticFile(beat.image)})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
borderBottom: `1px solid ${trackeepTheme.colors.border}`
}}
>
<div
style={{
position: 'absolute',
inset: 0,
background: 'linear-gradient(180deg, rgba(5,7,10,0.1) 0%, rgba(5,7,10,0) 42%, rgba(5,7,10,0.52) 100%)'
}}
/>
<div
style={{
position: 'absolute',
left: 14,
top: 14,
padding: '6px 10px',
borderRadius: trackeepTheme.radius.pill,
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
background: 'rgba(4, 8, 14, 0.84)',
color: trackeepTheme.colors.text,
fontSize: emphasis ? 12 : 11,
fontWeight: 700,
letterSpacing: '0.12em',
textTransform: 'uppercase'
}}
>
{beat.label}
</div>
</div>
<div style={{padding: emphasis ? '22px 24px 24px' : '16px 18px 18px', display: 'flex', flexDirection: 'column', gap: emphasis ? 12 : 10}}>
<div style={{display: 'flex', alignItems: 'center', gap: 10}}>
<IconBadge icon={beat.icon} size={15} />
<div style={{fontSize: emphasis ? 24 : 19, fontWeight: 700, color: trackeepTheme.colors.text, letterSpacing: '-0.045em'}}>{beat.title}</div>
</div>
<div style={{display: 'flex', flexDirection: 'column', gap: 6}}>
{beat.lines.map((line) => (
<div key={line} style={{fontSize: emphasis ? 15 : 13, lineHeight: 1.38, color: trackeepTheme.colors.textMuted}}>
{line}
</div>
))}
</div>
</div>
</div>
);
};
+66
View File
@@ -0,0 +1,66 @@
import {interpolate, useVideoConfig} from 'remotion';
import {IconSearch} from '@tabler/icons-react';
import {easedProgress, springIn} from '../lib/motion';
import {useTrailerConfig} from '../lib/trailer-config';
import {trackeepTheme} from '../theme/trackeep';
interface SearchFieldProps {
frame: number;
query?: string;
delay?: number;
placeholder?: string;
width?: number;
}
export const SearchField = ({
frame,
query,
delay = 0,
placeholder = 'Quick search',
width = 320
}: SearchFieldProps) => {
const {fps} = useVideoConfig();
const {effects} = useTrailerConfig();
const isLite = effects === 'lite';
const entrance = springIn(frame, fps, delay, 24);
const typing = easedProgress(frame, delay + 10, 54);
const typedLength = query ? Math.max(0, Math.floor(query.length * typing)) : 0;
const typedQuery = query ? query.slice(0, typedLength) : '';
const displayText = typedQuery || placeholder;
const isPlaceholder = typedLength === 0;
const cursorOpacity = Math.sin(frame / 3) > 0 ? 1 : 0.2;
return (
<div
style={{
width,
height: 44,
borderRadius: 10,
border: `1px solid ${trackeepTheme.colors.border}`,
background: 'rgba(10,12,16,0.66)',
display: 'flex',
alignItems: 'center',
gap: 10,
padding: '0 14px',
opacity: entrance,
transform: `translate3d(0, ${interpolate(entrance, [0, 1], [14, 0])}px, 0) scale(${interpolate(entrance, [0, 1], [0.97, 1])})`,
boxShadow: isLite ? 'none' : 'inset 0 1px 0 rgba(255,255,255,0.03)'
}}
>
<IconSearch size={16} stroke={1.9} color={trackeepTheme.colors.textMuted} />
<div
style={{
color: isPlaceholder ? trackeepTheme.colors.textMuted : trackeepTheme.colors.text,
fontSize: 18,
fontWeight: 500,
letterSpacing: '-0.02em',
display: 'flex',
alignItems: 'center'
}}
>
{displayText}
{query ? <span style={{opacity: cursorOpacity, color: trackeepTheme.colors.primary, marginLeft: 2}}>|</span> : null}
</div>
</div>
);
};
+115
View File
@@ -0,0 +1,115 @@
import trackeepLogo from '../../../frontend/public/trackeep.svg';
import {
IconBookmark,
IconBrandGithub,
IconCalendar,
IconChartLine,
IconChecklist,
IconClock,
IconFolder,
IconHome,
IconMessageCircle,
IconNotes,
IconSchool,
IconSparkles,
IconUsers,
IconVideo
} from '@tabler/icons-react';
import {Img, interpolate, useVideoConfig} from 'remotion';
import {springIn} from '../lib/motion';
import {trackeepTheme} from '../theme/trackeep';
interface SidebarShellProps {
frame: number;
items: string[];
active: string;
}
const navIconMap = {
Home: IconHome,
Bookmarks: IconBookmark,
Tasks: IconChecklist,
'Time Tracking': IconClock,
Calendar: IconCalendar,
Files: IconFolder,
Notes: IconNotes,
Messages: IconMessageCircle,
YouTube: IconVideo,
Members: IconUsers,
Learning: IconSchool,
Stats: IconChartLine,
GitHub: IconBrandGithub,
'AI Assistant': IconSparkles
} as const;
export const SidebarShell = ({frame, items, active}: SidebarShellProps) => {
const {fps} = useVideoConfig();
const entrance = springIn(frame, fps, 0, 28);
return (
<div
style={{
width: 278,
borderRight: `1px solid ${trackeepTheme.colors.border}`,
background: 'rgba(7,8,12,0.76)',
padding: '18px 14px 18px',
display: 'flex',
flexDirection: 'column',
gap: 14,
opacity: entrance,
transform: `translate3d(${interpolate(entrance, [0, 1], [-24, 0])}px, 0, 0)`
}}
>
<div style={{display: 'flex', alignItems: 'center', gap: 12, padding: '4px 8px 10px'}}>
<Img src={trackeepLogo} style={{width: 22, height: 22}} />
<div style={{fontSize: 18, fontWeight: 700, letterSpacing: '-0.03em', color: trackeepTheme.colors.text}}>Trackeep</div>
</div>
<div
style={{
height: 48,
borderRadius: 12,
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
background: 'rgba(255,255,255,0.02)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 14px',
color: trackeepTheme.colors.text,
fontSize: 17,
fontWeight: 600
}}
>
<span>Trackeep Workspace</span>
<span style={{color: trackeepTheme.colors.textMuted}}></span>
</div>
<div style={{display: 'flex', flexDirection: 'column', gap: 4}}>
{items.map((item, index) => {
const isActive = item === active;
const Icon = navIconMap[item as keyof typeof navIconMap] ?? IconFolder;
return (
<div
key={item}
style={{
height: 40,
borderRadius: 10,
display: 'flex',
alignItems: 'center',
gap: 12,
padding: '0 12px',
fontSize: 16,
fontWeight: isActive ? 600 : 500,
color: isActive ? trackeepTheme.colors.text : trackeepTheme.colors.textMuted,
background: isActive ? 'rgba(255,255,255,0.08)' : 'transparent',
border: isActive ? `1px solid rgba(255,255,255,0.04)` : '1px solid transparent',
opacity: springIn(frame, fps, 8 + index * 2, 18)
}}
>
<Icon size={16} stroke={1.9} color={isActive ? trackeepTheme.colors.primary : trackeepTheme.colors.textMuted} />
<span>{item}</span>
</div>
);
})}
</div>
</div>
);
};
+45
View File
@@ -0,0 +1,45 @@
import {interpolate} from 'remotion';
import type {ShowcaseIcon} from '../data/types';
import {IconBadge} from './IconBadge';
import {trackeepTheme} from '../theme/trackeep';
interface SignalBadgeProps {
x: number;
y: number;
progress: number;
label: string;
value: string;
icon: ShowcaseIcon;
}
export const SignalBadge = ({x, y, progress, label, value, icon}: SignalBadgeProps) => {
const translateY = interpolate(progress, [0, 1], [18, 0]);
const scale = interpolate(progress, [0, 1], [0.92, 1]);
return (
<div
style={{
position: 'absolute',
left: x,
top: y,
minWidth: 180,
padding: '14px 16px',
borderRadius: 20,
background: 'rgba(8, 12, 18, 0.88)',
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
display: 'flex',
alignItems: 'center',
gap: 12,
opacity: progress,
transform: `translate3d(0, ${translateY}px, 0) scale(${scale})`,
boxShadow: '0 18px 42px rgba(0, 0, 0, 0.3)'
}}
>
<IconBadge icon={icon} size={16} />
<div style={{display: 'flex', flexDirection: 'column', gap: 2}}>
<div style={{fontSize: 28, fontWeight: 700, color: trackeepTheme.colors.text, letterSpacing: '-0.05em'}}>{value}</div>
<div style={{fontSize: 14, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase', color: trackeepTheme.colors.textMuted}}>{label}</div>
</div>
</div>
);
};
+44
View File
@@ -0,0 +1,44 @@
import {interpolate, useVideoConfig} from 'remotion';
import {easedProgress, springIn} from '../lib/motion';
import {trackeepTheme} from '../theme/trackeep';
import {IconBadge} from './IconBadge';
interface TypingMessageProps {
frame: number;
delay?: number;
text: string;
timestamp?: string;
}
export const TypingMessage = ({frame, delay = 0, text, timestamp = '08:25 AM'}: TypingMessageProps) => {
const {fps} = useVideoConfig();
const entrance = springIn(frame, fps, delay, 28);
const typingProgress = easedProgress(frame, delay + 18, 86);
const visibleLength = Math.max(0, Math.floor(text.length * typingProgress));
const visibleText = text.slice(0, visibleLength);
const cursorOpacity = Math.sin(frame / 3) > 0 ? 1 : 0.2;
return (
<div
style={{
borderRadius: 24,
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
background: 'rgba(255,255,255,0.02)',
padding: '22px 24px',
display: 'flex',
gap: 18,
opacity: entrance,
transform: `translate3d(0, ${interpolate(entrance, [0, 1], [24, 0])}px, 0)`
}}
>
<IconBadge icon="ai" size={22} />
<div style={{display: 'flex', flexDirection: 'column', gap: 12, flex: 1}}>
<div style={{fontSize: 28, lineHeight: 1.3, color: trackeepTheme.colors.text, letterSpacing: '-0.03em', minHeight: 80}}>
{visibleText}
<span style={{opacity: cursorOpacity, color: trackeepTheme.colors.primary}}>|</span>
</div>
<div style={{fontSize: 18, color: trackeepTheme.colors.textMuted}}>{timestamp}</div>
</div>
</div>
);
};
+5
View File
@@ -0,0 +1,5 @@
import './styles/global.css';
import {registerRoot} from 'remotion';
import {RemotionRoot} from './Root';
registerRoot(RemotionRoot);
+28
View File
@@ -0,0 +1,28 @@
import type {MetricCard} from '../data/types';
export const formatMetricValue = (metric: MetricCard, value: number): string => {
const prefix = metric.prefix ?? '';
const suffix = metric.suffix ?? '';
if (metric.decimals !== undefined) {
return `${prefix}${value.toFixed(metric.decimals)}${suffix}`;
}
return `${prefix}${Math.round(value).toLocaleString('en-US')}${suffix}`;
};
export const formatStopwatch = (seconds: number): string => {
const minutes = Math.floor(seconds / 60);
const remainder = seconds % 60;
return `${String(minutes).padStart(2, '0')}:${String(remainder).padStart(2, '0')}`;
};
export const formatAnalyticsHours = (hours: number): string => {
const wholeHours = Math.floor(hours);
const minutes = Math.round((hours - wholeHours) * 60);
return `${wholeHours}h ${String(minutes).padStart(2, '0')}m`;
};
export const clamp = (value: number, min = 0, max = 1): number => {
return Math.min(max, Math.max(min, value));
};
+31
View File
@@ -0,0 +1,31 @@
import type {CSSProperties} from 'react';
import {Easing, interpolate, spring} from 'remotion';
export const springIn = (frame: number, fps: number, delay = 0, durationInFrames = 30): number => {
return spring({
frame: Math.max(0, frame - delay),
fps,
durationInFrames,
config: {
damping: 200,
stiffness: 140,
mass: 0.8
}
});
};
export const easedProgress = (frame: number, start: number, duration: number): number => {
return interpolate(frame, [start, start + duration], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: Easing.bezier(0.22, 1, 0.36, 1)
});
};
export const fadeSlideUpStyle = (progress: number, distance = 48): CSSProperties => {
return {
opacity: progress,
transform: `translate3d(0, ${interpolate(progress, [0, 1], [distance, 0])}px, 0) scale(${interpolate(progress, [0, 1], [0.96, 1])})`,
filter: `blur(${interpolate(progress, [0, 1], [10, 0])}px)`
};
};
+25
View File
@@ -0,0 +1,25 @@
import type {PropsWithChildren} from 'react';
import {createContext, useContext} from 'react';
export type TrailerLayout = 'landscape' | 'portrait';
export type TrailerEffects = 'full' | 'lite';
interface TrailerConfigValue {
layout: TrailerLayout;
effects: TrailerEffects;
}
const TrailerConfigContext = createContext<TrailerConfigValue>({
layout: 'landscape',
effects: 'full'
});
export const TrailerConfigProvider = ({children, layout, effects}: PropsWithChildren<TrailerConfigValue>) => {
return (
<TrailerConfigContext.Provider value={{layout, effects}}>
{children}
</TrailerConfigContext.Provider>
);
};
export const useTrailerConfig = () => useContext(TrailerConfigContext);
+6
View File
@@ -0,0 +1,6 @@
declare module '*.svg' {
const src: string;
export default src;
}
declare module '*.css';
+48
View File
@@ -0,0 +1,48 @@
import {useCurrentFrame} from 'remotion';
import {Backdrop} from '../components/Backdrop';
import {DemoSurface} from '../components/DemoSurface';
import {HighlightBox} from '../components/HighlightBox';
import {KineticHeadline} from '../components/KineticHeadline';
import {SignalBadge} from '../components/SignalBadge';
import {analyticsHeadline, analyticsSummary, routeScreens} from '../data/trailerData';
import {formatAnalyticsHours, formatMetricValue} from '../lib/format';
import {easedProgress} from '../lib/motion';
const SCALE = 1520 / 1280;
const s = (value: number) => value * SCALE;
export const AnalyticsScene = () => {
const frame = useCurrentFrame();
const hoursProgress = easedProgress(frame, 8, 28);
const tasksProgress = easedProgress(frame, 16, 28);
const bookmarksProgress = easedProgress(frame, 24, 28);
const commitsProgress = easedProgress(frame, 32, 28);
const summaryBox = easedProgress(frame, 14, 18);
const habitBox = easedProgress(frame, 38, 18);
const learningBox = easedProgress(frame, 58, 18);
return (
<Backdrop>
<KineticHeadline frame={frame} beat={analyticsHeadline} titleSize={66} subtitleSize={22} style={{left: 120, top: 92, width: 680}} />
<SignalBadge x={1050} y={112} progress={hoursProgress} label="Hours" value={formatAnalyticsHours(analyticsSummary[0].target * hoursProgress)} icon="time" />
<SignalBadge x={1260} y={112} progress={tasksProgress} label="Tasks" value={formatMetricValue(analyticsSummary[1], analyticsSummary[1].target * tasksProgress)} icon="tasks" />
<SignalBadge x={1470} y={112} progress={bookmarksProgress} label="Bookmarks" value={formatMetricValue(analyticsSummary[2], analyticsSummary[2].target * bookmarksProgress)} icon="bookmarks" />
<SignalBadge x={1680} y={112} progress={commitsProgress} label="Commits" value={formatMetricValue(analyticsSummary[3], analyticsSummary[3].target * commitsProgress)} icon="github" />
<DemoSurface
frame={frame}
src={routeScreens.analytics}
style={{left: 260, top: 186}}
rotateX={{from: 4.5, to: 1.25}}
rotateY={{from: -4, to: 1.75}}
scale={{from: 1.035, to: 1.005}}
translateX={{from: 40, to: -12}}
translateY={{from: 18, to: -4}}
imageScale={{from: 1.03, to: 1.01}}
>
<HighlightBox x={s(310)} y={s(184)} width={s(938)} height={s(100)} progress={summaryBox} label="Top-line metrics" />
<HighlightBox x={s(790)} y={s(310)} width={s(458)} height={s(190)} progress={habitBox} label="Habit tracking" align="top-right" />
<HighlightBox x={s(311)} y={s(526)} width={s(936)} height={s(182)} progress={learningBox} label="Learning progress" />
</DemoSurface>
</Backdrop>
);
};
+66
View File
@@ -0,0 +1,66 @@
import trackeepLogo from '../../../frontend/public/trackeep.svg';
import {Img, interpolate, useCurrentFrame} from 'remotion';
import {Backdrop} from '../components/Backdrop';
import {DemoSurface} from '../components/DemoSurface';
import {KineticHeadline} from '../components/KineticHeadline';
import {routeScreens} from '../data/trailerData';
import {trackeepTheme} from '../theme/trackeep';
export const ColdOpenScene = () => {
const frame = useCurrentFrame();
const logoOpacity = interpolate(frame, [0, 10, 24], [0, 1, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
const sweepWidth = interpolate(frame, [10, 30], [0, 260], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
return (
<Backdrop>
<div style={{position: 'absolute', inset: 0, padding: '118px 140px'}}>
<div style={{display: 'flex', alignItems: 'center', gap: 16, opacity: logoOpacity}}>
<Img src={trackeepLogo} style={{width: 36, height: 36}} />
<div style={{fontSize: 32, fontWeight: 700, letterSpacing: '-0.05em', color: trackeepTheme.colors.text}}>Trackeep</div>
</div>
<KineticHeadline
frame={frame}
beat={{
kicker: 'Real Product Capture',
title: 'Trackeep, in motion.',
subtitle: 'Fast-cut walkthrough of the live interface.',
align: 'left'
}}
titleSize={78}
subtitleSize={22}
style={{left: 140, top: 288, width: 680}}
/>
<div
style={{
position: 'absolute',
left: 140,
top: 610,
width: sweepWidth,
height: 5,
borderRadius: trackeepTheme.radius.pill,
background: `linear-gradient(90deg, ${trackeepTheme.colors.primary} 0%, rgba(57,185,255,0) 100%)`,
boxShadow: `0 0 36px ${trackeepTheme.colors.primaryGlow}`
}}
/>
</div>
<DemoSurface
frame={frame}
src={routeScreens.dashboard}
width={720}
height={405}
style={{left: 1080, top: 238, opacity: 0.52, filter: 'blur(0.3px)'}}
rotateX={{from: 9, to: 4}}
rotateY={{from: -14, to: -8}}
scale={{from: 1.06, to: 1.02}}
translateX={{from: 42, to: 0}}
imageScale={{from: 1.08, to: 1.04}}
/>
</Backdrop>
);
};
+49
View File
@@ -0,0 +1,49 @@
import {useCurrentFrame} from 'remotion';
import {Backdrop} from '../components/Backdrop';
import {DemoSurface} from '../components/DemoSurface';
import {HighlightBox} from '../components/HighlightBox';
import {KineticHeadline} from '../components/KineticHeadline';
import {SignalBadge} from '../components/SignalBadge';
import {dashboardHeadline, routeScreens} from '../data/trailerData';
import {easedProgress} from '../lib/motion';
const SCALE = 1520 / 1280;
const s = (value: number) => value * SCALE;
export const DashboardFlybyScene = () => {
const frame = useCurrentFrame();
const docsProgress = easedProgress(frame, 10, 26);
const tasksProgress = easedProgress(frame, 18, 28);
const productivityProgress = easedProgress(frame, 26, 28);
const metricsBox = easedProgress(frame, 12, 22);
const achievementsBox = easedProgress(frame, 30, 20);
const deadlinesBox = easedProgress(frame, 46, 20);
const sidebarBox = easedProgress(frame, 56, 20);
return (
<Backdrop>
<KineticHeadline frame={frame} beat={dashboardHeadline} titleSize={72} subtitleSize={22} style={{left: 120, top: 92, width: 620}} />
<SignalBadge x={120} y={330} progress={docsProgress} label="Documents" value={`${Math.round(18 * docsProgress)}`} icon="documents" />
<SignalBadge x={120} y={424} progress={tasksProgress} label="Active Work" value={`${Math.round(28 * tasksProgress)}`} icon="tasks" />
<SignalBadge x={120} y={518} progress={productivityProgress} label="Productivity" value={`${Math.round(78 * productivityProgress)}%`} icon="productivity" />
<DemoSurface
frame={frame}
src={routeScreens.dashboard}
style={{left: 310, top: 166}}
rotateX={{from: 7, to: 2}}
rotateY={{from: -8, to: 1.5}}
scale={{from: 1.06, to: 1.01}}
translateX={{from: 82, to: -24}}
translateY={{from: 24, to: -6}}
imageScale={{from: 1.05, to: 1.015}}
imageX={{from: -16, to: 0}}
imageY={{from: -8, to: 0}}
>
<HighlightBox x={s(308)} y={s(118)} width={s(942)} height={s(244)} progress={metricsBox} label="Metric density" />
<HighlightBox x={s(12)} y={s(78)} width={s(252)} height={s(640)} progress={sidebarBox} label="Route breadth" />
<HighlightBox x={s(310)} y={s(392)} width={s(458)} height={s(248)} progress={achievementsBox} label="Recent wins" />
<HighlightBox x={s(792)} y={s(392)} width={s(458)} height={s(248)} progress={deadlinesBox} label="Upcoming deadlines" align="top-right" />
</DemoSurface>
</Backdrop>
);
};
+14
View File
@@ -0,0 +1,14 @@
import {useCurrentFrame} from 'remotion';
import {Backdrop} from '../components/Backdrop';
import {EndCard} from '../components/EndCard';
import {endCard} from '../data/trailerData';
export const EndCardScene = () => {
const frame = useCurrentFrame();
return (
<Backdrop>
<EndCard frame={frame} title={endCard.title} subtitle={endCard.subtitle} />
</Backdrop>
);
};
@@ -0,0 +1,48 @@
import {useCurrentFrame} from 'remotion';
import {Backdrop} from '../components/Backdrop';
import {DemoSurface} from '../components/DemoSurface';
import {HighlightBox} from '../components/HighlightBox';
import {KineticHeadline} from '../components/KineticHeadline';
import {SignalBadge} from '../components/SignalBadge';
import {routeScreens, taskWorkflowStats, tasksHeadline} from '../data/trailerData';
import {easedProgress} from '../lib/motion';
const SCALE = 1520 / 1280;
const s = (value: number) => value * SCALE;
export const MetricsActivationScene = () => {
const frame = useCurrentFrame();
const totalProgress = easedProgress(frame, 8, 28);
const activeProgress = easedProgress(frame, 16, 28);
const completedProgress = easedProgress(frame, 24, 28);
const summaryBox = easedProgress(frame, 12, 18);
const filterBox = easedProgress(frame, 28, 18);
const firstTaskBox = easedProgress(frame, 46, 18);
const secondTaskBox = easedProgress(frame, 64, 18);
return (
<Backdrop>
<KineticHeadline frame={frame} beat={tasksHeadline} titleSize={68} subtitleSize={22} style={{left: 120, top: 92, width: 660}} />
<SignalBadge x={1160} y={112} progress={totalProgress} label={taskWorkflowStats[0].label} value={`${Math.round(taskWorkflowStats[0].value * totalProgress)}`} icon="tasks" />
<SignalBadge x={1372} y={112} progress={activeProgress} label={taskWorkflowStats[1].label} value={`${Math.round(taskWorkflowStats[1].value * activeProgress)}`} icon="productivity" />
<SignalBadge x={1584} y={112} progress={completedProgress} label={taskWorkflowStats[2].label} value={`${Math.round(taskWorkflowStats[2].value * completedProgress)}`} icon="analytics" />
<DemoSurface
frame={frame}
src={routeScreens.tasks}
style={{left: 260, top: 186}}
rotateX={{from: 5, to: 1.5}}
rotateY={{from: -5, to: 2}}
scale={{from: 1.04, to: 1.005}}
translateX={{from: 48, to: -18}}
translateY={{from: 18, to: -6}}
imageScale={{from: 1.035, to: 1.01}}
imageX={{from: -8, to: 0}}
>
<HighlightBox x={s(310)} y={s(170)} width={s(938)} height={s(82)} progress={summaryBox} label="Task totals" />
<HighlightBox x={s(310)} y={s(278)} width={s(938)} height={s(104)} progress={filterBox} label="Search and filters" />
<HighlightBox x={s(310)} y={s(406)} width={s(938)} height={s(132)} progress={firstTaskBox} label="High-priority task" />
<HighlightBox x={s(310)} y={s(558)} width={s(938)} height={s(132)} progress={secondTaskBox} label="Responsive fix" />
</DemoSurface>
</Backdrop>
);
};
+71
View File
@@ -0,0 +1,71 @@
import {interpolate, useCurrentFrame} from 'remotion';
import {Backdrop} from '../components/Backdrop';
import {IconBadge} from '../components/IconBadge';
import {KineticHeadline} from '../components/KineticHeadline';
import {RoutePreviewCard} from '../components/RoutePreviewCard';
import {montageHeadline, montageModules, routePreviews} from '../data/trailerData';
import {springIn} from '../lib/motion';
import {trackeepTheme} from '../theme/trackeep';
export const MontageScene = () => {
const frame = useCurrentFrame();
const centerGlow = springIn(frame, 30, 8, 22);
return (
<Backdrop>
<KineticHeadline
frame={frame}
beat={montageHeadline}
titleSize={70}
subtitleSize={22}
maxWidth={1120}
style={{left: 300, top: 84, width: 1320, alignItems: 'center'}}
/>
<div
style={{
position: 'absolute',
left: 430,
top: 232,
width: 1060,
height: 560,
borderRadius: 999,
background: 'radial-gradient(circle, rgba(57,185,255,0.2) 0%, rgba(57,185,255,0.05) 38%, rgba(57,185,255,0) 72%)',
opacity: centerGlow,
transform: `scale(${interpolate(centerGlow, [0, 1], [0.9, 1.08])})`
}}
/>
<RoutePreviewCard beat={routePreviews[0]} progress={springIn(frame, 30, 10, 22)} x={520} y={244} width={880} height={500} emphasis />
<RoutePreviewCard beat={routePreviews[1]} progress={springIn(frame, 30, 18, 22)} x={140} y={300} width={330} height={222} />
<RoutePreviewCard beat={routePreviews[3]} progress={springIn(frame, 30, 24, 22)} x={1450} y={300} width={330} height={222} />
<RoutePreviewCard beat={routePreviews[2]} progress={springIn(frame, 30, 30, 22)} x={220} y={610} width={360} height={236} />
<RoutePreviewCard beat={routePreviews[4]} progress={springIn(frame, 30, 36, 22)} x={1340} y={610} width={360} height={236} />
<div style={{position: 'absolute', left: 380, top: 866, width: 1160, display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 16}}>
{montageModules.map((module, index) => {
const progress = springIn(frame, 30, 44 + index * 4, 20);
const floatingY = Math.sin((frame + index * 5) / 12) * 3;
return (
<div
key={module.label}
style={{
height: 62,
padding: '0 18px',
borderRadius: 18,
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
background: 'rgba(8, 12, 18, 0.82)',
display: 'flex',
alignItems: 'center',
gap: 12,
opacity: progress,
transform: `translate3d(0, ${interpolate(progress, [0, 1], [14 - floatingY, floatingY])}px, 0) scale(${interpolate(progress, [0, 1], [0.94, 1])})`,
boxShadow: '0 18px 44px rgba(0, 0, 0, 0.34)'
}}
>
<IconBadge icon={module.icon} size={16} />
<div style={{fontSize: 18, fontWeight: 700, letterSpacing: '-0.03em', color: trackeepTheme.colors.text}}>{module.label}</div>
</div>
);
})}
</div>
</Backdrop>
);
};
+45
View File
@@ -0,0 +1,45 @@
import {useCurrentFrame} from 'remotion';
import {Backdrop} from '../components/Backdrop';
import {DemoSurface} from '../components/DemoSurface';
import {HighlightBox} from '../components/HighlightBox';
import {KineticHeadline} from '../components/KineticHeadline';
import {SignalBadge} from '../components/SignalBadge';
import {routeScreens, searchHeadline} from '../data/trailerData';
import {easedProgress} from '../lib/motion';
const SCALE = 1520 / 1280;
const s = (value: number) => value * SCALE;
export const SearchAiScene = () => {
const frame = useCurrentFrame();
const queryProgress = easedProgress(frame, 8, 24);
const latencyProgress = easedProgress(frame, 18, 24);
const matchesProgress = easedProgress(frame, 28, 24);
const inputBox = easedProgress(frame, 14, 18);
const firstResult = easedProgress(frame, 34, 18);
const secondResult = easedProgress(frame, 54, 18);
return (
<Backdrop>
<KineticHeadline frame={frame} beat={searchHeadline} titleSize={68} subtitleSize={22} style={{left: 120, top: 92, width: 660}} />
<SignalBadge x={1260} y={112} progress={queryProgress} label="Query" value="api" icon="search" />
<SignalBadge x={1472} y={112} progress={latencyProgress} label="Latency" value="37ms" icon="analytics" />
<SignalBadge x={1684} y={112} progress={matchesProgress} label="Matches" value="2" icon="tasks" />
<DemoSurface
frame={frame}
src={routeScreens.search}
style={{left: 260, top: 186}}
rotateX={{from: 4.5, to: 1.25}}
rotateY={{from: -4.5, to: 2}}
scale={{from: 1.04, to: 1.005}}
translateX={{from: 38, to: -10}}
translateY={{from: 18, to: -4}}
imageScale={{from: 1.03, to: 1.01}}
>
<HighlightBox x={s(304)} y={s(255)} width={s(952)} height={s(48)} progress={inputBox} label="Cross-content query" />
<HighlightBox x={s(304)} y={s(364)} width={s(952)} height={s(193)} progress={firstResult} label="Task result" />
<HighlightBox x={s(304)} y={s(574)} width={s(952)} height={s(144)} progress={secondResult} label="Note result" />
</DemoSurface>
</Backdrop>
);
};
+70
View File
@@ -0,0 +1,70 @@
import {interpolate, useCurrentFrame} from 'remotion';
import {Backdrop} from '../components/Backdrop';
import {DemoSurface} from '../components/DemoSurface';
import {HighlightBox} from '../components/HighlightBox';
import {KineticHeadline} from '../components/KineticHeadline';
import {SignalBadge} from '../components/SignalBadge';
import {routeScreens, timeHeadline, timeOverviewStats} from '../data/trailerData';
import {formatMetricValue, formatStopwatch} from '../lib/format';
import {easedProgress} from '../lib/motion';
import {trackeepTheme} from '../theme/trackeep';
const SCALE = 1520 / 1280;
const s = (value: number) => value * SCALE;
export const TimeTrackingScene = () => {
const frame = useCurrentFrame();
const timerProgress = easedProgress(frame, 8, 40);
const entriesProgress = easedProgress(frame, 16, 28);
const billableProgress = easedProgress(frame, 24, 30);
const timerBox = easedProgress(frame, 10, 18);
const overviewBox = easedProgress(frame, 30, 18);
const entriesBox = easedProgress(frame, 50, 18);
const buttonProgress = easedProgress(frame, 24, 14);
const pulse = 1 + Math.sin(frame / 4) * 0.03;
return (
<Backdrop>
<KineticHeadline frame={frame} beat={timeHeadline} titleSize={66} subtitleSize={22} style={{left: 120, top: 92, width: 650}} />
<SignalBadge x={1100} y={112} progress={timerProgress} label="Tracked Today" value={formatStopwatch(Math.round(315 * timerProgress))} icon="time" />
<SignalBadge x={1364} y={112} progress={entriesProgress} label="Entries" value={`${Math.round(timeOverviewStats[1].target * entriesProgress)}`} icon="analytics" />
<SignalBadge
x={1570}
y={112}
progress={billableProgress}
label="Billable"
value={formatMetricValue(timeOverviewStats[2], timeOverviewStats[2].target * billableProgress)}
icon="productivity"
/>
<DemoSurface
frame={frame}
src={routeScreens.timeTracking}
style={{left: 260, top: 188}}
rotateX={{from: 4.5, to: 1.5}}
rotateY={{from: -4, to: 2}}
scale={{from: 1.035, to: 1.005}}
translateX={{from: 42, to: -12}}
translateY={{from: 20, to: -6}}
imageScale={{from: 1.03, to: 1.01}}
>
<HighlightBox x={s(311)} y={s(120)} width={s(456)} height={s(412)} progress={timerBox} label="Quick capture" />
<HighlightBox x={s(792)} y={s(120)} width={s(456)} height={s(412)} progress={overviewBox} label="Today's overview" align="top-right" />
<HighlightBox x={s(312)} y={s(558)} width={s(936)} height={s(154)} progress={entriesBox} label="Billable entry" />
<div
style={{
position: 'absolute',
left: s(337),
top: s(362),
width: s(406),
height: s(36),
borderRadius: 10,
opacity: buttonProgress,
transform: `scale(${pulse})`,
boxShadow: `0 0 0 ${interpolate(buttonProgress, [0, 1], [0, 10])}px rgba(57,185,255,0.12), 0 0 46px ${trackeepTheme.colors.primarySoft}`,
border: `1px solid rgba(57, 185, 255, 0.65)`
}}
/>
</DemoSurface>
</Backdrop>
);
};
@@ -0,0 +1,35 @@
import {useCurrentFrame} from 'remotion';
import {AppChrome} from '../../components/AppChrome';
import {MetricTile} from '../../components/MetricTile';
import {ProgressModule} from '../../components/ProgressModule';
import {analyticsHeadline, analyticsSummary, habitBeat, learningBeat} from '../../data/trailerData';
import {formatAnalyticsHours, formatMetricValue} from '../../lib/format';
import {easedProgress} from '../../lib/motion';
import {PortraitSceneShell} from './PortraitSceneShell';
export const PortraitAnalyticsScene = () => {
const frame = useCurrentFrame();
return (
<PortraitSceneShell frame={frame} beat={analyticsHeadline}>
<div style={{position: 'absolute', left: 90, top: 470}}>
<AppChrome frame={frame} activeNav="Stats" sidebarItems={[]} showSidebar={false} width={900} height={980}>
<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 18, marginBottom: 18}}>
{analyticsSummary.map((metric, index) => {
const progress = easedProgress(frame, 16 + index * 7, 56);
const value = metric.label === 'Hours Tracked'
? formatAnalyticsHours(metric.target * progress)
: formatMetricValue(metric, metric.target * progress);
return <MetricTile key={metric.label} frame={frame} delay={12 + index * 5} label={metric.label} value={value} icon={metric.icon} compact />;
})}
</div>
<div style={{display: 'flex', flexDirection: 'column', gap: 18}}>
<ProgressModule frame={frame} delay={54} beat={learningBeat} />
<ProgressModule frame={frame} delay={66} beat={habitBeat} />
</div>
</AppChrome>
</div>
</PortraitSceneShell>
);
};
@@ -0,0 +1,50 @@
import trackeepLogo from '../../../../frontend/public/trackeep.svg';
import {Img, interpolate, useCurrentFrame} from 'remotion';
import {Backdrop} from '../../components/Backdrop';
import {trackeepTheme} from '../../theme/trackeep';
export const PortraitColdOpenScene = () => {
const frame = useCurrentFrame();
const logoScale = interpolate(frame, [0, 28], [0.85, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
const lineWidth = interpolate(frame, [18, 60], [0, 240], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
return (
<Backdrop>
<div style={{position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 28}}>
<div
style={{
width: 136,
height: 136,
borderRadius: 36,
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
background: 'rgba(57,185,255,0.08)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transform: `scale(${logoScale})`
}}
>
<Img src={trackeepLogo} style={{width: 72, height: 72}} />
</div>
<div style={{fontSize: 88, fontWeight: 700, letterSpacing: '-0.08em', color: trackeepTheme.colors.text}}>Trackeep</div>
<div style={{fontSize: 70, fontWeight: 700, lineHeight: 0.94, letterSpacing: '-0.06em', color: trackeepTheme.colors.text, textAlign: 'center', maxWidth: 860}}>
Track everything that matters.
</div>
<div
style={{
width: lineWidth,
height: 5,
borderRadius: 999,
background: `linear-gradient(90deg, ${trackeepTheme.colors.primary} 0%, rgba(57,185,255,0) 100%)`
}}
/>
</div>
</Backdrop>
);
};
@@ -0,0 +1,36 @@
import {useCurrentFrame} from 'remotion';
import {AppChrome} from '../../components/AppChrome';
import {MetricTile} from '../../components/MetricTile';
import {dashboardHeadline, dashboardHeroMetrics, dashboardSecondaryMetrics} from '../../data/trailerData';
import {PortraitSceneShell} from './PortraitSceneShell';
export const PortraitDashboardScene = () => {
const frame = useCurrentFrame();
return (
<PortraitSceneShell frame={frame} beat={dashboardHeadline}>
<div style={{position: 'absolute', left: 90, top: 470}}>
<AppChrome frame={frame} activeNav="Home" sidebarItems={[]} showSidebar={false} width={900} height={980}>
<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 18, marginBottom: 18}}>
{dashboardHeroMetrics.map((metric, index) => (
<MetricTile key={metric.label} frame={frame} delay={16 + index * 5} label={metric.label} value={String(metric.target)} icon={metric.icon} compact />
))}
</div>
<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 18}}>
{dashboardSecondaryMetrics.map((metric, index) => (
<MetricTile
key={metric.label}
frame={frame}
delay={38 + index * 5}
label={metric.label}
value={metric.decimals ? metric.target.toFixed(metric.decimals) + (metric.suffix ?? '') : `${metric.target}${metric.suffix ?? ''}`}
icon={metric.icon}
compact
/>
))}
</div>
</AppChrome>
</div>
</PortraitSceneShell>
);
};
@@ -0,0 +1,50 @@
import {useCurrentFrame} from 'remotion';
import {AppChrome} from '../../components/AppChrome';
import {ChartBars} from '../../components/ChartBars';
import {MetricTile} from '../../components/MetricTile';
import {ProgressModule} from '../../components/ProgressModule';
import {activationHeadline, activationMetrics, storageBeat, weeklyActivity} from '../../data/trailerData';
import {formatMetricValue} from '../../lib/format';
import {easedProgress} from '../../lib/motion';
import {PortraitSceneShell} from './PortraitSceneShell';
export const PortraitMetricsScene = () => {
const frame = useCurrentFrame();
return (
<PortraitSceneShell frame={frame} beat={activationHeadline}>
<div style={{position: 'absolute', left: 90, top: 446}}>
<AppChrome
frame={frame}
activeNav="Home"
sidebarItems={[]}
showSidebar={false}
width={900}
height={1120}
contentStyle={{padding: '24px 24px 26px'}}
>
<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 16, marginBottom: 16}}>
{activationMetrics.slice(0, 6).map((metric, index) => {
const progress = easedProgress(frame, 16 + index * 5, 42);
return (
<MetricTile
key={metric.label}
frame={frame}
delay={10 + index * 4}
label={metric.label}
value={formatMetricValue(metric, metric.target * progress)}
icon={metric.icon}
compact
/>
);
})}
</div>
<div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
<ChartBars frame={frame} delay={54} values={weeklyActivity} />
<ProgressModule frame={frame} delay={62} beat={storageBeat} />
</div>
</AppChrome>
</div>
</PortraitSceneShell>
);
};
@@ -0,0 +1,71 @@
import {interpolate, useCurrentFrame} from 'remotion';
import {IconBadge} from '../../components/IconBadge';
import {KineticHeadline} from '../../components/KineticHeadline';
import {RoutePreviewCard} from '../../components/RoutePreviewCard';
import {montageHeadline, montageModules, routePreviews} from '../../data/trailerData';
import {springIn} from '../../lib/motion';
import {trackeepTheme} from '../../theme/trackeep';
import {Backdrop} from '../../components/Backdrop';
export const PortraitMontageScene = () => {
const frame = useCurrentFrame();
const centerGlow = springIn(frame, 30, 8, 20);
return (
<Backdrop>
<KineticHeadline
frame={frame}
beat={{...montageHeadline, align: 'center'}}
titleSize={66}
subtitleSize={24}
kickerSize={15}
maxWidth={900}
style={{left: 90, top: 112, width: 900, alignItems: 'center'}}
/>
<div
style={{
position: 'absolute',
left: 110,
top: 420,
width: 860,
height: 540,
borderRadius: 999,
background: 'radial-gradient(circle, rgba(57,185,255,0.2) 0%, rgba(57,185,255,0.04) 42%, rgba(57,185,255,0) 75%)',
opacity: centerGlow,
transform: `scale(${interpolate(centerGlow, [0, 1], [0.9, 1.08])})`
}}
/>
<RoutePreviewCard beat={routePreviews[0]} progress={springIn(frame, 30, 10, 20)} x={160} y={418} width={760} height={476} emphasis />
<RoutePreviewCard beat={routePreviews[1]} progress={springIn(frame, 30, 18, 20)} x={120} y={952} width={390} height={248} />
<RoutePreviewCard beat={routePreviews[4]} progress={springIn(frame, 30, 24, 20)} x={570} y={952} width={390} height={248} />
<RoutePreviewCard beat={routePreviews[3]} progress={springIn(frame, 30, 30, 20)} x={160} y={1260} width={760} height={304} />
<div style={{position: 'absolute', left: 120, top: 1610, width: 840, display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 16}}>
{montageModules.map((module, index) => {
const entry = springIn(frame, 30, 38 + index * 4, 18);
const floatingY = Math.sin((frame + index * 5) / 12) * 2;
return (
<div
key={module.label}
style={{
height: 106,
borderRadius: 22,
border: `1px solid ${trackeepTheme.colors.borderStrong}`,
background: 'rgba(9,11,16,0.76)',
display: 'flex',
alignItems: 'center',
gap: 16,
padding: '0 22px',
opacity: entry,
transform: `translate3d(0, ${interpolate(entry, [0, 1], [20 - floatingY, floatingY])}px, 0) scale(${interpolate(entry, [0, 1], [0.94, 1])})`,
boxShadow: '0 20px 54px rgba(0, 0, 0, 0.38)'
}}
>
<IconBadge icon={module.icon} size={20} />
<div style={{fontSize: 30, fontWeight: 700, color: trackeepTheme.colors.text, letterSpacing: '-0.05em'}}>{module.label}</div>
</div>
);
})}
</div>
</Backdrop>
);
};
@@ -0,0 +1,27 @@
import type {ReactNode} from 'react';
import {Backdrop} from '../../components/Backdrop';
import {KineticHeadline} from '../../components/KineticHeadline';
import type {HeadlineBeat} from '../../data/types';
interface PortraitSceneShellProps {
frame: number;
beat: HeadlineBeat;
children: ReactNode;
}
export const PortraitSceneShell = ({frame, beat, children}: PortraitSceneShellProps) => {
return (
<Backdrop>
<KineticHeadline
frame={frame}
beat={{...beat, align: 'center'}}
titleSize={72}
subtitleSize={24}
kickerSize={15}
maxWidth={860}
style={{left: 110, top: 112, width: 860, alignItems: 'center'}}
/>
{children}
</Backdrop>
);
};
@@ -0,0 +1,55 @@
import {useCurrentFrame} from 'remotion';
import {AppChrome} from '../../components/AppChrome';
import {TypingMessage} from '../../components/TypingMessage';
import {searchBeat, searchHeadline} from '../../data/trailerData';
import {easedProgress} from '../../lib/motion';
import {trackeepTheme} from '../../theme/trackeep';
import {PortraitSceneShell} from './PortraitSceneShell';
export const PortraitSearchAiScene = () => {
const frame = useCurrentFrame();
const resultProgress = easedProgress(frame, 44, 54);
return (
<PortraitSceneShell frame={frame} beat={searchHeadline}>
<div style={{position: 'absolute', left: 90, top: 470}}>
<AppChrome
frame={frame}
activeNav="AI Assistant"
sidebarItems={[]}
showSidebar={false}
searchText={searchBeat.query}
searchDelay={18}
width={900}
height={980}
>
<div style={{display: 'flex', flexDirection: 'column', gap: 18}}>
<div style={{borderRadius: 22, border: `1px solid ${trackeepTheme.colors.border}`, background: 'rgba(255,255,255,0.02)', padding: '22px 24px'}}>
<div style={{fontSize: 28, fontWeight: 700, color: trackeepTheme.colors.text, marginBottom: 14}}>Priority results</div>
{['API Documentation', 'Review pull requests', 'Fix responsive issues'].map((item, index) => (
<div
key={item}
style={{
borderRadius: 16,
border: `1px solid ${trackeepTheme.colors.border}`,
padding: '16px 18px',
marginTop: index === 0 ? 0 : 12,
opacity: resultProgress
}}
>
<div style={{fontSize: 24, fontWeight: 600, color: trackeepTheme.colors.text}}>{item}</div>
<div style={{fontSize: 18, color: trackeepTheme.colors.textMuted, marginTop: 6}}>{['Closest deadline', 'Needs review', 'Shipping blocker'][index]}</div>
</div>
))}
</div>
<div style={{borderRadius: 22, border: `1px solid ${trackeepTheme.colors.border}`, background: 'rgba(255,255,255,0.02)', padding: '22px 24px'}}>
<div style={{fontSize: 28, fontWeight: 700, color: trackeepTheme.colors.text, marginBottom: 8}}>AI Assistant</div>
<div style={{fontSize: 18, color: trackeepTheme.colors.textMuted, marginBottom: 18}}>Your intelligent workspace companion</div>
<TypingMessage frame={frame} delay={72} text={searchBeat.reply} />
</div>
</div>
</AppChrome>
</div>
</PortraitSceneShell>
);
};
@@ -0,0 +1,61 @@
import {useCurrentFrame} from 'remotion';
import {AppChrome} from '../../components/AppChrome';
import {IconBadge} from '../../components/IconBadge';
import {timeHeadline, timeOverviewStats} from '../../data/trailerData';
import {formatMetricValue, formatStopwatch} from '../../lib/format';
import {easedProgress} from '../../lib/motion';
import {trackeepTheme} from '../../theme/trackeep';
import {PortraitSceneShell} from './PortraitSceneShell';
const panelStyle = {
borderRadius: 22,
border: `1px solid ${trackeepTheme.colors.border}`,
background: 'rgba(255,255,255,0.02)',
padding: '22px 24px'
};
export const PortraitTimeTrackingScene = () => {
const frame = useCurrentFrame();
const timerProgress = easedProgress(frame, 12, 96);
const timerSeconds = Math.round(315 * timerProgress);
const billableProgress = easedProgress(frame, 34, 72);
return (
<PortraitSceneShell frame={frame} beat={timeHeadline}>
<div style={{position: 'absolute', left: 90, top: 470}}>
<AppChrome frame={frame} activeNav="Time Tracking" sidebarItems={[]} showSidebar={false} width={900} height={1050}>
<div style={{display: 'flex', flexDirection: 'column', gap: 18}}>
<div style={{...panelStyle, minHeight: 360, display: 'flex', flexDirection: 'column', gap: 18}}>
<div style={{fontSize: 110, fontWeight: 700, letterSpacing: '-0.07em', color: trackeepTheme.colors.text, textAlign: 'center'}}>{formatStopwatch(timerSeconds)}</div>
<div style={{textAlign: 'center', fontSize: 30, color: trackeepTheme.colors.textMuted}}>Stopped</div>
<div style={{height: 60, borderRadius: 12, border: `1px solid ${trackeepTheme.colors.border}`, display: 'flex', alignItems: 'center', padding: '0 16px', color: trackeepTheme.colors.textMuted, fontSize: 22}}>What are you working on? (optional)</div>
<div style={{height: 60, borderRadius: 12, border: `1px solid ${trackeepTheme.colors.border}`, display: 'flex', alignItems: 'center', padding: '0 16px', color: trackeepTheme.colors.textMuted, fontSize: 22}}>Add tags...</div>
<div style={{height: 60, borderRadius: 12, background: trackeepTheme.colors.primary, color: '#03131d', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 28, fontWeight: 700}}>Start</div>
</div>
<div style={{...panelStyle}}>
<div style={{fontSize: 28, fontWeight: 700, color: trackeepTheme.colors.text, marginBottom: 18}}>Today's Overview</div>
<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 18}}>
{timeOverviewStats.map((stat, index) => {
const progress = index === 2 ? billableProgress : easedProgress(frame, 22 + index * 8, 56);
const animatedValue = stat.target * progress;
const value = stat.label === 'Total Time Today'
? formatStopwatch(Math.round(animatedValue))
: formatMetricValue(stat, animatedValue);
return (
<div key={stat.label} style={{display: 'flex', alignItems: 'center', gap: 12}}>
<IconBadge icon={stat.icon} />
<div>
<div style={{fontSize: 34, fontWeight: 700, color: trackeepTheme.colors.text, letterSpacing: '-0.05em'}}>{value}</div>
<div style={{fontSize: 18, color: trackeepTheme.colors.textMuted}}>{stat.label}</div>
</div>
</div>
);
})}
</div>
</div>
</div>
</AppChrome>
</div>
</PortraitSceneShell>
);
};
+24
View File
@@ -0,0 +1,24 @@
@import "@fontsource/inter/400.css";
@import "@fontsource/inter/500.css";
@import "@fontsource/inter/600.css";
@import "@fontsource/inter/700.css";
:root {
color-scheme: dark;
}
* {
box-sizing: border-box;
}
html,
body,
#root {
margin: 0;
width: 100%;
height: 100%;
background: #050608;
font-family: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+31
View File
@@ -0,0 +1,31 @@
export const trackeepTheme = {
colors: {
background: '#141415',
backgroundAlt: '#1a1a1a',
backgroundDeep: '#0b0d11',
surface: '#17191d',
surfaceRaised: '#202228',
surfaceSoft: '#121418',
border: '#262626',
borderStrong: '#2d3139',
text: '#fafafa',
textMuted: '#a3a3a3',
primary: '#39b9ff',
primarySoft: 'rgba(57, 185, 255, 0.16)',
primaryGlow: 'rgba(57, 185, 255, 0.28)',
success: '#61d88f',
warning: '#f8b84e',
danger: '#ff6868'
},
radius: {
sm: 8,
md: 12,
lg: 18,
pill: 999
},
shadow: {
soft: '0 18px 60px rgba(0, 0, 0, 0.34)',
card: '0 12px 32px rgba(0, 0, 0, 0.2)'
},
fontFamily: 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
} as const;
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"types": ["node"]
},
"include": ["src"]
}