CARDS

3D Tilt Profile Card

An interactive 3D profile card with smooth mouse tilt, layered parallax depth, cursor-follow glow and dynamic glare effects.

Available for work PRO
AR
FRONTEND DEVELOPER

Alex Rivera

Building clean interfaces, smooth interactions and memorable digital experiences.

HTML CSS JavaScript
42 Projects
18K Followers
4.9 Rating
↗ Move your cursor over the card
HTML
<div class="cr-tilt-profile" id="crTiltProfile">

  <div class="cr-tilt-profile__stage">

    <article class="cr-tilt-profile__card">

      <!-- CURSOR FOLLOW GLOW -->
      <div
        class="cr-tilt-profile__glow"
        aria-hidden="true"
      ></div>

      <!-- GLARE EFFECT -->
      <div
        class="cr-tilt-profile__glare"
        aria-hidden="true"
      ></div>


      <!-- DECORATIVE BACKGROUND -->
      <div
        class="cr-tilt-profile__decor"
        aria-hidden="true"
      >

        <span
          class="cr-tilt-profile__orb cr-tilt-profile__orb--one"
        ></span>

        <span
          class="cr-tilt-profile__orb cr-tilt-profile__orb--two"
        ></span>

        <span
          class="cr-tilt-profile__grid"
        ></span>

      </div>


      <!-- PROFILE CONTENT -->
      <div class="cr-tilt-profile__content">


        <!-- TOP STATUS -->
        <div
          class="cr-tilt-profile__top"
          data-depth="18"
        >

          <span class="cr-tilt-profile__status">

            <span
              class="cr-tilt-profile__status-dot"
            ></span>

            Available for work

          </span>

          <span class="cr-tilt-profile__badge">
            PRO
          </span>

        </div>


        <!-- AVATAR -->
        <div
          class="cr-tilt-profile__avatar-wrap"
          data-depth="34"
        >

          <div class="cr-tilt-profile__avatar">
            <span>AR</span>
          </div>

          <span
            class="cr-tilt-profile__avatar-ring"
          ></span>

        </div>


        <!-- PROFILE INFO -->
        <div
          class="cr-tilt-profile__info"
          data-depth="26"
        >

          <span class="cr-tilt-profile__eyebrow">
            FRONTEND DEVELOPER
          </span>

          <h2>Alex Rivera</h2>

          <p>
            Building clean interfaces, smooth interactions
            and memorable digital experiences.
          </p>

        </div>


        <!-- SKILLS -->
        <div
          class="cr-tilt-profile__skills"
          data-depth="22"
        >

          <span>HTML</span>
          <span>CSS</span>
          <span>JavaScript</span>

        </div>


        <!-- STATS -->
        <div
          class="cr-tilt-profile__stats"
          data-depth="20"
        >

          <div class="cr-tilt-profile__stat">
            <strong>42</strong>
            <span>Projects</span>
          </div>

          <div
            class="cr-tilt-profile__divider"
          ></div>

          <div class="cr-tilt-profile__stat">
            <strong>18K</strong>
            <span>Followers</span>
          </div>

          <div
            class="cr-tilt-profile__divider"
          ></div>

          <div class="cr-tilt-profile__stat">
            <strong>4.9</strong>
            <span>Rating</span>
          </div>

        </div>


        <!-- ACTIONS -->
        <div
          class="cr-tilt-profile__actions"
          data-depth="30"
        >

          <button
            class="cr-tilt-profile__button cr-tilt-profile__button--primary"
            type="button"
          >

            <span>View Profile</span>

            <svg
              viewBox="0 0 24 24"
              aria-hidden="true"
            >

              <path
                d="M5 12h14M13 6l6 6-6 6"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
              />

            </svg>

          </button>


          <button
            class="cr-tilt-profile__button cr-tilt-profile__button--icon"
            type="button"
            aria-label="Send message"
          >

            <svg
              viewBox="0 0 24 24"
              aria-hidden="true"
            >

              <path
                d="M21 3L9.5 14.5M21 3l-7 18-4.5-6.5L3 10l18-7z"
                fill="none"
                stroke="currentColor"
                stroke-width="1.8"
                stroke-linecap="round"
                stroke-linejoin="round"
              />

            </svg>

          </button>

        </div>

      </div>


      <!-- BOTTOM ACCENT -->
      <div
        class="cr-tilt-profile__accent"
        aria-hidden="true"
      ></div>

    </article>


    <!-- INTERACTION HINT -->
    <div class="cr-tilt-profile__hint">

      <span class="cr-tilt-profile__hint-icon">
        ↗
      </span>

      Move your cursor over the card

    </div>

  </div>

