mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2026-01-28 07:43:18 +00:00
🎬 Add PortalPresentation skeleton composition: implement main composition structure with 9 scene sequences for the 90-second cinematic Portal presentation video.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
224
videos/src/PortalPresentation.tsx
Normal file
224
videos/src/PortalPresentation.tsx
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
import { AbsoluteFill, Sequence, useVideoConfig, Img, staticFile } from "remotion";
|
||||||
|
import { inconsolataFont } from "./fonts/inconsolata";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PortalPresentation - Main composition for the Einundzwanzig Portal presentation video
|
||||||
|
*
|
||||||
|
* Scene Structure (90 seconds total @ 30fps = 2700 frames):
|
||||||
|
* 1. Logo Reveal (6s) - Frames 0-180
|
||||||
|
* 2. Portal Title (4s) - Frames 180-300
|
||||||
|
* 3. Dashboard Overview (12s) - Frames 300-660
|
||||||
|
* 4. Meine Meetups (12s) - Frames 660-1020
|
||||||
|
* 5. Top Länder (12s) - Frames 1020-1380
|
||||||
|
* 6. Top Meetups (10s) - Frames 1380-1680
|
||||||
|
* 7. Activity Feed (10s) - Frames 1680-1980
|
||||||
|
* 8. Call to Action (12s) - Frames 1980-2340
|
||||||
|
* 9. Outro (12s) - Frames 2340-2700
|
||||||
|
*/
|
||||||
|
export const PortalPresentation: React.FC = () => {
|
||||||
|
const { fps } = useVideoConfig();
|
||||||
|
|
||||||
|
// Scene durations in seconds
|
||||||
|
const SCENE_DURATIONS = {
|
||||||
|
logoReveal: 6,
|
||||||
|
portalTitle: 4,
|
||||||
|
dashboardOverview: 12,
|
||||||
|
meineMeetups: 12,
|
||||||
|
topLaender: 12,
|
||||||
|
topMeetups: 10,
|
||||||
|
activityFeed: 10,
|
||||||
|
callToAction: 12,
|
||||||
|
outro: 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate frame positions for each scene
|
||||||
|
const sceneFrames = {
|
||||||
|
logoReveal: { from: 0, duration: SCENE_DURATIONS.logoReveal * fps },
|
||||||
|
portalTitle: {
|
||||||
|
from: SCENE_DURATIONS.logoReveal * fps,
|
||||||
|
duration: SCENE_DURATIONS.portalTitle * fps,
|
||||||
|
},
|
||||||
|
dashboardOverview: {
|
||||||
|
from: (SCENE_DURATIONS.logoReveal + SCENE_DURATIONS.portalTitle) * fps,
|
||||||
|
duration: SCENE_DURATIONS.dashboardOverview * fps,
|
||||||
|
},
|
||||||
|
meineMeetups: {
|
||||||
|
from:
|
||||||
|
(SCENE_DURATIONS.logoReveal +
|
||||||
|
SCENE_DURATIONS.portalTitle +
|
||||||
|
SCENE_DURATIONS.dashboardOverview) *
|
||||||
|
fps,
|
||||||
|
duration: SCENE_DURATIONS.meineMeetups * fps,
|
||||||
|
},
|
||||||
|
topLaender: {
|
||||||
|
from:
|
||||||
|
(SCENE_DURATIONS.logoReveal +
|
||||||
|
SCENE_DURATIONS.portalTitle +
|
||||||
|
SCENE_DURATIONS.dashboardOverview +
|
||||||
|
SCENE_DURATIONS.meineMeetups) *
|
||||||
|
fps,
|
||||||
|
duration: SCENE_DURATIONS.topLaender * fps,
|
||||||
|
},
|
||||||
|
topMeetups: {
|
||||||
|
from:
|
||||||
|
(SCENE_DURATIONS.logoReveal +
|
||||||
|
SCENE_DURATIONS.portalTitle +
|
||||||
|
SCENE_DURATIONS.dashboardOverview +
|
||||||
|
SCENE_DURATIONS.meineMeetups +
|
||||||
|
SCENE_DURATIONS.topLaender) *
|
||||||
|
fps,
|
||||||
|
duration: SCENE_DURATIONS.topMeetups * fps,
|
||||||
|
},
|
||||||
|
activityFeed: {
|
||||||
|
from:
|
||||||
|
(SCENE_DURATIONS.logoReveal +
|
||||||
|
SCENE_DURATIONS.portalTitle +
|
||||||
|
SCENE_DURATIONS.dashboardOverview +
|
||||||
|
SCENE_DURATIONS.meineMeetups +
|
||||||
|
SCENE_DURATIONS.topLaender +
|
||||||
|
SCENE_DURATIONS.topMeetups) *
|
||||||
|
fps,
|
||||||
|
duration: SCENE_DURATIONS.activityFeed * fps,
|
||||||
|
},
|
||||||
|
callToAction: {
|
||||||
|
from:
|
||||||
|
(SCENE_DURATIONS.logoReveal +
|
||||||
|
SCENE_DURATIONS.portalTitle +
|
||||||
|
SCENE_DURATIONS.dashboardOverview +
|
||||||
|
SCENE_DURATIONS.meineMeetups +
|
||||||
|
SCENE_DURATIONS.topLaender +
|
||||||
|
SCENE_DURATIONS.topMeetups +
|
||||||
|
SCENE_DURATIONS.activityFeed) *
|
||||||
|
fps,
|
||||||
|
duration: SCENE_DURATIONS.callToAction * fps,
|
||||||
|
},
|
||||||
|
outro: {
|
||||||
|
from:
|
||||||
|
(SCENE_DURATIONS.logoReveal +
|
||||||
|
SCENE_DURATIONS.portalTitle +
|
||||||
|
SCENE_DURATIONS.dashboardOverview +
|
||||||
|
SCENE_DURATIONS.meineMeetups +
|
||||||
|
SCENE_DURATIONS.topLaender +
|
||||||
|
SCENE_DURATIONS.topMeetups +
|
||||||
|
SCENE_DURATIONS.activityFeed +
|
||||||
|
SCENE_DURATIONS.callToAction) *
|
||||||
|
fps,
|
||||||
|
duration: SCENE_DURATIONS.outro * fps,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbsoluteFill
|
||||||
|
className="bg-gradient-to-br from-zinc-900 to-zinc-800"
|
||||||
|
style={{ fontFamily: inconsolataFont }}
|
||||||
|
>
|
||||||
|
{/* Wallpaper Background */}
|
||||||
|
<Img
|
||||||
|
src={staticFile("einundzwanzig-wallpaper.png")}
|
||||||
|
className="absolute inset-0 w-full h-full object-cover opacity-20"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Scene 1: Logo Reveal (6s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.logoReveal.from}
|
||||||
|
durationInFrames={sceneFrames.logoReveal.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Logo Reveal" sceneNumber={1} />
|
||||||
|
</Sequence>
|
||||||
|
|
||||||
|
{/* Scene 2: Portal Title (4s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.portalTitle.from}
|
||||||
|
durationInFrames={sceneFrames.portalTitle.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Portal Title" sceneNumber={2} />
|
||||||
|
</Sequence>
|
||||||
|
|
||||||
|
{/* Scene 3: Dashboard Overview (12s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.dashboardOverview.from}
|
||||||
|
durationInFrames={sceneFrames.dashboardOverview.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Dashboard Overview" sceneNumber={3} />
|
||||||
|
</Sequence>
|
||||||
|
|
||||||
|
{/* Scene 4: Meine Meetups (12s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.meineMeetups.from}
|
||||||
|
durationInFrames={sceneFrames.meineMeetups.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Meine Meetups" sceneNumber={4} />
|
||||||
|
</Sequence>
|
||||||
|
|
||||||
|
{/* Scene 5: Top Länder (12s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.topLaender.from}
|
||||||
|
durationInFrames={sceneFrames.topLaender.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Top Länder" sceneNumber={5} />
|
||||||
|
</Sequence>
|
||||||
|
|
||||||
|
{/* Scene 6: Top Meetups (10s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.topMeetups.from}
|
||||||
|
durationInFrames={sceneFrames.topMeetups.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Top Meetups" sceneNumber={6} />
|
||||||
|
</Sequence>
|
||||||
|
|
||||||
|
{/* Scene 7: Activity Feed (10s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.activityFeed.from}
|
||||||
|
durationInFrames={sceneFrames.activityFeed.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Activity Feed" sceneNumber={7} />
|
||||||
|
</Sequence>
|
||||||
|
|
||||||
|
{/* Scene 8: Call to Action (12s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.callToAction.from}
|
||||||
|
durationInFrames={sceneFrames.callToAction.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Call to Action" sceneNumber={8} />
|
||||||
|
</Sequence>
|
||||||
|
|
||||||
|
{/* Scene 9: Outro (12s) */}
|
||||||
|
<Sequence
|
||||||
|
from={sceneFrames.outro.from}
|
||||||
|
durationInFrames={sceneFrames.outro.duration}
|
||||||
|
premountFor={fps}
|
||||||
|
>
|
||||||
|
<PlaceholderScene name="Outro" sceneNumber={9} />
|
||||||
|
</Sequence>
|
||||||
|
</AbsoluteFill>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder component for scenes that haven't been implemented yet.
|
||||||
|
* Displays a centered scene name with visual indicators.
|
||||||
|
*/
|
||||||
|
const PlaceholderScene: React.FC<{ name: string; sceneNumber: number }> = ({
|
||||||
|
name,
|
||||||
|
sceneNumber,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<AbsoluteFill className="flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-24 h-24 mx-auto mb-6 rounded-full bg-orange-500/20 border-2 border-orange-500/50 flex items-center justify-center">
|
||||||
|
<span className="text-4xl font-bold text-orange-500">{sceneNumber}</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-4xl font-bold text-white mb-2">{name}</h2>
|
||||||
|
<p className="text-lg text-neutral-400">Scene placeholder</p>
|
||||||
|
</div>
|
||||||
|
</AbsoluteFill>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { Composition } from "remotion";
|
|||||||
import { MyComposition } from "./Composition";
|
import { MyComposition } from "./Composition";
|
||||||
import { Nip05Tutorial } from "./Nip05Tutorial";
|
import { Nip05Tutorial } from "./Nip05Tutorial";
|
||||||
import { Nip05TutorialMobile } from "./Nip05TutorialMobile";
|
import { Nip05TutorialMobile } from "./Nip05TutorialMobile";
|
||||||
|
import { PortalPresentation } from "./PortalPresentation";
|
||||||
|
|
||||||
export const RemotionRoot: React.FC = () => {
|
export const RemotionRoot: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
@@ -31,6 +32,14 @@ export const RemotionRoot: React.FC = () => {
|
|||||||
width={1080}
|
width={1080}
|
||||||
height={1920}
|
height={1920}
|
||||||
/>
|
/>
|
||||||
|
<Composition
|
||||||
|
id="PortalPresentation"
|
||||||
|
component={PortalPresentation}
|
||||||
|
durationInFrames={90 * 30}
|
||||||
|
fps={30}
|
||||||
|
width={1920}
|
||||||
|
height={1080}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user