mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2026-01-28 07:43:18 +00:00
🎨 Add new Flux icons: implement multiple reusable icon components (e.g., hand-raised, hand-thumb-up, heart, hashtag, home) with variant support for improved UI consistency.
This commit is contained in:
3
videos/src/Composition.tsx
Normal file
3
videos/src/Composition.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export const MyComposition = () => {
|
||||
return null;
|
||||
};
|
||||
50
videos/src/Nip05Tutorial.tsx
Normal file
50
videos/src/Nip05Tutorial.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { AbsoluteFill, Sequence, useVideoConfig } from "remotion";
|
||||
import { IntroScene } from "./scenes/IntroScene";
|
||||
import { UIShowcaseScene } from "./scenes/UIShowcaseScene";
|
||||
import { InputDemoScene } from "./scenes/InputDemoScene";
|
||||
import { SaveButtonScene } from "./scenes/SaveButtonScene";
|
||||
import { VerificationScene } from "./scenes/VerificationScene";
|
||||
import { OutroScene } from "./scenes/OutroScene";
|
||||
import { AudioManager } from "./components/AudioManager";
|
||||
import { inconsolataFont } from "./fonts/inconsolata";
|
||||
|
||||
export const Nip05Tutorial: React.FC = () => {
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
return (
|
||||
<AbsoluteFill className="bg-gradient-to-br from-zinc-900 to-zinc-800" style={{ fontFamily: inconsolataFont }}>
|
||||
{/* Audio for entire video */}
|
||||
<AudioManager />
|
||||
|
||||
{/* Intro - 12 seconds (extended with registration and payment steps) */}
|
||||
<Sequence durationInFrames={12 * fps} premountFor={fps}>
|
||||
<IntroScene />
|
||||
</Sequence>
|
||||
|
||||
{/* UI Showcase - 6 seconds */}
|
||||
<Sequence from={12 * fps} durationInFrames={6 * fps} premountFor={fps}>
|
||||
<UIShowcaseScene />
|
||||
</Sequence>
|
||||
|
||||
{/* Input Demo - 8 seconds */}
|
||||
<Sequence from={18 * fps} durationInFrames={8 * fps} premountFor={fps}>
|
||||
<InputDemoScene />
|
||||
</Sequence>
|
||||
|
||||
{/* Save Button - 5 seconds */}
|
||||
<Sequence from={26 * fps} durationInFrames={5 * fps} premountFor={fps}>
|
||||
<SaveButtonScene />
|
||||
</Sequence>
|
||||
|
||||
{/* Verification - 5 seconds */}
|
||||
<Sequence from={31 * fps} durationInFrames={5 * fps} premountFor={fps}>
|
||||
<VerificationScene />
|
||||
</Sequence>
|
||||
|
||||
{/* Outro - 20 seconds */}
|
||||
<Sequence from={36 * fps} durationInFrames={20 * fps} premountFor={fps}>
|
||||
<OutroScene />
|
||||
</Sequence>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
50
videos/src/Nip05TutorialMobile.tsx
Normal file
50
videos/src/Nip05TutorialMobile.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { AbsoluteFill, Sequence, useVideoConfig } from "remotion";
|
||||
import { IntroSceneMobile } from "./scenes/mobile/IntroSceneMobile";
|
||||
import { UIShowcaseSceneMobile } from "./scenes/mobile/UIShowcaseSceneMobile";
|
||||
import { InputDemoSceneMobile } from "./scenes/mobile/InputDemoSceneMobile";
|
||||
import { SaveButtonSceneMobile } from "./scenes/mobile/SaveButtonSceneMobile";
|
||||
import { VerificationSceneMobile } from "./scenes/mobile/VerificationSceneMobile";
|
||||
import { OutroSceneMobile } from "./scenes/mobile/OutroSceneMobile";
|
||||
import { AudioManager } from "./components/AudioManager";
|
||||
import { inconsolataFont } from "./fonts/inconsolata";
|
||||
|
||||
export const Nip05TutorialMobile: React.FC = () => {
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
return (
|
||||
<AbsoluteFill className="bg-gradient-to-br from-zinc-900 to-zinc-800" style={{ fontFamily: inconsolataFont }}>
|
||||
{/* Audio for entire video */}
|
||||
<AudioManager />
|
||||
|
||||
{/* Intro - 12 seconds (extended with registration and payment steps) */}
|
||||
<Sequence durationInFrames={12 * fps} premountFor={fps}>
|
||||
<IntroSceneMobile />
|
||||
</Sequence>
|
||||
|
||||
{/* UI Showcase - 6 seconds */}
|
||||
<Sequence from={12 * fps} durationInFrames={6 * fps} premountFor={fps}>
|
||||
<UIShowcaseSceneMobile />
|
||||
</Sequence>
|
||||
|
||||
{/* Input Demo - 8 seconds */}
|
||||
<Sequence from={18 * fps} durationInFrames={8 * fps} premountFor={fps}>
|
||||
<InputDemoSceneMobile />
|
||||
</Sequence>
|
||||
|
||||
{/* Save Button - 5 seconds */}
|
||||
<Sequence from={26 * fps} durationInFrames={5 * fps} premountFor={fps}>
|
||||
<SaveButtonSceneMobile />
|
||||
</Sequence>
|
||||
|
||||
{/* Verification - 5 seconds */}
|
||||
<Sequence from={31 * fps} durationInFrames={5 * fps} premountFor={fps}>
|
||||
<VerificationSceneMobile />
|
||||
</Sequence>
|
||||
|
||||
{/* Outro - 20 seconds */}
|
||||
<Sequence from={36 * fps} durationInFrames={20 * fps} premountFor={fps}>
|
||||
<OutroSceneMobile />
|
||||
</Sequence>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
36
videos/src/Root.tsx
Normal file
36
videos/src/Root.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import "./index.css";
|
||||
import { Composition } from "remotion";
|
||||
import { MyComposition } from "./Composition";
|
||||
import { Nip05Tutorial } from "./Nip05Tutorial";
|
||||
import { Nip05TutorialMobile } from "./Nip05TutorialMobile";
|
||||
|
||||
export const RemotionRoot: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Composition
|
||||
id="MyComp"
|
||||
component={MyComposition}
|
||||
durationInFrames={60}
|
||||
fps={30}
|
||||
width={1280}
|
||||
height={720}
|
||||
/>
|
||||
<Composition
|
||||
id="Nip05Tutorial"
|
||||
component={Nip05Tutorial}
|
||||
durationInFrames={56 * 30}
|
||||
fps={30}
|
||||
width={1920}
|
||||
height={1080}
|
||||
/>
|
||||
<Composition
|
||||
id="Nip05TutorialMobile"
|
||||
component={Nip05TutorialMobile}
|
||||
durationInFrames={56 * 30}
|
||||
fps={30}
|
||||
width={1080}
|
||||
height={1920}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
181
videos/src/components/AnimatedLogo.tsx
Normal file
181
videos/src/components/AnimatedLogo.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import { useCurrentFrame, useVideoConfig, interpolate, spring, Img, staticFile } from "remotion";
|
||||
|
||||
interface AnimatedLogoProps {
|
||||
delay?: number;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
// 3D Bitcoin Logo with layered depth effect - transparent version
|
||||
const Bitcoin3DLogo: React.FC<{
|
||||
size: number;
|
||||
rotationY: number;
|
||||
scale: number;
|
||||
tiltX: number;
|
||||
}> = ({ size, rotationY, scale, tiltX }) => {
|
||||
// Number of layers for 3D depth effect
|
||||
const layers = 6;
|
||||
const layerDepth = 2;
|
||||
|
||||
// Calculate perspective shift based on rotation
|
||||
const perspectiveShiftX = Math.sin(rotationY) * 15;
|
||||
const perspectiveShiftY = Math.sin(tiltX) * 8;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative"
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
transform: `scale(${scale})`,
|
||||
transformStyle: "preserve-3d",
|
||||
perspective: "500px",
|
||||
opacity: 0.35, // Make the entire 3D Bitcoin very transparent
|
||||
}}
|
||||
>
|
||||
{/* Shadow layers (back) */}
|
||||
{Array.from({ length: layers }).map((_, i) => {
|
||||
const layerOffset = (layers - i) * layerDepth;
|
||||
const darkness = 0.4 + (i / layers) * 0.3;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`shadow-${i}`}
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
style={{
|
||||
transform: `translateX(${perspectiveShiftX + layerOffset * 0.5}px) translateY(${perspectiveShiftY + layerOffset * 0.3}px) translateZ(${-layerOffset}px)`,
|
||||
filter: `brightness(${darkness})`,
|
||||
opacity: i === 0 ? 0.5 : 0.1,
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={staticFile("bitcoin-logo.svg")}
|
||||
style={{
|
||||
width: size * 0.85,
|
||||
height: size * 0.85,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Main Bitcoin logo (front) */}
|
||||
<div
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
style={{
|
||||
transform: `translateX(${perspectiveShiftX}px) translateY(${perspectiveShiftY}px) translateZ(0px)`,
|
||||
filter: "drop-shadow(3px 3px 6px rgba(0, 0, 0, 0.3))",
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={staticFile("bitcoin-logo.svg")}
|
||||
style={{
|
||||
width: size * 0.85,
|
||||
height: size * 0.85,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Highlight layer (front-most) */}
|
||||
<div
|
||||
className="absolute inset-0 flex items-center justify-center pointer-events-none"
|
||||
style={{
|
||||
transform: `translateX(${perspectiveShiftX - 2}px) translateY(${perspectiveShiftY - 2}px) translateZ(5px)`,
|
||||
opacity: 0.2,
|
||||
filter: "brightness(1.5) blur(1px)",
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={staticFile("bitcoin-logo.svg")}
|
||||
style={{
|
||||
width: size * 0.85,
|
||||
height: size * 0.85,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AnimatedLogo: React.FC<AnimatedLogoProps> = ({ delay = 0, size = 200 }) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
// Main logo animation
|
||||
const logoSpring = spring({
|
||||
frame: frame - delay,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 80 },
|
||||
});
|
||||
|
||||
const logoScale = interpolate(logoSpring, [0, 1], [0, 1]);
|
||||
const logoRotation = interpolate(logoSpring, [0, 1], [0, 360]);
|
||||
|
||||
// Floating animation
|
||||
const floatY = interpolate(
|
||||
Math.sin((frame - delay) * 0.05),
|
||||
[-1, 1],
|
||||
[-10, 10]
|
||||
);
|
||||
|
||||
// Glow pulse
|
||||
const glowSpring = spring({
|
||||
frame: frame - delay,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const glowOpacity = interpolate(
|
||||
Math.sin((frame - delay) * 0.1),
|
||||
[-1, 1],
|
||||
[0.3, 0.7]
|
||||
);
|
||||
|
||||
// 3D rotation for Bitcoin logo
|
||||
const bitcoinRotationY = (frame - delay) * 0.04;
|
||||
const bitcoinTiltX = Math.sin((frame - delay) * 0.03) * 0.3;
|
||||
|
||||
return (
|
||||
<div className="relative" style={{ width: size, height: size }}>
|
||||
{/* Glow Effect */}
|
||||
<div
|
||||
className="absolute inset-0 blur-2xl"
|
||||
style={{
|
||||
opacity: glowOpacity * glowSpring,
|
||||
background: "radial-gradient(circle, #f7931a 0%, transparent 70%)",
|
||||
transform: `scale(${logoScale * 1.3})`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 3D Bitcoin Logo */}
|
||||
<div
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
style={{
|
||||
transform: `translateY(${floatY}px)`,
|
||||
}}
|
||||
>
|
||||
<Bitcoin3DLogo
|
||||
size={size}
|
||||
rotationY={bitcoinRotationY}
|
||||
scale={logoScale}
|
||||
tiltX={bitcoinTiltX}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* EINUNDZWANZIG Logo overlay */}
|
||||
<div
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
style={{
|
||||
transform: `scale(${logoScale}) rotate(${logoRotation}deg) translateY(${floatY}px)`,
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-square-inverted.svg")}
|
||||
style={{
|
||||
width: size * 0.45,
|
||||
height: size * 0.45,
|
||||
filter: "drop-shadow(0 0 15px rgba(247, 147, 26, 0.6))",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
182
videos/src/components/AudioManager.tsx
Normal file
182
videos/src/components/AudioManager.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { Audio } from "@remotion/media";
|
||||
import { Sequence, staticFile, useVideoConfig, useCurrentFrame, interpolate } from "remotion";
|
||||
|
||||
/**
|
||||
* AudioManager Component
|
||||
*
|
||||
* Manages all audio for the video including:
|
||||
* - Background music with fade-in/fade-out
|
||||
* - Sound effects for each scene
|
||||
*
|
||||
* Audio files should be placed in public/ folder
|
||||
*/
|
||||
|
||||
const BackgroundMusic: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, durationInFrames } = useVideoConfig();
|
||||
|
||||
// Fade-in for 1 second (30 frames at 30fps)
|
||||
const fadeInDuration = 1 * fps;
|
||||
// Fade-out for 3 seconds (90 frames at 30fps)
|
||||
const fadeOutDuration = 3 * fps;
|
||||
const fadeOutStart = durationInFrames - fadeOutDuration;
|
||||
|
||||
// Base volume
|
||||
const baseVolume = 0.3;
|
||||
|
||||
// Calculate volume with fade-in and fade-out
|
||||
let volume = baseVolume;
|
||||
|
||||
// Fade-in at the beginning
|
||||
if (frame < fadeInDuration) {
|
||||
volume = interpolate(frame, [0, fadeInDuration], [0, baseVolume], {
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
}
|
||||
// Fade-out at the end
|
||||
else if (frame >= fadeOutStart) {
|
||||
volume = interpolate(
|
||||
frame,
|
||||
[fadeOutStart, durationInFrames],
|
||||
[baseVolume, 0],
|
||||
{
|
||||
extrapolateLeft: "clamp",
|
||||
extrapolateRight: "clamp",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Audio src={staticFile("music/background-music.mp3")} volume={volume} loop />
|
||||
);
|
||||
};
|
||||
|
||||
export const AudioManager: React.FC = () => {
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Background Music - plays throughout the entire video with fade-in (1s) and fade-out (3s) */}
|
||||
<BackgroundMusic />
|
||||
|
||||
{/* ===== INTRO SCENE (0-12s) ===== */}
|
||||
|
||||
{/* Logo reveal chime */}
|
||||
<Sequence from={0.5 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/logo-reveal.mp3")}
|
||||
volume={0.5}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* Step 2 Lightning appears */}
|
||||
<Sequence from={5 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/ui-appear.mp3")}
|
||||
volume={0.4}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* Step 3 NIP-05 appears */}
|
||||
<Sequence from={7.5 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/success-chime.mp3")}
|
||||
volume={0.3}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* Call to action */}
|
||||
<Sequence from={9.5 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/ui-appear.mp3")}
|
||||
volume={0.3}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* ===== UI SHOWCASE SCENE (12-18s) ===== */}
|
||||
|
||||
{/* UI appear chime */}
|
||||
<Sequence from={12.5 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/ui-appear.mp3")}
|
||||
volume={0.3}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* ===== INPUT DEMO SCENE (18-26s) ===== */}
|
||||
|
||||
{/* Typing sound effect - plays during typing animation */}
|
||||
<Sequence from={18 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/typing.mp3")}
|
||||
volume={0.4}
|
||||
trimAfter={2 * fps} // Only play for 2 seconds
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
|
||||
{/* ===== SAVE BUTTON SCENE (26-31s) ===== */}
|
||||
|
||||
{/* Button hover/focus */}
|
||||
<Sequence from={27.5 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/button-hover.mp3")}
|
||||
volume={0.3}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* Button click */}
|
||||
<Sequence from={28 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/button-click.mp3")}
|
||||
volume={0.6}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* Success notification */}
|
||||
<Sequence from={28.5 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/success-chime.mp3")}
|
||||
volume={0.5}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* ===== VERIFICATION SCENE (31-36s) ===== */}
|
||||
|
||||
{/* Badge appear */}
|
||||
<Sequence from={31.5 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/badge-appear.mp3")}
|
||||
volume={0.4}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* Checkmark pop */}
|
||||
<Sequence from={33 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/checkmark-pop.mp3")}
|
||||
volume={0.3}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* ===== OUTRO SCENE (36-56s) ===== */}
|
||||
|
||||
{/* URL emphasis - pulsing sound */}
|
||||
<Sequence from={38 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/url-emphasis.mp3")}
|
||||
volume={0.5}
|
||||
/>
|
||||
</Sequence>
|
||||
|
||||
{/* Final chime */}
|
||||
<Sequence from={44 * fps}>
|
||||
<Audio
|
||||
src={staticFile("sfx/final-chime.mp3")}
|
||||
volume={0.4}
|
||||
/>
|
||||
</Sequence>
|
||||
</>
|
||||
);
|
||||
};
|
||||
70
videos/src/components/BitcoinEffect.tsx
Normal file
70
videos/src/components/BitcoinEffect.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";
|
||||
|
||||
interface BitcoinParticle {
|
||||
id: number;
|
||||
startX: number;
|
||||
startY: number;
|
||||
speed: number;
|
||||
size: number;
|
||||
rotation: number;
|
||||
rotationSpeed: number;
|
||||
}
|
||||
|
||||
export const BitcoinEffect: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { width, height } = useVideoConfig();
|
||||
|
||||
// Generate particles
|
||||
const particles: BitcoinParticle[] = Array.from({ length: 15 }, (_, i) => ({
|
||||
id: i,
|
||||
startX: (i * 137.5) % width, // Golden ratio distribution
|
||||
startY: -100 - (i * 80),
|
||||
speed: 2 + (i % 3) * 0.5,
|
||||
size: 30 + (i % 4) * 15,
|
||||
rotation: i * 30,
|
||||
rotationSpeed: 0.5 + (i % 3) * 0.3,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 pointer-events-none overflow-hidden">
|
||||
{particles.map((particle) => {
|
||||
const y = particle.startY + frame * particle.speed;
|
||||
const adjustedY = y % (height + 200);
|
||||
const opacity = interpolate(
|
||||
adjustedY,
|
||||
[0, 100, height - 100, height],
|
||||
[0, 0.3, 0.3, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
const rotation = particle.rotation + frame * particle.rotationSpeed;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={particle.id}
|
||||
className="absolute"
|
||||
style={{
|
||||
left: particle.startX,
|
||||
top: adjustedY,
|
||||
opacity,
|
||||
transform: `rotate(${rotation}deg)`,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width={particle.size}
|
||||
height={particle.size}
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="16" cy="16" r="16" fill="#f7931a" />
|
||||
<path
|
||||
d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.113-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.085-.745-.17-1.103-.258l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
6
videos/src/fonts/inconsolata.ts
Normal file
6
videos/src/fonts/inconsolata.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { loadFont } from "@remotion/google-fonts/Inconsolata";
|
||||
|
||||
// Load Inconsolata font with all weights
|
||||
const { fontFamily } = loadFont();
|
||||
|
||||
export const inconsolataFont = fontFamily;
|
||||
6
videos/src/index.css
Normal file
6
videos/src/index.css
Normal file
@@ -0,0 +1,6 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Set Inconsolata as the default font for everything */
|
||||
* {
|
||||
font-family: "Inconsolata", monospace;
|
||||
}
|
||||
4
videos/src/index.ts
Normal file
4
videos/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { registerRoot } from "remotion";
|
||||
import { RemotionRoot } from "./Root";
|
||||
|
||||
registerRoot(RemotionRoot);
|
||||
128
videos/src/scenes/InputDemoScene.tsx
Normal file
128
videos/src/scenes/InputDemoScene.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, Img, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../components/AnimatedLogo";
|
||||
|
||||
export const InputDemoScene: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
// Typing animation
|
||||
const typingText = "satoshi21";
|
||||
const typingProgress = interpolate(frame, [0, 2 * fps], [0, typingText.length], {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const currentText = typingText.slice(0, Math.floor(typingProgress));
|
||||
|
||||
// Cursor blink
|
||||
const cursorBlink = Math.floor(frame / 15) % 2 === 0;
|
||||
|
||||
// Card entrance
|
||||
const cardSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const cardY = interpolate(cardSpring, [0, 1], [100, 0]);
|
||||
const cardOpacity = interpolate(cardSpring, [0, 1], [0, 1]);
|
||||
|
||||
|
||||
// Rules box appears after typing
|
||||
const rulesSpring = spring({
|
||||
frame: frame - 2.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const rulesOpacity = interpolate(rulesSpring, [0, 1], [0, 1]);
|
||||
const rulesY = interpolate(rulesSpring, [0, 1], [30, 0]);
|
||||
|
||||
// Pointer animation
|
||||
const pointerSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const pointerX = interpolate(pointerSpring, [0, 1], [-100, 0]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Wallpaper Background */}
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-wallpaper.png")}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/75" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Animated EINUNDZWANZIG Logo */}
|
||||
<div className="absolute top-20 left-20">
|
||||
<AnimatedLogo size={250} delay={0.5 * fps} />
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-12">
|
||||
<h2 className="text-5xl font-bold text-white mb-16 text-center">
|
||||
Schritt 1: Handle eingeben
|
||||
</h2>
|
||||
|
||||
{/* Input Card */}
|
||||
<div
|
||||
className="bg-white rounded-2xl p-10 shadow-2xl max-w-3xl w-full relative"
|
||||
style={{
|
||||
opacity: cardOpacity,
|
||||
transform: `translateY(${cardY}px)`,
|
||||
}}
|
||||
>
|
||||
{/* Animated Pointer */}
|
||||
<div
|
||||
className="absolute -left-20 top-1/2 -translate-y-1/2 text-6xl"
|
||||
style={{
|
||||
transform: `translateX(${pointerX}px) translateY(-50%)`,
|
||||
}}
|
||||
>
|
||||
👉
|
||||
</div>
|
||||
|
||||
<label className="block text-xl font-medium text-neutral-700 mb-4">
|
||||
Dein NIP-05 Handle
|
||||
</label>
|
||||
<div className="flex items-center bg-white rounded-lg border-4 border-neutral-800 px-6 py-4 mb-6">
|
||||
<div className="flex-1 text-2xl text-neutral-800">
|
||||
{currentText}
|
||||
{cursorBlink && frame < 2.5 * fps && <span className="text-neutral-800">|</span>}
|
||||
</div>
|
||||
<div className="text-xl text-neutral-600 font-medium">@einundzwanzig.space</div>
|
||||
</div>
|
||||
|
||||
{/* Rules Box */}
|
||||
<div
|
||||
className="bg-neutral-100 rounded-lg border-2 border-neutral-300 p-5"
|
||||
style={{
|
||||
opacity: rulesOpacity,
|
||||
transform: `translateY(${rulesY}px)`,
|
||||
}}
|
||||
>
|
||||
<p className="text-sm text-neutral-700 font-medium mb-2">
|
||||
✅ Regeln für dein Handle:
|
||||
</p>
|
||||
<ul className="text-sm text-neutral-600 space-y-1">
|
||||
<li>• Nur Kleinbuchstaben (a-z)</li>
|
||||
<li>• Zahlen (0-9)</li>
|
||||
<li>• Zeichen: "-" und "_"</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info text */}
|
||||
<p
|
||||
className="text-neutral-300 text-center mt-8 text-lg max-w-2xl"
|
||||
style={{
|
||||
opacity: rulesOpacity,
|
||||
}}
|
||||
>
|
||||
Dein Handle wird automatisch kleingeschrieben und muss einzigartig sein.
|
||||
</p>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
330
videos/src/scenes/IntroScene.tsx
Normal file
330
videos/src/scenes/IntroScene.tsx
Normal file
@@ -0,0 +1,330 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, Img, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../components/AnimatedLogo";
|
||||
|
||||
// Lightning Payment Animation Component
|
||||
const LightningPaymentAnimation: React.FC<{ frame: number; fps: number; startFrame: number }> = ({
|
||||
frame,
|
||||
fps,
|
||||
startFrame,
|
||||
}) => {
|
||||
const relativeFrame = frame - startFrame;
|
||||
|
||||
// Only show after step 2 appears
|
||||
if (relativeFrame < 0.5 * fps) return null;
|
||||
|
||||
// Lightning bolt travel animation
|
||||
const boltProgress = interpolate(
|
||||
relativeFrame,
|
||||
[0.5 * fps, 1.5 * fps],
|
||||
[0, 1],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
|
||||
// Flash effect when payment completes
|
||||
const flashOpacity = interpolate(
|
||||
relativeFrame,
|
||||
[1.4 * fps, 1.5 * fps, 1.8 * fps],
|
||||
[0, 0.8, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
|
||||
// Success checkmark appears after flash
|
||||
const checkSpring = spring({
|
||||
frame: relativeFrame - 1.6 * fps,
|
||||
fps,
|
||||
config: { damping: 12, stiffness: 150 },
|
||||
});
|
||||
const checkScale = interpolate(checkSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Bolt position along the path
|
||||
const boltX = interpolate(boltProgress, [0, 1], [-60, 60]);
|
||||
const boltY = interpolate(boltProgress, [0, 0.5, 1], [0, -15, 0]);
|
||||
|
||||
// Bolt wobble
|
||||
const boltRotation = interpolate(
|
||||
Math.sin(relativeFrame * 0.4),
|
||||
[-1, 1],
|
||||
[-15, 15]
|
||||
);
|
||||
|
||||
// Bolt scale pulse
|
||||
const boltScale = interpolate(
|
||||
Math.sin(relativeFrame * 0.3),
|
||||
[-1, 1],
|
||||
[1, 1.3]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
{/* Lightning bolt traveling */}
|
||||
{boltProgress < 1 && (
|
||||
<div
|
||||
className="absolute text-4xl"
|
||||
style={{
|
||||
transform: `translateX(${boltX}px) translateY(${boltY}px) rotate(${boltRotation}deg) scale(${boltScale})`,
|
||||
filter: "drop-shadow(0 0 10px #f7931a) drop-shadow(0 0 20px #ffcc00)",
|
||||
}}
|
||||
>
|
||||
⚡
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Flash effect */}
|
||||
<div
|
||||
className="absolute inset-0 bg-yellow-400"
|
||||
style={{ opacity: flashOpacity }}
|
||||
/>
|
||||
|
||||
{/* Success indicator */}
|
||||
{checkScale > 0 && (
|
||||
<div
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-green-500 flex items-center justify-center"
|
||||
style={{
|
||||
transform: `translateY(-50%) scale(${checkScale})`,
|
||||
boxShadow: "0 0 15px rgba(34, 197, 94, 0.6)",
|
||||
}}
|
||||
>
|
||||
<span className="text-white text-xl font-bold">✓</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const IntroScene: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
// Logo entrance
|
||||
const logoSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const logoOpacity = interpolate(logoSpring, [0, 1], [0, 1]);
|
||||
const logoScale = interpolate(logoSpring, [0, 1], [0.5, 1]);
|
||||
|
||||
// Main title
|
||||
const titleSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const titleOpacity = interpolate(titleSpring, [0, 1], [0, 1]);
|
||||
const titleY = interpolate(titleSpring, [0, 1], [30, 0]);
|
||||
|
||||
// Subtitle
|
||||
const subtitleSpring = spring({
|
||||
frame: frame - 1 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const subtitleOpacity = interpolate(subtitleSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Step 1: Registration
|
||||
const step1Spring = spring({
|
||||
frame: frame - 2 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const step1Opacity = interpolate(step1Spring, [0, 1], [0, 1]);
|
||||
const step1X = interpolate(step1Spring, [0, 1], [-100, 0]);
|
||||
|
||||
// Step 2: Lightning Payment
|
||||
const step2Spring = spring({
|
||||
frame: frame - 4.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const step2Opacity = interpolate(step2Spring, [0, 1], [0, 1]);
|
||||
const step2X = interpolate(step2Spring, [0, 1], [100, 0]);
|
||||
|
||||
// Step 3: NIP-05 Benefit
|
||||
const step3Spring = spring({
|
||||
frame: frame - 7.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const step3Opacity = interpolate(step3Spring, [0, 1], [0, 1]);
|
||||
const step3Y = interpolate(step3Spring, [0, 1], [50, 0]);
|
||||
|
||||
// Arrow animations connecting steps
|
||||
const arrow1Spring = spring({
|
||||
frame: frame - 3.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const arrow1Opacity = interpolate(arrow1Spring, [0, 1], [0, 1]);
|
||||
|
||||
const arrow2Spring = spring({
|
||||
frame: frame - 7 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const arrow2Opacity = interpolate(arrow2Spring, [0, 1], [0, 1]);
|
||||
|
||||
// Final call to action
|
||||
const ctaSpring = spring({
|
||||
frame: frame - 9.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const ctaOpacity = interpolate(ctaSpring, [0, 1], [0, 1]);
|
||||
const ctaScale = interpolate(ctaSpring, [0, 1], [0.9, 1]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Wallpaper Background */}
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-wallpaper.png")}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-900/75" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Header with Logo and Title - Top Left */}
|
||||
<div className="absolute top-6 left-8 flex items-center gap-4">
|
||||
<div
|
||||
style={{
|
||||
opacity: logoOpacity,
|
||||
transform: `scale(${logoScale})`,
|
||||
}}
|
||||
>
|
||||
<AnimatedLogo size={80} delay={0} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
opacity: titleOpacity,
|
||||
transform: `translateY(${titleY}px)`,
|
||||
}}
|
||||
>
|
||||
<h1 className="text-3xl font-bold text-white">
|
||||
NIP-05 Verifikation
|
||||
</h1>
|
||||
<p
|
||||
className="text-base text-neutral-300"
|
||||
style={{ opacity: subtitleOpacity }}
|
||||
>
|
||||
So erhältst du deine verifizierte Nostr-Identität
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Steps Container - Larger Steps */}
|
||||
<div className="absolute top-[18%] left-0 right-0 px-8">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
{/* Step 1: Registration */}
|
||||
<div
|
||||
className="flex items-center gap-5 mb-4"
|
||||
style={{
|
||||
opacity: step1Opacity,
|
||||
transform: `translateX(${step1X}px)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0 w-16 h-16 rounded-full bg-orange-500 flex items-center justify-center border-4 border-orange-400 shadow-xl">
|
||||
<span className="text-3xl font-bold text-white">1</span>
|
||||
</div>
|
||||
<div className="flex-1 bg-white/10 backdrop-blur-sm rounded-2xl p-5 border-2 border-white/20 shadow-lg">
|
||||
<h3 className="text-2xl font-bold text-white mb-2 flex items-center gap-3">
|
||||
<span className="text-3xl">📝</span>
|
||||
Registrierung im Verein
|
||||
</h3>
|
||||
<p className="text-lg text-neutral-200">
|
||||
Werde Mitglied bei EINUNDZWANZIG und erhalte Zugang zu allen Vorteilen
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow 1 */}
|
||||
<div
|
||||
className="flex justify-center my-2"
|
||||
style={{ opacity: arrow1Opacity }}
|
||||
>
|
||||
<div className="text-4xl text-orange-500">↓</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2: Lightning Payment */}
|
||||
<div
|
||||
className="flex items-center gap-5 mb-4"
|
||||
style={{
|
||||
opacity: step2Opacity,
|
||||
transform: `translateX(${step2X}px)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0 w-16 h-16 rounded-full bg-orange-500 flex items-center justify-center border-4 border-orange-400 shadow-xl">
|
||||
<span className="text-3xl font-bold text-white">2</span>
|
||||
</div>
|
||||
<div className="flex-1 bg-white/10 backdrop-blur-sm rounded-2xl p-5 border-2 border-white/20 shadow-lg relative overflow-hidden">
|
||||
{/* Lightning glow effect */}
|
||||
<div
|
||||
className="absolute top-0 right-0 w-32 h-32 blur-3xl opacity-50"
|
||||
style={{
|
||||
background: "radial-gradient(circle, #f7931a 0%, transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
<h3 className="text-2xl font-bold text-white mb-2 flex items-center gap-3 relative z-10">
|
||||
<span className="text-3xl">⚡</span>
|
||||
Mitgliedsbeitrag via Lightning
|
||||
</h3>
|
||||
<p className="text-lg text-neutral-200 relative z-10">
|
||||
Zahle deinen Mitgliedsbeitrag schnell und einfach mit Bitcoin Lightning
|
||||
</p>
|
||||
{/* Lightning Payment Animation */}
|
||||
<LightningPaymentAnimation frame={frame} fps={fps} startFrame={4.5 * fps} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow 2 */}
|
||||
<div
|
||||
className="flex justify-center my-2"
|
||||
style={{ opacity: arrow2Opacity }}
|
||||
>
|
||||
<div className="text-4xl text-orange-500">↓</div>
|
||||
</div>
|
||||
|
||||
{/* Step 3: NIP-05 Benefit */}
|
||||
<div
|
||||
className="flex items-center gap-5"
|
||||
style={{
|
||||
opacity: step3Opacity,
|
||||
transform: `translateY(${step3Y}px)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0 w-16 h-16 rounded-full bg-orange-500 flex items-center justify-center border-4 border-orange-400 shadow-xl">
|
||||
<span className="text-3xl font-bold text-white">3</span>
|
||||
</div>
|
||||
<div className="flex-1 bg-gradient-to-r from-orange-500/20 to-orange-600/20 backdrop-blur-sm rounded-2xl p-5 border-2 border-orange-400 shadow-lg">
|
||||
<h3 className="text-2xl font-bold text-white mb-2 flex items-center gap-3">
|
||||
<span className="text-3xl">✓</span>
|
||||
NIP-05 Handle erstellen
|
||||
</h3>
|
||||
<p className="text-lg text-neutral-200">
|
||||
Erstelle dein persönliches NIP-05 Handle und verifiziere deine Nostr-Identität
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Call to Action */}
|
||||
<div
|
||||
className="absolute bottom-6 left-0 right-0 text-center"
|
||||
style={{
|
||||
opacity: ctaOpacity,
|
||||
transform: `scale(${ctaScale})`,
|
||||
}}
|
||||
>
|
||||
<p className="text-xl text-white font-bold mb-1">
|
||||
Los geht's!
|
||||
</p>
|
||||
<p className="text-base text-neutral-300">
|
||||
Im nächsten Video zeigen wir dir Schritt 3 im Detail
|
||||
</p>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
154
videos/src/scenes/OutroScene.tsx
Normal file
154
videos/src/scenes/OutroScene.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, Img, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../components/AnimatedLogo";
|
||||
|
||||
export const OutroScene: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
// Logo animation at top
|
||||
const logoSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const logoOpacity = interpolate(logoSpring, [0, 1], [0, 1]);
|
||||
const logoY = interpolate(logoSpring, [0, 1], [-50, 0]);
|
||||
|
||||
// Main SVG Logo
|
||||
const svgSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const svgScale = interpolate(svgSpring, [0, 1], [0.8, 1]);
|
||||
const svgOpacity = interpolate(svgSpring, [0, 1], [0, 1]);
|
||||
|
||||
// URL Animation - delayed and prominent
|
||||
const urlSpring = spring({
|
||||
frame: frame - 2 * fps,
|
||||
fps,
|
||||
config: { damping: 20, stiffness: 100 },
|
||||
});
|
||||
const urlScale = interpolate(urlSpring, [0, 1], [0.5, 1]);
|
||||
const urlOpacity = interpolate(urlSpring, [0, 1], [0, 1]);
|
||||
|
||||
// URL pulsing effect
|
||||
const urlPulse = interpolate(
|
||||
Math.sin((frame - 2 * fps) * 0.05),
|
||||
[-1, 1],
|
||||
[1, 1.05]
|
||||
);
|
||||
|
||||
// Call to action
|
||||
const ctaSpring = spring({
|
||||
frame: frame - 1 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const ctaOpacity = interpolate(ctaSpring, [0, 1], [0, 1]);
|
||||
const ctaY = interpolate(ctaSpring, [0, 1], [30, 0]);
|
||||
|
||||
// Footer appears last
|
||||
const footerSpring = spring({
|
||||
frame: frame - 4 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const footerOpacity = interpolate(footerSpring, [0, 1], [0, 1]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Wallpaper Background */}
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-wallpaper.png")}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/85" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Animated Logo Top */}
|
||||
<div
|
||||
className="absolute top-16 left-1/2 -translate-x-1/2"
|
||||
style={{
|
||||
opacity: logoOpacity,
|
||||
transform: `translateX(-50%) translateY(${logoY}px)`,
|
||||
}}
|
||||
>
|
||||
<AnimatedLogo size={180} delay={0} />
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-12">
|
||||
{/* EINUNDZWANZIG Logo SVG */}
|
||||
<div
|
||||
className="mb-16"
|
||||
style={{
|
||||
opacity: svgOpacity,
|
||||
transform: `scale(${svgScale})`,
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-horizontal-inverted.svg")}
|
||||
style={{ width: 700, height: "auto" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Call to Action Text */}
|
||||
<div
|
||||
style={{
|
||||
opacity: ctaOpacity,
|
||||
transform: `translateY(${ctaY}px)`,
|
||||
}}
|
||||
>
|
||||
<p className="text-4xl text-white text-center font-semibold mb-12">
|
||||
Werde jetzt Mitglied!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* URL - MAIN FOCUS - Very Large and Prominent */}
|
||||
<div
|
||||
className="relative"
|
||||
style={{
|
||||
opacity: urlOpacity,
|
||||
transform: `scale(${urlScale * urlPulse})`,
|
||||
}}
|
||||
>
|
||||
{/* Glow effect behind URL */}
|
||||
<div
|
||||
className="absolute inset-0 blur-3xl"
|
||||
style={{
|
||||
background: "radial-gradient(ellipse, #f7931a 0%, transparent 70%)",
|
||||
opacity: 0.4,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* URL Container with border */}
|
||||
<div className="relative bg-neutral-800/80 backdrop-blur-sm rounded-3xl px-20 py-12 border-4 border-orange-500 shadow-2xl">
|
||||
<p className="text-7xl text-white text-center font-bold tracking-wide">
|
||||
verein.einundzwanzig.space
|
||||
</p>
|
||||
|
||||
{/* Accent line */}
|
||||
<div className="h-2 bg-gradient-to-r from-transparent via-orange-500 to-transparent mt-6 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Info */}
|
||||
<div
|
||||
className="absolute bottom-20 left-0 right-0 text-center"
|
||||
style={{ opacity: footerOpacity }}
|
||||
>
|
||||
<p className="text-neutral-300 text-2xl mb-3 font-semibold">
|
||||
Mitglieder-Vorteile:
|
||||
</p>
|
||||
<p className="text-neutral-400 text-xl">
|
||||
🔗 Nostr Relay • ✓ NIP-05 • ⚡ Lightning Watchtower
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
128
videos/src/scenes/SaveButtonScene.tsx
Normal file
128
videos/src/scenes/SaveButtonScene.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, Img, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../components/AnimatedLogo";
|
||||
|
||||
export const SaveButtonScene: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const titleSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const titleOpacity = interpolate(titleSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Button entrance
|
||||
const buttonSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const buttonScale = interpolate(buttonSpring, [0, 1], [0.5, 1]);
|
||||
const buttonOpacity = interpolate(buttonSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Click animation
|
||||
const clickFrame = 2 * fps;
|
||||
const clickSpring = spring({
|
||||
frame: frame - clickFrame,
|
||||
fps,
|
||||
config: { damping: 10, stiffness: 200 },
|
||||
durationInFrames: 0.3 * fps,
|
||||
});
|
||||
const buttonPressScale = frame >= clickFrame ? interpolate(clickSpring, [0, 1], [0.95, 1]) : 1;
|
||||
|
||||
// Success animation
|
||||
const successSpring = spring({
|
||||
frame: frame - (clickFrame + 0.5 * fps),
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const successOpacity = interpolate(successSpring, [0, 1], [0, 1]);
|
||||
const successScale = interpolate(successSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Cursor pointer
|
||||
const cursorSpring = spring({
|
||||
frame: frame - 1.5 * fps,
|
||||
fps,
|
||||
config: { damping: 20, stiffness: 150 },
|
||||
});
|
||||
const cursorX = interpolate(cursorSpring, [0, 1], [-200, 0]);
|
||||
const cursorY = interpolate(cursorSpring, [0, 1], [100, 0]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Wallpaper Background */}
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-wallpaper.png")}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/70" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-12">
|
||||
<h2
|
||||
className="text-5xl font-bold text-white mb-20 text-center"
|
||||
style={{ opacity: titleOpacity }}
|
||||
>
|
||||
Schritt 2: Handle speichern
|
||||
</h2>
|
||||
|
||||
{/* Button Container */}
|
||||
<div className="relative">
|
||||
{/* Animated Cursor */}
|
||||
<div
|
||||
className="absolute text-5xl pointer-events-none z-10"
|
||||
style={{
|
||||
left: "50%",
|
||||
top: "50%",
|
||||
transform: `translate(calc(-50% + ${cursorX}px), calc(-50% + ${cursorY}px))`,
|
||||
}}
|
||||
>
|
||||
👆
|
||||
</div>
|
||||
|
||||
{/* Save Button */}
|
||||
<button
|
||||
className="bg-neutral-800 text-white font-bold text-3xl px-16 py-8 rounded-xl shadow-2xl border-4 border-neutral-600"
|
||||
style={{
|
||||
opacity: buttonOpacity,
|
||||
transform: `scale(${buttonScale * buttonPressScale})`,
|
||||
}}
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
{/* Success Message */}
|
||||
{frame >= clickFrame + 0.5 * fps && (
|
||||
<div
|
||||
className="absolute -bottom-24 left-1/2 -translate-x-1/2 whitespace-nowrap"
|
||||
style={{
|
||||
opacity: successOpacity,
|
||||
transform: `translateX(-50%) scale(${successScale})`,
|
||||
}}
|
||||
>
|
||||
<div className="bg-neutral-800 text-white px-8 py-4 rounded-lg shadow-xl flex items-center gap-4 border-2 border-neutral-600">
|
||||
<span className="text-2xl">✓</span>
|
||||
<span className="text-xl font-semibold">NIP-05 Handle gespeichert!</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 3D Success Logo */}
|
||||
{frame >= clickFrame + 0.5 * fps && (
|
||||
<div className="absolute top-20 right-20">
|
||||
<AnimatedLogo size={280} delay={clickFrame + 0.5 * fps} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-neutral-300 text-center mt-32 text-xl max-w-2xl" style={{ opacity: titleOpacity }}>
|
||||
Nach dem Speichern wird dein Handle in Kürze automatisch aktiviert.
|
||||
</p>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
114
videos/src/scenes/UIShowcaseScene.tsx
Normal file
114
videos/src/scenes/UIShowcaseScene.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, Img, staticFile } from "remotion";
|
||||
import { ThreeCanvas } from "@remotion/three";
|
||||
import { BitcoinEffect } from "../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../components/AnimatedLogo";
|
||||
|
||||
export const UIShowcaseScene: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, width, height } = useVideoConfig();
|
||||
|
||||
const cardSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 20, stiffness: 200 },
|
||||
});
|
||||
|
||||
const cardScale = interpolate(cardSpring, [0, 1], [0.8, 1]);
|
||||
const cardOpacity = interpolate(cardSpring, [0, 1], [0, 1]);
|
||||
|
||||
// 3D background elements
|
||||
const bgRotation = frame * 0.01;
|
||||
|
||||
const titleSpring = spring({
|
||||
frame: frame - 0.3 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const titleY = interpolate(titleSpring, [0, 1], [50, 0]);
|
||||
const titleOpacity = interpolate(titleSpring, [0, 1], [0, 1]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Wallpaper Background */}
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-wallpaper.png")}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/80" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Animated Logos in corners */}
|
||||
<div className="absolute top-10 left-10 opacity-50">
|
||||
<AnimatedLogo size={150} delay={0.2 * fps} />
|
||||
</div>
|
||||
<div className="absolute top-10 right-10 opacity-50">
|
||||
<AnimatedLogo size={150} delay={0.4 * fps} />
|
||||
</div>
|
||||
|
||||
{/* 3D Background */}
|
||||
<div className="absolute inset-0 opacity-20">
|
||||
<ThreeCanvas width={width} height={height}>
|
||||
<ambientLight intensity={0.3} />
|
||||
<directionalLight position={[5, 5, 5]} intensity={0.6} />
|
||||
<mesh rotation={[0, bgRotation, 0]} position={[-3, 0, 0]}>
|
||||
<sphereGeometry args={[1.5, 32, 32]} />
|
||||
<meshStandardMaterial color="#f7931a" wireframe />
|
||||
</mesh>
|
||||
<mesh rotation={[0, -bgRotation, 0]} position={[3, 0, 0]}>
|
||||
<octahedronGeometry args={[1.5]} />
|
||||
<meshStandardMaterial color="#f7931a" wireframe />
|
||||
</mesh>
|
||||
</ThreeCanvas>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-20">
|
||||
<h2
|
||||
className="text-5xl font-bold text-white mb-12 text-center"
|
||||
style={{
|
||||
opacity: titleOpacity,
|
||||
transform: `translateY(${titleY}px)`,
|
||||
}}
|
||||
>
|
||||
Die NIP-05 Oberfläche
|
||||
</h2>
|
||||
|
||||
{/* UI Card Mockup */}
|
||||
<div
|
||||
className="bg-neutral-50 rounded-xl p-8 border-4 border-neutral-200 shadow-2xl max-w-2xl w-full"
|
||||
style={{
|
||||
opacity: cardOpacity,
|
||||
transform: `scale(${cardScale})`,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-4 mb-6">
|
||||
<div className="w-12 h-12 rounded-full bg-neutral-800 flex items-center justify-center flex-shrink-0">
|
||||
<div className="text-white text-2xl">✓</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-2xl font-semibold text-neutral-800 mb-2">
|
||||
Get NIP-05 verified
|
||||
</h3>
|
||||
<p className="text-base text-neutral-600">
|
||||
Verifiziere deine Identität mit einem menschenlesbaren Nostr-Namen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input Preview */}
|
||||
<div className="space-y-3">
|
||||
<label className="block text-sm font-medium text-neutral-700">
|
||||
Dein NIP-05 Handle
|
||||
</label>
|
||||
<div className="flex items-center bg-white rounded-lg border-2 border-neutral-300 px-4 py-3">
|
||||
<div className="flex-1 text-neutral-400">dein-name</div>
|
||||
<div className="text-neutral-600 font-medium">@einundzwanzig.space</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
125
videos/src/scenes/VerificationScene.tsx
Normal file
125
videos/src/scenes/VerificationScene.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, Img, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../components/AnimatedLogo";
|
||||
|
||||
export const VerificationScene: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
const titleSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const titleOpacity = interpolate(titleSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Badge entrance
|
||||
const badgeSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 8 },
|
||||
});
|
||||
const badgeScale = interpolate(badgeSpring, [0, 1], [0, 1]);
|
||||
const badgeRotation = interpolate(badgeSpring, [0, 1], [-180, 0]);
|
||||
|
||||
// Handle list
|
||||
const listSpring = spring({
|
||||
frame: frame - 1.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const listOpacity = interpolate(listSpring, [0, 1], [0, 1]);
|
||||
const listY = interpolate(listSpring, [0, 1], [50, 0]);
|
||||
|
||||
// Checkmark particles
|
||||
const particle1Spring = spring({
|
||||
frame: frame - 2 * fps,
|
||||
fps,
|
||||
config: { damping: 15 },
|
||||
});
|
||||
const particle1Y = interpolate(particle1Spring, [0, 1], [0, -100]);
|
||||
const particle1Opacity = interpolate(particle1Spring, [0, 0.7, 1], [0, 1, 0]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Wallpaper Background */}
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-wallpaper.png")}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/75" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Animated EINUNDZWANZIG Logo */}
|
||||
<div className="absolute top-16 left-3/4 -translate-x-1/2">
|
||||
<AnimatedLogo size={320} delay={fps} />
|
||||
</div>
|
||||
|
||||
{/* Floating Checkmark Particles */}
|
||||
<div
|
||||
className="absolute top-1/3 left-1/4 text-6xl"
|
||||
style={{
|
||||
opacity: particle1Opacity,
|
||||
transform: `translateY(${particle1Y}px)`,
|
||||
}}
|
||||
>
|
||||
✓
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-12">
|
||||
<h2
|
||||
className="text-5xl font-bold text-white mb-16 text-center"
|
||||
style={{ opacity: titleOpacity }}
|
||||
>
|
||||
Verifizierung erfolgreich!
|
||||
</h2>
|
||||
|
||||
{/* Success Badge */}
|
||||
<div
|
||||
className="bg-white rounded-2xl p-8 shadow-2xl mb-12"
|
||||
style={{
|
||||
opacity: badgeScale,
|
||||
transform: `scale(${badgeScale}) rotate(${badgeRotation}deg)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="w-20 h-20 rounded-full bg-neutral-800 flex items-center justify-center">
|
||||
<span className="text-5xl text-white">✓</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-neutral-600 font-medium mb-1">Dein Handle ist aktiv:</p>
|
||||
<p className="text-3xl font-bold text-neutral-800">
|
||||
satoshi21@einundzwanzig.space
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Handle List */}
|
||||
<div
|
||||
className="bg-white/10 backdrop-blur-sm rounded-xl p-8 border-2 border-white/20 max-w-2xl w-full"
|
||||
style={{
|
||||
opacity: listOpacity,
|
||||
transform: `translateY(${listY}px)`,
|
||||
}}
|
||||
>
|
||||
<p className="text-xl font-semibold text-white mb-4">Deine aktivierten Handles:</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-4 bg-white/10 rounded-lg px-5 py-3">
|
||||
<span className="text-lg text-white">satoshi21@einundzwanzig.space</span>
|
||||
<span className="bg-neutral-800 text-white text-xs font-bold px-3 py-1 rounded-full border-2 border-neutral-600">
|
||||
OK
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-neutral-300 text-center mt-12 text-xl max-w-3xl" style={{ opacity: listOpacity }}>
|
||||
Dein NIP-05 Handle ist jetzt aktiv! Nostr-Clients zeigen ein Verifizierungs-Häkchen für dich an.
|
||||
</p>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
134
videos/src/scenes/mobile/InputDemoSceneMobile.tsx
Normal file
134
videos/src/scenes/mobile/InputDemoSceneMobile.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../../components/AnimatedLogo";
|
||||
|
||||
export const InputDemoSceneMobile: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, width } = useVideoConfig();
|
||||
|
||||
// Typing animation
|
||||
const typingText = "satoshi21";
|
||||
const typingProgress = interpolate(frame, [0, 2 * fps], [0, typingText.length], {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
const currentText = typingText.slice(0, Math.floor(typingProgress));
|
||||
|
||||
// Cursor blink
|
||||
const cursorBlink = Math.floor(frame / 15) % 2 === 0;
|
||||
|
||||
// Card entrance
|
||||
const cardSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const cardY = interpolate(cardSpring, [0, 1], [100, 0]);
|
||||
const cardOpacity = interpolate(cardSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Rules box appears after typing
|
||||
const rulesSpring = spring({
|
||||
frame: frame - 2.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const rulesOpacity = interpolate(rulesSpring, [0, 1], [0, 1]);
|
||||
const rulesY = interpolate(rulesSpring, [0, 1], [30, 0]);
|
||||
|
||||
// Pointer animation - from left
|
||||
const pointerSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const pointerX = interpolate(pointerSpring, [0, 1], [-120, 0]);
|
||||
const pointerOpacity = interpolate(pointerSpring, [0, 1], [0, 1]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Tiled Wallpaper Background */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `url(${staticFile("einundzwanzig-wallpaper.png")})`,
|
||||
backgroundSize: `${width}px auto`,
|
||||
backgroundRepeat: "repeat-y",
|
||||
backgroundPosition: "center top",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/80" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Animated EINUNDZWANZIG Logo */}
|
||||
<div className="absolute top-12 left-1/2 -translate-x-1/2">
|
||||
<AnimatedLogo size={120} delay={0.5 * fps} />
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-8">
|
||||
<h2 className="text-5xl font-bold text-white mb-12 text-center">
|
||||
Schritt 1: Handle eingeben
|
||||
</h2>
|
||||
|
||||
{/* Input Card - Full Width */}
|
||||
<div
|
||||
className="bg-white rounded-3xl p-10 shadow-2xl w-full relative"
|
||||
style={{
|
||||
opacity: cardOpacity,
|
||||
transform: `translateY(${cardY}px)`,
|
||||
}}
|
||||
>
|
||||
{/* Animated Pointer - from left */}
|
||||
<div
|
||||
className="absolute -left-24 top-1/2 -translate-y-1/2 text-8xl"
|
||||
style={{
|
||||
transform: `translateY(-50%) translateX(${pointerX}px)`,
|
||||
opacity: pointerOpacity,
|
||||
}}
|
||||
>
|
||||
👉
|
||||
</div>
|
||||
|
||||
<label className="block text-3xl font-medium text-neutral-700 mb-5">
|
||||
Dein NIP-05 Handle
|
||||
</label>
|
||||
<div className="bg-white rounded-2xl border-4 border-neutral-800 px-6 py-5 mb-8">
|
||||
<div className="text-4xl text-neutral-800 mb-2">
|
||||
{currentText}
|
||||
{cursorBlink && frame < 2.5 * fps && <span className="text-neutral-800">|</span>}
|
||||
</div>
|
||||
<div className="text-2xl text-neutral-600 font-medium">@einundzwanzig.space</div>
|
||||
</div>
|
||||
|
||||
{/* Rules Box */}
|
||||
<div
|
||||
className="bg-neutral-100 rounded-2xl border-2 border-neutral-300 p-6"
|
||||
style={{
|
||||
opacity: rulesOpacity,
|
||||
transform: `translateY(${rulesY}px)`,
|
||||
}}
|
||||
>
|
||||
<p className="text-2xl text-neutral-700 font-medium mb-4">
|
||||
✅ Regeln für dein Handle:
|
||||
</p>
|
||||
<ul className="text-xl text-neutral-600 space-y-2">
|
||||
<li>• Nur Kleinbuchstaben (a-z)</li>
|
||||
<li>• Zahlen (0-9)</li>
|
||||
<li>• Zeichen: "-" und "_"</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info text - Footer */}
|
||||
<p
|
||||
className="text-neutral-200 text-center mt-12 text-3xl px-4 font-medium"
|
||||
style={{
|
||||
opacity: rulesOpacity,
|
||||
}}
|
||||
>
|
||||
Dein Handle wird automatisch kleingeschrieben und muss einzigartig sein.
|
||||
</p>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
324
videos/src/scenes/mobile/IntroSceneMobile.tsx
Normal file
324
videos/src/scenes/mobile/IntroSceneMobile.tsx
Normal file
@@ -0,0 +1,324 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../../components/AnimatedLogo";
|
||||
|
||||
// Lightning Payment Animation Component
|
||||
const LightningPaymentAnimation: React.FC<{ frame: number; fps: number; startFrame: number }> = ({
|
||||
frame,
|
||||
fps,
|
||||
startFrame,
|
||||
}) => {
|
||||
const relativeFrame = frame - startFrame;
|
||||
|
||||
if (relativeFrame < 0.5 * fps) return null;
|
||||
|
||||
const boltProgress = interpolate(
|
||||
relativeFrame,
|
||||
[0.5 * fps, 1.5 * fps],
|
||||
[0, 1],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
|
||||
const flashOpacity = interpolate(
|
||||
relativeFrame,
|
||||
[1.4 * fps, 1.5 * fps, 1.8 * fps],
|
||||
[0, 0.8, 0],
|
||||
{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }
|
||||
);
|
||||
|
||||
const checkSpring = spring({
|
||||
frame: relativeFrame - 1.6 * fps,
|
||||
fps,
|
||||
config: { damping: 12, stiffness: 150 },
|
||||
});
|
||||
const checkScale = interpolate(checkSpring, [0, 1], [0, 1]);
|
||||
|
||||
const boltX = interpolate(boltProgress, [0, 1], [-80, 80]);
|
||||
const boltY = interpolate(boltProgress, [0, 0.5, 1], [0, -20, 0]);
|
||||
|
||||
const boltRotation = interpolate(
|
||||
Math.sin(relativeFrame * 0.4),
|
||||
[-1, 1],
|
||||
[-15, 15]
|
||||
);
|
||||
|
||||
const boltScale = interpolate(
|
||||
Math.sin(relativeFrame * 0.3),
|
||||
[-1, 1],
|
||||
[1, 1.3]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
{boltProgress < 1 && (
|
||||
<div
|
||||
className="absolute text-5xl"
|
||||
style={{
|
||||
transform: `translateX(${boltX}px) translateY(${boltY}px) rotate(${boltRotation}deg) scale(${boltScale})`,
|
||||
filter: "drop-shadow(0 0 12px #f7931a) drop-shadow(0 0 24px #ffcc00)",
|
||||
}}
|
||||
>
|
||||
⚡
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="absolute inset-0 bg-yellow-400"
|
||||
style={{ opacity: flashOpacity }}
|
||||
/>
|
||||
|
||||
{checkScale > 0 && (
|
||||
<div
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 w-14 h-14 rounded-full bg-green-500 flex items-center justify-center"
|
||||
style={{
|
||||
transform: `translateY(-50%) scale(${checkScale})`,
|
||||
boxShadow: "0 0 20px rgba(34, 197, 94, 0.6)",
|
||||
}}
|
||||
>
|
||||
<span className="text-white text-3xl font-bold">✓</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const IntroSceneMobile: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, width } = useVideoConfig();
|
||||
|
||||
// Logo entrance
|
||||
const logoSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const logoOpacity = interpolate(logoSpring, [0, 1], [0, 1]);
|
||||
const logoScale = interpolate(logoSpring, [0, 1], [0.5, 1]);
|
||||
|
||||
// Main title
|
||||
const titleSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const titleOpacity = interpolate(titleSpring, [0, 1], [0, 1]);
|
||||
const titleY = interpolate(titleSpring, [0, 1], [30, 0]);
|
||||
|
||||
// Subtitle
|
||||
const subtitleSpring = spring({
|
||||
frame: frame - 1 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const subtitleOpacity = interpolate(subtitleSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Step 1: Registration
|
||||
const step1Spring = spring({
|
||||
frame: frame - 2 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const step1Opacity = interpolate(step1Spring, [0, 1], [0, 1]);
|
||||
const step1Y = interpolate(step1Spring, [0, 1], [50, 0]);
|
||||
|
||||
// Step 2: Lightning Payment
|
||||
const step2Spring = spring({
|
||||
frame: frame - 4.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const step2Opacity = interpolate(step2Spring, [0, 1], [0, 1]);
|
||||
const step2Y = interpolate(step2Spring, [0, 1], [50, 0]);
|
||||
|
||||
// Step 3: NIP-05 Benefit
|
||||
const step3Spring = spring({
|
||||
frame: frame - 7.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const step3Opacity = interpolate(step3Spring, [0, 1], [0, 1]);
|
||||
const step3Y = interpolate(step3Spring, [0, 1], [50, 0]);
|
||||
|
||||
// Arrow animations
|
||||
const arrow1Spring = spring({
|
||||
frame: frame - 3.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const arrow1Opacity = interpolate(arrow1Spring, [0, 1], [0, 1]);
|
||||
|
||||
const arrow2Spring = spring({
|
||||
frame: frame - 7 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const arrow2Opacity = interpolate(arrow2Spring, [0, 1], [0, 1]);
|
||||
|
||||
// Final call to action
|
||||
const ctaSpring = spring({
|
||||
frame: frame - 9.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const ctaOpacity = interpolate(ctaSpring, [0, 1], [0, 1]);
|
||||
const ctaScale = interpolate(ctaSpring, [0, 1], [0.9, 1]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Tiled Wallpaper Background */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `url(${staticFile("einundzwanzig-wallpaper.png")})`,
|
||||
backgroundSize: `${width}px auto`,
|
||||
backgroundRepeat: "repeat-y",
|
||||
backgroundPosition: "center top",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-900/80" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Header with Logo and Title */}
|
||||
<div className="absolute top-12 left-0 right-0 flex flex-col items-center gap-4 px-8">
|
||||
<div
|
||||
style={{
|
||||
opacity: logoOpacity,
|
||||
transform: `scale(${logoScale})`,
|
||||
}}
|
||||
>
|
||||
<AnimatedLogo size={100} delay={0} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="text-center"
|
||||
style={{
|
||||
opacity: titleOpacity,
|
||||
transform: `translateY(${titleY}px)`,
|
||||
}}
|
||||
>
|
||||
<h1 className="text-5xl font-bold text-white">
|
||||
NIP-05 Verifikation
|
||||
</h1>
|
||||
<p
|
||||
className="text-2xl text-neutral-300 mt-3"
|
||||
style={{ opacity: subtitleOpacity }}
|
||||
>
|
||||
So erhältst du deine verifizierte Nostr-Identität
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Steps Container */}
|
||||
<div className="absolute top-[22%] left-0 right-0 px-8">
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Step 1: Registration */}
|
||||
<div
|
||||
className="flex items-start gap-5"
|
||||
style={{
|
||||
opacity: step1Opacity,
|
||||
transform: `translateY(${step1Y}px)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0 w-20 h-20 rounded-full bg-orange-500 flex items-center justify-center border-4 border-orange-400 shadow-xl">
|
||||
<span className="text-4xl font-bold text-white">1</span>
|
||||
</div>
|
||||
<div className="flex-1 bg-white/10 backdrop-blur-sm rounded-2xl p-6 border-2 border-white/20 shadow-lg">
|
||||
<h3 className="text-3xl font-bold text-white mb-2 flex items-center gap-3">
|
||||
<span className="text-4xl">📝</span>
|
||||
Registrierung im Verein
|
||||
</h3>
|
||||
<p className="text-xl text-neutral-200">
|
||||
Werde Mitglied bei EINUNDZWANZIG und erhalte Zugang zu allen Vorteilen
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow 1 */}
|
||||
<div
|
||||
className="flex justify-center -my-1"
|
||||
style={{ opacity: arrow1Opacity }}
|
||||
>
|
||||
<div className="text-5xl text-orange-500">↓</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2: Lightning Payment */}
|
||||
<div
|
||||
className="flex items-start gap-5"
|
||||
style={{
|
||||
opacity: step2Opacity,
|
||||
transform: `translateY(${step2Y}px)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0 w-20 h-20 rounded-full bg-orange-500 flex items-center justify-center border-4 border-orange-400 shadow-xl">
|
||||
<span className="text-4xl font-bold text-white">2</span>
|
||||
</div>
|
||||
<div className="flex-1 bg-white/10 backdrop-blur-sm rounded-2xl p-6 border-2 border-white/20 shadow-lg relative overflow-hidden">
|
||||
<div
|
||||
className="absolute top-0 right-0 w-40 h-40 blur-3xl opacity-50"
|
||||
style={{
|
||||
background: "radial-gradient(circle, #f7931a 0%, transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
<h3 className="text-3xl font-bold text-white mb-2 flex items-center gap-3 relative z-10">
|
||||
<span className="text-4xl">⚡</span>
|
||||
Beitrag via Lightning
|
||||
</h3>
|
||||
<p className="text-xl text-neutral-200 relative z-10">
|
||||
Zahle deinen Mitgliedsbeitrag schnell und einfach mit Bitcoin Lightning
|
||||
</p>
|
||||
<LightningPaymentAnimation frame={frame} fps={fps} startFrame={4.5 * fps} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow 2 */}
|
||||
<div
|
||||
className="flex justify-center -my-1"
|
||||
style={{ opacity: arrow2Opacity }}
|
||||
>
|
||||
<div className="text-5xl text-orange-500">↓</div>
|
||||
</div>
|
||||
|
||||
{/* Step 3: NIP-05 Benefit */}
|
||||
<div
|
||||
className="flex items-start gap-5"
|
||||
style={{
|
||||
opacity: step3Opacity,
|
||||
transform: `translateY(${step3Y}px)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0 w-20 h-20 rounded-full bg-orange-500 flex items-center justify-center border-4 border-orange-400 shadow-xl">
|
||||
<span className="text-4xl font-bold text-white">3</span>
|
||||
</div>
|
||||
<div className="flex-1 bg-gradient-to-r from-orange-500/20 to-orange-600/20 backdrop-blur-sm rounded-2xl p-6 border-2 border-orange-400 shadow-lg">
|
||||
<h3 className="text-3xl font-bold text-white mb-2 flex items-center gap-3">
|
||||
<span className="text-4xl">✓</span>
|
||||
NIP-05 Handle erstellen
|
||||
</h3>
|
||||
<p className="text-xl text-neutral-200">
|
||||
Erstelle dein persönliches NIP-05 Handle und verifiziere deine Nostr-Identität
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Call to Action - Footer */}
|
||||
<div
|
||||
className="absolute bottom-12 left-0 right-0 text-center px-8"
|
||||
style={{
|
||||
opacity: ctaOpacity,
|
||||
transform: `scale(${ctaScale})`,
|
||||
}}
|
||||
>
|
||||
<p className="text-5xl text-white font-bold mb-4">
|
||||
Los geht's!
|
||||
</p>
|
||||
<p className="text-3xl text-neutral-200 font-medium">
|
||||
Im nächsten Video zeigen wir dir Schritt 3 im Detail
|
||||
</p>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
161
videos/src/scenes/mobile/OutroSceneMobile.tsx
Normal file
161
videos/src/scenes/mobile/OutroSceneMobile.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, Img, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../../components/AnimatedLogo";
|
||||
|
||||
export const OutroSceneMobile: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, width } = useVideoConfig();
|
||||
|
||||
// Logo animation at top
|
||||
const logoSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const logoOpacity = interpolate(logoSpring, [0, 1], [0, 1]);
|
||||
const logoY = interpolate(logoSpring, [0, 1], [-50, 0]);
|
||||
|
||||
// Main SVG Logo
|
||||
const svgSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const svgScale = interpolate(svgSpring, [0, 1], [0.8, 1]);
|
||||
const svgOpacity = interpolate(svgSpring, [0, 1], [0, 1]);
|
||||
|
||||
// URL Animation - delayed and prominent
|
||||
const urlSpring = spring({
|
||||
frame: frame - 2 * fps,
|
||||
fps,
|
||||
config: { damping: 20, stiffness: 100 },
|
||||
});
|
||||
const urlScale = interpolate(urlSpring, [0, 1], [0.5, 1]);
|
||||
const urlOpacity = interpolate(urlSpring, [0, 1], [0, 1]);
|
||||
|
||||
// URL pulsing effect
|
||||
const urlPulse = interpolate(
|
||||
Math.sin((frame - 2 * fps) * 0.05),
|
||||
[-1, 1],
|
||||
[1, 1.02]
|
||||
);
|
||||
|
||||
// Call to action
|
||||
const ctaSpring = spring({
|
||||
frame: frame - 1 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const ctaOpacity = interpolate(ctaSpring, [0, 1], [0, 1]);
|
||||
const ctaY = interpolate(ctaSpring, [0, 1], [30, 0]);
|
||||
|
||||
// Footer appears last
|
||||
const footerSpring = spring({
|
||||
frame: frame - 4 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const footerOpacity = interpolate(footerSpring, [0, 1], [0, 1]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Tiled Wallpaper Background */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `url(${staticFile("einundzwanzig-wallpaper.png")})`,
|
||||
backgroundSize: `${width}px auto`,
|
||||
backgroundRepeat: "repeat-y",
|
||||
backgroundPosition: "center top",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/88" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Animated Logo Top */}
|
||||
<div
|
||||
className="absolute top-16 left-1/2 -translate-x-1/2"
|
||||
style={{
|
||||
opacity: logoOpacity,
|
||||
transform: `translateX(-50%) translateY(${logoY}px)`,
|
||||
}}
|
||||
>
|
||||
<AnimatedLogo size={160} delay={0} />
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-8">
|
||||
{/* EINUNDZWANZIG Logo SVG - Full Width */}
|
||||
<div
|
||||
className="mb-12 w-full flex justify-center"
|
||||
style={{
|
||||
opacity: svgOpacity,
|
||||
transform: `scale(${svgScale})`,
|
||||
}}
|
||||
>
|
||||
<Img
|
||||
src={staticFile("einundzwanzig-horizontal-inverted.svg")}
|
||||
style={{ width: "90%", maxWidth: 900, height: "auto" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Call to Action Text */}
|
||||
<div
|
||||
style={{
|
||||
opacity: ctaOpacity,
|
||||
transform: `translateY(${ctaY}px)`,
|
||||
}}
|
||||
>
|
||||
<p className="text-5xl text-white text-center font-semibold mb-12">
|
||||
Werde jetzt Mitglied!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* URL - MAIN FOCUS - Full Width */}
|
||||
<div
|
||||
className="relative w-full"
|
||||
style={{
|
||||
opacity: urlOpacity,
|
||||
transform: `scale(${urlScale * urlPulse})`,
|
||||
}}
|
||||
>
|
||||
{/* Glow effect behind URL */}
|
||||
<div
|
||||
className="absolute inset-0 blur-3xl"
|
||||
style={{
|
||||
background: "radial-gradient(ellipse, #f7931a 0%, transparent 70%)",
|
||||
opacity: 0.5,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* URL Container with border */}
|
||||
<div className="relative bg-neutral-800/80 backdrop-blur-sm rounded-3xl px-8 py-12 border-4 border-orange-500 shadow-2xl">
|
||||
<p className="text-4xl text-white text-center font-bold tracking-wide leading-tight">
|
||||
verein.einundzwanzig.space
|
||||
</p>
|
||||
|
||||
{/* Accent line */}
|
||||
<div className="h-2 bg-gradient-to-r from-transparent via-orange-500 to-transparent mt-8 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Info */}
|
||||
<div
|
||||
className="absolute bottom-16 left-0 right-0 text-center px-8"
|
||||
style={{ opacity: footerOpacity }}
|
||||
>
|
||||
<p className="text-neutral-200 text-4xl mb-6 font-bold">
|
||||
Mitglieder-Vorteile:
|
||||
</p>
|
||||
<div className="flex justify-center gap-8 flex-wrap text-neutral-300 text-3xl font-medium">
|
||||
<span>🔗 Nostr Relay</span>
|
||||
<span>✓ NIP-05</span>
|
||||
<span>⚡ Watchtower</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
134
videos/src/scenes/mobile/SaveButtonSceneMobile.tsx
Normal file
134
videos/src/scenes/mobile/SaveButtonSceneMobile.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../../components/AnimatedLogo";
|
||||
|
||||
export const SaveButtonSceneMobile: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, width } = useVideoConfig();
|
||||
|
||||
const titleSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const titleOpacity = interpolate(titleSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Button entrance
|
||||
const buttonSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 15, stiffness: 100 },
|
||||
});
|
||||
const buttonScale = interpolate(buttonSpring, [0, 1], [0.5, 1]);
|
||||
const buttonOpacity = interpolate(buttonSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Click animation
|
||||
const clickFrame = 2 * fps;
|
||||
const clickSpring = spring({
|
||||
frame: frame - clickFrame,
|
||||
fps,
|
||||
config: { damping: 10, stiffness: 200 },
|
||||
durationInFrames: 0.3 * fps,
|
||||
});
|
||||
const buttonPressScale = frame >= clickFrame ? interpolate(clickSpring, [0, 1], [0.92, 1]) : 1;
|
||||
|
||||
// Success animation
|
||||
const successSpring = spring({
|
||||
frame: frame - (clickFrame + 0.5 * fps),
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const successOpacity = interpolate(successSpring, [0, 1], [0, 1]);
|
||||
const successScale = interpolate(successSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Cursor pointer - from left
|
||||
const cursorSpring = spring({
|
||||
frame: frame - 1.5 * fps,
|
||||
fps,
|
||||
config: { damping: 20, stiffness: 150 },
|
||||
});
|
||||
const cursorX = interpolate(cursorSpring, [0, 1], [-200, 0]);
|
||||
const cursorOpacity = interpolate(cursorSpring, [0, 1], [0, 1]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Tiled Wallpaper Background */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `url(${staticFile("einundzwanzig-wallpaper.png")})`,
|
||||
backgroundSize: `${width}px auto`,
|
||||
backgroundRepeat: "repeat-y",
|
||||
backgroundPosition: "center top",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/75" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-8">
|
||||
<h2
|
||||
className="text-5xl font-bold text-white mb-24 text-center"
|
||||
style={{ opacity: titleOpacity }}
|
||||
>
|
||||
Schritt 2: Handle speichern
|
||||
</h2>
|
||||
|
||||
{/* Button Container */}
|
||||
<div className="relative">
|
||||
{/* Animated Cursor - from left */}
|
||||
<div
|
||||
className="absolute text-8xl pointer-events-none z-10"
|
||||
style={{
|
||||
left: "-120px",
|
||||
top: "50%",
|
||||
transform: `translateY(-50%) translateX(${cursorX}px)`,
|
||||
opacity: cursorOpacity,
|
||||
}}
|
||||
>
|
||||
👉
|
||||
</div>
|
||||
|
||||
{/* Save Button - Much Larger */}
|
||||
<button
|
||||
className="bg-neutral-800 text-white font-bold text-5xl px-24 py-10 rounded-2xl shadow-2xl border-4 border-neutral-600"
|
||||
style={{
|
||||
opacity: buttonOpacity,
|
||||
transform: `scale(${buttonScale * buttonPressScale})`,
|
||||
}}
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
|
||||
{/* Success Message */}
|
||||
{frame >= clickFrame + 0.5 * fps && (
|
||||
<div
|
||||
className="absolute -bottom-32 left-1/2 -translate-x-1/2 whitespace-nowrap"
|
||||
style={{
|
||||
opacity: successOpacity,
|
||||
transform: `translateX(-50%) scale(${successScale})`,
|
||||
}}
|
||||
>
|
||||
<div className="bg-neutral-800 text-white px-10 py-6 rounded-2xl shadow-xl flex items-center gap-5 border-2 border-neutral-600">
|
||||
<span className="text-4xl">✓</span>
|
||||
<span className="text-3xl font-semibold">NIP-05 Handle gespeichert!</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 3D Success Logo */}
|
||||
{frame >= clickFrame + 0.5 * fps && (
|
||||
<div className="absolute top-32 right-8">
|
||||
<AnimatedLogo size={200} delay={clickFrame + 0.5 * fps} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-neutral-200 text-center mt-48 text-3xl px-4 font-medium" style={{ opacity: titleOpacity }}>
|
||||
Nach dem Speichern wird dein Handle in Kürze automatisch aktiviert.
|
||||
</p>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
99
videos/src/scenes/mobile/UIShowcaseSceneMobile.tsx
Normal file
99
videos/src/scenes/mobile/UIShowcaseSceneMobile.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../../components/AnimatedLogo";
|
||||
|
||||
export const UIShowcaseSceneMobile: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, width } = useVideoConfig();
|
||||
|
||||
const cardSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 20, stiffness: 200 },
|
||||
});
|
||||
|
||||
const cardScale = interpolate(cardSpring, [0, 1], [0.8, 1]);
|
||||
const cardOpacity = interpolate(cardSpring, [0, 1], [0, 1]);
|
||||
|
||||
const titleSpring = spring({
|
||||
frame: frame - 0.3 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const titleY = interpolate(titleSpring, [0, 1], [50, 0]);
|
||||
const titleOpacity = interpolate(titleSpring, [0, 1], [0, 1]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Tiled Wallpaper Background */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `url(${staticFile("einundzwanzig-wallpaper.png")})`,
|
||||
backgroundSize: `${width}px auto`,
|
||||
backgroundRepeat: "repeat-y",
|
||||
backgroundPosition: "center top",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/85" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Animated Logos */}
|
||||
<div className="absolute top-16 left-8 opacity-60">
|
||||
<AnimatedLogo size={140} delay={0.2 * fps} />
|
||||
</div>
|
||||
<div className="absolute top-16 right-8 opacity-60">
|
||||
<AnimatedLogo size={140} delay={0.4 * fps} />
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-8">
|
||||
<h2
|
||||
className="text-5xl font-bold text-white mb-12 text-center"
|
||||
style={{
|
||||
opacity: titleOpacity,
|
||||
transform: `translateY(${titleY}px)`,
|
||||
}}
|
||||
>
|
||||
Die NIP-05 Oberfläche
|
||||
</h2>
|
||||
|
||||
{/* UI Card Mockup - Full Width */}
|
||||
<div
|
||||
className="bg-neutral-50 rounded-3xl p-10 border-4 border-neutral-200 shadow-2xl w-full"
|
||||
style={{
|
||||
opacity: cardOpacity,
|
||||
transform: `scale(${cardScale})`,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-6 mb-8">
|
||||
<div className="w-20 h-20 rounded-full bg-neutral-800 flex items-center justify-center flex-shrink-0">
|
||||
<div className="text-white text-5xl">✓</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-4xl font-semibold text-neutral-800 mb-3">
|
||||
Get NIP-05 verified
|
||||
</h3>
|
||||
<p className="text-2xl text-neutral-600 leading-relaxed">
|
||||
Verifiziere deine Identität mit einem menschenlesbaren Nostr-Namen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input Preview */}
|
||||
<div className="space-y-4">
|
||||
<label className="block text-2xl font-medium text-neutral-700">
|
||||
Dein NIP-05 Handle
|
||||
</label>
|
||||
<div className="flex items-center bg-white rounded-2xl border-4 border-neutral-300 px-6 py-5">
|
||||
<div className="flex-1 text-3xl text-neutral-400">dein-name</div>
|
||||
<div className="text-2xl text-neutral-600 font-medium">@einundzwanzig.space</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
147
videos/src/scenes/mobile/VerificationSceneMobile.tsx
Normal file
147
videos/src/scenes/mobile/VerificationSceneMobile.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, staticFile } from "remotion";
|
||||
import { BitcoinEffect } from "../../components/BitcoinEffect";
|
||||
import { AnimatedLogo } from "../../components/AnimatedLogo";
|
||||
|
||||
export const VerificationSceneMobile: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps, width } = useVideoConfig();
|
||||
|
||||
const titleSpring = spring({
|
||||
frame,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const titleOpacity = interpolate(titleSpring, [0, 1], [0, 1]);
|
||||
|
||||
// Badge entrance
|
||||
const badgeSpring = spring({
|
||||
frame: frame - 0.5 * fps,
|
||||
fps,
|
||||
config: { damping: 8 },
|
||||
});
|
||||
const badgeScale = interpolate(badgeSpring, [0, 1], [0, 1]);
|
||||
const badgeRotation = interpolate(badgeSpring, [0, 1], [-180, 0]);
|
||||
|
||||
// Handle list
|
||||
const listSpring = spring({
|
||||
frame: frame - 1.5 * fps,
|
||||
fps,
|
||||
config: { damping: 200 },
|
||||
});
|
||||
const listOpacity = interpolate(listSpring, [0, 1], [0, 1]);
|
||||
const listY = interpolate(listSpring, [0, 1], [50, 0]);
|
||||
|
||||
// Checkmark particles
|
||||
const particle1Spring = spring({
|
||||
frame: frame - 2 * fps,
|
||||
fps,
|
||||
config: { damping: 15 },
|
||||
});
|
||||
const particle1Y = interpolate(particle1Spring, [0, 1], [0, -120]);
|
||||
const particle1Opacity = interpolate(particle1Spring, [0, 0.7, 1], [0, 1, 0]);
|
||||
|
||||
const particle2Spring = spring({
|
||||
frame: frame - 2.2 * fps,
|
||||
fps,
|
||||
config: { damping: 15 },
|
||||
});
|
||||
const particle2Y = interpolate(particle2Spring, [0, 1], [0, -100]);
|
||||
const particle2Opacity = interpolate(particle2Spring, [0, 0.7, 1], [0, 1, 0]);
|
||||
|
||||
return (
|
||||
<AbsoluteFill>
|
||||
{/* Tiled Wallpaper Background */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `url(${staticFile("einundzwanzig-wallpaper.png")})`,
|
||||
backgroundSize: `${width}px auto`,
|
||||
backgroundRepeat: "repeat-y",
|
||||
backgroundPosition: "center top",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-neutral-950/80" />
|
||||
|
||||
{/* Bitcoin Effect */}
|
||||
<BitcoinEffect />
|
||||
|
||||
{/* Animated EINUNDZWANZIG Logo */}
|
||||
<div className="absolute top-12 right-8">
|
||||
<AnimatedLogo size={140} delay={fps} />
|
||||
</div>
|
||||
|
||||
{/* Floating Checkmark Particles */}
|
||||
<div
|
||||
className="absolute top-1/3 left-16 text-7xl text-green-400"
|
||||
style={{
|
||||
opacity: particle1Opacity,
|
||||
transform: `translateY(${particle1Y}px)`,
|
||||
}}
|
||||
>
|
||||
✓
|
||||
</div>
|
||||
<div
|
||||
className="absolute top-1/3 right-20 text-6xl text-green-400"
|
||||
style={{
|
||||
opacity: particle2Opacity,
|
||||
transform: `translateY(${particle2Y}px)`,
|
||||
}}
|
||||
>
|
||||
✓
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center px-8">
|
||||
<h2
|
||||
className="text-5xl font-bold text-white mb-12 text-center"
|
||||
style={{ opacity: titleOpacity }}
|
||||
>
|
||||
Verifizierung erfolgreich!
|
||||
</h2>
|
||||
|
||||
{/* Success Badge - Full Width */}
|
||||
<div
|
||||
className="bg-white rounded-3xl p-8 shadow-2xl mb-8 w-full"
|
||||
style={{
|
||||
opacity: badgeScale,
|
||||
transform: `scale(${badgeScale}) rotate(${badgeRotation}deg)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="w-24 h-24 rounded-full bg-neutral-800 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-6xl text-white">✓</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xl text-neutral-600 font-medium mb-2">Dein Handle ist aktiv:</p>
|
||||
<p className="text-3xl font-bold text-neutral-800 break-all">
|
||||
satoshi21@einundzwanzig.space
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Handle List - Full Width */}
|
||||
<div
|
||||
className="bg-white/10 backdrop-blur-sm rounded-3xl p-8 border-2 border-white/20 w-full"
|
||||
style={{
|
||||
opacity: listOpacity,
|
||||
transform: `translateY(${listY}px)`,
|
||||
}}
|
||||
>
|
||||
<p className="text-2xl font-semibold text-white mb-5">Deine aktivierten Handles:</p>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-4 bg-white/10 rounded-2xl px-6 py-5">
|
||||
<span className="text-2xl text-white flex-1 break-all">satoshi21@einundzwanzig.space</span>
|
||||
<span className="bg-neutral-800 text-white text-xl font-bold px-5 py-2 rounded-full border-2 border-neutral-600 flex-shrink-0">
|
||||
OK
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-neutral-200 text-center mt-12 text-3xl px-4 font-medium leading-relaxed" style={{ opacity: listOpacity }}>
|
||||
Dein NIP-05 Handle ist jetzt aktiv! Nostr-Clients zeigen ein Verifizierungs-Häkchen für dich an.
|
||||
</p>
|
||||
</div>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user