</div>
CSS
/* =========================================
   3D TILT PROFILE CARD
========================================= */

.cr-tilt-profile {
  --cr-bg: #070914;
  --cr-card: rgba(13, 17, 35, 0.86);
  --cr-card-border: rgba(255, 255, 255, 0.1);

  --cr-cyan: #22d3ee;
  --cr-cyan-soft: rgba(34, 211, 238, 0.16);

  --cr-purple: #a855f7;
  --cr-purple-soft: rgba(168, 85, 247, 0.16);

  --cr-text: #f8fafc;
  --cr-muted: #94a3b8;
  --cr-soft: #cbd5e1;

  width: 100%;
  min-height: 650px;

  display: flex;
  align-items: center;
  justify-content: center;

  padding: 48px 20px;

  background:
    radial-gradient(
      circle at 25% 20%,
      rgba(34, 211, 238, 0.08),
      transparent 32%
    ),
    radial-gradient(
      circle at 78% 78%,
      rgba(168, 85, 247, 0.1),
      transparent 35%
    ),
    var(--cr-bg);

  font-family:
    Inter,
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif;

  box-sizing: border-box;
  overflow: hidden;
}

.cr-tilt-profile *,
.cr-tilt-profile *::before,
.cr-tilt-profile *::after {
  box-sizing: border-box;
}


/* STAGE */

.cr-tilt-profile__stage {
  width: min(100%, 420px);

  display: flex;
  flex-direction: column;
  align-items: center;

  perspective: 1100px;
}


/* CARD */

.cr-tilt-profile__card {
  position: relative;

  width: 100%;
  min-height: 535px;

  padding: 28px;

  border: 1px solid var(--cr-card-border);
  border-radius: 28px;

  background:
    linear-gradient(
      145deg,
      rgba(255, 255, 255, 0.075),
      rgba(255, 255, 255, 0.018)
    ),
    var(--cr-card);

  box-shadow:
    0 30px 80px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.02) inset,
    0 0 55px rgba(34, 211, 238, 0.035);

  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);

  transform-style: preserve-3d;

  transform:
    rotateX(0deg)
    rotateY(0deg);

  transition:
    transform 180ms ease,
    box-shadow 250ms ease,
    border-color 250ms ease;

  overflow: hidden;
  will-change: transform;
}

.cr-tilt-profile__card:hover {
  border-color: rgba(34, 211, 238, 0.22);

  box-shadow:
    0 38px 95px rgba(0, 0, 0, 0.58),
    0 0 0 1px rgba(255, 255, 255, 0.025) inset,
    0 0 65px rgba(34, 211, 238, 0.07),
    0 0 80px rgba(168, 85, 247, 0.045);
}


/* CURSOR GLOW */

.cr-tilt-profile__glow {
  position: absolute;

  left: 50%;
  top: 50%;

  width: 320px;
  height: 320px;

  border-radius: 50%;

  background:
    radial-gradient(
      circle,
      rgba(34, 211, 238, 0.16) 0%,
      rgba(168, 85, 247, 0.08) 34%,
      transparent 70%
    );

  transform: translate(-50%, -50%);
  filter: blur(18px);

  opacity: 0.5;

  pointer-events: none;
  z-index: 0;

  transition: opacity 250ms ease;
}

.cr-tilt-profile__card:hover
.cr-tilt-profile__glow {
  opacity: 0.9;
}


/* GLARE */

