mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 13:12:44 +00:00
chore(videos): delete Remotion video app and adjust docs description
This commit is contained in:
1 parent
6879afa61c
commit
5987fdce7d
18 files changed
+2
-526
No files matched your search
@@ -46,7 +46,7 @@ flowchart LR
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `work/<1\|3\|7>/<Difficulty>/<Category>/<num>.<slug>.{js,py}` | Solutions, e.g. `work/1/Easy/Array/1.two-sum.py`. The top directory is the spaced-repetition bucket: `1` = first solve, `3`/`7` = the blind re-solves of that problem 3 and 7 days later. Machine-parsed header comment (title / `Difficulty:` / URL / `─` rule / statement) — preserve its exact shape |
|
| `work/<1\|3\|7>/<Difficulty>/<Category>/<num>.<slug>.{js,py}` | Solutions, e.g. `work/1/Easy/Array/1.two-sum.py`. The top directory is the spaced-repetition bucket: `1` = first solve, `3`/`7` = the blind re-solves of that problem 3 and 7 days later. Machine-parsed header comment (title / `Difficulty:` / URL / `─` rule / statement) — preserve its exact shape |
|
||||||
| `apps/cli/` | Flat Bun TS workspace: automation entries (shebang + top-level await) and libraries (no shebang, side-effect-free on import). Root `bun run` scripts delegate here |
|
| `apps/cli/` | Flat Bun TS workspace: automation entries (shebang + top-level await) and libraries (no shebang, side-effect-free on import). Root `bun run` scripts delegate here |
|
||||||
| `apps/docs/` | Blume site (`blume.config.ts`, `content/`, `components/` + `components.ts`, `islands/`, `public/`). `content/(0N-<phase>)/<topic>/<num>-<slug>.mdx` plus one `meta.ts` per phase and per topic folder, all generated by sync: the phase folder is a `(group)` (sidebar section, no route segment), the topic folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment (`/tree-dfs/invert-binary-tree`), the `meta.ts` files carry the exact labels, phase/topic numbers and icons so the sidebar reads in curriculum order with each topic a collapsible group. A problem page's badge row (above the title) is rendered, not written: the `PageHeader` slot (`components/ProblemBadges.astro`, wired in `components.ts`) reads the page's `topics:` (LeetCode's topic tags, from sync) and `video:` (NeetCode YouTube id, from `bun run videos`) frontmatter — both declared via `frontmatter.extend` in `blume.config.ts`, hand-rolled Standard Schemas, no zod — and links a tag the campaign teaches to its topic landing page (accent), any other tag to its guide page `/guides/<slug>` (neutral; LeetCode's problem list if the guide is gone), and the video as a red `Explanation` badge. Which tags the campaign teaches is ONE map, `apps/docs/topic-landings.ts` (tag slug → landing routes), read by both that component and sync. `content/guides/<slug>.mdx` (the `Guides` header tab) is one page per tag outside that map: sync creates a placeholder when the file is missing or empty and never touches it again (hand-owned; a guide whose tag no page carries any more is left alone), with frontmatter `list:` — the tag's LeetCode problem list, rendered by the same slot as an orange `LeetCode list` badge; sync also writes `guides/meta.ts`. A hand-written `(0N-<phase>)/<topic>/index.mdx` (`hidden: true`) is that topic's landing page: Blume ≥ 1.7.3 links the sidebar group row to it (`sliding-window/index.mdx` → `/sliding-window`) and the hidden flag drops the duplicate row. Two kinds of custom MDX component, both import-free on every page: `islands/<Name>.tsx` is hydrated React (auto-discovered; `ProgressDashboard`, `DependencySpine` — the README topic map as data, zoom/pan, nodes link to whatever route Blume's `/api/docs/navigation.json` reports for the topic folder, inert until a topic has pages), `components/<Name>.astro` registered in `components.ts` is static markup plus a custom element for behaviour, the way Blume's own `Frame` is built (`Carousel`/`CarouselImage`: `src` takes a URL, `/file.png`, or the page-relative `../../public/file.png`) |
|
| `apps/docs/` | Blume site (`blume.config.ts`, `content/`, `components/` + `components.ts`, `islands/`, `public/`). `content/(0N-<phase>)/<topic>/<num>-<slug>.mdx` plus one `meta.ts` per phase and per topic folder, all generated by sync (a phase's once, then hand-owned): the phase folder is a `(group)` (sidebar section, no route segment), the topic folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment (`/tree-dfs/invert-binary-tree`), the `meta.ts` files carry the exact labels, phase/topic numbers and icons so the sidebar reads in curriculum order with each topic a collapsible group. A problem page's badge row (above the title) is rendered, not written: the `PageHeader` slot (`components/ProblemBadges.astro`, wired in `components.ts`) reads the page's `topics:` (LeetCode's topic tags, from sync) and `video:` (NeetCode YouTube id, from `bun run videos`) frontmatter — both declared via `frontmatter.extend` in `blume.config.ts`, hand-rolled Standard Schemas, no zod — and links a tag the campaign teaches to its topic landing page (accent), any other tag to its guide page `/guides/<slug>` (neutral; LeetCode's problem list if the guide is gone), and the video as a red `Explanation` badge. The reverse direction is the `PageFooter` slot (`components/BacklinksList.astro`): an Obsidian-style `<num> <name>` list under every topic landing and guide page of the problems whose badges link there, plus a topic's own folder members (so a topic no tag names, `hybrid-structures`, still lists its problems); problem pages are recognised by their `<num>. <name>` title and render no list. Which tags the campaign teaches is ONE map, `apps/docs/topic-landings.ts` (tag slug → landing routes), and where a badge points is ONE function beside it, `tagTarget()` — read by both components (so badges and backlinks cannot disagree) and the map by sync. `content/guides/<slug>.mdx` (the `Guides` header tab) is one page per tag outside that map: sync creates a placeholder when the file is missing or empty and never touches it again (hand-owned; a guide whose tag no page carries any more is left alone), with frontmatter `list:` — the tag's LeetCode problem list, rendered by the same slot as an orange `LeetCode list` badge; sync also writes `guides/meta.ts`. A hand-written `(0N-<phase>)/<topic>/index.mdx` (`hidden: true`) is that topic's landing page: Blume ≥ 1.7.3 links the sidebar group row to it (`sliding-window/index.mdx` → `/sliding-window`) and the hidden flag drops the duplicate row. Two kinds of custom MDX component, both import-free on every page: `islands/<Name>.tsx` is hydrated React (auto-discovered; `ProgressDashboard`, `DependencySpine` — the README topic map as data, zoom/pan, nodes link to whatever route Blume's `/api/docs/navigation.json` reports for the topic folder, inert until a topic has pages), `components/<Name>.astro` registered in `components.ts` is static markup plus a custom element for behaviour, the way Blume's own `Frame` is built (`Carousel`/`CarouselImage`: `src` takes a URL, `/file.png`, or the page-relative `../../public/file.png`) |
|
||||||
| `apps/api/` | Cloudflare Worker workspace: `src/` modules, `data/schedule.json` (the plan: `date → { topics | problems | gate | sweep }`, human-edited, bundled at deploy), `migrations/` (append-only; `0002` is the +3/+7 ladder rebuild) |
|
| `apps/api/` | Cloudflare Worker workspace: `src/` modules, `data/schedule.json` (the plan: `date → { topics | problems | gate | sweep }`, human-edited, bundled at deploy), `migrations/` (append-only; `0002` is the +3/+7 ladder rebuild) |
|
||||||
| `.github/workflows/` | deploy (docs → Pages with `GITHUB_TOKEN` for the releases-backed changelog, Worker → Cloudflare, on every main push), close-solved (issues + `/admin/solved` + Project `Set`/`Difficulty`, needs `SRS_ADMIN_KEY` and `PROJECT_PAT`; then the `release` job: timever tag + release per finished topic, `contents: write`), close-topics, sync-d1 (issue edits → `/admin/reconcile`), spaced-repetition (daily 06:00 ET cron → today's review issue; `GITHUB_TOKEN` only, no secrets) |
|
| `.github/workflows/` | deploy (docs → Pages with `GITHUB_TOKEN` for the releases-backed changelog, Worker → Cloudflare, on every main push), close-solved (issues + `/admin/solved` + Project `Set`/`Difficulty`, needs `SRS_ADMIN_KEY` and `PROJECT_PAT`; then the `release` job: timever tag + release per finished topic, `contents: write`), close-topics, sync-d1 (issue edits → `/admin/reconcile`), spaced-repetition (daily 06:00 ET cron → today's review issue; `GITHUB_TOKEN` only, no secrets) |
|
||||||
| `.githooks/` | Tracked git hooks; `bun install` points `core.hooksPath` here (root `prepare` script, no husky). `pre-commit` runs `bun run sync` and stages only what sync changed (pages, `apps/docs/redirects.ts`), so a solution, its page and its `/<num>` short link land in one commit; it refuses to run while `work/1` has anything unstaged (sync reads the tree, so the page would not match the commit), and leaves pre-existing dirt under `apps/docs/content` (in-progress `## Approach` prose) unstaged. `git commit --no-verify` for a deliberate partial commit |
|
| `.githooks/` | Tracked git hooks; `bun install` points `core.hooksPath` here (root `prepare` script, no husky). `pre-commit` runs `bun run sync` and stages only what sync changed (pages, `apps/docs/redirects.ts`), so a solution, its page and its `/<num>` short link land in one commit; it refuses to run while `work/1` has anything unstaged (sync reads the tree, so the page would not match the commit), and leaves pre-existing dirt under `apps/docs/content` (in-progress `## Approach` prose) unstaged. `git commit --no-verify` for a deliberate partial commit |
|
||||||
@@ -110,7 +110,7 @@ curl -X POST -H "Authorization: Bearer $LINK_KEY" \
|
|||||||
- `apps/api/src/index.ts` — router + cron dispatch; `apps/api/wrangler.jsonc` — bindings (`DB`, `EMAIL`), crons, vars; secrets `GH_PAT`/`WEBHOOK_SECRET`/`LINK_KEY` via `wrangler secret put`.
|
- `apps/api/src/index.ts` — router + cron dispatch; `apps/api/wrangler.jsonc` — bindings (`DB`, `EMAIL`), crons, vars; secrets `GH_PAT`/`WEBHOOK_SECRET`/`LINK_KEY` via `wrangler secret put`.
|
||||||
- `apps/api/src/digest.ts` / `apps/api/src/email.tsx` — the daily digest, split data/presentation. `digest.ts` reads D1 into a `DigestData`; `email.tsx` owns every colour and every sentence, and renders both the HTML and (via its own `plainDigest`, not React Email's `plainText` mode, which flattens the tables) the text alternative. The retrieval rules hold by construction: `DigestRow` has no title and no issue field, so a review or drill line *cannot* leak the topic or a solution link. Subject is `(Day N/56) LeetCode Daily Digest`.
|
- `apps/api/src/digest.ts` / `apps/api/src/email.tsx` — the daily digest, split data/presentation. `digest.ts` reads D1 into a `DigestData`; `email.tsx` owns every colour and every sentence, and renders both the HTML and (via its own `plainDigest`, not React Email's `plainText` mode, which flattens the tables) the text alternative. The retrieval rules hold by construction: `DigestRow` has no title and no issue field, so a review or drill line *cannot* leak the topic or a solution link. Subject is `(Day N/56) LeetCode Daily Digest`.
|
||||||
- `apps/api/src/png.ts` — hand-rolled PNG encoder (RGB8, one IDAT, zlib via `CompressionStream("deflate")`, CRC32, 5×7 bitmap font). It exists because every major email client refuses remote SVG, so `/chart/heatmap.png` rasters the heatmap for the digest while `/chart/heatmap.svg` keeps serving the README byte-for-byte. Both come from one `heatmapCells()` so the two pictures cannot drift.
|
- `apps/api/src/png.ts` — hand-rolled PNG encoder (RGB8, one IDAT, zlib via `CompressionStream("deflate")`, CRC32, 5×7 bitmap font). It exists because every major email client refuses remote SVG, so `/chart/heatmap.png` rasters the heatmap for the digest while `/chart/heatmap.svg` keeps serving the README byte-for-byte. Both come from one `heatmapCells()` so the two pictures cannot drift.
|
||||||
- `apps/cli/sync.ts` — work→docs contract: on existing pages only the frontmatter `icon:` and the `## Solution` section are regenerated, and `topics:` (LeetCode's `topicTags` as `{ name, slug }`, fetched by GraphQL) is filled once — on create, or on any run that finds it missing — never refreshed (delete the key to refetch; offline, the page is written without it and the next sync retries). `## Solution` is the latter bounded by `solutionEnd()` at the next unfenced top-level heading; human prose before it and any sections after it are never touched. Never hand-write solution pages or edit inside `## Solution`. Two classifications, two homes: the sidebar group is the README topic (`readmeTopics()`, first listing wins for a problem under two topics; a warm-up outside the plan groups under its category at `order: 0`, ahead of topic 1) and the `work/1/<Difficulty>/<Category>/` category, which only places an unplanned problem — the page's topic badges are LeetCode's tags, not either classification. Topics nest under their README phase (`## Phase N — Name` headings, shortened via `PHASE_ALIASES`): `content/(0N-<phase>)/<topic>/`, both `meta.ts` script-owned and rewritten every run (phase: `Phase N: Name`, `order: N`; topic: label, curriculum order, `TOPIC_ICONS` icon, `display: "group"`). The phase folder is a Blume `(group)` — no route segment, so `/tree-dfs` is unchanged and a landing page there is one level deep, never at `/` — and the topic folder is the real segment; never put a topic `index.mdx` directly inside a `(group)`. A page is `rename`d when its topic or phase changes; a folder every page has left is deleted (climbing into an emptied phase folder) unless it still holds a hand-written `index.mdx` (sync never writes, moves, or deletes one — so page-relative asset paths in a moved landing page are yours to fix). Slugs come from `slugify()`, which glues a leading number to the next token (`1d-dynamic-programming`) because Blume strips `<digits>-` prefixes from routes. The hand-owned parts have their own owners: `## Approach` (a Mermaid flowchart tracing the page's code, placed *before* `## Solution` so a re-sync keeps it) is written per the `problem-approach` skill (`/problem-approach` fills every page missing one); the frontmatter `video:` (the NeetCode walkthrough, rendered as the red `Explanation` badge) is added by `bun run videos --write` (`apps/cli/videos.ts`).
|
- `apps/cli/sync.ts` — work→docs contract: on existing pages only the frontmatter `icon:` and the `## Solution` section are regenerated, and `topics:` (LeetCode's `topicTags` as `{ name, slug }`, fetched by GraphQL) is filled once — on create, or on any run that finds it missing — never refreshed (delete the key to refetch; offline, the page is written without it and the next sync retries). `## Solution` is the latter bounded by `solutionEnd()` at the next unfenced top-level heading; human prose before it and any sections after it are never touched. Never hand-write solution pages or edit inside `## Solution`. Two classifications, two homes: the sidebar group is the README topic (`readmeTopics()`, first listing wins for a problem under two topics; a warm-up outside the plan groups under its category at `order: 0`, ahead of topic 1) and the `work/1/<Difficulty>/<Category>/` category, which only places an unplanned problem — the page's topic badges are LeetCode's tags, not either classification. Topics nest under their README phase (`## Phase N — Name` headings, shortened via `PHASE_ALIASES`): `content/(0N-<phase>)/<topic>/`. The topic `meta.ts` is script-owned and rewritten every run (label, curriculum order, `TOPIC_ICONS` icon, `display: "group"`); the phase `meta.ts` is written once when missing (`Phase N: Name`, `order: N`) and hand-owned after — rename its title freely. Never rewriting it loses nothing: its order and name are also the folder name, so a README change to either lands in a new folder with a fresh meta. The phase folder is a Blume `(group)` — no route segment, so `/tree-dfs` is unchanged and a landing page there is one level deep, never at `/` — and the topic folder is the real segment; never put a topic `index.mdx` directly inside a `(group)`. A page is `rename`d when its topic or phase changes; a folder every page has left is deleted (climbing into an emptied phase folder) unless it still holds a hand-written `index.mdx` (sync never writes, moves, or deletes one — so page-relative asset paths in a moved landing page are yours to fix). Slugs come from `slugify()`, which glues a leading number to the next token (`1d-dynamic-programming`) because Blume strips `<digits>-` prefixes from routes. The hand-owned parts have their own owners: `## Approach` (a Mermaid flowchart tracing the page's code, placed *before* `## Solution` so a re-sync keeps it) is written per the `problem-approach` skill (`/problem-approach` fills every page missing one); the frontmatter `video:` (the NeetCode walkthrough, rendered as the red `Explanation` badge) is added by `bun run videos --write` (`apps/cli/videos.ts`).
|
||||||
- `apps/cli/work.ts` — the `work/` layout: bucket paths, `isSolution()`/`solvedInBucket()` (the stub test, shared with `close-solved.ts`), `firstSolved()` (one `git log`, ET dates, keyed by LC number so past layout moves don't matter), and `reviewQueues()` (what each window still owes). Everything that needs to know where a solution file lives, or when it was solved, goes through here.
|
- `apps/cli/work.ts` — the `work/` layout: bucket paths, `isSolution()`/`solvedInBucket()` (the stub test, shared with `close-solved.ts`), `firstSolved()` (one `git log`, ET dates, keyed by LC number so past layout moves don't matter), and `reviewQueues()` (what each window still owes). Everything that needs to know where a solution file lives, or when it was solved, goes through here.
|
||||||
- `apps/api/src/stats.ts` / `apps/cli/stats.ts` / `apps/cli/tui.ts` / `apps/cli/api.ts` — the read side. `buildStats()` is the ONE aggregation: phases, ladder, difficulty, per-topic temperature, gates, the 14-day due queue, and `heat` (every campaign day, zeros and future days included — the docs page windows the tail off it rather than asking a second question). Two readers consume it, the docs island and `bun run stats`, so its shape is both their contract: change all three together. `tui.ts` is hand-rolled ANSI (bars, sparklines, heat cells, alternate screen) on charts.ts's palette, and every chart differs by GLYPH as well as by colour so a piped or `NO_COLOR` dashboard still reads. The TUI recomputes nothing: the only local fact it shows is the `work/` solution count, which is inventory, not schedule.
|
- `apps/api/src/stats.ts` / `apps/cli/stats.ts` / `apps/cli/tui.ts` / `apps/cli/api.ts` — the read side. `buildStats()` is the ONE aggregation: phases, ladder, difficulty, per-topic temperature, gates, the 14-day due queue, and `heat` (every campaign day, zeros and future days included — the docs page windows the tail off it rather than asking a second question). Two readers consume it, the docs island and `bun run stats`, so its shape is both their contract: change all three together. `tui.ts` is hand-rolled ANSI (bars, sparklines, heat cells, alternate screen) on charts.ts's palette, and every chart differs by GLYPH as well as by colour so a piped or `NO_COLOR` dashboard still reads. The TUI recomputes nothing: the only local fact it shows is the `work/` solution count, which is inventory, not schedule.
|
||||||
- `README.md` live charts are Worker endpoints (`/chart/*.svg`, `/badge/gate.svg`, 5-min Camo cache); the docs `/progress` page fetches `/api/stats` client-side (`apps/docs/islands/ProgressDashboard.tsx`).
|
- `README.md` live charts are Worker endpoints (`/chart/*.svg`, `/badge/gate.svg`, 5-min Camo cache); the docs `/progress` page fetches `/api/stats` client-side (`apps/docs/islands/ProgressDashboard.tsx`).
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
.DS_Store
|
|
||||||
.env
|
|
||||||
|
|
||||||
# Ignore the output video from Git but not videos you import into src/.
|
|
||||||
out
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"useTabs": false,
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"tabWidth": 2
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# 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).
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { config } from "@remotion/eslint-config-flat";
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "videos",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "My Remotion video",
|
|
||||||
"repository": {},
|
|
||||||
"license": "UNLICENSED",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@remotion/cli": "4.0.528",
|
|
||||||
"@remotion/zod-types": "4.0.528",
|
|
||||||
"react": "19.2.3",
|
|
||||||
"react-dom": "19.2.3",
|
|
||||||
"remotion": "4.0.528",
|
|
||||||
"zod": "4.5.4",
|
|
||||||
"@remotion/tailwind-v4": "4.0.528",
|
|
||||||
"tailwindcss": "4.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@remotion/eslint-config-flat": "4.0.528",
|
|
||||||
"@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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// See all configuration options: https://remotion.dev/docs/config
|
|
||||||
// Each option also is available as a CLI flag: https://remotion.dev/docs/cli
|
|
||||||
|
|
||||||
// Note: When using the Node.JS APIs, the config file doesn't apply. Instead, pass options directly to the APIs
|
|
||||||
|
|
||||||
import { Config } from "@remotion/cli/config";
|
|
||||||
import { enableTailwind } from '@remotion/tailwind-v4';
|
|
||||||
|
|
||||||
Config.setRspack(true);
|
|
||||||
Config.setVideoImageFormat("jpeg");
|
|
||||||
Config.setOverwriteOutput(true);
|
|
||||||
Config.overrideBundlerConfig(enableTailwind);
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import {
|
|
||||||
AbsoluteFill,
|
|
||||||
interpolate,
|
|
||||||
Sequence,
|
|
||||||
spring,
|
|
||||||
useCurrentFrame,
|
|
||||||
useVideoConfig,
|
|
||||||
} from "remotion";
|
|
||||||
import { Logo } from "./HelloWorld/Logo";
|
|
||||||
import { Subtitle } from "./HelloWorld/Subtitle";
|
|
||||||
import { Title } from "./HelloWorld/Title";
|
|
||||||
|
|
||||||
export type HelloWorldProps = {
|
|
||||||
readonly titleText: string;
|
|
||||||
readonly titleColor: string;
|
|
||||||
readonly logoColor1: string;
|
|
||||||
readonly logoColor2: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HelloWorld: React.FC<HelloWorldProps> = ({
|
|
||||||
titleText: propOne,
|
|
||||||
titleColor: propTwo,
|
|
||||||
logoColor1,
|
|
||||||
logoColor2,
|
|
||||||
}) => {
|
|
||||||
const frame = useCurrentFrame();
|
|
||||||
const { durationInFrames, fps } = useVideoConfig();
|
|
||||||
|
|
||||||
// Animate from 0 to 1 after 25 frames
|
|
||||||
const logoTranslationProgress = spring({
|
|
||||||
frame: frame - 25,
|
|
||||||
fps,
|
|
||||||
config: {
|
|
||||||
damping: 100,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Move the logo up by 150 pixels once the transition starts
|
|
||||||
const logoTranslation = interpolate(
|
|
||||||
logoTranslationProgress,
|
|
||||||
[0, 1],
|
|
||||||
[0, -150],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fade out the animation at the end
|
|
||||||
const opacity = interpolate(
|
|
||||||
frame,
|
|
||||||
[durationInFrames - 25, durationInFrames - 15],
|
|
||||||
[1, 0],
|
|
||||||
{
|
|
||||||
extrapolateLeft: "clamp",
|
|
||||||
extrapolateRight: "clamp",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// A <AbsoluteFill> is just a absolutely positioned <div>!
|
|
||||||
return (
|
|
||||||
<AbsoluteFill style={{ backgroundColor: "white" }}>
|
|
||||||
<AbsoluteFill style={{ opacity }}>
|
|
||||||
<AbsoluteFill style={{ transform: `translateY(${logoTranslation}px)` }}>
|
|
||||||
<Logo logoColor1={logoColor1} logoColor2={logoColor2} />
|
|
||||||
</AbsoluteFill>
|
|
||||||
{/* Sequences can shift the time for its children! */}
|
|
||||||
<Sequence from={35}>
|
|
||||||
<Title titleText={propOne} titleColor={propTwo} />
|
|
||||||
</Sequence>
|
|
||||||
{/* The subtitle will only enter on the 75th frame. */}
|
|
||||||
<Sequence from={75}>
|
|
||||||
<Subtitle />
|
|
||||||
</Sequence>
|
|
||||||
</AbsoluteFill>
|
|
||||||
</AbsoluteFill>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { random, useVideoConfig } from "remotion";
|
|
||||||
|
|
||||||
const getCircumferenceOfArc = (rx: number, ry: number) => {
|
|
||||||
return Math.PI * 2 * Math.sqrt((rx * rx + ry * ry) / 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
const rx = 135;
|
|
||||||
const ry = 300;
|
|
||||||
const cx = 960;
|
|
||||||
const cy = 540;
|
|
||||||
const arcLength = getCircumferenceOfArc(rx, ry);
|
|
||||||
const strokeWidth = 30;
|
|
||||||
|
|
||||||
export const Arc: React.FC<{
|
|
||||||
progress: number;
|
|
||||||
rotation: number;
|
|
||||||
rotateProgress: number;
|
|
||||||
color1: string;
|
|
||||||
color2: string;
|
|
||||||
}> = ({ progress, rotation, rotateProgress, color1, color2 }) => {
|
|
||||||
const { width, height } = useVideoConfig();
|
|
||||||
|
|
||||||
// Each svg Id must be unique to not conflict with each other
|
|
||||||
const [gradientId] = useState(() => String(random(null)));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
viewBox={`0 0 ${width} ${height}`}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
transform: `rotate(${rotation * rotateProgress}deg)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id={gradientId} x1="0%" y1="0%" x2="0%" y2="100%">
|
|
||||||
<stop offset="0%" stopColor={color1} />
|
|
||||||
<stop offset="100%" stopColor={color2} />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<ellipse
|
|
||||||
cx={cx}
|
|
||||||
cy={cy}
|
|
||||||
rx={rx}
|
|
||||||
ry={ry}
|
|
||||||
fill="none"
|
|
||||||
stroke={`url(#${gradientId})`}
|
|
||||||
strokeDasharray={arcLength}
|
|
||||||
strokeDashoffset={arcLength - arcLength * progress}
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeWidth={strokeWidth}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { random, useVideoConfig } from "remotion";
|
|
||||||
|
|
||||||
export const Atom: React.FC<{
|
|
||||||
scale: number;
|
|
||||||
color1: string;
|
|
||||||
color2: string;
|
|
||||||
}> = ({ scale, color1, color2 }) => {
|
|
||||||
const config = useVideoConfig();
|
|
||||||
|
|
||||||
// Each SVG ID must be unique to not conflict with each other
|
|
||||||
const [gradientId] = useState(() => String(random(null)));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
viewBox={`0 0 ${config.width} ${config.height}`}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
transform: `scale(${scale})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="0%">
|
|
||||||
<stop offset="0%" stopColor={color1} />
|
|
||||||
<stop offset="100%" stopColor={color2} />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<circle
|
|
||||||
r={70}
|
|
||||||
cx={config.width / 2}
|
|
||||||
cy={config.height / 2}
|
|
||||||
fill={`url(#${gradientId})`}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import {
|
|
||||||
AbsoluteFill,
|
|
||||||
interpolate,
|
|
||||||
spring,
|
|
||||||
useCurrentFrame,
|
|
||||||
useVideoConfig,
|
|
||||||
} from "remotion";
|
|
||||||
import { Arc } from "./Arc";
|
|
||||||
import { Atom } from "./Atom";
|
|
||||||
|
|
||||||
export type LogoProps = {
|
|
||||||
readonly logoColor1: string;
|
|
||||||
readonly logoColor2: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Logo: React.FC<LogoProps> = ({
|
|
||||||
logoColor1: color1,
|
|
||||||
logoColor2: color2,
|
|
||||||
}) => {
|
|
||||||
const videoConfig = useVideoConfig();
|
|
||||||
const frame = useCurrentFrame();
|
|
||||||
|
|
||||||
const development = spring({
|
|
||||||
config: {
|
|
||||||
damping: 100,
|
|
||||||
mass: 0.5,
|
|
||||||
},
|
|
||||||
fps: videoConfig.fps,
|
|
||||||
frame,
|
|
||||||
});
|
|
||||||
|
|
||||||
const rotationDevelopment = spring({
|
|
||||||
config: {
|
|
||||||
damping: 100,
|
|
||||||
mass: 0.5,
|
|
||||||
},
|
|
||||||
fps: videoConfig.fps,
|
|
||||||
frame,
|
|
||||||
});
|
|
||||||
|
|
||||||
const scale = spring({
|
|
||||||
frame,
|
|
||||||
config: {
|
|
||||||
mass: 0.5,
|
|
||||||
},
|
|
||||||
fps: videoConfig.fps,
|
|
||||||
});
|
|
||||||
|
|
||||||
const logoRotation = interpolate(
|
|
||||||
frame,
|
|
||||||
[0, videoConfig.durationInFrames],
|
|
||||||
[0, 360],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AbsoluteFill
|
|
||||||
style={{
|
|
||||||
transform: `scale(${scale}) rotate(${logoRotation}deg)`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Arc
|
|
||||||
rotateProgress={rotationDevelopment}
|
|
||||||
progress={development}
|
|
||||||
rotation={30}
|
|
||||||
color1={color1}
|
|
||||||
color2={color2}
|
|
||||||
/>
|
|
||||||
<Arc
|
|
||||||
rotateProgress={rotationDevelopment}
|
|
||||||
rotation={90}
|
|
||||||
progress={development}
|
|
||||||
color1={color1}
|
|
||||||
color2={color2}
|
|
||||||
/>
|
|
||||||
<Arc
|
|
||||||
rotateProgress={rotationDevelopment}
|
|
||||||
rotation={-30}
|
|
||||||
progress={development}
|
|
||||||
color1={color1}
|
|
||||||
color2={color2}
|
|
||||||
/>
|
|
||||||
<Atom scale={rotationDevelopment} color1={color1} color2={color2} />
|
|
||||||
</AbsoluteFill>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { interpolate, useCurrentFrame } from "remotion";
|
|
||||||
import { COLOR_1, FONT_FAMILY } from "./constants";
|
|
||||||
|
|
||||||
const subtitle: React.CSSProperties = {
|
|
||||||
fontFamily: FONT_FAMILY,
|
|
||||||
fontSize: 40,
|
|
||||||
textAlign: "center",
|
|
||||||
position: "absolute",
|
|
||||||
bottom: 140,
|
|
||||||
width: "100%",
|
|
||||||
};
|
|
||||||
|
|
||||||
const codeStyle: React.CSSProperties = {
|
|
||||||
color: COLOR_1,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Subtitle: React.FC = () => {
|
|
||||||
const frame = useCurrentFrame();
|
|
||||||
const opacity = interpolate(frame, [0, 30], [0, 1]);
|
|
||||||
return (
|
|
||||||
<div style={{ ...subtitle, opacity }}>
|
|
||||||
Edit <code style={codeStyle}>src/Root.tsx</code> and save to reload.
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { spring, useCurrentFrame, useVideoConfig } from "remotion";
|
|
||||||
import { FONT_FAMILY } from "./constants";
|
|
||||||
|
|
||||||
const title: React.CSSProperties = {
|
|
||||||
fontFamily: FONT_FAMILY,
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontSize: 100,
|
|
||||||
textAlign: "center",
|
|
||||||
position: "absolute",
|
|
||||||
bottom: 160,
|
|
||||||
width: "100%",
|
|
||||||
};
|
|
||||||
|
|
||||||
const word: React.CSSProperties = {
|
|
||||||
marginLeft: 10,
|
|
||||||
marginRight: 10,
|
|
||||||
display: "inline-block",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Title: React.FC<{
|
|
||||||
readonly titleText: string;
|
|
||||||
readonly titleColor: string;
|
|
||||||
}> = ({ titleText, titleColor }) => {
|
|
||||||
const videoConfig = useVideoConfig();
|
|
||||||
const frame = useCurrentFrame();
|
|
||||||
|
|
||||||
const words = titleText.split(" ");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<h1 style={title}>
|
|
||||||
{words.map((t, i) => {
|
|
||||||
const delay = i * 5;
|
|
||||||
|
|
||||||
const scale = spring({
|
|
||||||
fps: videoConfig.fps,
|
|
||||||
frame: frame - delay,
|
|
||||||
config: {
|
|
||||||
damping: 200,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
key={t}
|
|
||||||
style={{
|
|
||||||
...word,
|
|
||||||
color: titleColor,
|
|
||||||
transform: `scale(${scale})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</h1>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Change any of these to update your video live.
|
|
||||||
|
|
||||||
export const COLOR_1 = "#86A8E7";
|
|
||||||
|
|
||||||
export const FONT_FAMILY = "SF Pro Text, Helvetica, Arial, sans-serif";
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import "./index.css";
|
|
||||||
import { Composition } from "remotion";
|
|
||||||
import { HelloWorld } from "./HelloWorld";
|
|
||||||
import { Logo } from "./HelloWorld/Logo";
|
|
||||||
|
|
||||||
// Each <Composition> is an entry in the sidebar!
|
|
||||||
|
|
||||||
export const RemotionRoot: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Composition
|
|
||||||
// You can take the "id" to render a video:
|
|
||||||
// npx remotion render HelloWorld
|
|
||||||
id="HelloWorld"
|
|
||||||
component={HelloWorld}
|
|
||||||
durationInFrames={150}
|
|
||||||
fps={30}
|
|
||||||
width={1920}
|
|
||||||
height={1080}
|
|
||||||
// You can override these props for each render:
|
|
||||||
// https://www.remotion.dev/docs/parametrized-rendering
|
|
||||||
defaultProps={{
|
|
||||||
titleText: "Welcome to Remotion",
|
|
||||||
titleColor: "#000000",
|
|
||||||
logoColor1: "#91EAE4",
|
|
||||||
logoColor2: "#86A8E7",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Mount any React component to make it show up in the sidebar and work on it individually! */}
|
|
||||||
<Composition
|
|
||||||
id="OnlyLogo"
|
|
||||||
component={Logo}
|
|
||||||
durationInFrames={150}
|
|
||||||
fps={30}
|
|
||||||
width={1920}
|
|
||||||
height={1080}
|
|
||||||
defaultProps={{
|
|
||||||
logoColor1: "#91dAE2",
|
|
||||||
logoColor2: "#86A8E7",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// This is your entry file! Refer to it when you render:
|
|
||||||
// npx remotion render <entry-file> HelloWorld out/video.mp4
|
|
||||||
|
|
||||||
import { registerRoot } from "remotion";
|
|
||||||
import { RemotionRoot } from "./Root";
|
|
||||||
|
|
||||||
registerRoot(RemotionRoot);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"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"]
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user