{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "StickerPeel-TS-CSS",
	"title": "StickerPeel",
	"description": "Sticker corner lift + peel interaction using 3D transform and shadow depth.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "StickerPeel.css",
			"target": "@components/StickerPeel.css",
			"content": ":root {\n  --sticker-rotate: 30deg;\n  --sticker-p: 10px;\n  --sticker-peelback-hover: 30%;\n  --sticker-peelback-active: 40%;\n  --sticker-peel-easing: power3.out;\n  --sticker-peel-hover-easing: power2.out;\n  --sticker-start: calc(-1 * var(--sticker-p));\n  --sticker-end: calc(100% + var(--sticker-p));\n  --sticker-shadow-opacity: 0.6;\n  --sticker-lighting-constant: 0.1;\n  --peel-direction: 0deg;\n}\n\n.sticker-container {\n  position: relative;\n  transform: rotate(var(--peel-direction));\n  transform-origin: center;\n}\n\n.sticker-container * {\n  -webkit-user-select: none;\n  user-select: none;\n  -webkit-touch-callout: none;\n  -webkit-tap-highlight-color: transparent;\n}\n\n.sticker-main {\n  clip-path: polygon(\n    var(--sticker-start) var(--sticker-start),\n    var(--sticker-end) var(--sticker-start),\n    var(--sticker-end) var(--sticker-end),\n    var(--sticker-start) var(--sticker-end)\n  );\n  transition: clip-path 0.6s ease-out;\n  filter: url(#dropShadow);\n}\n\n.sticker-main > * {\n  transform: rotate(calc(-1 * var(--peel-direction)));\n}\n\n.sticker-lighting {\n  filter: url(#pointLight);\n}\n\n.sticker-container:hover .sticker-main,\n.sticker-container.touch-active .sticker-main {\n  clip-path: polygon(\n    var(--sticker-start) var(--sticker-peelback-hover),\n    var(--sticker-end) var(--sticker-peelback-hover),\n    var(--sticker-end) var(--sticker-end),\n    var(--sticker-start) var(--sticker-end)\n  );\n}\n\n.sticker-container:active .sticker-main {\n  clip-path: polygon(\n    var(--sticker-start) var(--sticker-peelback-active),\n    var(--sticker-end) var(--sticker-peelback-active),\n    var(--sticker-end) var(--sticker-end),\n    var(--sticker-start) var(--sticker-end)\n  );\n}\n\n.sticker-image {\n  transform: rotate(var(--sticker-rotate));\n}\n\n.flap {\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  left: 0;\n  top: calc(-100% - var(--sticker-p) - var(--sticker-p));\n  clip-path: polygon(\n    var(--sticker-start) var(--sticker-start),\n    var(--sticker-end) var(--sticker-start),\n    var(--sticker-end) var(--sticker-start),\n    var(--sticker-start) var(--sticker-start)\n  );\n  transform: scaleY(-1);\n  transition: all 0.6s ease-out;\n}\n\n.flap > * {\n  transform: rotate(calc(-1 * var(--peel-direction)));\n}\n\n.sticker-container:hover .flap,\n.sticker-container.touch-active .flap {\n  clip-path: polygon(\n    var(--sticker-start) var(--sticker-start),\n    var(--sticker-end) var(--sticker-start),\n    var(--sticker-end) var(--sticker-peelback-hover),\n    var(--sticker-start) var(--sticker-peelback-hover)\n  );\n  top: calc(-100% + 2 * var(--sticker-peelback-hover) - 1px);\n}\n\n.sticker-container:active .flap {\n  clip-path: polygon(\n    var(--sticker-start) var(--sticker-start),\n    var(--sticker-end) var(--sticker-start),\n    var(--sticker-end) var(--sticker-peelback-active),\n    var(--sticker-start) var(--sticker-peelback-active)\n  );\n  top: calc(-100% + 2 * var(--sticker-peelback-active) - 1px);\n}\n\n.flap-lighting {\n  filter: url(#pointLightFlipped);\n}\n\n.flap-image {\n  transform: rotate(var(--sticker-rotate));\n  filter: url(#expandAndFill);\n}\n\n.draggable {\n  position: absolute;\n  cursor: grab;\n  -webkit-transform: translateZ(0);\n  transform: translateZ(0);\n}\n\n.draggable:active {\n  cursor: grabbing;\n}\n\n@media (hover: none) and (pointer: coarse) {\n  .draggable {\n    cursor: default;\n  }\n\n  .sticker-container {\n    touch-action: none;\n  }\n}\n\n.sticker-image,\n.flap-image {\n  width: var(--sticker-width, 200px);\n}\n\n.sticker-main,\n.flap {\n  will-change: clip-path, transform;\n}\n\n.sticker-ripple {\n  position: absolute;\n  border-radius: 50%;\n  background: rgba(255, 255, 255, 0.6);\n  pointer-events: none;\n  z-index: 10;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "StickerPeel.tsx",
			"content": "import { useRef, useEffect, useMemo, type CSSProperties } from 'react';\nimport { gsap } from 'gsap';\nimport { Draggable } from 'gsap/Draggable';\nimport './StickerPeel.css';\n\ngsap.registerPlugin(Draggable);\n\ninterface StickerPeelProps {\n  imageSrc: string;\n  rotate?: number;\n  peelBackHoverPct?: number;\n  peelBackActivePct?: number;\n  peelEasing?: string;\n  peelHoverEasing?: string;\n  width?: number;\n  shadowIntensity?: number;\n  lightingIntensity?: number;\n  initialPosition?: 'center' | 'random' | { x: number; y: number };\n  peelDirection?: number;\n  className?: string;\n}\n\ninterface CSSVars extends CSSProperties {\n  '--sticker-rotate'?: string;\n  '--sticker-p'?: string;\n  '--sticker-peelback-hover'?: string;\n  '--sticker-peelback-active'?: string;\n  '--sticker-peel-easing'?: string;\n  '--sticker-peel-hover-easing'?: string;\n  '--sticker-width'?: string;\n  '--sticker-shadow-opacity'?: number;\n  '--sticker-lighting-constant'?: number;\n  '--peel-direction'?: string;\n}\n\nconst StickerPeel: React.FC<StickerPeelProps> = ({\n  imageSrc,\n  rotate = 30,\n  peelBackHoverPct = 30,\n  peelBackActivePct = 40,\n  peelEasing = 'power3.out',\n  peelHoverEasing = 'power2.out',\n  width = 200,\n  shadowIntensity = 0.6,\n  lightingIntensity = 0.1,\n  initialPosition = 'center',\n  peelDirection = 0,\n  className = ''\n}) => {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const dragTargetRef = useRef<HTMLDivElement>(null);\n  const pointLightRef = useRef<SVGFEPointLightElement>(null);\n  const pointLightFlippedRef = useRef<SVGFEPointLightElement>(null);\n  const draggableInstanceRef = useRef<Draggable | null>(null);\n\n  const defaultPadding = 10;\n\n  useEffect(() => {\n    const target = dragTargetRef.current;\n    if (!target) return;\n\n    let startX = 0,\n      startY = 0;\n\n    if (initialPosition === 'center') {\n      return;\n    }\n\n    if (typeof initialPosition === 'object' && initialPosition.x !== undefined && initialPosition.y !== undefined) {\n      startX = initialPosition.x;\n      startY = initialPosition.y;\n    }\n\n    gsap.set(target, { x: startX, y: startY });\n  }, [initialPosition]);\n\n  useEffect(() => {\n    const target = dragTargetRef.current;\n    if (!target) return;\n\n    const boundsEl = target.parentNode as HTMLElement;\n\n    const draggable = Draggable.create(target, {\n      type: 'x,y',\n      bounds: boundsEl,\n      inertia: true,\n      onDrag(this: Draggable) {\n        const rot = gsap.utils.clamp(-24, 24, this.deltaX * 0.4);\n        gsap.to(target, { rotation: rot, duration: 0.15, ease: 'power1.out' });\n      },\n      onDragEnd() {\n        const rotationEase = 'power2.out';\n        const duration = 0.8;\n        gsap.to(target, { rotation: 0, duration, ease: rotationEase });\n      }\n    });\n\n    draggableInstanceRef.current = draggable[0];\n\n    const handleResize = () => {\n      if (draggableInstanceRef.current) {\n        draggableInstanceRef.current.update();\n\n        const currentX = gsap.getProperty(target, 'x') as number;\n        const currentY = gsap.getProperty(target, 'y') as number;\n\n        const boundsRect = boundsEl.getBoundingClientRect();\n        const targetRect = target.getBoundingClientRect();\n\n        const maxX = boundsRect.width - targetRect.width;\n        const maxY = boundsRect.height - targetRect.height;\n\n        const newX = Math.max(0, Math.min(currentX, maxX));\n        const newY = Math.max(0, Math.min(currentY, maxY));\n\n        if (newX !== currentX || newY !== currentY) {\n          gsap.to(target, {\n            x: newX,\n            y: newY,\n            duration: 0.3,\n            ease: 'power2.out'\n          });\n        }\n      }\n    };\n\n    window.addEventListener('resize', handleResize);\n    window.addEventListener('orientationchange', handleResize);\n\n    return () => {\n      window.removeEventListener('resize', handleResize);\n      window.removeEventListener('orientationchange', handleResize);\n      if (draggableInstanceRef.current) {\n        draggableInstanceRef.current.kill();\n      }\n    };\n  }, []);\n\n  useEffect(() => {\n    const updateLight = (e: Event) => {\n      const mouseEvent = e as MouseEvent;\n      const rect = containerRef.current?.getBoundingClientRect();\n      if (!rect) return;\n\n      const x = mouseEvent.clientX - rect.left;\n      const y = mouseEvent.clientY - rect.top;\n\n      if (pointLightRef.current) {\n        gsap.set(pointLightRef.current, { attr: { x, y } });\n      }\n\n      const normalizedAngle = Math.abs(peelDirection % 360);\n      if (pointLightFlippedRef.current) {\n        if (normalizedAngle !== 180) {\n          gsap.set(pointLightFlippedRef.current, {\n            attr: { x, y: rect.height - y }\n          });\n        } else {\n          gsap.set(pointLightFlippedRef.current, {\n            attr: { x: -1000, y: -1000 }\n          });\n        }\n      }\n    };\n\n    const container = containerRef.current;\n    const eventType = 'mousemove';\n\n    if (container) {\n      container.addEventListener(eventType, updateLight);\n      return () => container.removeEventListener(eventType, updateLight);\n    }\n  }, [peelDirection]);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const handleTouchStart = () => {\n      container.classList.add('touch-active');\n    };\n\n    const handleTouchEnd = () => {\n      container.classList.remove('touch-active');\n    };\n\n    container.addEventListener('touchstart', handleTouchStart);\n    container.addEventListener('touchend', handleTouchEnd);\n    container.addEventListener('touchcancel', handleTouchEnd);\n\n    return () => {\n      container.removeEventListener('touchstart', handleTouchStart);\n      container.removeEventListener('touchend', handleTouchEnd);\n      container.removeEventListener('touchcancel', handleTouchEnd);\n    };\n  }, []);\n\n  const cssVars: CSSVars = useMemo(\n    () => ({\n      '--sticker-rotate': `${rotate}deg`,\n      '--sticker-p': `${defaultPadding}px`,\n      '--sticker-peelback-hover': `${peelBackHoverPct}%`,\n      '--sticker-peelback-active': `${peelBackActivePct}%`,\n      '--sticker-peel-easing': peelEasing,\n      '--sticker-peel-hover-easing': peelHoverEasing,\n      '--sticker-width': `${width}px`,\n      '--sticker-shadow-opacity': shadowIntensity,\n      '--sticker-lighting-constant': lightingIntensity,\n      '--peel-direction': `${peelDirection}deg`\n    }),\n    [\n      rotate,\n      peelBackHoverPct,\n      peelBackActivePct,\n      peelEasing,\n      peelHoverEasing,\n      width,\n      shadowIntensity,\n      lightingIntensity,\n      peelDirection\n    ]\n  );\n\n  return (\n    <div className={`draggable ${className}`} ref={dragTargetRef} style={cssVars}>\n      <svg width=\"0\" height=\"0\">\n        <defs>\n          <filter id=\"pointLight\">\n            <feGaussianBlur stdDeviation=\"1\" result=\"blur\" />\n            <feSpecularLighting\n              result=\"spec\"\n              in=\"blur\"\n              specularExponent=\"100\"\n              specularConstant={lightingIntensity}\n              lightingColor=\"white\"\n            >\n              <fePointLight ref={pointLightRef} x=\"100\" y=\"100\" z=\"300\" />\n            </feSpecularLighting>\n            <feComposite in=\"spec\" in2=\"SourceGraphic\" result=\"lit\" />\n            <feComposite in=\"lit\" in2=\"SourceAlpha\" operator=\"in\" />\n          </filter>\n\n          <filter id=\"pointLightFlipped\">\n            <feGaussianBlur stdDeviation=\"10\" result=\"blur\" />\n            <feSpecularLighting\n              result=\"spec\"\n              in=\"blur\"\n              specularExponent=\"100\"\n              specularConstant={lightingIntensity * 7}\n              lightingColor=\"white\"\n            >\n              <fePointLight ref={pointLightFlippedRef} x=\"100\" y=\"100\" z=\"300\" />\n            </feSpecularLighting>\n            <feComposite in=\"spec\" in2=\"SourceGraphic\" result=\"lit\" />\n            <feComposite in=\"lit\" in2=\"SourceAlpha\" operator=\"in\" />\n          </filter>\n\n          <filter id=\"dropShadow\">\n            <feDropShadow\n              dx=\"2\"\n              dy=\"4\"\n              stdDeviation={3 * shadowIntensity}\n              floodColor=\"black\"\n              floodOpacity={shadowIntensity}\n            />\n          </filter>\n\n          <filter id=\"expandAndFill\">\n            <feOffset dx=\"0\" dy=\"0\" in=\"SourceAlpha\" result=\"shape\" />\n            <feFlood floodColor=\"rgb(179,179,179)\" result=\"flood\" />\n            <feComposite operator=\"in\" in=\"flood\" in2=\"shape\" />\n          </filter>\n        </defs>\n      </svg>\n\n      <div className=\"sticker-container\" ref={containerRef}>\n        <div className=\"sticker-main\">\n          <div className=\"sticker-lighting\">\n            <img\n              src={imageSrc}\n              alt=\"\"\n              className=\"sticker-image\"\n              draggable=\"false\"\n              onContextMenu={e => e.preventDefault()}\n            />\n          </div>\n        </div>\n\n        <div className=\"flap\">\n          <div className=\"flap-lighting\">\n            <img\n              src={imageSrc}\n              alt=\"\"\n              className=\"flap-image\"\n              draggable=\"false\"\n              onContextMenu={e => e.preventDefault()}\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default StickerPeel;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"gsap@^3.13.0"
	]
}