.cr-tilt-profile__glare {
  position: absolute;
  inset: 0;

  background:
    linear-gradient(
      120deg,
      transparent 20%,
      rgba(255, 255, 255, 0.035) 40%,
      rgba(255, 255, 255, 0.12) 50%,
      rgba(255, 255, 255, 0.025) 60%,
      transparent 80%
    );

  opacity: 0;
  pointer-events: none;

  mix-blend-mode: screen;

  z-index: 6;

  transition: opacity 250ms ease;
}

.cr-tilt-profile__card:hover
.cr-tilt-profile__glare {
  opacity: 0.5;
}


/* DECORATIVE BACKGROUND */

.cr-tilt-profile__decor {
  position: absolute;
  inset: 0;

  pointer-events: none;
  overflow: hidden;

  z-index: 0;
}

.cr-tilt-profile__orb {
  position: absolute;

  width: 190px;
  height: 190px;

  border-radius: 50%;

  filter: blur(12px);
  opacity: 0.18;
}

.cr-tilt-profile__orb--one {
  top: -90px;
  right: -65px;

  background:
    radial-gradient(
      circle,
      var(--cr-cyan),
      transparent 68%
    );
}

.cr-tilt-profile__orb--two {
  bottom: -100px;
  left: -75px;

  background:
    radial-gradient(
      circle,
      var(--cr-purple),
      transparent 68%
    );
}

.cr-tilt-profile__grid {
  position: absolute;
  inset: 0;

  background-image:
    linear-gradient(
      rgba(255, 255, 255, 0.018) 1px,
      transparent 1px
    ),
    linear-gradient(
      90deg,
      rgba(255, 255, 255, 0.018) 1px,
      transparent 1px
    );

  background-size: 28px 28px;

  mask-image:
    linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0.8),
      transparent 80%
    );

  -webkit-mask-image:
    linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0.8),
      transparent 80%
    );
}


/* CONTENT */

.cr-tilt-profile__content {
  position: relative;

  z-index: 3;

  display: flex;
  flex-direction: column;

  min-height: 475px;

  transform-style: preserve-3d;
}

.cr-tilt-profile [data-depth] {
  position: relative;

  transform-style: preserve-3d;
  will-change: transform;

  transition: transform 180ms ease;
}


/* TOP ROW */

.cr-tilt-profile__top {
  display: flex;
  align-items: center;
  justify-content: space-between;

  margin-bottom: 28px;
}

