padhle - post migration
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
.DS_Store
|
||||
.env
|
||||
|
||||
# Ignore the output video from Git but not videos you import into src/.
|
||||
out
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"tabWidth": 2
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Remotion video
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/remotion-dev/logo">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/remotion-dev/logo/raw/main/animated-logo-banner-dark.apng">
|
||||
<img alt="Animated Remotion Logo" src="https://github.com/remotion-dev/logo/raw/main/animated-logo-banner-light.gif">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Welcome to your Remotion project!
|
||||
|
||||
## Commands
|
||||
|
||||
**Install Dependencies**
|
||||
|
||||
```console
|
||||
npm i
|
||||
```
|
||||
|
||||
**Start Preview**
|
||||
|
||||
```console
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**Render video**
|
||||
|
||||
```console
|
||||
npx remotion render
|
||||
```
|
||||
|
||||
**Upgrade Remotion**
|
||||
|
||||
```console
|
||||
npx remotion upgrade
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
Get started with Remotion by reading the [fundamentals page](https://www.remotion.dev/docs/the-fundamentals).
|
||||
|
||||
## Help
|
||||
|
||||
We provide help on our [Discord server](https://discord.gg/6VzzNDwUwV).
|
||||
|
||||
## Issues
|
||||
|
||||
Found an issue with Remotion? [File an issue here](https://github.com/remotion-dev/remotion/issues/new).
|
||||
|
||||
## License
|
||||
|
||||
Note that for some entities a company license is needed. [Read the terms here](https://github.com/remotion-dev/remotion/blob/main/LICENSE.md).
|
||||
@@ -0,0 +1,3 @@
|
||||
import { config } from "@remotion/eslint-config-flat";
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "my-video",
|
||||
"version": "1.0.0",
|
||||
"description": "My Remotion video",
|
||||
"repository": {},
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@remotion/cli": "4.0.522",
|
||||
"@remotion/google-fonts": "4.0.522",
|
||||
"@remotion/media": "4.0.522",
|
||||
"@remotion/tailwind-v4": "4.0.522",
|
||||
"@remotion/transitions": "4.0.522",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"remotion": "4.0.522",
|
||||
"tailwindcss": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remotion/eslint-config-flat": "4.0.522",
|
||||
"@types/react": "19.2.7",
|
||||
"@types/web": "0.0.166",
|
||||
"eslint": "9.19.0",
|
||||
"prettier": "3.8.1",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "remotion studio",
|
||||
"build": "remotion bundle",
|
||||
"upgrade": "remotion upgrade",
|
||||
"lint": "eslint src && tsc"
|
||||
},
|
||||
"sideEffects": [
|
||||
"*.css"
|
||||
],
|
||||
"allowScripts": {
|
||||
"esbuild@0.28.1": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Note: When using the Node.JS APIs, the config file
|
||||
* doesn't apply. Instead, pass options directly to the APIs.
|
||||
*
|
||||
* All configuration options: https://remotion.dev/docs/config
|
||||
*/
|
||||
|
||||
import { Config } from "@remotion/cli/config";
|
||||
import { enableTailwind } from '@remotion/tailwind-v4';
|
||||
|
||||
Config.setRspack(true);
|
||||
Config.setVideoImageFormat("jpeg");
|
||||
Config.setOverwriteOutput(true);
|
||||
Config.overrideBundlerConfig(enableTailwind);
|
||||
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 432 KiB |
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 246 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 378 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 97 KiB |
@@ -0,0 +1,2 @@
|
||||
// Placeholder — all compositions are registered in Root.tsx.
|
||||
export {};
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
staticFile,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from "remotion";
|
||||
import { Audio } from "@remotion/media";
|
||||
import { TransitionSeries, linearTiming } from "@remotion/transitions";
|
||||
import { fade } from "@remotion/transitions/fade";
|
||||
import { Intro, INTRO_FRAMES } from "./scenes/Intro";
|
||||
import { Selector, SELECT_FRAMES } from "./scenes/Selector";
|
||||
import { Chat, CHAT_FRAMES } from "./scenes/Chat";
|
||||
import { Grounded, GROUNDED_FRAMES } from "./scenes/Grounded";
|
||||
import { CTA, CTA_FRAMES } from "./scenes/CTA";
|
||||
|
||||
const TRANSITION = 18;
|
||||
|
||||
export const MAIN_FRAMES =
|
||||
INTRO_FRAMES +
|
||||
SELECT_FRAMES +
|
||||
CHAT_FRAMES +
|
||||
GROUNDED_FRAMES +
|
||||
CTA_FRAMES -
|
||||
4 * TRANSITION;
|
||||
|
||||
const Music: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { durationInFrames, fps } = useVideoConfig();
|
||||
const fadeOut = interpolate(
|
||||
frame,
|
||||
[durationInFrames - 2 * fps, durationInFrames - 0.4 * fps],
|
||||
[1, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
return (
|
||||
<Audio
|
||||
src={staticFile("padhle-music.mp3")}
|
||||
volume={(f) =>
|
||||
interpolate(f, [0, 1 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
}) * fadeOut
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const MainVideo: React.FC = () => (
|
||||
<AbsoluteFill style={{ backgroundColor: "#000" }}>
|
||||
<Music />
|
||||
<TransitionSeries>
|
||||
<TransitionSeries.Sequence durationInFrames={INTRO_FRAMES} name="Intro">
|
||||
<Intro />
|
||||
</TransitionSeries.Sequence>
|
||||
<TransitionSeries.Transition
|
||||
presentation={fade()}
|
||||
timing={linearTiming({ durationInFrames: TRANSITION })}
|
||||
/>
|
||||
<TransitionSeries.Sequence durationInFrames={SELECT_FRAMES} name="Selector">
|
||||
<Selector />
|
||||
</TransitionSeries.Sequence>
|
||||
<TransitionSeries.Transition
|
||||
presentation={fade()}
|
||||
timing={linearTiming({ durationInFrames: TRANSITION })}
|
||||
/>
|
||||
<TransitionSeries.Sequence durationInFrames={CHAT_FRAMES} name="Chat">
|
||||
<Chat />
|
||||
</TransitionSeries.Sequence>
|
||||
<TransitionSeries.Transition
|
||||
presentation={fade()}
|
||||
timing={linearTiming({ durationInFrames: TRANSITION })}
|
||||
/>
|
||||
<TransitionSeries.Sequence durationInFrames={GROUNDED_FRAMES} name="Grounded">
|
||||
<Grounded />
|
||||
</TransitionSeries.Sequence>
|
||||
<TransitionSeries.Transition
|
||||
presentation={fade()}
|
||||
timing={linearTiming({ durationInFrames: TRANSITION })}
|
||||
/>
|
||||
<TransitionSeries.Sequence durationInFrames={CTA_FRAMES} name="CTA">
|
||||
<CTA />
|
||||
</TransitionSeries.Sequence>
|
||||
</TransitionSeries>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
import { Composition, Folder } from "remotion";
|
||||
import "./index.css";
|
||||
import { MainVideo, MAIN_FRAMES } from "./MainVideo";
|
||||
import { Intro, INTRO_FRAMES } from "./scenes/Intro";
|
||||
import { Selector, SELECT_FRAMES } from "./scenes/Selector";
|
||||
import { Chat, CHAT_FRAMES } from "./scenes/Chat";
|
||||
import { Grounded, GROUNDED_FRAMES } from "./scenes/Grounded";
|
||||
import { CTA, CTA_FRAMES } from "./scenes/CTA";
|
||||
import { WIDTH, HEIGHT, FPS } from "./theme";
|
||||
|
||||
export const RemotionRoot: React.FC = () => (
|
||||
<>
|
||||
<Folder name="Padhle-Demo">
|
||||
<Composition
|
||||
id="Intro"
|
||||
component={Intro}
|
||||
durationInFrames={INTRO_FRAMES}
|
||||
fps={FPS}
|
||||
width={WIDTH}
|
||||
height={HEIGHT}
|
||||
/>
|
||||
<Composition
|
||||
id="Selector"
|
||||
component={Selector}
|
||||
durationInFrames={SELECT_FRAMES}
|
||||
fps={FPS}
|
||||
width={WIDTH}
|
||||
height={HEIGHT}
|
||||
/>
|
||||
<Composition
|
||||
id="Chat"
|
||||
component={Chat}
|
||||
durationInFrames={CHAT_FRAMES}
|
||||
fps={FPS}
|
||||
width={WIDTH}
|
||||
height={HEIGHT}
|
||||
/>
|
||||
<Composition
|
||||
id="Grounded"
|
||||
component={Grounded}
|
||||
durationInFrames={GROUNDED_FRAMES}
|
||||
fps={FPS}
|
||||
width={WIDTH}
|
||||
height={HEIGHT}
|
||||
/>
|
||||
<Composition
|
||||
id="CTA"
|
||||
component={CTA}
|
||||
durationInFrames={CTA_FRAMES}
|
||||
fps={FPS}
|
||||
width={WIDTH}
|
||||
height={HEIGHT}
|
||||
/>
|
||||
</Folder>
|
||||
<Composition
|
||||
id="Padhle"
|
||||
component={MainVideo}
|
||||
durationInFrames={MAIN_FRAMES}
|
||||
fps={FPS}
|
||||
width={WIDTH}
|
||||
height={HEIGHT}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
@@ -0,0 +1,4 @@
|
||||
import { registerRoot } from "remotion";
|
||||
import { RemotionRoot } from "./Root";
|
||||
|
||||
registerRoot(RemotionRoot);
|
||||
@@ -0,0 +1,308 @@
|
||||
import React from "react";
|
||||
import { interpolate, useCurrentFrame } from "remotion";
|
||||
import { COLORS, headingFont, bodyFont, SNAP } from "./theme";
|
||||
|
||||
// ── Brand bolt + wordmark ──────────────────────────────────────────────
|
||||
export const Bolt: React.FC<{ size?: number; color?: string }> = ({
|
||||
size = 240,
|
||||
color = COLORS.accent,
|
||||
}) => (
|
||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
||||
<path d="M13.2 1.8 3.6 13.6h6.6l-1.2 8.6L18.6 10h-6.9l1.5-8.2Z" fill={color} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Small icon chip (Material-ish glyph drawn as text) — mirrors selector icons.
|
||||
export const LogoMark: React.FC<{ size?: number }> = ({ size = 64 }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size * 0.28,
|
||||
background: COLORS.accent,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: size * 0.42,
|
||||
height: size * 0.66,
|
||||
background: COLORS.white,
|
||||
clipPath:
|
||||
"polygon(55% 0, 8% 55%, 45% 55%, 42% 100%, 90% 42%, 55% 42%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── TopNav pill (real padhle header pill) ──────────────────────────────
|
||||
export const Pill: React.FC<{
|
||||
label: string;
|
||||
active?: boolean;
|
||||
from?: number;
|
||||
scale?: number;
|
||||
fps?: number;
|
||||
}> = ({ label, active = false, from = 0, scale = 1, fps = 30 }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const p = interpolate(frame, [from, from + 7], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 34 * scale,
|
||||
fontWeight: 700,
|
||||
color: active ? COLORS.white : COLORS.accent,
|
||||
whiteSpace: "nowrap",
|
||||
background: active ? COLORS.black : COLORS.white,
|
||||
border: `${active ? 0 : 3}px solid ${COLORS.accent}`,
|
||||
borderRadius: 999,
|
||||
padding: `${12 * scale}px ${26 * scale}px`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
opacity: p,
|
||||
translate: `0 ${(1 - p) * 30}px`,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: 8 * scale,
|
||||
height: 8 * scale,
|
||||
borderRadius: "50%",
|
||||
background: active ? COLORS.white : COLORS.accent,
|
||||
}}
|
||||
/>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Selector option card (real SelectorFlow card) ─────────────────────
|
||||
export const SelectCard: React.FC<{
|
||||
icon?: string | null;
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
from?: number;
|
||||
index?: number;
|
||||
}> = ({ icon, label, selected = false, from = 0, index = 0 }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const p = interpolate(frame, [from, from + 6], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
});
|
||||
const scaleIn = interpolate(frame, [from, from + 5], [0.5, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
output: "perceptual-scale",
|
||||
});
|
||||
const lift = interpolate(frame, [from, from + 6], [index * 0.02, 0], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 10,
|
||||
padding: "26px 16px",
|
||||
border: `3px solid ${selected ? COLORS.black : "#e5e5e7"}`,
|
||||
borderRadius: 18,
|
||||
background: selected ? COLORS.black : COLORS.white,
|
||||
color: selected ? COLORS.white : COLORS.ink,
|
||||
opacity: p,
|
||||
scale: scaleIn,
|
||||
translate: `0 ${(1 - p) * 36}px`,
|
||||
boxShadow: lift * 8 >= 1 ? "0px 12px 24px rgba(17,24,39,0.18)" : "none",
|
||||
}}
|
||||
>
|
||||
{icon && (
|
||||
<span style={{ fontSize: 34, lineHeight: 1, color: selected ? COLORS.white : COLORS.accent }}>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 22,
|
||||
fontWeight: 600,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Chat bubble (real padhle Message) ─────────────────────────────────
|
||||
export const MsgBubble: React.FC<{
|
||||
text: string;
|
||||
user?: boolean;
|
||||
from?: number;
|
||||
w?: string;
|
||||
}> = ({ text, user = false, from = 0, w = "86%" }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const p = interpolate(frame, [from, from + 7], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
});
|
||||
const userStyle = {
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 30,
|
||||
fontWeight: 600,
|
||||
color: COLORS.white,
|
||||
background: COLORS.accent,
|
||||
borderRadius: 24,
|
||||
padding: "22px 30px",
|
||||
maxWidth: w,
|
||||
opacity: p,
|
||||
translate: `0 ${(1 - p) * 30}px`,
|
||||
};
|
||||
const asstStyle = {
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 30,
|
||||
fontWeight: 500,
|
||||
color: COLORS.ink,
|
||||
background: COLORS.white,
|
||||
border: "3px solid #e5e5e7",
|
||||
borderRadius: 24,
|
||||
padding: "22px 30px",
|
||||
maxWidth: w,
|
||||
opacity: p,
|
||||
translate: `0 ${(1 - p) * 30}px`,
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...(user ? userStyle : asstStyle),
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── KaTeX-style math block (rendered as styled text) ──────────────────
|
||||
export const MathBlock: React.FC<{
|
||||
tex: React.ReactNode;
|
||||
from?: number;
|
||||
}> = ({ tex, from = 0 }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const p = interpolate(frame, [from, from + 7], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
fontSize: 44,
|
||||
fontStyle: "italic",
|
||||
color: COLORS.ink,
|
||||
textAlign: "center",
|
||||
background: COLORS.surface,
|
||||
border: "3px solid #e5e5e7",
|
||||
borderRadius: 18,
|
||||
padding: "22px 30px",
|
||||
opacity: p,
|
||||
}}
|
||||
>
|
||||
{tex}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Caption bar (bottom, safe area) ───────────────────────────────────
|
||||
export const CapBar: React.FC<{
|
||||
caption: string;
|
||||
from?: number;
|
||||
k?: number;
|
||||
align?: "center";
|
||||
}> = ({ caption, from = 0, k = 1 }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const p = interpolate(frame, [from, from + 14], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 90,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "0 60px",
|
||||
opacity: p,
|
||||
translate: `0 ${(1 - p) * 24}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 40 * k,
|
||||
fontWeight: 700,
|
||||
color: COLORS.muted,
|
||||
textAlign: "center",
|
||||
background: "rgba(252,248,250,0.92)",
|
||||
borderRadius: 16,
|
||||
padding: "14px 26px",
|
||||
boxShadow: "0 6px 18px rgba(17,24,39,0.18)",
|
||||
}}
|
||||
>
|
||||
{caption}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Sidebar-ish brand rail (optional left frame) ──────────────────────
|
||||
export const BrandRail: React.FC<{ w?: number }> = ({ w = 300 }) => (
|
||||
<div
|
||||
style={{
|
||||
width: w,
|
||||
height: "100%",
|
||||
background: COLORS.white,
|
||||
borderRight: "3px solid #e5e5e7",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", padding: "40px 40px", gap: 18 }}>
|
||||
<LogoMark size={48} />
|
||||
<span style={{ fontFamily: headingFont, fontSize: 40, fontWeight: 700, color: COLORS.ink }}>
|
||||
padhle
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 24,
|
||||
color: COLORS.muted,
|
||||
padding: "0 40px",
|
||||
marginTop: 40,
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
{["New Chat", "Math", "Science", "History", "English"].join("\n")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,108 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AbsoluteFill,
|
||||
|
||||
interpolate,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from "remotion";
|
||||
import { COLORS, headingFont, bodyFont, FPS, SNAP } from "../theme";
|
||||
import { Bolt } from "../padhle";
|
||||
|
||||
export const CTA_FRAMES = 5 * FPS;
|
||||
|
||||
export const CTA: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const boltP = interpolate(frame, [0.15 * fps, 0.5 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
});
|
||||
const headIn = interpolate(frame, [1.2 * fps, 1.7 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const ctaIn = interpolate(frame, [2.6 * fps, 3.2 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
name="CTA"
|
||||
style={{
|
||||
backgroundColor: COLORS.accent,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
gap: 60,
|
||||
paddingTop: 260,
|
||||
}}
|
||||
>
|
||||
<div style={{ scale: 1 + (1 - ctaIn) * 0.3, opacity: 1 }}>
|
||||
<Bolt size={200} color={COLORS.white} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 110,
|
||||
fontWeight: 900,
|
||||
color: COLORS.white,
|
||||
textAlign: "center",
|
||||
scale: boltP,
|
||||
}}
|
||||
>
|
||||
Study
|
||||
<br />
|
||||
with confidence.
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
paddingBottom: 340,
|
||||
gap: 26,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 46,
|
||||
fontWeight: 700,
|
||||
color: COLORS.accent,
|
||||
background: COLORS.white,
|
||||
borderRadius: 999,
|
||||
padding: "22px 60px",
|
||||
textAlign: "center",
|
||||
opacity: ctaIn,
|
||||
translate: `0 ${(1 - ctaIn) * 30}px`,
|
||||
}}
|
||||
>
|
||||
Start with padhle
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 32,
|
||||
fontWeight: 500,
|
||||
color: "rgba(255,255,255,0.92)",
|
||||
textAlign: "center",
|
||||
opacity: headIn,
|
||||
}}
|
||||
>
|
||||
Free to start. Grades 6–12.
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AbsoluteFill,
|
||||
interpolate,
|
||||
Sequence,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from "remotion";
|
||||
import { COLORS, headingFont, FPS, SNAP } from "../theme";
|
||||
import { MsgBubble, MathBlock, CapBar } from "../padhle";
|
||||
|
||||
export const CHAT_FRAMES = 12 * FPS;
|
||||
|
||||
export const Chat: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
// streaming typewriter for the assistant answer
|
||||
const answer =
|
||||
"Photosynthesis is how plants turn sunlight into food. The simplified equation:";
|
||||
const typed = Math.round(
|
||||
interpolate(frame, [1.6 * fps, 6.0 * fps], [0, answer.length], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
name="Chat"
|
||||
style={{
|
||||
backgroundColor: COLORS.surface,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{/* chat screen header */}
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 220,
|
||||
background: COLORS.white,
|
||||
borderBottom: "3px solid #e5e5e7",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 16,
|
||||
padding: "0 30px",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 40,
|
||||
fontWeight: 700,
|
||||
color: COLORS.ink,
|
||||
}}
|
||||
>
|
||||
Grade 10 · Science · Photosynthesis
|
||||
</span>
|
||||
</AbsoluteFill>
|
||||
|
||||
{/* conversation */}
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
padding: "0 70px",
|
||||
}}
|
||||
>
|
||||
{/* user question bubble */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: 300,
|
||||
}}
|
||||
>
|
||||
<MsgBubble
|
||||
text="Explain how photosynthesis works in simple terms"
|
||||
user
|
||||
from={0.5 * fps}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* assistant streaming answer + math */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
marginTop: 36,
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
{/* school avatar */}
|
||||
<div
|
||||
style={{
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 16,
|
||||
background: COLORS.accent,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginRight: 18,
|
||||
opacity: interpolate(frame, [1.4 * fps, 1.7 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 26,
|
||||
height: 40,
|
||||
background: COLORS.white,
|
||||
clipPath:
|
||||
"polygon(55% 0, 8% 55%, 45% 55%, 42% 100%, 90% 42%, 55% 42%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ maxWidth: "72%" }}>
|
||||
<MsgBubble
|
||||
text={answer.slice(0, typed)}
|
||||
from={1.6 * fps}
|
||||
user={false}
|
||||
w="100%"
|
||||
/>
|
||||
<div style={{ height: 14 }} />
|
||||
<MathBlock
|
||||
from={6.4 * fps}
|
||||
tex={
|
||||
<>
|
||||
6CO<sub>2</sub> + 6H<sub>2</sub>O + light {"→"} C<sub>6</sub>H
|
||||
<sub>12</sub>O<sub>6</sub> + 6O<sub>2</sub>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
|
||||
<Sequence name="CaptionChat" from={0.8 * fps}>
|
||||
<CapBar caption="Ask in your own words." from={0} />
|
||||
</Sequence>
|
||||
<CapBar caption="Watch it break down, step by step." from={7.2 * fps} />
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AbsoluteFill,
|
||||
|
||||
interpolate,
|
||||
Sequence,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from "remotion";
|
||||
import { COLORS, headingFont, bodyFont, FPS, SNAP } from "../theme";
|
||||
import { CapBar } from "../padhle";
|
||||
|
||||
export const GROUNDED_FRAMES = 9 * FPS;
|
||||
|
||||
export const Grounded: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const bookIn = interpolate(frame, [0.2 * fps, 0.9 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
});
|
||||
const citeIn = interpolate(frame, [1.4 * fps, 2.1 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const lineIn = interpolate(frame, [4.2 * fps, 5.0 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
name="Grounded"
|
||||
style={{
|
||||
backgroundColor: COLORS.surface,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "0 90px",
|
||||
}}
|
||||
>
|
||||
{/* headline */}
|
||||
<div
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 72,
|
||||
fontWeight: 900,
|
||||
color: COLORS.ink,
|
||||
textAlign: "center",
|
||||
opacity: bookIn,
|
||||
translate: `0 ${(1 - bookIn) * 40}px`,
|
||||
}}
|
||||
>
|
||||
Grounded in your textbook.
|
||||
</div>
|
||||
|
||||
{/* source card — mimics a textbook reference */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 20,
|
||||
border: "3px solid #e5e5e7",
|
||||
borderRadius: 22,
|
||||
background: COLORS.white,
|
||||
padding: "30px 34px",
|
||||
marginTop: 60,
|
||||
opacity: citeIn,
|
||||
translate: `0 ${(1 - citeIn) * 30}px`,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 40,
|
||||
color: COLORS.ink,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 30, color: COLORS.muted }}>
|
||||
Answered from
|
||||
</div>
|
||||
<div style={{ fontWeight: 900, fontSize: 42 }}>
|
||||
Grade 10 · Science
|
||||
</div>
|
||||
<div style={{ fontWeight: 700, fontSize: 40, color: COLORS.accent }}>
|
||||
Chapter 2: Photosynthesis
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* key line */}
|
||||
<div
|
||||
style={{
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 44,
|
||||
color: COLORS.ink,
|
||||
fontWeight: 600,
|
||||
textAlign: "center",
|
||||
marginTop: 70,
|
||||
opacity: lineIn,
|
||||
}}
|
||||
>
|
||||
Answers come only from the chapter you chose.
|
||||
<br />
|
||||
No guesswork. No made-up facts.
|
||||
</div>
|
||||
|
||||
<Sequence name="GroundedCaps" from={0}>
|
||||
<CapBar caption="Every answer cites its source." from={2.6 * fps} />
|
||||
</Sequence>
|
||||
<CapBar caption="Built for board exams." from={6.5 * fps} />
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AbsoluteFill,
|
||||
|
||||
interpolate,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from "remotion";
|
||||
import { COLORS, headingFont, bodyFont, FPS, SNAP } from "../theme";
|
||||
import { Bolt, CapBar } from "../padhle";
|
||||
|
||||
export const INTRO_FRAMES = 5 * FPS;
|
||||
|
||||
export const Intro: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const boltIn = interpolate(frame, [0.1 * fps, 0.5 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
output: "perceptual-scale",
|
||||
});
|
||||
const wordIn = interpolate(frame, [0.7 * fps, 1.2 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const subIn = interpolate(frame, [1.5 * fps, 2.1 * fps], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
name="Intro"
|
||||
style={{
|
||||
backgroundColor: COLORS.black,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(120% 80% at 50% 42%, rgba(255,255,255,0.16) 0%, transparent 68%)",
|
||||
}}
|
||||
/>
|
||||
<AbsoluteFill
|
||||
style={{ justifyContent: "center", alignItems: "center" }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
scale: boltIn,
|
||||
opacity: interpolate(boltIn, [0, 1], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Bolt size={360} />
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingTop: 520,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 130,
|
||||
fontWeight: 900,
|
||||
color: COLORS.white,
|
||||
opacity: wordIn,
|
||||
translate: `0 ${(1 - wordIn) * 40}px`,
|
||||
}}
|
||||
>
|
||||
padhle
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
paddingBottom: 300,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 46,
|
||||
fontWeight: 600,
|
||||
color: "rgba(255,255,255,0.92)",
|
||||
maxWidth: "80%",
|
||||
textAlign: "center",
|
||||
opacity: subIn,
|
||||
}}
|
||||
>
|
||||
Your AI tutor, grounded in your actual textbook.
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
<CapBar caption="Bring your syllabus. Ask anything." from={3.4 * fps} />
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
import React from "react";
|
||||
import {
|
||||
AbsoluteFill,
|
||||
|
||||
interpolate,
|
||||
Sequence,
|
||||
useCurrentFrame,
|
||||
useVideoConfig,
|
||||
} from "remotion";
|
||||
import { COLORS, headingFont, bodyFont, FPS, SNAP } from "../theme";
|
||||
import { Pill, SelectCard, CapBar, LogoMark } from "../padhle";
|
||||
|
||||
export const SELECT_FRAMES = 12 * FPS;
|
||||
|
||||
const TITLE = {
|
||||
grade: "Choose your standard",
|
||||
subject: "Pick a subject",
|
||||
chapter: "Choose your chapter",
|
||||
};
|
||||
const SUBT = {
|
||||
grade: "Grade 6 – 12",
|
||||
subject: "What are we studying?",
|
||||
chapter: "Subject-aware chapters",
|
||||
};
|
||||
|
||||
const GRADES: Item[] = [
|
||||
{ id: "grade-6", label: "Grade 6", icon: "6" },
|
||||
{ id: "grade-7", label: "Grade 7", icon: "7" },
|
||||
{ id: "grade-8", label: "Grade 8", icon: "8" },
|
||||
{ id: "grade-9", label: "Grade 9", icon: "9" },
|
||||
{ id: "grade-10", label: "Grade 10", icon: "10" },
|
||||
{ id: "grade-11", label: "Grade 11", icon: "11" },
|
||||
{ id: "grade-12", label: "Grade 12", icon: "12" },
|
||||
];
|
||||
|
||||
const SUBJECTS: Item[] = [
|
||||
{ id: "math", label: "Mathematics" },
|
||||
{ id: "science", label: "Science" },
|
||||
{ id: "history", label: "History" },
|
||||
{ id: "language", label: "Language" },
|
||||
{ id: "english", label: "English" },
|
||||
{ id: "geography", label: "Geography" },
|
||||
];
|
||||
|
||||
const SCIENCE_CH = [
|
||||
{ id: "ch-1", label: "Ch 1: Nutrition in Plants" },
|
||||
{ id: "ch-2", label: "Ch 2: Photosynthesis" },
|
||||
{ id: "ch-3", label: "Ch 3: Human Physiology" },
|
||||
{ id: "ch-4", label: "Ch 4: Chemistry Basics" },
|
||||
{ id: "ch-5", label: "Ch 5: Motion & Force" },
|
||||
{ id: "ch-6", label: "Ch 6: Electricity" },
|
||||
].map((c) => ({ ...c, icon: null }));
|
||||
|
||||
type Item = { id: string; label: string; icon?: string | null };
|
||||
|
||||
const Grid: React.FC<{ step: string; at: number }> = ({ step, at }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const p = interpolate(frame, [at, at + 16], [0, 1], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
easing: SNAP,
|
||||
});
|
||||
const items: Item[] =
|
||||
step === "grade" ? GRADES : step === "subject" ? SUBJECTS : SCIENCE_CH;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontWeight: 800,
|
||||
color: COLORS.ink,
|
||||
fontSize: 52,
|
||||
textAlign: "center",
|
||||
opacity: p,
|
||||
translate: `0 ${(1 - p) * 40}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 52,
|
||||
fontWeight: 800,
|
||||
color: COLORS.ink,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{TITLE[step as keyof typeof TITLE]}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: bodyFont,
|
||||
fontSize: 30,
|
||||
color: COLORS.muted,
|
||||
textAlign: "center",
|
||||
marginTop: 8,
|
||||
}}
|
||||
>
|
||||
{SUBT[step as keyof typeof SUBT]}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
gap: 20,
|
||||
marginTop: 36,
|
||||
padding: "0 20px",
|
||||
}}
|
||||
>
|
||||
{items.map((it, i) => (
|
||||
<SelectCard
|
||||
key={it.id}
|
||||
icon={it.icon}
|
||||
label={it.label}
|
||||
selected={false}
|
||||
from={at + 8 + i * 1.5}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Selector: React.FC = () => {
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
return (
|
||||
<AbsoluteFill
|
||||
name="Selector"
|
||||
style={{
|
||||
backgroundColor: COLORS.surface,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{/* app frame: topnav pills */}
|
||||
<AbsoluteFill
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 300,
|
||||
background: COLORS.white,
|
||||
borderBottom: "3px solid #e5e5e7",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: 24,
|
||||
padding: "0 30px",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
||||
<LogoMark size={56} />
|
||||
<span
|
||||
style={{
|
||||
fontFamily: headingFont,
|
||||
fontSize: 44,
|
||||
fontWeight: 700,
|
||||
color: COLORS.ink,
|
||||
}}
|
||||
>
|
||||
padhle
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ width: 24 }} />
|
||||
<Pill label="Grade 10" from={0.9 * fps} active={false} />
|
||||
<Pill
|
||||
label="Science"
|
||||
from={5.3 * fps}
|
||||
active={false}
|
||||
/>
|
||||
<Pill
|
||||
label="Chapter 2"
|
||||
from={9.1 * fps}
|
||||
active={true}
|
||||
/>
|
||||
</AbsoluteFill>
|
||||
|
||||
{/* selector grid per step — each window exclusive */}
|
||||
<Sequence
|
||||
from={1.1 * fps}
|
||||
durationInFrames={4.5 * fps - 1.1 * fps}
|
||||
layout="none"
|
||||
>
|
||||
<Grid step="grade" at={0.15 * fps} />
|
||||
<CapBar caption="Start with your grade — 6 to 12." from={2.0 * fps} />
|
||||
</Sequence>
|
||||
<Sequence
|
||||
from={4.7 * fps}
|
||||
durationInFrames={8.5 * fps - 4.7 * fps}
|
||||
layout="none"
|
||||
>
|
||||
<Grid step="subject" at={0} />
|
||||
<CapBar caption="Then your subject…" from={0.5 * fps} />
|
||||
</Sequence>
|
||||
<Sequence
|
||||
from={8.7 * fps}
|
||||
durationInFrames={12.0 * fps - 8.7 * fps}
|
||||
layout="none"
|
||||
>
|
||||
<Grid step="chapter" at={0} />
|
||||
<CapBar caption="Chapters update for your subject." from={0.5 * fps} />
|
||||
</Sequence>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { loadFont as loadMontserrat } from "@remotion/google-fonts/Montserrat";
|
||||
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
|
||||
import { Easing } from "remotion";
|
||||
|
||||
export const { fontFamily: headingFont } = loadMontserrat("normal", {
|
||||
weights: ["700", "900"],
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const { fontFamily: bodyFont } = loadInter("normal", {
|
||||
weights: ["400", "500", "600", "700"],
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
// Rapid, punchy entrance — short duration + slight overshoot for staccato energy.
|
||||
export const SNAP = Easing.bezier(0.05, 1.2, 0.25, 1);
|
||||
export const HIT = Easing.out(Easing.back(1.7));
|
||||
|
||||
export const COLORS = {
|
||||
accent: "#000000",
|
||||
black: "#000000",
|
||||
white: "#ffffff",
|
||||
surface: "#fafafa",
|
||||
ink: "#0a0a0a",
|
||||
muted: "#6b6b6b",
|
||||
line: "#e5e5e5",
|
||||
};
|
||||
|
||||
export const FPS = 30;
|
||||
export const WIDTH = 1080;
|
||||
export const HEIGHT = 1920;
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "Preserve",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"lib": ["es2015"],
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noUnusedLocals": true
|
||||
},
|
||||
"exclude": ["remotion.config.ts"]
|
||||
}
|
||||