<!-- Cursor -->
<div class="cursor">
  <div class="cursor__circle cursor__circle--small"></div>
  <div class="cursor__circle cursor__circle--large"></div>
</div>
<!-- End Cursor -->

<!-- Hero Header -->
<section class="hero-header">
  <div class="hero-header__grid">
    <div class="hero-header__content">
      <h2 class="hero-header__title">Try to <span data-pointer="hover">hover me!</span></h2>
      <p class="hero-header__description"></p>
    </div>
    <div class="hero-header__grid-overlay"></div>
  </div>
</section>
<!-- End Hero Header -->
/* @start: Base */
:root {
  --color-white: #FFFFFF;
  --color-black: #000000;
  --color-red  : #E11D48;
}

*, *::before, *::after {
    box-sizing: border-box;
}

* {
    margin: 0;
}

html {
    position: relative;
    top: auto;
    width: 100%;
    min-height: -webkit-fill-available;
    -webkit-marquee-increment: 0vw;
    font-size: 16px;
    font-kerning: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeSpeed;
    background-color: var(--color-black);
    overflow: hidden;
}

body {
    position: relative;
    width: 100%;
    min-height: 100vh;
  font-family: 'Outfit';
    min-height: -webkit-fill-available;
    background-color: var(--color-black);
}

/* @start: Hero Header */
.hero-header {
  position: relative;

    &__grid {
        position: relative;
        width: 100vw;
        height: 100vh;
        background: linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
                    linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
        background-size: 30px 30px;
        overflow: hidden;
    }

    &__grid-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
        background: linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);
        pointer-events: none;
    }

    &__content {
        position: relative;
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
    flex-direction: column;
        z-index: 2;
    }

    &__title {
        color: var(--color-white);
        font-size: clamp(24px,3vw,90px);
        font-weight: 800;
        letter-spacing: -1px;

        span {
            text-decoration: underline;
        }

    }
  
  &__description {
    color: var(--color-white);
    font-size: 12px;
    text-transform: uppercase;
    text-align: center;
    margin-top: 20px;
  }

}

/* @start: Cursor */
.cursor {
    position: fixed;
    top: 0;
    left: 0;
    pointer-events: none;
    z-index: 98;

    &__circle {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        border-radius: 50%;

        &--small {
            width: 10px;
            height: 10px;
            background-color: var(--color-white);
        }

        &--large {
            width: 40px;
            height: 40px;
            border: 2px solid var(--color-red);
        }

    }

}

.cursor-hide {
    display: none;
}
View Compiled
import gsap from "https://esm.sh/gsap";
import lerp from "https://esm.sh/lerp";
import Bowser from "https://esm.sh/bowser";

// Cursor
const Cursor = (() => {

    const cursorArea  = document.querySelector('.cursor');
    const cursorSmall = document.querySelector('.cursor__circle--small');
    const cursorLarge = document.querySelector('.cursor__circle--large');

    if (!cursorSmall || !cursorLarge) {
        console.error('Cursor elements not found in the DOM.');
        return null;
    }

    let smallPos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
    let largePos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
    let mousePos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };

    const updateCursor = () => {

        smallPos.x = lerp(smallPos.x, mousePos.x, 0.2);
        smallPos.y = lerp(smallPos.y, mousePos.y, 0.2);

        largePos.x = lerp(largePos.x, mousePos.x, 0.1);
        largePos.y = lerp(largePos.y, mousePos.y, 0.1);

        gsap.set(cursorSmall, { x: smallPos.x, y: smallPos.y });
        gsap.set(cursorLarge, { x: largePos.x, y: largePos.y });

    };

    const handleMouseMove = (e) => {

        mousePos.x = e.clientX;
        mousePos.y = e.clientY;

    };

    const handlePointerEnter = (e) => {

        const pointerType = e.target.getAttribute('data-pointer');

        // Check the value of `data-pointer` and apply animations accordingly.
        // You can extend this logic to add more animations for different `data-pointer` values.
        if ( pointerType == 'hover' ) {

            gsap.to(cursorLarge, { scale: 2, opacity: 1, duration: 0.3 });
            gsap.to(cursorSmall, { scale: 0, opacity: 0, duration: 0.3 });

        }

    };

    const handlePointerLeave = () => {

        gsap.to(cursorLarge, { scale: 1, opacity: 1, duration: 0.3 });
        gsap.to(cursorSmall, { scale: 1, opacity: 1, duration: 0.3 });

    };

    const addPointerEvents = () => {

        document.querySelectorAll('[data-pointer]').forEach((element) => {

            element.addEventListener('mouseenter', handlePointerEnter);
            element.addEventListener('mouseleave', handlePointerLeave);

        });

    };

    const removePointerEvents = () => {

        document.querySelectorAll('[data-pointer]').forEach((element) => {

            element.removeEventListener('mouseenter', handlePointerEnter);
            element.removeEventListener('mouseleave', handlePointerLeave);

        });

    };

    const init = () => {

        window.addEventListener('mousemove', handleMouseMove);
        addPointerEvents();
        gsap.ticker.add(updateCursor);

    };

    const destroy = () => {

        window.removeEventListener('mousemove', handleMouseMove);
        removePointerEvents();
        gsap.ticker.remove(updateCursor);

    };

    const disable = () => {
    
    destroy();
        cursorArea.classList.add('cursor-hide');

    }

    return { init, destroy, disable };

})();

export default Cursor;

// Init
document.addEventListener('DOMContentLoaded', () => {
  
  // Check Browser & Touch Devices
  const browser = Bowser.getParser(window.navigator.userAgent);
  const isHandheld = browser.getPlatformType() === 'tablet' || browser.getPlatformType() === 'mobile';
  const descriptionLog = document.querySelector('.hero-header__description');

    if (!isHandheld) {
    Cursor.init();
    descriptionLog.textContent = 'Non-touch device detected. Initializing cursor.';
    console.log('Non-touch device detected. Initializing cursor.');
  } else {
    Cursor.disable();
    descriptionLog.textContent = 'Touch device detected. Disabling cursor.';
    console.log('Touch device detected. Disabling cursor.');
  }

});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.