.cr-tilt-profile__status {
  display: inline-flex;
  align-items: center;

  gap: 8px;

  color: var(--cr-soft);

  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.cr-tilt-profile__status-dot {
  position: relative;

  width: 8px;
  height: 8px;

  border-radius: 50%;

  background: #34d399;

  box-shadow:
    0 0 0 4px rgba(52, 211, 153, 0.08),
    0 0 14px rgba(52, 211, 153, 0.55);
}

.cr-tilt-profile__status-dot::after {
  content: "";

  position: absolute;
  inset: -4px;

  border: 1px solid rgba(52, 211, 153, 0.25);
  border-radius: inherit;

  animation:
    crTiltStatusPulse 2.2s ease-out infinite;
}

@keyframes crTiltStatusPulse {

  0% {
    transform: scale(0.7);
    opacity: 0.8;
  }

  70%,
  100% {
    transform: scale(1.8);
    opacity: 0;
  }

}

.cr-tilt-profile__badge {
  padding: 6px 10px;

  border:
    1px solid rgba(168, 85, 247, 0.25);

  border-radius: 999px;

  background:
    rgba(168, 85, 247, 0.08);

  color: #d8b4fe;

  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.14em;

  box-shadow:
    0 0 18px rgba(168, 85, 247, 0.08);
}


/* AVATAR */

.cr-tilt-profile__avatar-wrap {
  position: relative;

  width: 104px;
  height: 104px;

  margin: 2px auto 22px;

  display: flex;
  align-items: center;
  justify-content: center;
}

.cr-tilt-profile__avatar {
  position: relative;

  width: 88px;
  height: 88px;

  display: flex;
  align-items: center;
  justify-content: center;

  border:
    1px solid rgba(255, 255, 255, 0.12);

  border-radius: 26px;

  background:
    linear-gradient(
      135deg,
      rgba(34, 211, 238, 0.22),
      rgba(168, 85, 247, 0.22)
    ),
    #11162b;

  box-shadow:
    0 15px 35px rgba(0, 0, 0, 0.3),
    0 0 30px rgba(34, 211, 238, 0.1);

  transform: rotate(-4deg);

  z-index: 2;
}

.cr-tilt-profile__avatar::before {
  content: "";

  position: absolute;
  inset: 5px;

  border:
    1px solid rgba(255, 255, 255, 0.08);

  border-radius: 21px;

  pointer-events: none;
}

.cr-tilt-profile__avatar span {
  background:
    linear-gradient(
      135deg,
      #67e8f9,
      #c084fc
    );

  -webkit-background-clip: text;
  background-clip: text;

  color: transparent;

  font-size: 27px;
  font-weight: 800;
  letter-spacing: -0.04em;
}

.cr-tilt-profile__avatar-ring {
  position: absolute;
  inset: 0;

  border:
    1px dashed rgba(34, 211, 238, 0.2);

  border-radius: 32px;

  animation:
    crTiltAvatarRing 14s linear infinite;
}

@keyframes crTiltAvatarRing {

  to {
    transform: rotate(360deg);
  }

}


/* PROFILE INFO */

.cr-tilt-profile__info {
  text-align: center;
}

.cr-tilt-profile__eyebrow {
  display: block;

  margin-bottom: 7px;

  color: var(--cr-cyan);

  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.16em;
}

.cr-tilt-profile__info h2 {
  margin: 0;

  color: var(--cr-text);

  font-size: clamp(25px, 5vw, 31px);
  font-weight: 750;
  line-height: 1.1;
  letter-spacing: -0.035em;
}

.cr-tilt-profile__info p {
  max-width: 310px;

  margin: 11px auto 0;

  color: var(--cr-muted);

  font-size: 12px;
  line-height: 1.65;
}


/* SKILLS */

.cr-tilt-profile__skills {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;

  gap: 7px;

  margin-top: 18px;
}

.cr-tilt-profile__skills span {
  padding: 6px 10px;

  border:
    1px solid rgba(255, 255, 255, 0.07);

  border-radius: 999px;

  background:
    rgba(255, 255, 255, 0.035);

  color: #b6c2d3;

  font-size: 9px;
  font-weight: 650;

  transition:
    border-color 180ms ease,
    color 180ms ease,
    background 180ms ease,
    transform 180ms ease;
}

.cr-tilt-profile__skills span:hover {
  transform: translateY(-2px);

  border-color:
    rgba(34, 211, 238, 0.24);

  background:
    rgba(34, 211, 238, 0.07);

  color: #67e8f9;
}


/* STATS */

.cr-tilt-profile__stats {
  display: grid;

  grid-template-columns:
    1fr auto 1fr auto 1fr;

  align-items: center;

  margin-top: 24px;
  padding: 15px 10px;

  border-top:
    1px solid rgba(255, 255, 255, 0.065);

  border-bottom:
    1px solid rgba(255, 255, 255, 0.065);
}

.cr-tilt-profile__stat {
  text-align: center;
}

.cr-tilt-profile__stat strong {
  display: block;

  color: var(--cr-text);

  font-size: 16px;
  font-weight: 750;
  line-height: 1.2;
}

.cr-tilt-profile__stat span {
  display: block;

  margin-top: 4px;

  color: #748198;

  font-size: 8px;
  font-weight: 600;

  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.cr-tilt-profile__divider {
  width: 1px;
  height: 25px;

  background:
    linear-gradient(
      to bottom,
      transparent,
      rgba(255, 255, 255, 0.12),
      transparent
    );
}


/* ACTION BUTTONS */

.cr-tilt-profile__actions {
  display: grid;

  grid-template-columns:
    1fr 46px;

  gap: 10px;

  margin-top: auto;
  padding-top: 24px;
}

.cr-tilt-profile__button {
  appearance: none;
  -webkit-appearance: none;

  border: 0;
  outline: none;

  font: inherit;
  cursor: pointer;

  transition:
    transform 180ms ease,
    box-shadow 180ms ease,
    border-color 180ms ease,
    background 180ms ease;
}

.cr-tilt-profile__button--primary {
  min-height: 46px;

  display: flex;
  align-items: center;
  justify-content: center;

  gap: 9px;

  padding: 0 18px;

  border:
    1px solid rgba(34, 211, 238, 0.3);

  border-radius: 13px;

  background:
    linear-gradient(
      135deg,
      rgba(34, 211, 238, 0.15),
      rgba(168, 85, 247, 0.13)
    );

  color: #e6fbff;

  font-size: 11px;
  font-weight: 700;

  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.07),
    0 8px 22px rgba(0, 0, 0, 0.18);
}

.cr-tilt-profile__button--primary svg {
  width: 16px;
  height: 16px;

  transition: transform 180ms ease;
}

.cr-tilt-profile__button--primary:hover {
  transform: translateY(-2px);

  border-color:
    rgba(34, 211, 238, 0.52);

  background:
    linear-gradient(
      135deg,
      rgba(34, 211, 238, 0.22),
      rgba(168, 85, 247, 0.19)
    );

  box-shadow:
    0 12px 28px rgba(34, 211, 238, 0.09),
    inset 0 1px 0 rgba(255, 255, 255, 0.09);
}

.cr-tilt-profile__button--primary:hover svg {
  transform: translateX(3px);
}

.cr-tilt-profile__button--icon {
  width: 46px;
  height: 46px;

  display: flex;
  align-items: center;
  justify-content: center;

  border:
    1px solid rgba(255, 255, 255, 0.09);

  border-radius: 13px;

  background:
    rgba(255, 255, 255, 0.035);

  color: #b9c5d6;
}

.cr-tilt-profile__button--icon svg {
  width: 17px;
  height: 17px;
}

.cr-tilt-profile__button--icon:hover {
  transform: translateY(-2px);

  border-color:
    rgba(168, 85, 247, 0.32);

  background:
    rgba(168, 85, 247, 0.09);

  color: #d8b4fe;
}


/* BOTTOM ACCENT */

.cr-tilt-profile__accent {
  position: absolute;

  left: 18%;
  right: 18%;
  bottom: -1px;

  height: 2px;

  background:
    linear-gradient(
      90deg,
      transparent,
      var(--cr-cyan),
      var(--cr-purple),
      transparent
    );

  box-shadow:
    0 0 20px rgba(34, 211, 238, 0.28),
    0 0 30px rgba(168, 85, 247, 0.2);

  opacity: 0.8;

  pointer-events: none;

  z-index: 8;
}


/* INTERACTION HINT */

.cr-tilt-profile__hint {
  display: flex;
  align-items: center;
  justify-content: center;

  gap: 7px;

  margin-top: 18px;

  color: #5f6c82;

  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.03em;
}

.cr-tilt-profile__hint-icon {
  color: var(--cr-cyan);

  font-size: 13px;

  animation:
    crTiltHintMove 1.8s ease-in-out infinite;
}

@keyframes crTiltHintMove {

  0%,
  100% {
    transform: translate(0, 0);
  }

  50% {
    transform: translate(3px, -3px);
  }

}


/* KEYBOARD FOCUS */

.cr-tilt-profile__button:focus-visible {
  outline: 2px solid var(--cr-cyan);
  outline-offset: 3px;
}


/* MOBILE / TOUCH */

@media (max-width: 520px) {

  .cr-tilt-profile {
    min-height: auto;
    padding: 32px 16px;
  }

  .cr-tilt-profile__stage {
    width: 100%;
    max-width: 390px;
  }

  .cr-tilt-profile__card {
    min-height: 510px;
    padding: 23px 20px;
    border-radius: 24px;
  }

  .cr-tilt-profile__top {
    margin-bottom: 23px;
  }

  .cr-tilt-profile__avatar-wrap {
    width: 94px;
    height: 94px;

    margin-bottom: 19px;
  }

  .cr-tilt-profile__avatar {
    width: 80px;
    height: 80px;

    border-radius: 23px;
  }

  .cr-tilt-profile__avatar::before {
    border-radius: 18px;
  }

  .cr-tilt-profile__avatar span {
    font-size: 24px;
  }

  .cr-tilt-profile__info h2 {
    font-size: 27px;
  }

  .cr-tilt-profile__info p {
    font-size: 11px;
  }

  .cr-tilt-profile__stats {
    padding-left: 2px;
    padding-right: 2px;
  }

}


/* DEVICES WITHOUT HOVER */

@media (hover: none), (pointer: coarse) {

  .cr-tilt-profile__hint {
    display: none;
  }

  .cr-tilt-profile__card {
    transform: none !important;
  }

  .cr-tilt-profile [data-depth] {
    transform: none !important;
  }

  .cr-tilt-profile__glare {
    display: none;
  }

}


/* REDUCED MOTION */

@media (prefers-reduced-motion: reduce) {

  .cr-tilt-profile__card,
  .cr-tilt-profile [data-depth],
  .cr-tilt-profile__button,
  .cr-tilt-profile__button svg,
  .cr-tilt-profile__skills span {
    transition: none !important;
  }

  .cr-tilt-profile__status-dot::after,
  .cr-tilt-profile__avatar-ring,
  .cr-tilt-profile__hint-icon {
    animation: none !important;
  }

}
JavaScript
(() => {

  const component =
    document.querySelector("#crTiltProfile");

  if (!component) return;

  const card =
    component.querySelector(
      ".cr-tilt-profile__card"
    );

  const glow =
    component.querySelector(
      ".cr-tilt-profile__glow"
    );

  const glare =
    component.querySelector(
      ".cr-tilt-profile__glare"
    );

  const depthItems =
    component.querySelectorAll(
      "[data-depth]"
    );

  if (!card || !glow || !glare) return;


  /* SETTINGS */

  const MAX_TILT = 10;
  const PARALLAX_STRENGTH = 0.32;

  const canHover =
    window.matchMedia(
      "(hover: hover) and (pointer: fine)"
    );

  const reduceMotion =
    window.matchMedia(
      "(prefers-reduced-motion: reduce)"
    );

  let targetRotateX = 0;
  let targetRotateY = 0;

  let currentRotateX = 0;
  let currentRotateY = 0;

  let pointerX = 0;
  let pointerY = 0;

  let isHovering = false;
  let animationFrame = null;


  /* HELPERS */

  const clamp = (value, min, max) => {
    return Math.min(
      Math.max(value, min),
      max
    );
  };


  /* DEPTH / PARALLAX */

  const updateDepthItems = () => {

    depthItems.forEach((item) => {

      const depth =
        Number.parseFloat(
          item.dataset.depth
        ) || 0;

      const moveX =
        pointerX *
        depth *
        PARALLAX_STRENGTH;

      const moveY =
        pointerY *
        depth *
        PARALLAX_STRENGTH;

      item.style.transform =
        `translate3d(
          ${moveX}px,
          ${moveY}px,
          ${depth}px
        )`;

    });

  };


  /* RESET DEPTH */

  const resetDepthItems = () => {

    depthItems.forEach((item) => {

      item.style.transform =
        "translate3d(0px, 0px, 0px)";

    });

  };


  /* SMOOTH ANIMATION */

  const animate = () => {

    currentRotateX +=
      (targetRotateX - currentRotateX) *
      0.12;

    currentRotateY +=
      (targetRotateY - currentRotateY) *
      0.12;

    card.style.transform =
      `rotateX(${currentRotateX}deg)
       rotateY(${currentRotateY}deg)`;

    if (
      isHovering ||
      Math.abs(currentRotateX) > 0.01 ||
      Math.abs(currentRotateY) > 0.01
    ) {

      animationFrame =
        requestAnimationFrame(animate);

    } else {

      currentRotateX = 0;
      currentRotateY = 0;

      card.style.transform =
        "rotateX(0deg) rotateY(0deg)";

      animationFrame = null;

    }

  };


  /* POINTER MOVE */

  const handlePointerMove = (event) => {

    if (
      !canHover.matches ||
      reduceMotion.matches
    ) {
      return;
    }

    const rect =
      card.getBoundingClientRect();

    const x =
      event.clientX - rect.left;

    const y =
      event.clientY - rect.top;

    const normalizedX =
      clamp(
        (x / rect.width) * 2 - 1,
        -1,
        1
      );

    const normalizedY =
      clamp(
        (y / rect.height) * 2 - 1,
        -1,
        1
      );

    pointerX = normalizedX;
    pointerY = normalizedY;

    targetRotateX =
      normalizedY * -MAX_TILT;

    targetRotateY =
      normalizedX * MAX_TILT;


    /* CURSOR FOLLOW GLOW */

    glow.style.left = `${x}px`;
    glow.style.top = `${y}px`;


    /* DYNAMIC GLARE */

    const glareX =
      50 + normalizedX * 35;

    const glareY =
      50 + normalizedY * 35;

    glare.style.background =
      `radial-gradient(
        circle at ${glareX}% ${glareY}%,
        rgba(255, 255, 255, 0.20) 0%,
        rgba(103, 232, 249, 0.08) 18%,
        rgba(168, 85, 247, 0.04) 34%,
        transparent 62%
      )`;


    /* LAYERED PARALLAX */

    updateDepthItems();

  };


  /* POINTER ENTER */

  const handlePointerEnter = () => {

    if (
      !canHover.matches ||
      reduceMotion.matches
    ) {
      return;
    }

    isHovering = true;

    glow.style.opacity = "0.9";
    glare.style.opacity = "0.55";

    if (!animationFrame) {

      animationFrame =
        requestAnimationFrame(animate);

    }

  };


  /* POINTER LEAVE */

  const handlePointerLeave = () => {

    isHovering = false;

    targetRotateX = 0;
    targetRotateY = 0;

    pointerX = 0;
    pointerY = 0;

    glow.style.left = "50%";
    glow.style.top = "50%";
    glow.style.opacity = "0.5";

    glare.style.opacity = "0";

    resetDepthItems();

    if (!animationFrame) {

      animationFrame =
        requestAnimationFrame(animate);

    }

  };


  /* EVENT LISTENERS */

  card.addEventListener(
    "pointerenter",
    handlePointerEnter
  );

  card.addEventListener(
    "pointermove",
    handlePointerMove
  );

  card.addEventListener(
    "pointerleave",
    handlePointerLeave
  );


  /* WINDOW / TAB SAFETY RESET */

  window.addEventListener("blur", () => {

    if (isHovering) {
      handlePointerLeave();
    }

  });

  document.addEventListener(
    "visibilitychange",
    () => {

      if (
        document.hidden &&
        isHovering
      ) {
        handlePointerLeave();
      }

    }
  );


  /* INPUT MODE CHANGE */

  const handleInputModeChange = () => {

    if (
      !canHover.matches ||
      reduceMotion.matches
    ) {

      handlePointerLeave();

      card.style.transform =
        "rotateX(0deg) rotateY(0deg)";

      resetDepthItems();

    }

  };

  canHover.addEventListener(
    "change",
    handleInputModeChange
  );

  reduceMotion.addEventListener(
    "change",
    handleInputModeChange
  );

})();

How This 3D Tilt Profile Card Works

The 3D Tilt Profile Card combines a glass-style profile layout with pointer-based tilt, layered parallax, cursor lighting, and subtle CSS animations. The outer .cr-tilt-profile__stage uses perspective: 1100px, while the card and its internal depth layers use transform-style: preserve-3d. This gives the JavaScript enough 3D space to rotate the card and move individual sections forward along the Z-axis.

The card itself is limited to 420px through its stage and has a minimum height of 535px. Its visual style comes from a semi-transparent dark background, a 28px border radius, layered box shadows, and backdrop-filter: blur(20px). Cyan and purple radial gradients appear both behind the component and inside decorative orbs, while a faint 28px grid adds texture without interfering with the profile content.

Several content groups include a data-depth attribute. The top status row uses a depth of 18, the avatar uses 34, profile information uses 26, skills use 22, stats use 20, and the action area uses 30. JavaScript reads these values and applies translate3d() transforms. Because each group has a different depth, the sections move by different amounts as the pointer travels across the card.

The tilt calculation begins with the pointer position inside the card. JavaScript converts the X and Y coordinates to values between -1 and 1, then uses MAX_TILT = 10 to limit rotation to roughly 10 degrees in each direction. Instead of immediately jumping to the target rotation, the animate() function interpolates the current rotation toward it by 0.12 on each animation frame. requestAnimationFrame() keeps this movement smooth while the card is active.

The same normalized pointer values drive the parallax effect. Each data-depth value is multiplied by PARALLAX_STRENGTH, which is set to 0.32. Higher-depth elements therefore travel farther and also receive a matching positive Z translation.

Lighting follows the pointer as well. A 320px circular glow updates its left and top positions to match the cursor. The glare layer is generated dynamically as a radial gradient, with its focal point shifting by up to 35% from the center. This makes the highlight respond to the same movement as the tilt instead of behaving like a fixed overlay.

For more detail about the 3D transform system used for the rotation and layered depth, see the MDN CSS transform documentation.

There are also CSS-only animations. The green availability indicator pulses every 2.2 seconds, the dashed avatar ring completes a rotation every 14 seconds, and the cursor hint moves diagonally on a 1.8-second loop. Skill chips and both action buttons have their own small hover translations.

The component accounts for different input and accessibility conditions. On coarse pointers or devices without hover, the tilt and depth transforms are disabled, the glare is hidden, and the interaction hint disappears. The prefers-reduced-motion media query removes transitions and decorative animations for users who request reduced motion.

Customizing the 3D Tilt Profile Card

Most of the appearance can be adjusted through the CSS variables and existing sizing values, while the strength of the interactive movement is controlled by two JavaScript constants.

  • Change --cr-cyan and --cr-purple to create a different accent palette.
  • Adjust --cr-bg, --cr-card, --cr-text, and --cr-muted to customize the dark theme and typography colors.
  • Change the card’s 28px border radius or 28px padding to alter its shape and spacing.
  • Modify MAX_TILT = 10 to increase or reduce the maximum pointer-driven rotation.
  • Adjust PARALLAX_STRENGTH = 0.32 to control how far the internal depth layers move.
  • Edit the individual data-depth values to change how strongly the status, avatar, profile information, skills, stats, and buttons project forward.
  • Change the 320px glow dimensions or its radial-gradient colors to modify the cursor-follow lighting.
  • Replace the profile name, initials, role, description, skills, statistics, status, and button labels directly in the HTML.

Where to Use This 3D Tilt Profile Card

This component works best where a compact profile needs more visual interaction than a standard static card while still keeping the important information readable.

  • Developer and designer portfolio profile sections
  • Freelancer introduction cards
  • Creative team member showcases
  • Personal landing pages
  • Community or creator profiles
  • Dashboard user summaries
  • Agency team sections
  • Interactive profile previews

For layouts that need several interactive cards rather than one detailed profile, the 3D Hover Monster Cards component provides another related card-based interaction.

Because the JavaScript checks hover capability and reduced-motion preferences before applying its pointer effects, the card can retain a simpler static presentation where its full 3D interaction is not appropriate.

Frequently Asked Questions

How does the card create the 3D tilt effect?

JavaScript tracks pointermove events, converts the cursor coordinates into normalized values from -1 to 1, and maps them to rotateX() and rotateY(). The rotation is capped by the MAX_TILT value of 10 degrees and smoothly interpolated with requestAnimationFrame().

What are the data-depth attributes used for?

They assign different depth values to content groups. JavaScript uses each value with PARALLAX_STRENGTH to calculate X and Y movement and passes the depth itself to the Z position of translate3d(). This creates layered parallax inside the tilted card.

Does the 3D effect run on touch devices?

No. The script checks (hover: hover) and (pointer: fine), while the CSS also disables card and depth transforms for coarse pointers or devices without hover. The glare and cursor instruction are removed in that mode as well.

Does the component support reduced-motion preferences?

Yes. The CSS prefers-reduced-motion: reduce rule disables transitions and the status, avatar-ring, and hint animations. JavaScript also checks the same preference and avoids running the interactive pointer tilt when reduced motion is requested.

The 3D Tilt Profile Card uses CSS depth styling and pointer-aware JavaScript together, while keeping the underlying profile structure straightforward to edit for different people, roles, skills, and statistics.

Scroll to Top