🎨 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,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>
);
};