Shivendra Kumar

Cinematic Image Showcase

Heroes

About

A high-end, immersive hero component featuring a 3D parallax image card, refractive glass overlays, and dynamic typography. Designed for photography portfolios or studio landing pages.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Cinematic Image Showcase to your project and fits it to your theme.

Code

How Cinematic Image Showcase is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import React, { useState, useEffect, useRef } from 'react';

const OpticStudio: React.FC = () => {
  const [activeIndex, setActiveIndex] = useState(0);
  const [mousePos, setMousePos] = useState({ x: 50, y: 50 });
  const [tilt, setTilt] = useState({ x: 0, y: 0 });
  
  const sceneRef = useRef<HTMLDivElement>(null);
  const images = [
    "https://cdn.21st.dev/assets/mirror/80/809bd85ebb846e015e1ebed1be96035c0a9fcafecc66a80b2bcb61807c1125f2.jpg",
    "https://cdn.21st.dev/assets/mirror/b1/b1b989c58e989fab33a8d0a0df3db5d492e005cd9483067dd36072197ca64920.jpg",
    "https://cdn.21st.dev/assets/mirror/e5/e5ef2d30267a4e7f81bc0a61283385f0e59c348d8e69a017f1a87b5323fd8abc.jpg"
  ];

  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      // Update global mouse percentage for grid/spotlight
      const xPct = (e.clientX / window.innerWidth) * 100;
      const yPct = (e.clientY / window.innerHeight) * 100;
      setMousePos({ x: xPct, y: yPct });

      // Calculate 3D Tilt
      const xAxis = (window.innerWidth / 2 - e.clientX) / 25;
      const yAxis = (window.innerHeight / 2 - e.clientY) / 25;
      setTilt({ x: xAxis, y: yAxis });
    };

    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, []);

  const resetTilt = () => setTilt({ x: 0, y: 0 });

  return (
    <div style={containerStyle}>
      {/* Dynamic Styles Injection */}
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@900&family=JetBrains+Mono:wght@300;500&family=Playfair+Display:ital,wght@0,900;1,900&display=swap');
        
        :root {
          --bg: #050505;
          --accent: #e0e0e0;
          --primary: #ffffff;
          --dim: #444;
          --glass: rgba(255, 255, 255, 0.03);
          --border: rgba(255, 255, 255, 0.1);
          --font-main: 'Inter', sans-serif;
          --font-serif: 'Playfair Display', serif;
          --font-mono: 'JetBrains Mono', monospace;
        }

        @keyframes pulse {
          0% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
          100% { transform: translate(-50%, -55%) scale(1.2); opacity: 0.5; }
        }

        .word-reveal {
          display: block;
          overflow: hidden;
          animation: reveal 1s cubic-bezier(0.23, 1, 0.32, 1) forwards;
          opacity: 0;
          transform: translateY(50px);
        }

        @keyframes reveal {
          to { opacity: 1; transform: translateY(0); }
        }
      `}</style>

      {/* Background Layers */}
      <div style={{
        ...opticGridStyle,
        '--mouse-x': `${mousePos.x}%`,
        '--mouse-y': `${mousePos.y}%`
      } as React.CSSProperties} className="optic-grid-effect" />
      <div style={ambientGlowStyle} />

      {/* HUD Navigation */}
      <div style={hudStyle}>
        <div style={hudTopStyle}>
          <div style={{ fontFamily: 'var(--font-mono)', fontWeight: 500, fontSize: '1.2rem', cursor: 'pointer', pointerEvents: 'auto' }}>
            OPTIC // STUDIO
          </div>
          <nav style={navLinksStyle}>
            {['ARCHIVE', 'EXHIBITIONS', 'PROCESS', 'STUDIO'].map(link => (
              <a key={link} href={`#${link}`} style={navLinkItemStyle}>{link}</a>
            ))}
          </nav>
        </div>
        <div style={viewfinderStyle} />
        <div style={{ ...focusCornerStyle, top: '4rem', left: '4rem', borderRight: 'none', borderBottom: 'none' }} />
        <div style={{ ...focusCornerStyle, top: '4rem', right: '4rem', borderLeft: 'none', borderBottom: 'none' }} />
        <div style={{ ...focusCornerStyle, bottom: '4rem', left: '4rem', borderRight: 'none', borderTop: 'none' }} />
        <div style={{ ...focusCornerStyle, bottom: '4rem', right: '4rem', borderLeft: 'none', borderTop: 'none' }} />
      </div>

      {/* Main Content */}
      <main style={heroStyle}>
        <div style={{ zIndex: 20 }}>
          <h1 style={headlineStyle}>
            <span className="word-reveal" style={{ animationDelay: '0.2s' }}>Moments</span>
            <span className="word-reveal" style={{ 
                animationDelay: '0.35s', 
                fontFamily: 'var(--font-serif)', 
                fontStyle: 'italic', 
                color: 'transparent', 
                WebkitTextStroke: '1px var(--primary)',
                marginLeft: '10%' 
            }}>That</span>
            <span className="word-reveal" style={{ animationDelay: '0.5s', marginTop: '-0.1em' }}>Breathe.</span>
          </h1>
        </div>

        <div 
          ref={sceneRef} 
          style={heroRightStyle} 
          onMouseLeave={resetTilt}
        >
          <div style={{
            ...imageContainerStyle,
            transform: `rotateY(${tilt.x}deg) rotateX(${tilt.y}deg)`
          }}>
            <img 
              src={images[activeIndex]} 
              alt="Cinematic" 
              style={mainImageStyle} 
            />
            <div style={{
              ...refractiveOverlayStyle,
              maskImage: `radial-gradient(circle at ${mousePos.x}% ${mousePos.y}%, transparent 150px, black 300px)`,
              WebkitMaskImage: `radial-gradient(circle at ${mousePos.x}% ${mousePos.y}%, transparent 150px, black 300px)`
            }} />
            
            <div style={{ ...dataLabelStyle, top: '10%', right: '-5%' }}>LOC: 35.6895° N, 139.6917° E</div>
            <div style={{ ...dataLabelStyle, bottom: '20%', left: '-10%' }}>CAM: 50MM F/1.2 LENS</div>
            <div style={{ ...dataLabelStyle, top: '40%', left: '-15%' }}>STORY: THE SILENT VIGIL</div>
          </div>
        </div>
      </main>

      {/* Footer Preview */}
      <div style={scrollPreviewStyle}>
        {images.map((img, i) => (
          <div 
            key={i} 
            onClick={() => setActiveIndex(i)}
            style={{
              ...previewBoxStyle,
              width: activeIndex === i ? '100px' : '60px',
              borderColor: activeIndex === i ? 'var(--primary)' : 'var(--border)'
            }}
          >
            <img src={img} alt="preview" style={{ ...previewImgStyle, opacity: activeIndex === i ? 1 : 0.4 }} />
          </div>
        ))}
      </div>
    </div>
  );
};

