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 →Preview
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.

Source
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;
}
}
Dependencies
gsap