diff --git a/apps/web/public/images/animations/morph_splash.svg b/apps/web/public/images/animations/morph_splash.svg new file mode 100644 index 0000000..7171471 --- /dev/null +++ b/apps/web/public/images/animations/morph_splash.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/images/animations/morph_splash_light.svg b/apps/web/public/images/animations/morph_splash_light.svg new file mode 100644 index 0000000..59525fe --- /dev/null +++ b/apps/web/public/images/animations/morph_splash_light.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 7e6d5cc..b71bc59 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -318,4 +318,12 @@ } /* Soft aura behind the hero title (decorative). Gentle breathing scale/opacity. */ @keyframes hero-aura { 0%, 100% { opacity: 0.55; transform: translateX(-50%) scale(1); } 50% { opacity: 0.8; transform: translateX(-50%) scale(1.08); } } -.hero-aura { animation: hero-aura 7s ease-in-out infinite; } \ No newline at end of file +.hero-aura { animation: hero-aura 7s ease-in-out infinite; } +/* Pre-paint splash suppression. The inline script stamps this attribute when the + visitor has already entered this session (or asked for reduced motion), so the + server-rendered overlay is hidden in the very first painted frame instead of being + removed a frame later by hydration -- which is what the refresh flash was. The overlay + is `fixed`, so this removes nothing from the document flow and shifts no layout. */ +html[data-splash="skip"] #splash-overlay { + display: none !important; +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index d06f0de..c14b68c 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -111,6 +111,28 @@ export default function RootLayout({ type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteData) }} /> + {/* + The splash is server-rendered, so on a refresh the browser painted it and only + THEN did hydration decide this visitor had already entered and remove it. That + one painted frame is the flash. React cannot prevent it: even useLayoutEffect + runs after the first paint of the server HTML. + + So the decision moves before paint. This script is synchronous and in , + which means it runs before the body is painted; it stamps the root element and + a plain CSS rule (globals.css, [data-splash="skip"]) hides the overlay in the + same frame it would otherwise have appeared in. The splash is `fixed`, so + hiding it moves nothing and costs no layout shift. + + Kept deliberately tiny and dependency-free because it is render-blocking. + */} +