{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "ProfileCard-JS-TW",
	"title": "ProfileCard",
	"description": "Animated profile card glare with 3D hover effect.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "ProfileCard/ProfileCard.jsx",
			"content": "import React, { useEffect, useRef, useCallback, useMemo } from 'react';\n\nconst DEFAULT_INNER_GRADIENT = 'linear-gradient(145deg,#60496e8c 0%,#71C4FF44 100%)';\n\nconst ANIMATION_CONFIG = {\n  INITIAL_DURATION: 1200,\n  INITIAL_X_OFFSET: 70,\n  INITIAL_Y_OFFSET: 60,\n  DEVICE_BETA_OFFSET: 20,\n  ENTER_TRANSITION_MS: 180\n};\n\nconst clamp = (v, min = 0, max = 100) => Math.min(Math.max(v, min), max);\nconst round = (v, precision = 3) => parseFloat(v.toFixed(precision));\nconst adjust = (v, fMin, fMax, tMin, tMax) => round(tMin + ((tMax - tMin) * (v - fMin)) / (fMax - fMin));\n\n// Inject keyframes once\nconst KEYFRAMES_ID = 'pc-keyframes';\nif (typeof document !== 'undefined' && !document.getElementById(KEYFRAMES_ID)) {\n  const style = document.createElement('style');\n  style.id = KEYFRAMES_ID;\n  style.textContent = `\n    @keyframes pc-holo-bg {\n      0% { background-position: 0 var(--background-y), 0 0, center; }\n      100% { background-position: 0 var(--background-y), 90% 90%, center; }\n    }\n  `;\n  document.head.appendChild(style);\n}\n\nconst ProfileCardComponent = ({\n  avatarUrl = '<Placeholder for avatar URL>',\n  iconUrl = '<Placeholder for icon URL>',\n  grainUrl = '<Placeholder for grain URL>',\n  innerGradient,\n  behindGlowEnabled = true,\n  behindGlowColor,\n  behindGlowSize,\n  className = '',\n  enableTilt = true,\n  enableMobileTilt = false,\n  mobileTiltSensitivity = 5,\n  miniAvatarUrl,\n  name = 'Javi A. Torres',\n  title = 'Software Engineer',\n  handle = 'javicodes',\n  status = 'Online',\n  contactText = 'Contact',\n  showUserInfo = true,\n  onContactClick\n}) => {\n  const wrapRef = useRef(null);\n  const shellRef = useRef(null);\n\n  const enterTimerRef = useRef(null);\n  const leaveRafRef = useRef(null);\n\n  const tiltEngine = useMemo(() => {\n    if (!enableTilt) return null;\n\n    let rafId = null;\n    let running = false;\n    let lastTs = 0;\n\n    let currentX = 0;\n    let currentY = 0;\n    let targetX = 0;\n    let targetY = 0;\n\n    const DEFAULT_TAU = 0.14;\n    const INITIAL_TAU = 0.6;\n    let initialUntil = 0;\n\n    const setVarsFromXY = (x, y) => {\n      const shell = shellRef.current;\n      const wrap = wrapRef.current;\n      if (!shell || !wrap) return;\n\n      const width = shell.clientWidth || 1;\n      const height = shell.clientHeight || 1;\n\n      const percentX = clamp((100 / width) * x);\n      const percentY = clamp((100 / height) * y);\n\n      const centerX = percentX - 50;\n      const centerY = percentY - 50;\n\n      const properties = {\n        '--pointer-x': `${percentX}%`,\n        '--pointer-y': `${percentY}%`,\n        '--background-x': `${adjust(percentX, 0, 100, 35, 65)}%`,\n        '--background-y': `${adjust(percentY, 0, 100, 35, 65)}%`,\n        '--pointer-from-center': `${clamp(Math.hypot(percentY - 50, percentX - 50) / 50, 0, 1)}`,\n        '--pointer-from-top': `${percentY / 100}`,\n        '--pointer-from-left': `${percentX / 100}`,\n        '--rotate-x': `${round(-(centerX / 5))}deg`,\n        '--rotate-y': `${round(centerY / 4)}deg`\n      };\n\n      for (const [k, v] of Object.entries(properties)) wrap.style.setProperty(k, v);\n    };\n\n    const step = ts => {\n      if (!running) return;\n      if (lastTs === 0) lastTs = ts;\n      const dt = (ts - lastTs) / 1000;\n      lastTs = ts;\n\n      const tau = ts < initialUntil ? INITIAL_TAU : DEFAULT_TAU;\n      const k = 1 - Math.exp(-dt / tau);\n\n      currentX += (targetX - currentX) * k;\n      currentY += (targetY - currentY) * k;\n\n      setVarsFromXY(currentX, currentY);\n\n      const stillFar = Math.abs(targetX - currentX) > 0.05 || Math.abs(targetY - currentY) > 0.05;\n\n      if (stillFar || document.hasFocus()) {\n        rafId = requestAnimationFrame(step);\n      } else {\n        running = false;\n        lastTs = 0;\n        if (rafId) {\n          cancelAnimationFrame(rafId);\n          rafId = null;\n        }\n      }\n    };\n\n    const start = () => {\n      if (running) return;\n      running = true;\n      lastTs = 0;\n      rafId = requestAnimationFrame(step);\n    };\n\n    return {\n      setImmediate(x, y) {\n        currentX = x;\n        currentY = y;\n        setVarsFromXY(currentX, currentY);\n      },\n      setTarget(x, y) {\n        targetX = x;\n        targetY = y;\n        start();\n      },\n      toCenter() {\n        const shell = shellRef.current;\n        if (!shell) return;\n        this.setTarget(shell.clientWidth / 2, shell.clientHeight / 2);\n      },\n      beginInitial(durationMs) {\n        initialUntil = performance.now() + durationMs;\n        start();\n      },\n      getCurrent() {\n        return { x: currentX, y: currentY, tx: targetX, ty: targetY };\n      },\n      cancel() {\n        if (rafId) cancelAnimationFrame(rafId);\n        rafId = null;\n        running = false;\n        lastTs = 0;\n      }\n    };\n  }, [enableTilt]);\n\n  const getOffsets = (evt, el) => {\n    const rect = el.getBoundingClientRect();\n    return { x: evt.clientX - rect.left, y: evt.clientY - rect.top };\n  };\n\n  const handlePointerMove = useCallback(\n    event => {\n      const shell = shellRef.current;\n      if (!shell || !tiltEngine) return;\n      const { x, y } = getOffsets(event, shell);\n      tiltEngine.setTarget(x, y);\n    },\n    [tiltEngine]\n  );\n\n  const handlePointerEnter = useCallback(\n    event => {\n      const shell = shellRef.current;\n      if (!shell || !tiltEngine) return;\n\n      shell.classList.add('active');\n      shell.classList.add('entering');\n      if (enterTimerRef.current) window.clearTimeout(enterTimerRef.current);\n      enterTimerRef.current = window.setTimeout(() => {\n        shell.classList.remove('entering');\n      }, ANIMATION_CONFIG.ENTER_TRANSITION_MS);\n\n      const { x, y } = getOffsets(event, shell);\n      tiltEngine.setTarget(x, y);\n    },\n    [tiltEngine]\n  );\n\n  const handlePointerLeave = useCallback(() => {\n    const shell = shellRef.current;\n    if (!shell || !tiltEngine) return;\n\n    tiltEngine.toCenter();\n\n    const checkSettle = () => {\n      const { x, y, tx, ty } = tiltEngine.getCurrent();\n      const settled = Math.hypot(tx - x, ty - y) < 0.6;\n      if (settled) {\n        shell.classList.remove('active');\n        leaveRafRef.current = null;\n      } else {\n        leaveRafRef.current = requestAnimationFrame(checkSettle);\n      }\n    };\n    if (leaveRafRef.current) cancelAnimationFrame(leaveRafRef.current);\n    leaveRafRef.current = requestAnimationFrame(checkSettle);\n  }, [tiltEngine]);\n\n  const handleDeviceOrientation = useCallback(\n    event => {\n      const shell = shellRef.current;\n      if (!shell || !tiltEngine) return;\n\n      const { beta, gamma } = event;\n      if (beta == null || gamma == null) return;\n\n      const centerX = shell.clientWidth / 2;\n      const centerY = shell.clientHeight / 2;\n      const x = clamp(centerX + gamma * mobileTiltSensitivity, 0, shell.clientWidth);\n      const y = clamp(\n        centerY + (beta - ANIMATION_CONFIG.DEVICE_BETA_OFFSET) * mobileTiltSensitivity,\n        0,\n        shell.clientHeight\n      );\n\n      tiltEngine.setTarget(x, y);\n    },\n    [tiltEngine, mobileTiltSensitivity]\n  );\n\n  useEffect(() => {\n    if (!enableTilt || !tiltEngine) return;\n\n    const shell = shellRef.current;\n    if (!shell) return;\n\n    const pointerMoveHandler = handlePointerMove;\n    const pointerEnterHandler = handlePointerEnter;\n    const pointerLeaveHandler = handlePointerLeave;\n    const deviceOrientationHandler = handleDeviceOrientation;\n\n    shell.addEventListener('pointerenter', pointerEnterHandler);\n    shell.addEventListener('pointermove', pointerMoveHandler);\n    shell.addEventListener('pointerleave', pointerLeaveHandler);\n\n    const handleClick = () => {\n      if (!enableMobileTilt || location.protocol !== 'https:') return;\n      const anyMotion = window.DeviceMotionEvent;\n      if (anyMotion && typeof anyMotion.requestPermission === 'function') {\n        anyMotion\n          .requestPermission()\n          .then(state => {\n            if (state === 'granted') {\n              window.addEventListener('deviceorientation', deviceOrientationHandler);\n            }\n          })\n          .catch(console.error);\n      } else {\n        window.addEventListener('deviceorientation', deviceOrientationHandler);\n      }\n    };\n    shell.addEventListener('click', handleClick);\n\n    const initialX = (shell.clientWidth || 0) - ANIMATION_CONFIG.INITIAL_X_OFFSET;\n    const initialY = ANIMATION_CONFIG.INITIAL_Y_OFFSET;\n    tiltEngine.setImmediate(initialX, initialY);\n    tiltEngine.toCenter();\n    tiltEngine.beginInitial(ANIMATION_CONFIG.INITIAL_DURATION);\n\n    return () => {\n      shell.removeEventListener('pointerenter', pointerEnterHandler);\n      shell.removeEventListener('pointermove', pointerMoveHandler);\n      shell.removeEventListener('pointerleave', pointerLeaveHandler);\n      shell.removeEventListener('click', handleClick);\n      window.removeEventListener('deviceorientation', deviceOrientationHandler);\n      if (enterTimerRef.current) window.clearTimeout(enterTimerRef.current);\n      if (leaveRafRef.current) cancelAnimationFrame(leaveRafRef.current);\n      tiltEngine.cancel();\n      shell.classList.remove('entering');\n    };\n  }, [\n    enableTilt,\n    enableMobileTilt,\n    tiltEngine,\n    handlePointerMove,\n    handlePointerEnter,\n    handlePointerLeave,\n    handleDeviceOrientation\n  ]);\n\n  const cardRadius = '30px';\n\n  const cardStyle = useMemo(\n    () => ({\n      '--icon': iconUrl ? `url(${iconUrl})` : 'none',\n      '--grain': grainUrl ? `url(${grainUrl})` : 'none',\n      '--inner-gradient': innerGradient ?? DEFAULT_INNER_GRADIENT,\n      '--behind-glow-color': behindGlowColor ?? 'rgba(125, 190, 255, 0.67)',\n      '--behind-glow-size': behindGlowSize ?? '50%',\n      '--pointer-x': '50%',\n      '--pointer-y': '50%',\n      '--pointer-from-center': '0',\n      '--pointer-from-top': '0.5',\n      '--pointer-from-left': '0.5',\n      '--card-opacity': '0',\n      '--rotate-x': '0deg',\n      '--rotate-y': '0deg',\n      '--background-x': '50%',\n      '--background-y': '50%',\n      '--card-radius': cardRadius,\n      '--sunpillar-1': 'hsl(2, 100%, 73%)',\n      '--sunpillar-2': 'hsl(53, 100%, 69%)',\n      '--sunpillar-3': 'hsl(93, 100%, 69%)',\n      '--sunpillar-4': 'hsl(176, 100%, 76%)',\n      '--sunpillar-5': 'hsl(228, 100%, 74%)',\n      '--sunpillar-6': 'hsl(283, 100%, 73%)',\n      '--sunpillar-clr-1': 'var(--sunpillar-1)',\n      '--sunpillar-clr-2': 'var(--sunpillar-2)',\n      '--sunpillar-clr-3': 'var(--sunpillar-3)',\n      '--sunpillar-clr-4': 'var(--sunpillar-4)',\n      '--sunpillar-clr-5': 'var(--sunpillar-5)',\n      '--sunpillar-clr-6': 'var(--sunpillar-6)'\n    }),\n    [iconUrl, grainUrl, innerGradient, behindGlowColor, behindGlowSize, cardRadius]\n  );\n\n  const handleContactClick = useCallback(() => {\n    onContactClick?.();\n  }, [onContactClick]);\n\n  // Complex styles that require CSS variables and can't be done with Tailwind\n  const shineStyle = {\n    maskImage: 'var(--icon)',\n    maskMode: 'luminance',\n    maskRepeat: 'repeat',\n    maskSize: '150%',\n    maskPosition: 'top calc(200% - (var(--background-y) * 5)) left calc(100% - var(--background-x))',\n    filter: 'brightness(0.66) contrast(1.33) saturate(0.33) opacity(0.5)',\n    animation: 'pc-holo-bg 18s linear infinite',\n    animationPlayState: 'running',\n    mixBlendMode: 'color-dodge',\n    '--space': '5%',\n    '--angle': '-45deg',\n    transform: 'translate3d(0, 0, 1px)',\n    overflow: 'hidden',\n    zIndex: 3,\n    background: 'transparent',\n    backgroundSize: 'cover',\n    backgroundPosition: 'center',\n    backgroundImage: `\n      repeating-linear-gradient(\n        0deg,\n        var(--sunpillar-clr-1) calc(var(--space) * 1),\n        var(--sunpillar-clr-2) calc(var(--space) * 2),\n        var(--sunpillar-clr-3) calc(var(--space) * 3),\n        var(--sunpillar-clr-4) calc(var(--space) * 4),\n        var(--sunpillar-clr-5) calc(var(--space) * 5),\n        var(--sunpillar-clr-6) calc(var(--space) * 6),\n        var(--sunpillar-clr-1) calc(var(--space) * 7)\n      ),\n      repeating-linear-gradient(\n        var(--angle),\n        #0e152e 0%,\n        hsl(180, 10%, 60%) 3.8%,\n        hsl(180, 29%, 66%) 4.5%,\n        hsl(180, 10%, 60%) 5.2%,\n        #0e152e 10%,\n        #0e152e 12%\n      ),\n      radial-gradient(\n        farthest-corner circle at var(--pointer-x) var(--pointer-y),\n        hsla(0, 0%, 0%, 0.1) 12%,\n        hsla(0, 0%, 0%, 0.15) 20%,\n        hsla(0, 0%, 0%, 0.25) 120%\n      )\n    `.replace(/\\s+/g, ' '),\n    gridArea: '1 / -1',\n    borderRadius: cardRadius,\n    pointerEvents: 'none'\n  };\n\n  const glareStyle = {\n    transform: 'translate3d(0, 0, 1.1px)',\n    overflow: 'hidden',\n    backgroundImage: `radial-gradient(\n      farthest-corner circle at var(--pointer-x) var(--pointer-y),\n      hsl(248, 25%, 80%) 12%,\n      hsla(207, 40%, 30%, 0.8) 90%\n    )`,\n    mixBlendMode: 'overlay',\n    filter: 'brightness(0.8) contrast(1.2)',\n    zIndex: 4,\n    gridArea: '1 / -1',\n    borderRadius: cardRadius,\n    pointerEvents: 'none'\n  };\n\n  return (\n    <div\n      ref={wrapRef}\n      className={`relative touch-none ${className}`.trim()}\n      style={{ perspective: '500px', transform: 'translate3d(0, 0, 0.1px)', ...cardStyle }}\n    >\n      {behindGlowEnabled && (\n        <div\n          className=\"absolute inset-0 z-0 pointer-events-none transition-opacity duration-200 ease-out\"\n          style={{\n            background: `radial-gradient(circle at var(--pointer-x) var(--pointer-y), var(--behind-glow-color) 0%, transparent var(--behind-glow-size))`,\n            filter: 'blur(50px) saturate(1.1)',\n            opacity: 'calc(0.8 * var(--card-opacity))'\n          }}\n        />\n      )}\n      <div ref={shellRef} className=\"relative z-[1] group\">\n        <section\n          className=\"grid relative overflow-hidden backface-hidden\"\n          style={{\n            height: '80svh',\n            maxHeight: '540px',\n            aspectRatio: '0.718',\n            borderRadius: cardRadius,\n            backgroundBlendMode: 'color-dodge, normal, normal, normal',\n            boxShadow:\n              'rgba(0, 0, 0, 0.8) calc((var(--pointer-from-left) * 10px) - 3px) calc((var(--pointer-from-top) * 20px) - 6px) 20px -5px',\n            transition: 'transform 1s ease',\n            transform: 'translateZ(0) rotateX(0deg) rotateY(0deg)',\n            background: 'rgba(0, 0, 0, 0.9)'\n          }}\n          onMouseEnter={e => {\n            e.currentTarget.style.transition = 'none';\n            e.currentTarget.style.transform = 'translateZ(0) rotateX(var(--rotate-y)) rotateY(var(--rotate-x))';\n          }}\n          onMouseLeave={e => {\n            const shell = shellRef.current;\n            if (shell?.classList.contains('entering')) {\n              e.currentTarget.style.transition = 'transform 180ms ease-out';\n            } else {\n              e.currentTarget.style.transition = 'transform 1s ease';\n            }\n            e.currentTarget.style.transform = 'translateZ(0) rotateX(0deg) rotateY(0deg)';\n          }}\n        >\n          <div\n            className=\"absolute inset-0\"\n            style={{\n              backgroundImage: 'var(--inner-gradient)',\n              backgroundColor: 'rgba(0, 0, 0, 0.9)',\n              borderRadius: cardRadius,\n              display: 'grid',\n              gridArea: '1 / -1'\n            }}\n          >\n            {/* Shine layer */}\n            <div style={shineStyle} />\n\n            {/* Glare layer */}\n            <div style={glareStyle} />\n\n            {/* Avatar content */}\n            <div\n              className=\"overflow-visible backface-hidden\"\n              style={{\n                mixBlendMode: 'luminosity',\n                transform: 'translateZ(2px)',\n                gridArea: '1 / -1',\n                borderRadius: cardRadius,\n                pointerEvents: 'none'\n              }}\n            >\n              <img\n                className=\"w-full absolute left-1/2 bottom-[-1px] backface-hidden will-change-transform transition-transform duration-[120ms] ease-out\"\n                src={avatarUrl}\n                alt={`${name || 'User'} avatar`}\n                loading=\"lazy\"\n                style={{\n                  transformOrigin: '50% 100%',\n                  transform:\n                    'translateX(calc(-50% + (var(--pointer-from-left) - 0.5) * 6px)) translateZ(0) scaleY(calc(1 + (var(--pointer-from-top) - 0.5) * 0.02)) scaleX(calc(1 + (var(--pointer-from-left) - 0.5) * 0.01))',\n                  borderRadius: cardRadius\n                }}\n                onError={e => {\n                  const t = e.target;\n                  t.style.display = 'none';\n                }}\n              />\n              {showUserInfo && (\n                <div\n                  className=\"absolute z-[2] flex items-center justify-between backdrop-blur-[30px] border border-white/10 pointer-events-auto\"\n                  style={{\n                    '--ui-inset': '20px',\n                    '--ui-radius-bias': '6px',\n                    bottom: 'var(--ui-inset)',\n                    left: 'var(--ui-inset)',\n                    right: 'var(--ui-inset)',\n                    background: 'rgba(255, 255, 255, 0.1)',\n                    borderRadius: 'calc(max(0px, var(--card-radius) - var(--ui-inset) + var(--ui-radius-bias)))',\n                    padding: '12px 14px'\n                  }}\n                >\n                  <div className=\"flex items-center gap-3\">\n                    <div\n                      className=\"rounded-full overflow-hidden border border-white/10 flex-shrink-0\"\n                      style={{ width: '48px', height: '48px' }}\n                    >\n                      <img\n                        className=\"w-full h-full object-cover rounded-full\"\n                        src={miniAvatarUrl || avatarUrl}\n                        alt={`${name || 'User'} mini avatar`}\n                        loading=\"lazy\"\n                        style={{ display: 'block', gridArea: 'auto', borderRadius: '50%', pointerEvents: 'auto' }}\n                        onError={e => {\n                          const t = e.target;\n                          t.style.opacity = '0.5';\n                          t.src = avatarUrl;\n                        }}\n                      />\n                    </div>\n                    <div className=\"flex flex-col items-start gap-1.5\">\n                      <div className=\"text-sm font-medium text-white/90 leading-none\">@{handle}</div>\n                      <div className=\"text-sm text-white/70 leading-none\">{status}</div>\n                    </div>\n                  </div>\n                  <button\n                    className=\"border border-white/10 rounded-lg px-4 py-3 text-xs font-semibold text-white/90 cursor-pointer backdrop-blur-[10px] transition-all duration-200 ease-out hover:border-white/40 hover:-translate-y-px\"\n                    onClick={handleContactClick}\n                    style={{ pointerEvents: 'auto', display: 'block', gridArea: 'auto', borderRadius: '8px' }}\n                    type=\"button\"\n                    aria-label={`Contact ${name || 'user'}`}\n                  >\n                    {contactText}\n                  </button>\n                </div>\n              )}\n            </div>\n\n            {/* Details content */}\n            <div\n              className=\"max-h-full overflow-hidden text-center relative z-[5]\"\n              style={{\n                transform:\n                  'translate3d(calc(var(--pointer-from-left) * -6px + 3px), calc(var(--pointer-from-top) * -6px + 3px), 0.1px)',\n                mixBlendMode: 'luminosity',\n                gridArea: '1 / -1',\n                borderRadius: cardRadius,\n                pointerEvents: 'none'\n              }}\n            >\n              <div className=\"w-full absolute flex flex-col\" style={{ top: '3em', display: 'flex', gridArea: 'auto' }}>\n                <h3\n                  className=\"font-semibold m-0\"\n                  style={{\n                    fontSize: 'min(5svh, 3em)',\n                    backgroundImage: 'linear-gradient(to bottom, #fff, #6f6fbe)',\n                    backgroundSize: '1em 1.5em',\n                    WebkitTextFillColor: 'transparent',\n                    backgroundClip: 'text',\n                    WebkitBackgroundClip: 'text',\n                    display: 'block',\n                    gridArea: 'auto',\n                    borderRadius: '0',\n                    pointerEvents: 'auto'\n                  }}\n                >\n                  {name}\n                </h3>\n                <p\n                  className=\"font-semibold whitespace-nowrap mx-auto w-min\"\n                  style={{\n                    position: 'relative',\n                    top: '-12px',\n                    fontSize: '16px',\n                    margin: '0 auto',\n                    backgroundImage: 'linear-gradient(to bottom, #fff, #4a4ac0)',\n                    backgroundSize: '1em 1.5em',\n                    WebkitTextFillColor: 'transparent',\n                    backgroundClip: 'text',\n                    WebkitBackgroundClip: 'text',\n                    display: 'block',\n                    gridArea: 'auto',\n                    borderRadius: '0',\n                    pointerEvents: 'auto'\n                  }}\n                >\n                  {title}\n                </p>\n              </div>\n            </div>\n          </div>\n        </section>\n      </div>\n    </div>\n  );\n};\n\nconst ProfileCard = React.memo(ProfileCardComponent);\nexport default ProfileCard;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}