Header 156
HeroText Align LeftImage/Video BottomButtonsMultiple Images

Code
relume-header156.tsx
import { Button, type ButtonProps } from "@/components/ui/button";
import { useMediaQuery } from "@/hooks/use-media-query";
import { motion, useScroll, useTransform } from "framer-motion";
import { useRef } from "react";
type ImageProps = {
src: string;
alt?: string;
};
type Props = {
heading: string;
description: string;
buttons: ButtonProps[];
images: ImageProps[];
};
export type Header156Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
export const Header156 = (props: Header156Props) => {
const { heading, description, buttons, images } = {
...Header156Defaults,
...props,
};
const containerRef = useRef<HTMLElement>(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start start", "end end"],
});
const isMobile = useMediaQuery("(max-width: 767px)");
const isTablet = useMediaQuery("(min-width: 768px) and (max-width: 991px)");
const desktopOffsets = [
{ x: "-20%", y: "-20%", scale: 0.8 },
{ x: "0%", y: "110%", scale: 0.8 },
{ x: "0%", y: "-60%", scale: 0.8 },
{ x: "20%", y: "50%", scale: 0.8 },
];
const mobileOffsets = [
{ x: "0%", y: "-10vh", scale: 0.8 },
{ x: "10%", y: "-15vh", scale: 0.8 },
{ x: "-10%", y: "20vh", scale: 0.8 },
{ x: "0%", y: "15vh", scale: 0.8 },
];
const tabletOffsets = [
{ x: "0%", y: "-30%", scale: 0.8 },
{ x: "0%", y: "140%", scale: 0.8 },
{ x: "0%", y: "-60%", scale: 0.8 },
{ x: "0%", y: "118%", scale: 0.8 },
];
const imageOffsets = isMobile ? mobileOffsets : isTablet ? tabletOffsets : desktopOffsets;
const transforms = imageOffsets.map((offset) => ({
x: useTransform(scrollYProgress, [0, 1], ["0%", offset.x]),
y: useTransform(scrollYProgress, [0, 1], ["0%", offset.y]),
scale: useTransform(scrollYProgress, [0, 1], [1, offset.scale]),
}));
const imageTransforms = imageOffsets.map(() => useTransform(scrollYProgress, [0, 1], [1.2, 1]));
const descriptionOpacity = useTransform(scrollYProgress, [0.9, 1], [0, 1]);
return (
<section ref={containerRef} className="relative min-h-[180vh] lg:mb-28">
<div className="px-[5%] pt-16 md:pt-24 lg:pt-28">
<div className="container">
<div className="grid grid-cols-1 items-start gap-x-12 gap-y-5 md:grid-cols-2 md:gap-y-8 lg:gap-x-20 lg:gap-y-16">
<h1 className="text-h1 font-bold">{heading}</h1>
<div>
<p className="text-medium">{description}</p>
<div className="mt-6 flex flex-wrap gap-4 md:mt-8">
{buttons.map((button, index) => (
<Button key={index} {...button}>
{button.title}
</Button>
))}
</div>
</div>
</div>
</div>
</div>
<div className="sticky top-0 flex items-start justify-center px-[5%] pt-12 pb-16 md:h-screen md:py-20">
<div className="container">
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
{images.map((image, index) => (
<motion.div
key={index}
className="w-full overflow-hidden rounded-image"
style={{
x: transforms[index]?.x ?? "0%",
y: transforms[index]?.y ?? "0%",
scale: transforms[index]?.scale ?? 1,
}}
>
<motion.img
src={image.src}
className="aspect-2/3 size-full object-cover md:aspect-3/4"
style={{ scale: imageTransforms[index] ?? 1 }}
alt={image.alt}
/>
</motion.div>
))}
</div>
</div>
<motion.div
style={{
opacity: descriptionOpacity,
}}
className="absolute self-center text-center"
>
<div className="px-[5%]">
<div className="mx-auto w-full max-w-md">
<p className="text-medium">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in
eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum
nulla, ut commodo diam libero vitae erat.
</p>
</div>
</div>
</motion.div>
</div>
</section>
);
};
export const Header156Defaults: Props = {
heading: "Medium length hero heading goes here",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
buttons: [{ title: "Button" }, { title: "Button", variant: "secondary" }],
images: [
{
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
alt: "Relume placeholder image 1",
},
{
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
alt: "Relume placeholder image 2",
},
{
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
alt: "Relume placeholder image 3",
},
{
src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
alt: "Relume placeholder image 4",
},
],
};npm i @radix-ui/react-accordion @radix-ui/react-dialog @radix-ui/react-slot @radix-ui/react-tabs class-variance-authority@0.7.1 clsx@^2.1.1 embla-carousel-react@^8.5.2 framer-motion motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2


