Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
609da76
feat: add splash screen with animated logo video
rabden Feb 1, 2026
e386994
feat: implement premium splash screen with theme-adaptive animated logo
rabden Feb 1, 2026
3af8f2d
chore: add animated-logo-white.mp4 asset
rabden Feb 1, 2026
c403114
fix(a11y): add reduced motion support and error handling to splash sc…
github-actions[bot] Feb 1, 2026
fe2d1a8
refactor: improve splash screen robustness and interaction behavior
rabden Feb 2, 2026
857af3b
fix(splash-screen): improve robustness and follow codebase guidelines
github-actions[bot] Feb 2, 2026
23a6c3f
Update src/components/splash-screen.tsx
rabden Feb 2, 2026
2664d66
fix(splash-screen): only modify scroll lock when visible
github-actions[bot] Feb 2, 2026
371caa8
refactor: add test-id and refine scroll-lock logic in splash screen
rabden Feb 2, 2026
86659ef
fix(splash-screen): use useSyncExternalStore for hydration-safe reduc…
github-actions[bot] Feb 2, 2026
db75dac
refactor: implement hydration-safe reduced motion detection and refin…
rabden Feb 2, 2026
215b610
Merge branch 'main' into splash-screen-adding
BillChirico Feb 3, 2026
9791412
fix: remove duplicate @types/node entries from lockfile
github-actions[bot] Feb 3, 2026
66325be
feat(intro): implement shiny metallic intro section with theme-specif…
rabden Feb 5, 2026
31ee121
Update src/app/globals.css
rabden Feb 5, 2026
cd70db7
refactor(intro): refine UX, accessibility, and reliability
rabden Feb 5, 2026
45f57b4
Merge branch 'main' into splash-screen-adding
rabden Feb 5, 2026
915a177
feat(seo): add structured data, breadcrumbs, and fix metadata across …
BillChirico Feb 5, 2026
aec1f0b
chore(deps): update dependencies in package.json and pnpm-lock.yaml
BillChirico Feb 5, 2026
178a0b4
refactor(intro): remove video animation phase
rabden Feb 6, 2026
bf2d6ae
refactor(intro): shrink text and replace static logo with video
rabden Feb 6, 2026
e50f018
fix: add cleanup handlers and fix JSON-LD serialization
github-actions[bot] Feb 6, 2026
dd47bc2
refactor(intro): freeze logo video and add spring pop entrance
rabden Feb 6, 2026
14637bd
refactor(intro): remove drop shadows, make text non-interactible, and…
rabden Feb 6, 2026
636934a
fix: address code review issues in intro section and structured data
github-actions[bot] Feb 6, 2026
00a2f1e
refactor(intro): mandatory cinematic intro, SEO fixes, and accessibil…
rabden Feb 6, 2026
3da1d5b
refactor: `IntroSection` now internally handles bot detection to skip…
rabden Feb 6, 2026
41d9d8d
fix: address code review issues in intro section and structured data
github-actions[bot] Feb 6, 2026
4dc6de3
refactor: fix hydration mismatch, improve intro stability, and reorga…
rabden Feb 7, 2026
5cc9661
Update src/components/homepage-client.tsx
rabden Feb 7, 2026
22619c8
fix: resolve syntax error and stabilize handleComplete callback
github-actions[bot] Feb 7, 2026
dc46911
Apply suggestion from @coderabbitai[bot]
rabden Feb 7, 2026
151d943
fix: remove duplicate motion.div in splash-screen.tsx
github-actions[bot] Feb 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/animated-logo-white.mp4
Binary file not shown.
Binary file added public/animated-logo.webm
Binary file not shown.
4 changes: 2 additions & 2 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--bg-depth-gradient-from: #3c90ff;
--bg-depth-gradient-from: #559fff;
/* Blue 100 */
--bg-depth-gradient-to: #1b73ff;
--bg-depth-gradient-to: #3281ff;
/* Blue 500 */
--particle-color-1: #2563eb;
/* Blue 600 */
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CookieConsentBanner } from "@/components/cookie-consent-banner";
import { ConditionalAnalytics } from "@/components/conditional-analytics";
import { SkipLink } from "@/components/skip-link";
import { SmoothScroll } from "@/components/providers/smooth-scroll";
import { SplashScreen } from "@/components/splash-screen";
import { generateOrganizationSchema } from "@/lib/structured-data";
import {
safeJsonLdSerialize,
Expand Down Expand Up @@ -106,6 +107,7 @@ export default function RootLayout({
<body
className={`${jetbrainsMono.variable} ${manrope.variable} ${spaceGrotesk.variable} antialiased font-sans`}
>
<SplashScreen />
<div className="noise" />
<SmoothScroll>
<SkipLink />
Expand Down
2 changes: 1 addition & 1 deletion src/components/animated-background.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export function AnimatedBackground({ className = "" }: { className?: string }) {
}
}
.animate-fade-in {
animation: custom-fade-in 3s 1s ease-out forwards;
animation: custom-fade-in 3s 6s ease-out forwards;
Comment thread
rabden marked this conversation as resolved.
Outdated
opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
Expand Down
123 changes: 123 additions & 0 deletions src/components/splash-screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"use client";
Comment thread
rabden marked this conversation as resolved.

import { useState, useEffect, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { cn } from "@/lib/utils";

export function SplashScreen() {
const [isVisible, setIsVisible] = useState(true);
Comment thread
rabden marked this conversation as resolved.
Outdated
const [showVideo, setShowVideo] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);

// Timer refs to allow precise clearing
const initialTimerRef = useRef<NodeJS.Timeout | null>(null);
const dismissTimerRef = useRef<NodeJS.Timeout | null>(null);
const fallbackTimerRef = useRef<NodeJS.Timeout | null>(null);

const dismissSplash = () => {
setIsVisible(false);
// Ensure all timers are cleared when dismissing
if (initialTimerRef.current) clearTimeout(initialTimerRef.current);
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
if (fallbackTimerRef.current) clearTimeout(fallbackTimerRef.current);
// Final safety: ensure scroll is restored
document.body.style.overflow = "unset";
};

useEffect(() => {
// Respect user's motion preferences
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;

if (prefersReducedMotion) {
setTimeout(() => setIsVisible(false), 0);
return;
}

// Phase 1: 1.5s delay before video appears
initialTimerRef.current = setTimeout(() => {
setShowVideo(true);

// Robustness: Start a max-duration fallback timer once we intend to show the video.
// If the video fails to load or the 'ended' event doesn't fire, we still dismiss.
fallbackTimerRef.current = setTimeout(() => {
dismissSplash();
}, 10000);
}, 1500);

return () => {
if (initialTimerRef.current) clearTimeout(initialTimerRef.current);
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
if (fallbackTimerRef.current) clearTimeout(fallbackTimerRef.current);
};
}, []);

useEffect(() => {
// Prevent scrolling while splash screen is visible
if (isVisible) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "unset";
}

return () => {
document.body.style.overflow = "unset";
};
}, [isVisible]);
Comment thread
rabden marked this conversation as resolved.

const handleVideoEnd = () => {
// Phase 3: Hold after video ends before fading out
dismissTimerRef.current = setTimeout(() => {
dismissSplash();
}, 1500);
};

const handleVideoError = () => {
console.error("SplashScreen video failed to load, dismissing.");
dismissSplash();
};
Comment thread
rabden marked this conversation as resolved.

if (!isVisible) return null;

return (
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.8, ease: "easeInOut" }}
// Block interactions while splash is visible
className="fixed inset-0 z-[10000] bg-background flex items-center justify-center pointer-events-auto"
aria-hidden="true"
Comment thread
rabden marked this conversation as resolved.
Outdated
>
Comment thread
rabden marked this conversation as resolved.
<AnimatePresence>
{showVideo && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 1, ease: "easeOut" }}
className="w-full max-w-[500px] px-6"
>
<video
ref={videoRef}
src="/animated-logo.webm"
Comment thread
rabden marked this conversation as resolved.
Comment thread
rabden marked this conversation as resolved.
Comment thread
rabden marked this conversation as resolved.
Comment thread
rabden marked this conversation as resolved.
autoPlay
muted
playsInline
Comment thread
rabden marked this conversation as resolved.
onEnded={handleVideoEnd}
onError={handleVideoError}
className={cn(
"w-full h-auto",
"invert hue-rotate-180 brightness-125 contrast-110 mix-blend-multiply",
"dark:invert-0 dark:hue-rotate-0 dark:brightness-110 dark:contrast-100 dark:mix-blend-screen"
)}
/>
Comment thread
rabden marked this conversation as resolved.
</motion.div>
)}
</AnimatePresence>
</motion.div>
)}
</AnimatePresence>
);
Comment thread
rabden marked this conversation as resolved.
}
Loading