AnimationsIntermediateApril 8, 2026

Pixel Blocks Transition

A menu overlay built from 20 columns of square pixel blocks. When toggled, blocks fade in with shuffled random delays (Fisher-Yates) creating a scattered fill effect. The menu fades in after the pixels settle, then reverses on close.

View Full Demo →

Component Vault

No-5 Studio

index.jsx
"use client";
import { useState, useEffect, useMemo } from "react";
import { motion } from "framer-motion";
import styles from "./styles.module.css";

const NUM_COLS = 20;

// Fisher-Yates shuffle — returns a new array
function shuffle(arr) {
  const a = [...arr];
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

const blockAnim = {
  initial: { opacity: 0 },
  open: (delay) => ({ opacity: 1, transition: { duration: 0, delay: 0.03 * delay } }),
  closed: (delay) => ({ opacity: 0, transition: { duration: 0, delay: 0.03 * delay } }),
};

const menuAnim = {
  initial:  { opacity: 0 },
  open:   { opacity: 1, transition: { delay: 0.5, duration: 0.3 } },
  closed: { opacity: 0, transition: { duration: 0.15 } },
};

export default function PixelBlocksTransition({ color = "#111111" }) {
  const [isOpen, setIsOpen] = useState(false);
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const update = () =>
      setDimensions({ width: window.innerWidth, height: window.innerHeight });
    update();
    window.addEventListener("resize", update);
    return () => window.removeEventListener("resize", update);
  }, []);

  // Pre-compute stable shuffled delay indexes for all 20 columns
  const columnDelays = useMemo(() => {
    if (!dimensions.width) return [];
    const blockSize = dimensions.width * 0.05;
    const numBlocks = Math.ceil(dimensions.height / blockSize);
    return Array.from({ length: NUM_COLS }, () =>
      shuffle(Array.from({ length: numBlocks }, (_, i) => i))
    );
  }, [dimensions.width, dimensions.height]);

  return (
    <div className={styles.demo}>
      {/* Page content */}
      <div className={styles.page}>
        <h2 className={styles.pageTitle}>Component Vault</h2>
        <p className={styles.pageSub}>No-5 Studio</p>
      </div>

      {/* Burger toggle */}
      <button
        className={`${styles.burger} ${isOpen ? styles.burgerOpen : ""}`}
        onClick={() => setIsOpen((v) => !v)}
        aria-label="Toggle menu"
      >
        <span /><span /><span />
      </button>

      {/* Pixel block overlay */}
      {columnDelays.length > 0 && (
        <div className={styles.pixelBg} aria-hidden="true">
          {columnDelays.map((delays, colIdx) => (
            <div key={colIdx} className={styles.column}>
              {delays.map((delay, blockIdx) => (
                <motion.div
                  key={blockIdx}
                  className={styles.block}
                  variants={blockAnim}
                  custom={delay}
                  initial="initial"
                  animate={isOpen ? "open" : "closed"}
                  style={{ backgroundColor: color }}
                />
              ))}
            </div>
          ))}
        </div>
      )}

      {/* Menu — appears after pixels fill in */}
      <motion.nav
        className={styles.menu}
        variants={menuAnim}
        initial="initial"
        animate={isOpen ? "open" : "closed"}
      >
        {["Work", "About", "Lab", "Contact"].map((item) => (
          <p key={item} className={styles.menuItem}>{item}</p>
        ))}
      </motion.nav>
    </div>
  );
}
styles.module.css
.demo {
  position: relative;
  width: 100%;
  height: 100dvh;
  overflow: hidden;
  background: #f5f5f0;
  color: #111;
}

/* ── Page content ── */

.page {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.pageTitle {
  font-size: clamp(2.5rem, 8vw, 6rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  margin: 0;
}

.pageSub {
  font-size: 1rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.4;
  margin: 0;
}

/* ── Burger button ── */

.burger {
  position: absolute;
  top: 1.5rem;
  right: 1.5rem;
  z-index: 200;
  width: 2.5rem;
  height: 2.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}

.burger span {
  display: block;
  width: 100%;
  height: 1.5px;
  background: #111;
  transform-origin: center;
  transition: transform 0.3s ease, opacity 0.2s;
}

.burgerOpen span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); background: #f5f5f0; }
.burgerOpen span:nth-child(2) { opacity: 0; background: #f5f5f0; }
.burgerOpen span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); background: #f5f5f0; }

/* ── Pixel overlay ── */

.pixelBg {
  position: absolute;
  inset: 0;
  display: flex;
  z-index: 50;
  pointer-events: none;
  overflow: hidden;
}

.column {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.block {
  width: 100%;
  aspect-ratio: 1;
}

/* ── Menu ── */

.menu {
  position: absolute;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  pointer-events: none;
}

.menuItem {
  font-size: clamp(2.5rem, 7vw, 5rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  color: #f5f5f0;
  margin: 0;
  cursor: pointer;
  pointer-events: all;
  transition: opacity 0.2s;
}

.menuItem:hover {
  opacity: 0.5;
}
  • framer-motion

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.