SectionsSimpleApril 16, 2026

About Intro

A 12-column grid about section with a line-mask heading reveal, responsive text, and a sticky autoplay video on the right column. Stacks to single column on mobile with video above text.

View Full Demo →

Myself

Passionate about merging design and engineering, I craft smooth, interactive experiences with purpose. With a focus on motion, performance, and detail, I help bring digital products to life for forward-thinking brands around the world.

Passionate about merging design and engineering, I craft smooth, interactive experiences with purpose. With a focus on motion, performance, and detail, I help bring digital products to life for forward-thinking brands around the world.

demo.jsx
import AboutIntro from "./index.jsx";
import { aboutIntro } from "../demo-data.js";

export default function AboutIntroDemo() {
  return <AboutIntro {...aboutIntro} />;
}
index.jsx
"use client";

import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import styles from "./styles.module.css";

gsap.registerPlugin(ScrollTrigger);

function Media({ videoSrc, imageSrc, poster, className }) {
  if (videoSrc) {
    return (
      <video
        className={className}
        src={videoSrc}
        autoPlay
        muted
        loop
        playsInline
        poster={poster}
      />
    );
  }
  if (imageSrc) {
    return <img className={className} src={imageSrc} alt="" />;
  }
  return null;
}

export default function AboutIntro({
  heading = "Myself",
  text = "",
  videoSrc,
  imageSrc,
  poster,
}) {
  const sectionRef = useRef(null);
  const headingLineRef = useRef(null);
  const mobileTextLineRef = useRef(null);

  useEffect(() => {
    const ctx = gsap.context(() => {
      /* ---- Heading line reveal ---- */
      if (headingLineRef.current) {
        gsap.from(headingLineRef.current, {
          yPercent: 100,
          duration: 1,
          ease: "power4.out",
          scrollTrigger: {
            trigger: headingLineRef.current,
            start: "top 85%",
            once: true,
          },
        });
      }

      /* ---- Mobile text line reveal ---- */
      if (mobileTextLineRef.current) {
        gsap.from(mobileTextLineRef.current, {
          yPercent: 100,
          duration: 1,
          ease: "power4.out",
          scrollTrigger: {
            trigger: mobileTextLineRef.current,
            start: "top 85%",
            once: true,
          },
        });
      }
    }, sectionRef);

    return () => ctx.revert();
  }, []);

  return (
    <section ref={sectionRef} className={styles.section}>
      {/* Left column — content */}
      <div className={styles.content}>
        {/* Heading */}
        <h4 className={styles.heading}>
          <span className={styles.lineMask}>
            <span ref={headingLineRef} className={styles.line}>
              {heading}
            </span>
          </span>
        </h4>

        {/* Mobile media */}
        <div className={styles.mediaMobile}>
          <div className={styles.mediaWrap}>
            <Media videoSrc={videoSrc} imageSrc={imageSrc} poster={poster} className={styles.mediaElement} />
          </div>
        </div>

        {/* Desktop paragraph */}
        <p className={styles.textDesktop}>{text}</p>

        {/* Mobile paragraph with line-mask */}
        <p className={styles.textMobile}>
          <span className={styles.lineMask}>
            <span ref={mobileTextLineRef} className={styles.line}>
              {text}
            </span>
          </span>
        </p>
      </div>

      {/* Right column — desktop media */}
      <div className={styles.mediaDesktop}>
        <div className={styles.stickyWrap}>
          <div className={styles.mediaWrap}>
            <Media videoSrc={videoSrc} imageSrc={imageSrc} className={styles.mediaElement} />
          </div>
        </div>
      </div>
    </section>
  );
}
styles.module.css
/* ---- Grid layout ---- */
.section {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  width: 100%;
  padding: 4rem 2rem;
}

/* ---- Content column ---- */
.content {
  grid-column: 1 / -1;
}

/* ---- Heading ---- */
.heading {
  font-size: clamp(0.875rem, 1.2vw, 1rem);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #000;
  margin-bottom: 2rem;
}

/* ---- Line mask (reveal wrapper) ---- */
.lineMask {
  overflow: hidden;
  display: block;
}

.line {
  display: block;
  will-change: transform;
}

/* ---- Text ---- */
.textDesktop {
  display: none;
  font-size: clamp(16px, 1.4vw, 20px);
  line-height: 1.55;
  color: rgba(0, 0, 0, 0.8);
  max-width: 55ch;
}

.textMobile {
  font-size: 1rem;
  line-height: 1.55;
  color: rgba(0, 0, 0, 0.8);
}

/* ---- Media (video or image) ---- */
.mediaWrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 0.5rem;
}

.mediaElement {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---- Mobile media ---- */
.mediaMobile {
  margin-bottom: 2rem;
}

/* ---- Desktop media column ---- */
.mediaDesktop {
  display: none;
}

.stickyWrap {
  position: sticky;
  top: 15vh;
}

/* ---- Desktop ---- */
@media (min-width: 1024px) {
  .section {
    grid-template-columns: repeat(12, 1fr);
    gap: 3rem;
    padding: 6rem 2rem;
  }

  .content {
    grid-column: 1 / 8;
  }

  .mediaDesktop {
    display: block;
    grid-column: 8 / 13;
  }

  .textDesktop {
    display: block;
  }

  .textMobile {
    display: none;
  }

  .mediaMobile {
    display: none;
  }
}

/* ---- Mobile ---- */
@media (max-width: 1023px) {
  .section {
    padding: 3rem 1rem;
  }
}
  • gsap

May 11, 2026

SECTIONS

Dual Push Cards

Two-up CTA card section with scroll-driven parallax on each card's background image. Cards scale down and drift vertically as you scroll past. Glassmorphic blur buttons at bottom-left. Stacks on mobile, side-by-side grid on desktop.

May 4, 2026

SECTIONS

Portfolio Grid

A responsive portfolio showcase section with a header tagline, blinking cursor counters, a 2-up/4-col project card grid with hover-zoom images and data-label metadata, plus a full-width CTA button. Scroll-triggered fade and move-up animations via GSAP.

May 1, 2026

SECTIONS

Logo Wall Cycle

A responsive logo grid that cycles through brand logos with smooth GSAP-powered swap animations. Shows 8 logos on desktop and 6 on tablet, shuffling hidden logos into view on a timed loop. Pauses when out of viewport or tab is hidden.