Web InnoventixFreeCode

Blinds Horizontal Effect

Original · free

Horizontal venetian blinds open to reveal the image on hover.

byWeb InnoventixReact + Tailwind
blindshorizontalimageeffect
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-blinds-horizontal.json
imgfx-blinds-horizontal.tsx
"use client";

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

const SLAT_COUNT = 16;
const SLATS = Array.from({ length: SLAT_COUNT }, (_, i) => i);

export default function ImgfxBlindsHorizontal() {
  const reduce = useReducedMotion();

  return (
    <section className="relative w-full overflow-hidden bg-zinc-50 px-6 py-20 sm:py-28 dark:bg-zinc-950">
      <style>{`
        .imgfxbh-stage {
          perspective: 1400px;
          perspective-origin: 50% 40%;
        }
        .imgfxbh-slat {
          position: absolute;
          left: 0;
          right: 0;
          transform-origin: top center;
          transform: rotateX(0deg);
          backface-visibility: hidden;
          transition: transform 620ms cubic-bezier(0.22, 1, 0.36, 1);
          will-change: transform;
        }
        .imgfxbh-stage:hover .imgfxbh-slat,
        .imgfxbh-stage:focus-visible .imgfxbh-slat {
          transform: rotateX(-88deg);
        }
        @media (hover: none) {
          .imgfxbh-stage:active .imgfxbh-slat {
            transform: rotateX(-88deg);
          }
        }
        @media (prefers-reduced-motion: reduce) {
          .imgfxbh-slat {
            transition: opacity 300ms linear;
            transform: none !important;
          }
          .imgfxbh-stage:hover .imgfxbh-slat,
          .imgfxbh-stage:focus-visible .imgfxbh-slat,
          .imgfxbh-stage:active .imgfxbh-slat {
            opacity: 0;
          }
        }
      `}</style>

      <motion.div
        initial={reduce ? false : { opacity: 0, y: 24 }}
        whileInView={reduce ? undefined : { opacity: 1, y: 0 }}
        viewport={{ once: true, amount: 0.4 }}
        transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
        className="mx-auto flex max-w-2xl flex-col items-center text-center"
      >
        <p className="text-xs font-semibold uppercase tracking-[0.25em] text-indigo-600 dark:text-indigo-400">
          Image Effect
        </p>
        <h2 className="mt-3 text-3xl font-semibold tracking-tight text-zinc-900 sm:text-4xl dark:text-zinc-50">
          Venetian Blinds
        </h2>
        <p className="mt-3 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400">
          Horizontal slats fold open in a soft cascade to reveal the photo behind them.
        </p>

        <figure className="mt-10 w-full">
          <div
            tabIndex={0}
            aria-label="Interactive venetian blinds. Hover or focus to open the slats and reveal the photograph."
            className="imgfxbh-stage group relative mx-auto w-full max-w-xl cursor-pointer overflow-hidden rounded-2xl outline-none ring-1 ring-zinc-900/10 transition-shadow duration-500 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:ring-white/10"
          >
            <div className="relative aspect-[4/3] w-full">
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img
                src="/img/gallery/g14.webp"
                alt="Warm architectural interior gradually uncovered as the venetian blinds open"
                loading="lazy"
                draggable={false}
                className="absolute inset-0 h-full w-full object-cover"
              />

              {/* Blind slats layer */}
              <div className="pointer-events-none absolute inset-0" aria-hidden="true">
                {SLATS.map((i) => {
                  const height = 100 / SLAT_COUNT;
                  return (
                    <div
                      key={i}
                      className="imgfxbh-slat bg-gradient-to-b from-zinc-100 via-zinc-200 to-zinc-300 shadow-[0_1px_2px_rgba(0,0,0,0.18)] dark:from-zinc-700 dark:via-zinc-800 dark:to-zinc-900"
                      style={{
                        top: `${i * height}%`,
                        height: `${height + 0.4}%`,
                        transitionDelay: `${i * 34}ms`,
                        borderBottom: "1px solid rgba(0,0,0,0.14)",
                      }}
                    >
                      <span className="absolute inset-x-0 top-0 h-px bg-white/50 dark:bg-white/10" />
                    </div>
                  );
                })}
              </div>

              {/* Subtle sheen once revealed */}
              <div className="pointer-events-none absolute inset-0 rounded-2xl bg-gradient-to-t from-black/10 via-transparent to-white/5 opacity-0 transition-opacity duration-700 group-hover:opacity-100" />
            </div>
          </div>

          <figcaption className="mt-4 text-center text-xs font-medium uppercase tracking-widest text-zinc-400 dark:text-zinc-500">
            Hover to open the blinds
          </figcaption>
        </figure>
      </motion.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 →