Blocks

Layout 488

standard
Text Align CenterImage/Video Center1 ColumnImageBackground ImageFeature Section
Layout 488

Code

relume-layout488.tsx
"use client";

import { motion, useScroll, useTransform } from "motion/react";
import { useMediaQuery } from "@/hooks/use-media-query";
import { useRef } from "react";

type ImageProps = {
  src: string;
  alt: string;
};

type Props = {
  leftHeading: string;
  image: ImageProps;
  rightHeading: string;
  mobileHeading: string;
};

export type Layout488Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;

export const Layout488 = (props: Layout488Props) => {
  const { leftHeading, image, rightHeading, mobileHeading } = {
    ...Layout488Defaults,
    ...props,
  };

  const isMobile = useMediaQuery("(max-width: 767px)");

  const sectionRef = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: sectionRef,
    offset: ["start start", "end end"],
  });

  const createTransform = (mobileValues: number[], desktopValues: number[]) =>
    useTransform(scrollYProgress, [0, 0.6], isMobile ? mobileValues : desktopValues);

  const leftHeadingTranslate = { x: useTransform(scrollYProgress, [0, 0.6], ["10rem", "0rem"]) };
  const rightHeadingTranslate = { x: useTransform(scrollYProgress, [0, 0.6], ["-10rem", "0rem"]) };

  const imageScale = createTransform([0.7, 1], [0.2, 1]);

  const overlayMotion = {
    width: useTransform(
      scrollYProgress,
      [0.6, 1],
      isMobile ? ["10vw", "250vw"] : ["10vw", "200vw"],
    ),
    height: useTransform(
      scrollYProgress,
      [0.6, 1],
      isMobile ? ["10vw", "250vw"] : ["10vw", "200vw"],
    ),
  };

  return (
    <section ref={sectionRef} className="relative h-[200vh]">
      <div className="sticky top-0 grid h-svh grid-cols-[60%] content-center items-center justify-center justify-items-center gap-4 overflow-hidden px-[5%] md:grid-cols-[40%_max-content_40%] md:gap-8 lg:h-screen">
        <motion.h2
          className="relative z-10 hidden justify-self-end text-[4rem] font-bold whitespace-nowrap md:flex lg:text-[6rem]"
          style={leftHeadingTranslate}
        >
          {leftHeading}
        </motion.h2>
        <motion.div
          className="relative order-first flex w-full max-w-[24rem] min-w-16 items-center justify-center md:order-none"
          style={{ scale: imageScale }}
        >
          <img
            src={image.src}
            alt={image.alt}
            className="aspect-square size-full rounded-full object-cover"
          />
          <motion.div
            className="absolute -z-10 rounded-full bg-scheme-foreground"
            style={overlayMotion}
          />
        </motion.div>
        <motion.h2
          className="relative z-10 hidden justify-self-start text-[4rem] font-bold whitespace-nowrap md:flex lg:text-[6rem]"
          style={rightHeadingTranslate}
        >
          {rightHeading}
        </motion.h2>
        <motion.h2 className="relative z-10 mt-10 flex justify-self-center text-h2 font-bold whitespace-nowrap md:hidden">
          {mobileHeading}
        </motion.h2>
      </div>
      <div className="absolute inset-0 -z-10 mt-[100vh]" />
    </section>
  );
};

export const Layout488Defaults: Props = {
  leftHeading: "Heading",
  image: {
    src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
    alt: "Relume placeholder",
  },
  rightHeading: "goes here",
  mobileHeading: "Heading goes here",
};

npm i @radix-ui/react-slot class-variance-authority@0.7.1 clsx@^2.1.1 motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2