AnimationsSimpleApril 8, 2026

Image Scale on Hover

Image starts slightly scaled up (1.1) inside an overflow-hidden container and returns to scale(1) on hover. The container clips the overflow, giving the image room to breathe inward.

View Full Demo →
demo.jsx
import ImageScaleOnHover from "./index.jsx";
import { imageScaleOnHover } from "@/content/animations/demo-data.js";
import styles from "./demo.module.css";

export default function ImageScaleOnHoverDemo() {
  return (
    <div className={styles.demo}>
      <div className={styles.top}>
        <p className={styles.label}>Portfolio — 2024</p>
        <h2 className={styles.heading}>Selected Work</h2>
      </div>
      <ImageScaleOnHover {...imageScaleOnHover} />
    </div>
  );
}
index.jsx
import styles from "./styles.module.css";

export default function ImageScaleOnHover({ items = [] }) {
  return (
    <div className={styles.grid}>
      {items.map((item) => (
        <a key={item.label} href={item.href} className={styles.item}>
          <div className={styles.imageWrap}>
            <div
              className={styles.image}
              style={{ backgroundImage: `url(${item.image})` }}
            />
          </div>
          <span className={styles.label}>{item.label}</span>
        </a>
      ))}
    </div>
  );
}
demo.module.css
.demo {
  min-height: 100vh;
  background: #0a0a0a;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 5rem 3rem;
  gap: 3rem;
}

.top {
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.label {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.35);
}

.heading {
  font-size: clamp(2rem, 5vw, 4rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1;
  color: #ffffff;
}

/* Override item label color for dark bg */
.demo :global(.label) {
  color: rgba(255, 255, 255, 0.6);
}
styles.module.css
.grid {
  display: flex;
  gap: 2rem;
  padding: 3rem;
  justify-content: center;
  flex-wrap: wrap;
}

.item {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  text-decoration: none;
  color: #1a1a1a;
}

.imageWrap {
  width: 200px;
  height: 260px;
  overflow: hidden;
  border-radius: 6px;
}

.image {
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center;
  transform: scale(1.1);
  transition: transform 700ms cubic-bezier(0.25, 1, 0.5, 1);
}

.item:hover .image {
  transform: scale(1);
}

.label {
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: -0.01em;
}

Apr 27, 2026

ANIMATIONS

Cursor Hover Label

A custom cursor label that follows the mouse and fades in when hovering trigger elements. Uses GSAP quickTo for smooth tracking. Trigger elements use data-cursor-label attributes to set label text. Inspired by portfolio/agency sites like Studio PIC.

Apr 27, 2026

ANIMATIONS

Section Transition 01

GSAP ScrollTrigger-based section transition system. Sibling sections opt into parallax, pin, or reveal modes via data attributes. Supports y offset, overlay opacity, and overlay color per section. Mobile strategy simplifies motion on smaller screens.

Apr 27, 2026

ANIMATIONS

Text Reveal 01

GSAP SplitText-based text reveal system. Text elements opt in with data-reveal-01 and split into lines, words, or characters. Supports load-time reveals, scroll-triggered reveals, scrubbed scroll reveals, and manual split-only mode for custom timelines. Per-element overrides for duration, stagger, delay, ease, and replay behavior.