import { AbsoluteFill, useCurrentFrame, useVideoConfig, interpolate, spring, Img, staticFile, Sequence, } from "remotion"; import { Audio } from "@remotion/media"; import { SPRING_CONFIGS, TIMING, GLOW_CONFIG, secondsToFrames, } from "../../config/timing"; /** * PortalTitleScene - Scene 2: Title Card (4 seconds / 120 frames @ 30fps) * * Animation sequence: * 1. "EINUNDZWANZIG PORTAL" types in character by character * 2. Blinking cursor during typing * 3. Subtitle fades in after title completes * 4. Audio: typing sound during type animation, ui-appear for subtitle */ export const PortalTitleScene: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // Main title text const titleText = "EINUNDZWANZIG PORTAL"; const subtitleText = "Das Herzstück der deutschsprachigen Bitcoin-Community"; // Calculate typed characters for title using centralized timing const typedTitleChars = Math.min( titleText.length, Math.floor(frame / TIMING.CHAR_FRAMES) ); const typedTitle = titleText.slice(0, typedTitleChars); // Title typing complete frame const titleCompleteFrame = titleText.length * TIMING.CHAR_FRAMES; // Cursor blink effect - only show while typing or shortly after // Fine-tuned: cursor stays visible longer for better visual continuity const cursorVisibleDuration = secondsToFrames(1.2, fps); const showCursor = frame < titleCompleteFrame + cursorVisibleDuration; const cursorOpacity = showCursor ? interpolate( frame % TIMING.CURSOR_BLINK_FRAMES, [0, TIMING.CURSOR_BLINK_FRAMES / 2, TIMING.CURSOR_BLINK_FRAMES], [1, 0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" } ) : 0; // Subtitle entrance - after title typing completes with a pause // Fine-tuned: slightly longer pause (0.4s instead of 0.3s) for better pacing const subtitleDelay = titleCompleteFrame + secondsToFrames(0.4, fps); const subtitleSpring = spring({ frame: frame - subtitleDelay, fps, config: SPRING_CONFIGS.SMOOTH, }); const subtitleOpacity = interpolate(subtitleSpring, [0, 1], [0, 1]); const subtitleY = interpolate(subtitleSpring, [0, 1], [20, 0]); // Background glow that pulses subtly using centralized config const glowIntensity = interpolate( Math.sin(frame * GLOW_CONFIG.FREQUENCY.SLOW), [-1, 1], GLOW_CONFIG.INTENSITY.SUBTLE ); // Scene entrance fade from intro scene // Fine-tuned: faster entrance (0.25s) for snappier transition const entranceFade = interpolate( frame, [0, secondsToFrames(0.25, fps)], [0, 1], { extrapolateRight: "clamp", } ); return ( {/* Audio: typing sound */} {/* Audio: ui-appear for subtitle */} {/* Wallpaper Background */}
{/* Dark gradient overlay */}
{/* Center glow effect */}
{/* Content container */}
{/* Title with typing animation */}

{typedTitle} |

{/* Subtitle */}

{subtitleText}

{/* Decorative line under subtitle */}
{/* Vignette overlay */}
); };