🎨 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:
HolgerHatGarKeineNode
2026-01-23 23:00:02 +01:00
parent 578e4f13fc
commit b30fec150c
792 changed files with 307541 additions and 117 deletions

View File

@@ -0,0 +1,3 @@
export const MyComposition = () => {
return null;
};

View 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>
);
};

View 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
View 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}
/>
</>
);
};

View 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>
);
};

View 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>
</>
);
};

View 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>
);
};

View 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
View 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
View File

@@ -0,0 +1,4 @@
import { registerRoot } from "remotion";
import { RemotionRoot } from "./Root";
registerRoot(RemotionRoot);

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};