// --- Styles Object ---

const containerStyle: React.CSSProperties = {
  backgroundColor: '#050505',
  color: '#ffffff',
  fontFamily: "'Inter', sans-serif",
  height: '100vh',
  width: '100vw',
  overflow: 'hidden',
  position: 'relative'
};

const opticGridStyle: React.CSSProperties = {
  position: 'fixed',
  inset: 0,
  backgroundImage: `linear-gradient(rgba(255,255,255,0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.1) 1px, transparent 1px)`,
  backgroundSize: '80px 80px',
  backgroundPosition: 'center center',
  opacity: 0.2,
  zIndex: 1,
  pointerEvents: 'none'
};

const ambientGlowStyle: React.CSSProperties = {
  position: 'fixed',
  width: '60vw',
  height: '60vw',
  top: '50%',
  left: '50%',
  transform: 'translate(-50%, -50%)',
  background: 'radial-gradient(circle, rgba(255,255,255,0.05) 0%, transparent 70%)',
  filter: 'blur(100px)',
  zIndex: 0,
  animation: 'pulse 10s infinite alternate ease-in-out'
};

const heroStyle: React.CSSProperties = {
  position: 'relative',
  display: 'grid',
  gridTemplateColumns: '1fr 1.2fr',
  height: '100vh',
  width: '100%',
  zIndex: 10,
  padding: '4rem',
  alignItems: 'center'
};

const headlineStyle: React.CSSProperties = {
  fontSize: 'clamp(4rem, 10vw, 12rem)',
  lineHeight: 0.85,
  fontWeight: 900,
  textTransform: 'uppercase',
  letterSpacing: '-0.04em',
  pointerEvents: 'none'
};

