Web InnoventixFreeCode

Stamp Effect

Original · free

A postage-stamp perforated frame around the image.

byWeb InnoventixReact + Tailwind
stampimageeffect
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/imgfx-stamp.json
imgfx-stamp.tsx
"use client";

import { useRef } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";

type Stamp = {
  src: string;
  alt: string;
  country: string;
  value: string;
  tilt: number;
  z: number;
  lift: string;
};

const STAMPS: Stamp[] = [
  {
    src: "/img/gallery/g19.webp",
    alt: "Sunlit mountain ridge fading into layered morning haze",
    country: "Postes",
    value: "12c",
    tilt: -6,
    z: 10,
    lift: "translate-y-4",
  },
  {
    src: "/img/gallery/g07.webp",
    alt: "Quiet coastline where pale sand meets a calm tide",
    country: "Correos",
    value: "45c",
    tilt: 3,
    z: 30,
    lift: "-translate-y-3",
  },
  {
    src: "/img/gallery/g28.webp",
    alt: "Dense forest canopy seen from directly overhead",
    country: "Air Mail",
    value: "08c",
    tilt: 7,
    z: 20,
    lift: "translate-y-6",
  },
];

export default function ImgfxStamp() {
  const ref = useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, margin: "-80px" });
  const reduce = useReducedMotion();

  return (
    <section className="relative w-full overflow-hidden bg-neutral-100 px-6 py-24 dark:bg-neutral-950 sm:px-10 sm:py-28">
      <style>{`
        @keyframes imgfxstamp-in {
          from { opacity: 0; transform: translateY(18px) scale(.96); }
          to   { opacity: 1; transform: translateY(0) scale(1); }
        }
        .imgfxstamp-perf {
          --imgfxstamp-r: 6.5px;
          --imgfxstamp-p: 20px;
          -webkit-mask:
            radial-gradient(var(--imgfxstamp-r) at 50% 0,   #0000 98%, #000) 0 0    / var(--imgfxstamp-p) 100% repeat-x,
            radial-gradient(var(--imgfxstamp-r) at 50% 100%, #0000 98%, #000) 0 100% / var(--imgfxstamp-p) 100% repeat-x,
            radial-gradient(var(--imgfxstamp-r) at 0 50%,   #0000 98%, #000) 0 0    / 100% var(--imgfxstamp-p) repeat-y,
            radial-gradient(var(--imgfxstamp-r) at 100% 50%, #0000 98%, #000) 100% 0 / 100% var(--imgfxstamp-p) repeat-y;
          -webkit-mask-composite: source-in;
          mask:
            radial-gradient(var(--imgfxstamp-r) at 50% 0,   #0000 98%, #000) 0 0    / var(--imgfxstamp-p) 100% repeat-x,
            radial-gradient(var(--imgfxstamp-r) at 50% 100%, #0000 98%, #000) 0 100% / var(--imgfxstamp-p) 100% repeat-x,
            radial-gradient(var(--imgfxstamp-r) at 0 50%,   #0000 98%, #000) 0 0    / 100% var(--imgfxstamp-p) repeat-y,
            radial-gradient(var(--imgfxstamp-r) at 100% 50%, #0000 98%, #000) 100% 0 / 100% var(--imgfxstamp-p) repeat-y;
          mask-composite: intersect;
        }
        .imgfxstamp-card {
          transition: transform .5s cubic-bezier(.2,.8,.2,1);
          will-change: transform;
        }
        .imgfxstamp-card:hover {
          transform: rotate(0deg) translateY(-10px) scale(1.03) !important;
          z-index: 40;
        }
        .imgfxstamp-mark {
          transition: opacity .45s ease, transform .45s ease;
        }
        .imgfxstamp-card:hover .imgfxstamp-mark {
          opacity: .85;
          transform: rotate(-14deg) scale(1);
        }
        @media (prefers-reduced-motion: reduce) {
          .imgfxstamp-card,
          .imgfxstamp-card:hover,
          .imgfxstamp-mark { transition: none; transform: none; }
          .imgfxstamp-card:hover { transform: none !important; }
        }
      `}</style>

      <div className="mx-auto max-w-5xl text-center">
        <p className="text-xs font-semibold uppercase tracking-[0.35em] text-rose-600 dark:text-rose-400">
          Image Effect · Perforated Stamp
        </p>
        <h2 className="mt-4 text-3xl font-bold tracking-tight text-neutral-900 dark:text-neutral-50 sm:text-4xl">
          Every photo, franked and mailed
        </h2>
        <p className="mx-auto mt-4 max-w-xl text-base text-neutral-600 dark:text-neutral-400">
          A punched postage-stamp frame with real perforated edges, a paper border, and a
          cancellation mark that lands on hover.
        </p>
      </div>

      <div
        ref={ref}
        className="mx-auto mt-16 flex max-w-4xl flex-wrap items-center justify-center gap-x-2 gap-y-10 sm:gap-x-6"
      >
        {STAMPS.map((s, i) => (
          <motion.figure
            key={s.src}
            className={`imgfxstamp-card group relative ${s.lift}`}
            style={{
              zIndex: s.z,
              transform: `rotate(${s.tilt}deg)`,
              animation:
                inView && !reduce
                  ? `imgfxstamp-in .7s cubic-bezier(.2,.8,.2,1) ${i * 0.12}s both`
                  : undefined,
              opacity: inView || reduce ? 1 : 0,
            }}
          >
            <div className="drop-shadow-xl">
              <div className="imgfxstamp-perf bg-[#faf7f0] p-4 dark:bg-[#f3efe6]">
                <div className="relative w-40 overflow-hidden ring-1 ring-black/10 sm:w-48">
                  {/* eslint-disable-next-line @next/next/no-img-element */}
                  <img
                    src={s.src}
                    alt={s.alt}
                    loading="lazy"
                    draggable={false}
                    className="aspect-[3/4] w-full select-none object-cover"
                  />

                  <span className="pointer-events-none absolute left-2 top-2 rounded-sm bg-neutral-900/70 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-widest text-white backdrop-blur-sm">
                    {s.country}
                  </span>
                  <span className="pointer-events-none absolute bottom-2 right-2 font-serif text-2xl font-black leading-none text-white [text-shadow:0_1px_3px_rgba(0,0,0,.55)]">
                    {s.value}
                  </span>

                  <span
                    aria-hidden
                    className="imgfxstamp-mark pointer-events-none absolute -right-3 -top-3 grid h-16 w-16 rotate-[-24deg] scale-90 place-items-center rounded-full border-[3px] border-rose-700/70 text-center font-mono text-[7px] font-bold uppercase leading-tight tracking-tight text-rose-700/70 opacity-0"
                  >
                    Web
                    <br />
                    Innoventix
                    <br />
                    14·07·26
                  </span>
                </div>
              </div>
            </div>
            <figcaption className="sr-only">{s.alt}</figcaption>
          </motion.figure>
        ))}
      </div>

      <p className="mx-auto mt-14 max-w-md text-center text-sm text-neutral-500 dark:text-neutral-500">
        Hover any stamp to straighten it and drop the postmark.
      </p>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →