Web InnoventixFreeCode

Ticket Cut Effect

Original · free

A ticket-style frame with notched edges.

byWeb InnoventixReact + Tailwind
ticketcutimageeffect
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-ticket-cut.json
imgfx-ticket-cut.tsx
"use client";

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

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

  const barcode: number[] = [
    2, 1, 3, 1, 1, 2, 4, 1, 2, 1, 3, 2, 1, 1, 2, 3, 1, 2, 1, 4, 1, 2, 2, 1, 3,
    1, 1, 2, 1, 3,
  ];

  return (
    <section className="relative w-full overflow-hidden bg-neutral-100 px-6 py-20 sm:py-28 dark:bg-neutral-950">
      <style>{`
        @keyframes tkc-shine {
          0%   { transform: translateX(-120%) skewX(-12deg); opacity: 0; }
          14%  { opacity: 1; }
          60%  { opacity: 1; }
          100% { transform: translateX(220%) skewX(-12deg); opacity: 0; }
        }
        @keyframes tkc-float {
          0%, 100% { transform: translateY(0); }
          50%      { transform: translateY(-6px); }
        }
        .tkc-ticket {
          --tkc-tear: 71%;
          -webkit-mask:
            radial-gradient(circle 16px at left var(--tkc-tear), transparent 15px, #000 15.5px),
            radial-gradient(circle 16px at right var(--tkc-tear), transparent 15px, #000 15.5px);
          -webkit-mask-composite: source-in;
          mask:
            radial-gradient(circle 16px at left var(--tkc-tear), transparent 15px, #000 15.5px),
            radial-gradient(circle 16px at right var(--tkc-tear), transparent 15px, #000 15.5px);
          mask-composite: intersect;
        }
        .tkc-wrap { animation: tkc-float 6s ease-in-out infinite; }
        .tkc-shine {
          animation: tkc-shine 1.15s cubic-bezier(0.4, 0, 0.2, 1) forwards;
        }
        @media (prefers-reduced-motion: reduce) {
          .tkc-wrap { animation: none; }
          .tkc-shine { animation: none; opacity: 0; }
        }
      `}</style>

      <div className="mx-auto max-w-2xl">
        <motion.div
          initial={reduce ? false : { opacity: 0, y: 24 }}
          animate={inView && !reduce ? { opacity: 1, y: 0 } : undefined}
          transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
          className="mb-12 text-center"
        >
          <p className="mb-3 text-xs font-semibold uppercase tracking-[0.35em] text-rose-500 dark:text-rose-400">
            Image Effect
          </p>
          <h2 className="text-4xl font-black tracking-tight text-neutral-900 sm:text-5xl dark:text-neutral-50">
            Ticket Cut
          </h2>
          <p className="mx-auto mt-4 max-w-md text-sm leading-relaxed text-neutral-500 dark:text-neutral-400">
            A die-cut ticket frame — punched notches and a torn perforation
            line. Hover the pass for a foil sweep.
          </p>
        </motion.div>

        <motion.div
          ref={ref}
          initial={reduce ? false : { opacity: 0, y: 40, rotate: -2 }}
          animate={
            inView && !reduce ? { opacity: 1, y: 0, rotate: 0 } : undefined
          }
          transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1], delay: 0.1 }}
          className="flex justify-center"
        >
          <div className="tkc-wrap group relative w-full max-w-sm [filter:drop-shadow(0_24px_40px_rgba(0,0,0,0.28))]">
            <div className="tkc-ticket relative aspect-[3/4.35] overflow-hidden rounded-[26px] bg-white transition-transform duration-500 ease-out group-hover:scale-[1.015] dark:bg-neutral-900">
              {/* Photo fills the ticket */}
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img
                src="/img/gallery/g12.webp"
                alt="Warm golden-hour portrait sitting behind a die-cut event ticket"
                loading="lazy"
                draggable={false}
                className="absolute inset-0 h-[71%] w-full object-cover"
              />

              {/* Foil sweep on hover */}
              <div className="pointer-events-none absolute inset-0 top-0 h-[71%] overflow-hidden">
                <div className="tkc-shine absolute inset-y-0 left-0 w-1/3 bg-gradient-to-r from-transparent via-white/60 to-transparent opacity-0 [animation-play-state:paused] group-hover:[animation-play-state:running]" />
              </div>

              {/* Legibility scrim over lower photo */}
              <div className="pointer-events-none absolute inset-x-0 top-[42%] h-[29%] bg-gradient-to-b from-transparent to-black/45" />

              {/* Caption riding the photo */}
              <div className="absolute inset-x-0 top-[52%] px-6 text-white">
                <p className="text-[10px] font-semibold uppercase tracking-[0.3em] text-white/70">
                  Admit One
                </p>
                <p className="mt-1 text-2xl font-black leading-tight tracking-tight drop-shadow">
                  Golden Hour Sessions
                </p>
              </div>

              {/* Perforation line at the tear */}
              <div className="pointer-events-none absolute inset-x-4 top-[71%] -translate-y-1/2 border-t-2 border-dashed border-neutral-300 dark:border-neutral-700" />

              {/* Stub */}
              <div className="absolute inset-x-0 bottom-0 top-[71%] flex items-center justify-between gap-4 px-6">
                <div>
                  <p className="text-[9px] font-semibold uppercase tracking-[0.3em] text-neutral-400 dark:text-neutral-500">
                    Series / Seat
                  </p>
                  <p className="text-lg font-black tracking-tight text-neutral-900 dark:text-neutral-100">
                    07&nbsp;·&nbsp;A21
                  </p>
                  <p className="mt-0.5 text-[10px] font-medium tabular-nums text-neutral-400 dark:text-neutral-500">
                    No. 004821
                  </p>
                </div>

                {/* Barcode */}
                <div
                  aria-hidden="true"
                  className="flex h-10 items-stretch gap-[2px]"
                >
                  {barcode.map((w, i) => (
                    <span
                      key={i}
                      style={{ width: `${w}px` }}
                      className="block bg-neutral-900 dark:bg-neutral-200"
                    />
                  ))}
                </div>
              </div>
            </div>
          </div>
        </motion.div>

        <motion.p
          initial={reduce ? false : { opacity: 0 }}
          animate={inView && !reduce ? { opacity: 1 } : undefined}
          transition={{ duration: 0.6, delay: 0.35 }}
          className="mt-10 text-center text-xs font-medium tracking-wide text-neutral-400 dark:text-neutral-500"
        >
          Pure CSS mask notches · perforated tear · foil sweep on hover
        </motion.p>
      </div>
    </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 →