const heroRightStyle: React.CSSProperties = {
  position: 'relative',
  height: '80vh',
  perspective: '2000px',
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center'
};

const imageContainerStyle: React.CSSProperties = {
  position: 'relative',
  width: '80%',
  height: '100%',
  transformStyle: 'preserve-3d',
  transition: 'transform 0.6s cubic-bezier(0.23, 1, 0.32, 1)'
};

const mainImageStyle: React.CSSProperties = {
  width: '100%',
  height: '100%',
  objectFit: 'cover',
  filter: 'grayscale(0.2) contrast(1.1)',
  boxShadow: '0 50px 100px rgba(0,0,0,0.5)',
  transition: 'opacity 0.3s ease'
};

const refractiveOverlayStyle: React.CSSProperties = {
  position: 'absolute',
  inset: '-20px',
  backdropFilter: 'blur(0px)',
  border: '1px solid rgba(255,255,255,0.1)',
  zIndex: 25,
  pointerEvents: 'none',
  background: 'linear-gradient(135deg, rgba(255,255,255,0.05) 0%, transparent 100%)'
};

const dataLabelStyle: React.CSSProperties = {
  position: 'absolute',
  fontFamily: 'var(--font-mono)',
  fontSize: '0.7rem',
  textTransform: 'uppercase',
  background: 'rgba(255, 255, 255, 0.03)',
  backdropFilter: 'blur(10px)',
  padding: '8px 12px',
  border: '1px solid rgba(255, 255, 255, 0.1)',
  letterSpacing: '0.1em',
  display: 'flex',
  alignItems: 'center',
  gap: '10px',
  zIndex: 40,
  color: '#fff'
};

const hudStyle: React.CSSProperties = {
  position: 'fixed',
  inset: 0,
  pointerEvents: 'none',
  zIndex: 100,
  padding: '2rem'
};

const hudTopStyle: React.CSSProperties = {
  display: 'flex',
  justifyContent: 'space-between',
  alignItems: 'flex-start'
};

const navLinksStyle: React.CSSProperties = {
  display: 'flex',
  gap: '3rem',
  fontFamily: 'var(--font-mono)',
  fontSize: '0.8rem',
  pointerEvents: 'auto'
};

const navLinkItemStyle: React.CSSProperties = {
  color: '#fff',
  textDecoration: 'none',
  opacity: 0.6
};

const focusCornerStyle: React.CSSProperties = {
  position: 'absolute',
  width: '40px',
  height: '40px',
  border: '1px solid #444'
};

const scrollPreviewStyle: React.CSSProperties = {
  position: 'absolute',
  bottom: '4rem',
  left: '4rem',
  display: 'flex',
  gap: '1rem',
  zIndex: 50
};

const previewBoxStyle: React.CSSProperties = {
  height: '80px',
  background: '#444',
  position: 'relative',
  overflow: 'hidden',
  border: '1px solid rgba(255,255,255,0.1)',
  transition: '0.5s cubic-bezier(0.23, 1, 0.32, 1)',
  cursor: 'pointer'
};

const previewImgStyle: React.CSSProperties = {
  width: '100%',
  height: '100%',
  objectFit: 'cover',
  transition: '0.5s'
};

const viewfinderStyle: React.CSSProperties = {
  position: 'absolute',
  top: '50%',
  left: '50%',
  transform: 'translate(-50%, -50%)',
  width: '30px',
  height: '30px',
  border: '1px solid transparent'
};

export default OpticStudio;

More by Shivendra Kumar

View all
  • Halide Topo Hero

    Halide Topo Hero

    2K
  • Neural Access Login

    Neural Access Login

    348
  • Thermal Roasting Landing Page

    Thermal Roasting Landing Page

  • OxideLoomInterface

    OxideLoomInterface

  • Bioluminescent Login

    Bioluminescent Login

  • footer

    footer

More in Heroes

View all
  • SaaS Template

    SaaS Template

    2.4K
  • Hero

    Hero

    1.2K
  • Responsive Hero Banner

    Responsive Hero Banner

    2K
  • Centered Hero with Image Fan

    Centered Hero with Image Fan

    920
  • HeroSectionWithGradient

    HeroSectionWithGradient

    334
  • Editorial Collage Hero

    Editorial Collage Hero

    425
  • Hero 03

    Hero 03

    314
  • Glass Fold

    Glass Fold

    196