{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "StickerPeel-TS-TW",
	"title": "StickerPeel",
	"description": "Sticker corner lift + peel interaction using 3D transform and shadow depth.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "StickerPeel/StickerPeel.tsx",
			"content": "import { useRef, useEffect, useMemo, type CSSProperties } from 'react';\nimport { gsap } from 'gsap';\nimport { Draggable } from 'gsap/Draggable';\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  '--sticker-start'?: string;\n  '--sticker-end'?: 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 = 12;\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      '--sticker-start': `calc(-1 * ${defaultPadding}px)`,\n      '--sticker-end': `calc(100% + ${defaultPadding}px)`\n    }),\n    [\n      rotate,\n      peelBackHoverPct,\n      peelBackActivePct,\n      peelEasing,\n      peelHoverEasing,\n      width,\n      shadowIntensity,\n      lightingIntensity,\n      peelDirection,\n      defaultPadding\n    ]\n  );\n\n  const stickerMainStyle: CSSProperties = {\n    clipPath: `polygon(var(--sticker-start) var(--sticker-start), var(--sticker-end) var(--sticker-start), var(--sticker-end) var(--sticker-end), var(--sticker-start) var(--sticker-end))`,\n    transition: 'clip-path 0.6s ease-out',\n    filter: 'url(#dropShadow)',\n    willChange: 'clip-path, transform'\n  };\n\n  const flapStyle: CSSProperties = {\n    clipPath: `polygon(var(--sticker-start) var(--sticker-start), var(--sticker-end) var(--sticker-start), var(--sticker-end) var(--sticker-start), var(--sticker-start) var(--sticker-start))`,\n    top: `calc(-100% - var(--sticker-p) - var(--sticker-p))`,\n    transform: 'scaleY(-1)',\n    transition: 'all 0.6s ease-out',\n    willChange: 'clip-path, transform'\n  };\n\n  const imageStyle: CSSProperties = {\n    transform: `rotate(calc(${rotate}deg - ${peelDirection}deg))`,\n    width: `${width}px`\n  };\n\n  const shadowImageStyle: CSSProperties = {\n    ...imageStyle,\n    filter: 'url(#expandAndFill)'\n  };\n\n  return (\n    <div\n      className={`absolute cursor-grab active:cursor-grabbing transform-gpu ${className}`}\n      ref={dragTargetRef}\n      style={cssVars}\n    >\n      <style\n        dangerouslySetInnerHTML={{\n          __html: `\n          .sticker-container:hover .sticker-main,\n          .sticker-container.touch-active .sticker-main {\n            clip-path: polygon(var(--sticker-start) var(--sticker-peelback-hover), var(--sticker-end) var(--sticker-peelback-hover), var(--sticker-end) var(--sticker-end), var(--sticker-start) var(--sticker-end)) !important;\n          }\n          .sticker-container:hover .sticker-flap,\n          .sticker-container.touch-active .sticker-flap {\n            clip-path: polygon(var(--sticker-start) var(--sticker-start), var(--sticker-end) var(--sticker-start), var(--sticker-end) var(--sticker-peelback-hover), var(--sticker-start) var(--sticker-peelback-hover)) !important;\n            top: calc(-100% + 2 * var(--sticker-peelback-hover) - 1px) !important;\n          }\n          .sticker-container:active .sticker-main {\n            clip-path: polygon(var(--sticker-start) var(--sticker-peelback-active), var(--sticker-end) var(--sticker-peelback-active), var(--sticker-end) var(--sticker-end), var(--sticker-start) var(--sticker-end)) !important;\n          }\n          .sticker-container:active .sticker-flap {\n            clip-path: polygon(var(--sticker-start) var(--sticker-start), var(--sticker-end) var(--sticker-start), var(--sticker-end) var(--sticker-peelback-active), var(--sticker-start) var(--sticker-peelback-active)) !important;\n            top: calc(-100% + 2 * var(--sticker-peelback-active) - 1px) !important;\n          }\n        `\n        }}\n      />\n\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\n        className=\"sticker-container relative select-none touch-none sm:touch-auto\"\n        ref={containerRef}\n        style={{\n          WebkitUserSelect: 'none',\n          userSelect: 'none',\n          WebkitTouchCallout: 'none',\n          WebkitTapHighlightColor: 'transparent',\n          transform: `rotate(${peelDirection}deg)`,\n          transformOrigin: 'center'\n        }}\n      >\n        <div className=\"sticker-main\" style={stickerMainStyle}>\n          <div style={{ filter: 'url(#pointLight)' }}>\n            <img\n              src={imageSrc}\n              alt=\"\"\n              className=\"block\"\n              style={imageStyle}\n              draggable=\"false\"\n              onContextMenu={e => e.preventDefault()}\n            />\n          </div>\n        </div>\n\n        <div className=\"absolute top-4 left-2 w-full h-full opacity-40\" style={{ filter: 'brightness(0) blur(8px)' }}>\n          <div className=\"sticker-flap\" style={flapStyle}>\n            <img\n              src={imageSrc}\n              alt=\"\"\n              className=\"block\"\n              style={shadowImageStyle}\n              draggable=\"false\"\n              onContextMenu={e => e.preventDefault()}\n            />\n          </div>\n        </div>\n\n        <div className=\"sticker-flap absolute w-full h-full left-0\" style={flapStyle}>\n          <div style={{ filter: 'url(#pointLightFlipped)' }}>\n            <img\n              src={imageSrc}\n              alt=\"\"\n              className=\"block\"\n              style={shadowImageStyle}\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"
	]
}