diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index 9b56441..e34c840 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -228,3 +228,15 @@
scroll-behavior: auto !important;
}
}
+
+/* Splash hero idle animations as CSS (browser/compositor driven) instead of
+ framer-motion, so they cost no JS main-thread time. The reduced-motion block
+ above neutralizes them. */
+@keyframes splash-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
+@keyframes splash-breath { 0%, 100% { opacity: 0.9; } 50% { opacity: 1; } }
+@keyframes splash-ring-a { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -1; } }
+@keyframes splash-ring-b { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 1; } }
+.splash-float { animation: splash-float 6s ease-in-out infinite; }
+.splash-breath { animation: splash-breath 5s ease-in-out infinite; }
+.splash-ring-a { animation: splash-ring-a 5s linear infinite; }
+.splash-ring-b { animation: splash-ring-b 7s linear infinite; }
diff --git a/apps/web/src/components/landing/hero-splash.tsx b/apps/web/src/components/landing/hero-splash.tsx
index 462784b..ff18473 100644
--- a/apps/web/src/components/landing/hero-splash.tsx
+++ b/apps/web/src/components/landing/hero-splash.tsx
@@ -35,29 +35,22 @@ function SuchMark({ animate }: { animate: boolean }) {
{/* two Cherenkov glow segments circling the ring in opposite directions */}
{animate && (
<>
-
-
>
)}
{/* letters with a gentle breath */}
-
+
{/* S */}
{/* U */}
@@ -66,7 +59,7 @@ function SuchMark({ animate }: { animate: boolean }) {
{/* H */}
-
+
);
}
@@ -144,14 +137,11 @@ export function HeroSplash({ onEnter, sectionRef }: { onEnter: () => void; secti
style={{ animationDuration: "4.5s" }}
/>
diff --git a/packages/ui/src/procedural/nebula-field.tsx b/packages/ui/src/procedural/nebula-field.tsx
index 4eebb2a..f9c050d 100644
--- a/packages/ui/src/procedural/nebula-field.tsx
+++ b/packages/ui/src/procedural/nebula-field.tsx
@@ -1,6 +1,6 @@
'use client';
-import { motion, useAnimation, useScroll, useTransform } from "framer-motion";
+import { motion, useAnimation } from "framer-motion";
import { useEffect, useState, useCallback, useMemo } from "react";
import { cn } from "../lib/utils";
import { Monitor } from "lucide-react";
@@ -79,16 +79,12 @@ const StreamLine = ({ d, themeMode, onHit, width, index }: StreamLineProps) => {
);
};
-export const NebulaField = ({ className, density = 8, themeMode = 'dark', position }: NebulaProps) => {
+export const NebulaField = ({ className, density = 4, themeMode = 'dark', position }: NebulaProps) => {
// === ALL HOOKS MUST BE CALLED BEFORE ANY CONDITIONAL RETURNS ===
const monitorControls = useAnimation();
const [monitorColor, setMonitorColor] = useState(themeMode === 'light' ? "text-sky-500" : "text-cyan-400/70");
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
- // Parallax Scroll hooks (called unconditionally)
- const { scrollY } = useScroll();
- const yRange = useTransform(scrollY, [0, 1000], [0, 250]);
-
// Position calculations
const computerX = position?.x ?? 18;
const computerY = position?.y ?? 18;
@@ -191,7 +187,7 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark', positi
{/* Slowly-pulsing Cherenkov "reactor pool" bloom */}
@@ -222,11 +217,10 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark', positi
-
{paths.map((d: string, i: number) => (
))}
-
+
);
};