STYLED - Add CSS hero entrance, title halo, and scroll cue

Replace the framer-motion hero entrance with pure-CSS staggered animations so
the hero is painted immediately instead of flashing blank until hydration.
Add a focused brand glow behind the hero title and an animated scroll cue at
the bottom of the home hero. Entrance and cue respect prefers-reduced-motion.
This commit is contained in:
moh
2026-09-20 18:53:48 +02:00
parent bb9f1a2f6e
commit 5b019052b1
2 changed files with 102 additions and 37 deletions
+13 -37
View File
@@ -1,30 +1,11 @@
"use client";
import type { ReactNode } from "react";
import { ArrowDown } from "lucide-react";
import { motion } from "framer-motion";
import { Container } from "@/components/layout/container";
import { HeroMotionBackdrop } from "@/components/layout/hero-motion-backdrop";
import { cn } from "@/lib/utils";
const heroItemVariants = {
hidden: { opacity: 0, y: 16 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.54, ease: [0.22, 1, 0.36, 1] as [number, number, number, number] },
},
};
const heroContainerVariants = {
hidden: {},
visible: {
transition: { staggerChildren: 0.13, delayChildren: 0.08 },
},
};
type HeroTone = "default" | "accent" | "soft";
type HeroLineAlign = "start" | "center" | "end";
@@ -128,6 +109,14 @@ export function HeroShell({
{children}
</div>
</Container>
{variant === "home" ? (
<HeroScrollLink
href="#home-content"
label="Scroll to content"
className="hero-scroll-anchor hidden sm:inline-flex"
/>
) : null}
</section>
);
}
@@ -136,27 +125,14 @@ export function HeroContentMotion({
children,
className,
}: HeroContentMotionProps) {
return (
<motion.div
className={className}
initial="hidden"
animate="visible"
variants={heroContainerVariants}
>
{children}
</motion.div>
);
return <div className={cn("hero-content", className)}>{children}</div>;
}
export function HeroMotionItem({
children,
className,
}: HeroContentMotionProps) {
return (
<motion.div className={className} variants={heroItemVariants}>
{children}
</motion.div>
);
return <div className={cn("hero-rise", className)}>{children}</div>;
}
export function HeroTitle({
@@ -167,7 +143,7 @@ export function HeroTitle({
}: HeroTitleProps) {
const isArabic = locale === "ar";
const titleClassName = cn(
"text-balance font-semibold text-[hsl(var(--hero-ink))]",
"hero-title hero-rise text-balance font-semibold text-[hsl(var(--hero-ink))]",
variant === "home"
? isArabic
? "mt-6 flex w-full max-w-[26rem] flex-col gap-y-1 px-4 text-[clamp(2.85rem,11vw,5.9rem)] leading-[1.06] tracking-[-0.03em] sm:max-w-[30rem] sm:px-0 md:max-w-[36rem] lg:max-w-[32rem]"
@@ -177,7 +153,7 @@ export function HeroTitle({
);
return (
<motion.h1 className={titleClassName} variants={heroItemVariants}>
<h1 className={titleClassName}>
{lines.map((line, index) => (
<span
key={`${index}-${line.text}`}
@@ -202,7 +178,7 @@ export function HeroTitle({
)}
</span>
))}
</motion.h1>
</h1>
);
}