diff --git a/public/images/landing_videos/Whisper Money - Dark - Encryption.mp4 b/public/images/landing_videos/Whisper Money - Dark - Encryption.mp4
new file mode 100644
index 00000000..6bbd1449
Binary files /dev/null and b/public/images/landing_videos/Whisper Money - Dark - Encryption.mp4 differ
diff --git a/public/images/landing_videos/Whisper Money - Light - Encryption.mp4 b/public/images/landing_videos/Whisper Money - Light - Encryption.mp4
new file mode 100644
index 00000000..2766f4b6
Binary files /dev/null and b/public/images/landing_videos/Whisper Money - Light - Encryption.mp4 differ
diff --git a/resources/js/components/landing/encryption-video-player.tsx b/resources/js/components/landing/encryption-video-player.tsx
new file mode 100644
index 00000000..ec749792
--- /dev/null
+++ b/resources/js/components/landing/encryption-video-player.tsx
@@ -0,0 +1,114 @@
+import { cn } from '@/lib/utils';
+import { RotateCcwIcon } from 'lucide-react';
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+interface EncryptionVideoPlayerProps {
+ lightSrc: string;
+ darkSrc: string;
+ className?: string;
+}
+
+export default function EncryptionVideoPlayer({
+ lightSrc,
+ darkSrc,
+ className,
+}: EncryptionVideoPlayerProps) {
+ const lightVideoRef = useRef(null);
+ const darkVideoRef = useRef(null);
+ const containerRef = useRef(null);
+ const [hasEnded, setHasEnded] = useState(false);
+ const [isHovering, setIsHovering] = useState(false);
+ const hasPlayedRef = useRef(false);
+
+ const handleReplay = useCallback(() => {
+ const lightVideo = lightVideoRef.current;
+ const darkVideo = darkVideoRef.current;
+
+ if (lightVideo) {
+ lightVideo.currentTime = 0;
+ lightVideo.play();
+ }
+ if (darkVideo) {
+ darkVideo.currentTime = 0;
+ darkVideo.play();
+ }
+ setHasEnded(false);
+ }, []);
+
+ const handleEnded = useCallback(() => {
+ setHasEnded(true);
+ }, []);
+
+ useEffect(() => {
+ const container = containerRef.current;
+ const lightVideo = lightVideoRef.current;
+ const darkVideo = darkVideoRef.current;
+
+ if (!container || !lightVideo || !darkVideo) return;
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting && !hasPlayedRef.current) {
+ hasPlayedRef.current = true;
+ lightVideo.play();
+ darkVideo.play();
+ }
+ });
+ },
+ {
+ threshold: 0.5,
+ },
+ );
+
+ observer.observe(container);
+
+ return () => {
+ observer.disconnect();
+ };
+ }, []);
+
+ return (
+ setIsHovering(true)}
+ onMouseLeave={() => setIsHovering(false)}
+ >
+
+
+
+
+
+ {/* Replay button - visible on hover when video has ended */}
+ {hasEnded && (
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/resources/js/pages/welcome.tsx b/resources/js/pages/welcome.tsx
index 349321fc..5ad8ab51 100644
--- a/resources/js/pages/welcome.tsx
+++ b/resources/js/pages/welcome.tsx
@@ -1,3 +1,4 @@
+import EncryptionVideoPlayer from '@/components/landing/encryption-video-player';
import Header from '@/components/partials/header';
import { Button } from '@/components/ui/button';
import {
@@ -20,7 +21,6 @@ import {
CodeIcon,
EyeOffIcon,
FileUpIcon,
- KeyIcon,
LockIcon,
PieChartIcon,
ShieldCheckIcon,
@@ -342,11 +342,13 @@ export default function Welcome({
-
-
-
-
-
+
+
+
Your Private Key
@@ -357,32 +359,36 @@ export default function Welcome({
-
-
-
-
-
- Client-Side Encryption
-
-
- Your transactions, accounts, and budgets
- are encrypted on your device before
- syncing to the cloud.
-
-
-
-
-
-
-
-
- Zero-Knowledge Architecture
-
-
- We store encrypted data we can't read.
- Even if our servers were compromised,
- your data stays secure.
-
+
+ {[
+ {
+ icon: LockIcon,
+ title: 'Client-Side Encryption',
+ description:
+ 'Your transactions, accounts, and budgets are encrypted on your device before syncing to the cloud.',
+ },
+ {
+ icon: ShieldCheckIcon,
+ title: 'Zero-Knowledge Architecture',
+ description:
+ "We store encrypted data we can't read. Even if our servers were compromised, your data stays secure.",
+ },
+ ].map((item) => (
+
+
+
+
+
+ {item.title}
+
+
+ {item.description}
+
+
+ ))}