// React ver of this awesome pen: https://codepen.io/tahazsh/pen/wvQwqYv
body
  #app
     
     
View Compiled
@import url("https://fonts.googleapis.com/css2?family=Alfa+Slab+One&family=Chivo&display=swap");

:root {
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

* {
  margin: 0;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  overflow: hidden;
  background-image: linear-gradient(
    160deg in oklch,
    color(display-p3 0.1 0.6 0.9) 15%,
    color(display-p3 0.4 0.2 0.8) 99%
  );
  font-family: Chivo, sans-serif;
}

.card {
  padding: 10px;
  cursor: pointer;

  img {
    aspect-ratio: 16 / 9;
    width: 100%;
    object-fit: cover;
  }

  .content {
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    box-shadow: rgba(255, 255, 255, 0.3) 0 5vw 6vw -8vw,
      rgba(255, 255, 255, 0) 0 4.5vw 5vw -6vw,
      rgba(50, 50, 80, 0.5) 0px 4vw 8vw -2vw,
      rgba(0, 0, 0, 0.8) 0px 4vw 5vw -3vw;
    transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
    background-color: rgb(6, 6, 6);
    border-radius: 10px;
    width: 100%;
    max-width: 420px;
    will-change: transform;
    transition: transform 0.25s ease-out;

    h2 {
      font-family: "Alfa Slab One";
      font-size: 2.2rem;
      color: #f4f4f4;
      padding: 1rem 1rem 0 1rem;
      text-shadow: 2px 4px 3px rgba(255, 255, 255, 0.3);
    }

    p {
      font-size: 1.2rem;
      color: #b4b4b4;
      padding: 0 1rem 0;
      margin-bottom: 1rem;
      display: -webkit-box;
      -webkit-line-clamp: 4;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }

    &:hover h2 {
      text-decoration: underline;
    }
  }
}

.gloss {
  opacity: 0;
  z-index: 10;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 255, 255, 1) 0%,
    rgba(255, 255, 255, 0) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  position: absolute;
  will-change: opacity;

  .gloss--shine {
    transition: 600ms opacity ease-out;
  }
}
View Compiled
const { app } = window;
const { useState, useRef, useEffect } = React;

const useGlossEffect = (cardRef, cardContentRef, glossRef) => {
  const mapNumberRange = (n, a, b, c, d) => {
    return ((n - a) * (d - c)) / (b - a) + c;
  };

  const addShineClass = () => {
    requestAnimationFrame(() => {
      glossRef.current.classList.add("gloss--shine");
    });
  };

  const calculateTransformValues = (pointerX, pointerY, cardRect) => {
    const halfWidth = cardRect.width / 2;
    const halfHeight = cardRect.height / 2;
    const cardCenterX = cardRect.left + halfWidth;
    const cardCenterY = cardRect.top + halfHeight;
    const deltaX = pointerX - cardCenterX;
    const deltaY = pointerY - cardCenterY;
    const distanceToCenter = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
    const maxDistance = Math.max(halfWidth, halfHeight);
    const degree = mapNumberRange(distanceToCenter, 0, maxDistance, 0, 10);
    const rx = mapNumberRange(deltaY, 0, halfWidth, 0, 1);
    const ry = mapNumberRange(deltaX, 0, halfHeight, 0, 1);
    return { rx, ry, degree, distanceToCenter, maxDistance };
  };

  const applyTransform = (rx, ry, degree, distanceToCenter, maxDistance) => {
    const cardTransform = `perspective(400px) rotate3d(${-rx}, ${ry}, 0, ${degree}deg)`;
    const glossTransform = `translate(${-ry * 100}%, ${-rx * 100}%) scale(2.4)`;
    const glossOpacity = mapNumberRange(distanceToCenter, 0, maxDistance, 0, 0.6);

    cardContentRef.current.style.transform = cardTransform;
    glossRef.current.style.transform = glossTransform;
    glossRef.current.style.opacity = glossOpacity.toString();
  };

  const handleMouseMove = ({ clientX, clientY }) => {
    const card = cardRef.current;
    const cardRect = card.getBoundingClientRect();

    const {
      rx,
      ry,
      degree,
      distanceToCenter,
      maxDistance
    } = calculateTransformValues(clientX, clientY, cardRect);

    applyTransform(rx, ry, degree, distanceToCenter, maxDistance);
  };

  const handleMouseLeave = () => {
    cardContentRef.current.style.transform = null;
    glossRef.current.style.opacity = 0;
  };

  useEffect(() => {
    const card = cardRef.current;

    addShineClass();

    card.addEventListener("mousemove", handleMouseMove);
    card.addEventListener("mouseleave", handleMouseLeave);

    return () => {
      card.removeEventListener("mousemove", handleMouseMove);
      card.removeEventListener("mouseleave", handleMouseLeave);
    };
  }, [cardRef, cardContentRef, glossRef]);
};

const Card = () => {
  const cardRef = useRef(null);
  const cardContentRef = useRef(null);
  const glossRef = useRef(null);

  useGlossEffect(cardRef, cardContentRef, glossRef);

  return (
    <div className="card" ref={cardRef}>
      <div className="content" ref={cardContentRef}>
        <div className="gloss" ref={glossRef} />
        <img
          src="https://images.pexels.com/photos/145683/pexels-photo-145683.jpeg"
          alt="chain links"
        />
        <h2>Predictive Analytics</h2>
        <p>
          Unleash the potential of AI-driven sentiment analysis, analyzing
          social media and customer feedback to gain valuable insights and adapt
          business strategies. Discover the power of AI-powered demand
          forecasting, optimizing inventory management and minimizing supply
          chain disruptions for improved profitability
        </p>
      </div>
    </div>
  );
};

const root = ReactDOM.createRoot(app);
root.render(<Card />);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/react@18/umd/react.development.js
  2. https://unpkg.com/react-dom@18/umd/react-dom.development.js