1import{r as i,j as e,c8 as le,c9 as pe,ca as me,a1 as r,ao as ve,$ as Re,B as we,T as W}from"./index-CQKK0Imd.js";import{u as ye,C as he,T as be,P as Ce,a as Te,b as xe,c as ke}from"./useComponentProps-CEvMaZsz.js";import{C as Se}from"./Customize-D9LDBJtX.js";import{D as Ee}from"./Dependencies-Bm-Pofg-.js";import{P as oe}from"./PreviewSlider-Ch78y-a-.js";import{P as se}from"./PreviewSwitch-Bwuf6q8S.js";import{P as ce}from"./PreviewColorPickerCustom-CxB8ex0e.js";import"./index-2zoGWYkB.js";import"./backgroundThemeProps-DVgeaD0m.js";import"./ShikiCode-C19L5K1F.js";import"./aiExport-CBeFnU7D.js";import"./IconSelect-D5LzCXat.js";import"./sparkles-Df4Xp1Rb.js";import"./file-code-2-CTN1MbwE.js";import"./preview-slider-zh8UBlwx.js";import"./copy-DhagMDuz.js";import"./useScrubGesture-Db-j4kh3.js";const He=i.forwardRef(function(t,u){const{templateAreas:b,column:y,row:l,autoFlow:v,autoRows:n,templateRows:c,autoColumns:o,templateColumns:f,inline:h,...a}=t;return e.jsx(le.div,{...a,ref:u,css:[{display:h?"inline-grid":"grid",gridTemplateAreas:b,gridAutoColumns:o,gridColumn:y,gridRow:l,gridAutoFlow:v,gridAutoRows:n,gridTemplateRows:c,gridTemplateColumns:f},t.css]})});function ie(s){return me(s,t=>t==="auto"?"auto":`span ${t}/span ${t}`)}const N=i.forwardRef(function(t,u){const{area:b,colSpan:y,colStart:l,colEnd:v,rowEnd:n,rowSpan:c,rowStart:o,...f}=t,h=i.useMemo(()=>pe({gridArea:b,gridColumn:ie(y),gridRow:ie(c),gridColumnStart:l,gridColumnEnd:v,gridRowStart:o,gridRowEnd:n}),[b,y,l,v,n,c,o]);return e.jsx(le.div,{ref:u,css:[h,t.css],...f})}),ae=s=>{let t=s==null?void 0:s.parentElement;for(;t&&t!==document.documentElement;){const u=getComputedStyle(t);if(u.transform!=="none"||u.perspective!=="none"||u.filter!=="none"||u.willChange.includes("transform")||u.willChange.includes("perspective")||u.willChange.includes("filter")||/paint|layout|strict|content/.test(u.contain))return t;t=t.parentElement}return null},ue=s=>{if(!s)return{x:0,y:0};const t=s.getBoundingClientRect();return{x:t.left+s.clientLeft,y:t.top+s.clientTop}},Le=({targetSelector:s=".cursor-target",spinDuration:t=2,hideDefaultCursor:u=!0,hoverDuration:b=.2,parallaxOn:y=!0,cursorColor:l="#ffffff",cursorColorOnTarget:v})=>{const n=i.useRef(null),c=i.useRef(null),o=i.useRef(null),f=i.useRef(null),h=i.useRef(null),a=i.useRef(!1),x=i.useRef(null),Y=i.useRef(null),F=i.useRef(0),X=i.useMemo(()=>{if(typeof window>"u")return!1;const D="ontouchstart"in window||navigator.maxTouchPoints>0,C=window.innerWidth<=768,S=navigator.userAgent||navigator.vendor||window.opera,k=/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(S.toLowerCase());return D&&C||k},[]),j=i.useMemo(()=>({borderWidth:3,cornerSize:12}),[]),$=i.useCallback((D,C)=>{if(!n.current)return;const{x:S,y:g}=ue(h.current);r.to(n.current,{x:D-S,y:C-g,duration:.1,ease:"power3.out"})},[]);return i.useEffect(()=>{if(X||!n.current)return;const D=document.body.style.cursor;u&&(document.body.style.cursor="none");const C=n.current;c.current=C.querySelectorAll(".target-cursor-corner"),h.current=ae(C);const S=()=>ue(h.current);let g=null,k=null,M=null;const q=d=>{k&&d.removeEventListener("mouseleave",k),k=null},Q=S();r.set(C,{xPercent:-50,yPercent:-50,x:window.innerWidth/2-Q.x,y:window.innerHeight/2-Q.y}),(()=>{o.current&&o.current.kill(),o.current=r.timeline({repeat:-1}).to(C,{rotation:"+=360",duration:t,ease:"none"})})();const fe=()=>{if(!x.current||!n.current||!c.current)return;const d=F.current;if(d===0)return;const A=r.getProperty(n.current,"x"),L=r.getProperty(n.current,"y");Array.from(c.current).forEach((p,E)=>{const R=r.getProperty(p,"x"),w=r.getProperty(p,"y"),P=x.current[E].x-A,O=x.current[E].y-L,z=R+(P-R)*d,G=w+(O-w)*d,U=d>=.99?y?.2:0:.05;r.to(p,{x:z,y:G,duration:U,ease:U===0?"none":"power1.out",overwrite:"auto"})})};Y.current=fe;const V=d=>$(d.clientX,d.clientY);window.addEventListener("mousemove",V);const _=()=>{if(!g||!n.current)return;const{x:d,y:A}=S(),L=r.getProperty(n.current,"x")+d,T=r.getProperty(n.current,"y")+A,p=document.elementFromPoint(L,T);p&&(p===g||p.closest(s)===g)||k&&k()};window.addEventListener("scroll",_,{passive:!0});const J=()=>{f.current&&(r.to(f.current,{scale:.7,duration:.3}),r.to(n.current,{scale:.9,duration:.2}))},K=()=>{f.current&&(r.to(f.current,{scale:1,duration:.3}),r.to(n.current,{scale:1,duration:.2}))};window.addEventListener("mousedown",J),window.addEventListener("mouseup",K);const Z=d=>{var te;const A=d.target,L=[];let T=A;for(;T&&T!==document.body;)T.matches(s)&&L.push(T),T=T.parentElement;const p=L[0]||null;if(!p||!n.current||!c.current||g===p)return;
1g&&q(g),M&&(clearTimeout(M),M=null),g=p;const E=Array.from(c.current);E.forEach(H=>r.killTweensOf(H,"x,y")),r.killTweensOf(n.current,"rotation"),(te=o.current)==null||te.pause(),r.set(n.current,{rotation:0}),v&&(r.to(E,{borderColor:v,duration:.15,ease:"power2.out"}),f.current&&r.to(f.current,{backgroundColor:v,duration:.15,ease:"power2.out"}));const R=p.getBoundingClientRect(),{borderWidth:w,cornerSize:P}=j,{x:O,y:z}=S(),G=r.getProperty(n.current,"x"),U=r.getProperty(n.current,"y");x.current=[{x:R.left-w-O,y:R.top-w-z},{x:R.right+w-P-O,y:R.top-w-z},{x:R.right+w-P-O,y:R.bottom+w-P-z},{x:R.left-w-O,y:R.bottom+w-P-z}],a.current=!0,r.ticker.add(Y.current),r.to(F,{current:1,duration:b,ease:"power2.out"}),E.forEach((H,m)=>{r.to(H,{x:x.current[m].x-G,y:x.current[m].y-U,duration:.2,ease:"power2.out"})});const re=()=>{if(r.ticker.remove(Y.current),a.current=!1,x.current=null,r.set(F,{current:0,overwrite:!0}),g=null,v&&c.current&&(r.to(Array.from(c.current),{borderColor:l,duration:.15,ease:"power2.out"}),f.current&&r.to(f.current,{backgroundColor:l,duration:.15,ease:"power2.out"})),c.current){const H=Array.from(c.current);r.killTweensOf(H,"x,y");const{cornerSize:m}=j,B=[{x:-m*1.5,y:-m*1.5},{x:m*.5,y:-m*1.5},{x:m*.5,y:m*.5},{x:-m*1.5,y:m*.5}],de=r.timeline();H.forEach((ge,ne)=>{de.to(ge,{x:B[ne].x,y:B[ne].y,duration:.3,ease:"power3.out"},0)})}M=setTimeout(()=>{if(!g&&n.current&&o.current){const m=r.getProperty(n.current,"rotation")%360;o.current.kill(),o.current=r.timeline({repeat:-1}).to(n.current,{rotation:"+=360",duration:t,ease:"none"}),r.to(n.current,{rotation:m+360,duration:t*(1-m/360),ease:"none",onComplete:()=>{var B;(B=o.current)==null||B.restart()}})}M=null},50),q(p)};k=re,p.addEventListener("mouseleave",re)};window.addEventListener("mouseover",Z,{passive:!0});const ee=()=>{h.current=ae(C)};return window.addEventListener("resize",ee),()=>{var d;Y.current&&r.ticker.remove(Y.current),window.removeEventListener("mousemove",V),window.removeEventListener("mouseover",Z),window.removeEventListener("scroll",_),window.removeEventListener("resize",ee),window.removeEventListener("mousedown",J),window.removeEventListener("mouseup",K),g&&q(g),(d=o.current)==null||d.kill(),document.body.style.cursor=D,a.current=!1,x.current=null,F.current=0}},[s,t,$,j,u,X,b,y,l,v]),i.useEffect(()=>{X||!n.current||!o.current||o.current.isActive()&&(o.current.kill(),o.current=r.timeline({repeat:-1}).to(n.current,{rotation:"+=360",duration:t,ease:"none"}))},[t,X]),X||typeof document>"u"?null:ve.createPortal(e.jsxs("div",{ref:n,className:"target-cursor-wrapper",children:[e.jsx("div",{ref:f,className:"target-cursor-dot",style:{backgroundColor:l}}),e.jsx("div",{className:"target-cursor-corner corner-tl",style:{borderColor:l}}),e.jsx("div",{className:"target-cursor-corner corner-tr",style:{borderColor:l}}),e.jsx("div",{className:"target-cursor-corner corner-br",style:{borderColor:l}}),e.jsx("div",{className:"target-cursor-corner corner-bl",style:{borderColor:l}})]}),document.body)},Pe=`import { useEffect, useRef, useCallback, useMemo } from 'react'; 2import { createPortal } from 'react-dom'; 3import { gsap } from 'gsap'; 4import './TargetCursor.css'; 5 6// A position: fixed element is positioned relative to the viewport UNLESS an 7// ancestor establishes a containing block (transform, perspective, filter, 8// will-change of those, or contain). When that happens, the cursor's translate 9// no longer maps to viewport coordinates, so we measure and compensate for it. 10const getContainingBlock = element => { 11 let node = element?.parentElement; 12 while (node && node !== document.documentElement) { 13 const style = getComputedStyle(node); 14 if ( 15 style.transform !== 'none' || 16 style.perspective !== 'none' || 17 style.filter !== 'none' || 18 style.willChange.includes('transform') || 19 style.willChange.includes('perspective') || 20 style.willChange.includes('filter') || 21 /paint|layout|strict|content/.test(style.contain) 22 ) { 23 return node; 24 } 25 node = node.parentElement; 26 } 27 return null; 28}; 29 30const getContainingBlockOffset = block => { 31 if (!block) return { x: 0, y: 0 }; 32 const rect = block.getBoundingClientRect(); 33 return { x: rect.left + block.clientLeft, y: rect.top + block.clientTop }; 34}; 35 36const TargetCursor = ({
37 targetSelector = '.cursor-target', 38 spinDuration = 2, 39 hideDefaultCursor = true, 40 hoverDuration = 0.2, 41 parallaxOn = true, 42 cursorColor = '#ffffff', 43 cursorColorOnTarget 44}) => { 45 const cursorRef = useRef(null); 46 const cornersRef = useRef(null); 47 const spinTl = useRef(null); 48 const dotRef = useRef(null); 49 const containingBlockRef = useRef(null); 50 51 const isActiveRef = useRef(false); 52 const targetCornerPositionsRef = useRef(null); 53 const tickerFnRef = useRef(null); 54 const activeStrengthRef = useRef(0); 55 56 const isMobile = useMemo(() => { 57 if (typeof window === 'undefined') return false; 58 const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0; 59 const isSmallScreen = window.innerWidth <= 768; 60 const userAgent = navigator.userAgent || navigator.vendor || window.opera; 61 const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i; 62 const isMobileUserAgent = mobileRegex.test(userAgent.toLowerCase()); 63 return (hasTouchScreen && isSmallScreen) || isMobileUserAgent; 64 }, []); 65 66 const constants = useMemo( 67 () => ({ 68 borderWidth: 3, 69 cornerSize: 12 70 }), 71 [] 72 ); 73 74 const moveCursor = useCallback((x, y) => { 75 if (!cursorRef.current) return; 76 const { x: offsetX, y: offsetY } = getContainingBlockOffset(containingBlockRef.current); 77 gsap.to(cursorRef.current, { 78 x: x - offsetX, 79 y: y - offsetY, 80 duration: 0.1, 81 ease: 'power3.out' 82 }); 83 }, []); 84 85 useEffect(() => { 86 if (isMobile || !cursorRef.current) return; 87 88 const originalCursor = document.body.style.cursor; 89 if (hideDefaultCursor) { 90 document.body.style.cursor = 'none'; 91 } 92 93 const cursor = cursorRef.current; 94 cornersRef.current = cursor.querySelectorAll('.target-cursor-corner'); 95 96 containingBlockRef.current = getContainingBlock(cursor); 97 const getOffset = () => getContainingBlockOffset(containingBlockRef.current); 98 99 let activeTarget = null; 100 let currentLeaveHandler = null; 101 let resumeTimeout = null; 102 103 const cleanupTarget = target => { 104 if (currentLeaveHandler) { 105 target.removeEventListener('mouseleave', currentLeaveHandler); 106 } 107 currentLeaveHandler = null; 108 }; 109 110 const initialOffset = getOffset(); 111 gsap.set(cursor, { 112 xPercent: -50, 113 yPercent: -50, 114 x: window.innerWidth / 2 - initialOffset.x, 115 y: window.innerHeight / 2 - initialOffset.y 116 }); 117 118 const createSpinTimeline = () => { 119 if (spinTl.current) { 120 spinTl.current.kill(); 121 } 122 spinTl.current = gsap 123 .timeline({ repeat: -1 }) 124 .to(cursor, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 125 }; 126 127 createSpinTimeline(); 128 129 const tickerFn = () => { 130 if (!targetCornerPositionsRef.current || !cursorRef.current || !cornersRef.current) { 131 return; 132 } 133 134 const strength = activeStrengthRef.current; 135 if (strength === 0) return; 136 137 const cursorX = gsap.getProperty(cursorRef.current, 'x'); 138 const cursorY = gsap.getProperty(cursorRef.current, 'y'); 139 140 const corners = Array.from(cornersRef.current); 141 corners.forEach((corner, i) => { 142 const currentX = gsap.getProperty(corner, 'x'); 143 const currentY = gsap.getProperty(corner, 'y'); 144 145 const targetX = targetCornerPositionsRef.current[i].x - cursorX; 146 const targetY = targetCornerPositionsRef.current[i].y - cursorY; 147 148 const finalX = currentX + (targetX - currentX) * strength; 149 const finalY = currentY + (targetY - currentY) * strength; 150 151 const duration = strength >= 0.99 ? (parallaxOn ? 0.2 : 0) : 0.05; 152 153 gsap.to(corner, { 154 x: finalX, 155 y: finalY, 156 duration: duration, 157 ease: duration === 0 ? 'none' : 'power1.out', 158 overwrite: 'auto' 159 }); 160 }); 161 }; 162 163 tickerFnRef.current = tickerFn; 164 165 const moveHandler = e => moveCursor(e.clientX, e.clientY); 166 window.addEventListener('mousemove', moveHandler); 167 168 const scrollHandler = () => { 169 if (!activeTarget || !cursorRef.current) return; 170 const { x: offsetX, y: offsetY } = getOffset(); 171 const mouseX = gsap.getProperty(cursorRef.current, 'x') + offsetX; 172 const mouseY = gsap.getProperty(cursorRef.current, 'y') + offsetY; 173 const elementUnderMouse = document.elementFromPoint(mouseX, mouseY); 174 const isStillOverTarget = 175 elementUnderMouse && 176 (elementUnderMouse === activeTarget || elementUnderMouse.closest(target
176Selector) === activeTarget); 177 if (!isStillOverTarget) { 178 if (currentLeaveHandler) { 179 currentLeaveHandler(); 180 } 181 } 182 }; 183 window.addEventListener('scroll', scrollHandler, { passive: true }); 184 185 const mouseDownHandler = () => { 186 if (!dotRef.current) return; 187 gsap.to(dotRef.current, { scale: 0.7, duration: 0.3 }); 188 gsap.to(cursorRef.current, { scale: 0.9, duration: 0.2 }); 189 }; 190 191 const mouseUpHandler = () => { 192 if (!dotRef.current) return; 193 gsap.to(dotRef.current, { scale: 1, duration: 0.3 }); 194 gsap.to(cursorRef.current, { scale: 1, duration: 0.2 }); 195 }; 196 197 window.addEventListener('mousedown', mouseDownHandler); 198 window.addEventListener('mouseup', mouseUpHandler); 199 200 const enterHandler = e => { 201 const directTarget = e.target; 202 const allTargets = []; 203 let current = directTarget; 204 while (current && current !== document.body) { 205 if (current.matches(targetSelector)) { 206 allTargets.push(current); 207 } 208 current = current.parentElement; 209 } 210 const target = allTargets[0] || null; 211 if (!target || !cursorRef.current || !cornersRef.current) return; 212 if (activeTarget === target) return; 213 if (activeTarget) { 214 cleanupTarget(activeTarget); 215 } 216 if (resumeTimeout) { 217 clearTimeout(resumeTimeout); 218 resumeTimeout = null; 219 } 220 221 activeTarget = target; 222 const corners = Array.from(cornersRef.current); 223 corners.forEach(corner => gsap.killTweensOf(corner, 'x,y')); 224 225 gsap.killTweensOf(cursorRef.current, 'rotation'); 226 spinTl.current?.pause(); 227 gsap.set(cursorRef.current, { rotation: 0 }); 228 229 if (cursorColorOnTarget) { 230 gsap.to(corners, { 231 borderColor: cursorColorOnTarget, 232 duration: 0.15, 233 ease: 'power2.out' 234 }); 235 if (dotRef.current) { 236 gsap.to(dotRef.current, { 237 backgroundColor: cursorColorOnTarget, 238 duration: 0.15, 239 ease: 'power2.out' 240 }); 241 } 242 } 243 244 const rect = target.getBoundingClientRect(); 245 const { borderWidth, cornerSize } = constants; 246 const { x: offsetX, y: offsetY } = getOffset(); 247 const cursorX = gsap.getProperty(cursorRef.current, 'x'); 248 const cursorY = gsap.getProperty(cursorRef.current, 'y'); 249 250 targetCornerPositionsRef.current = [ 251 { x: rect.left - borderWidth - offsetX, y: rect.top - borderWidth - offsetY }, 252 { x: rect.right + borderWidth - cornerSize - offsetX, y: rect.top - borderWidth - offsetY }, 253 { x: rect.right + borderWidth - cornerSize - offsetX, y: rect.bottom + borderWidth - cornerSize - offsetY }, 254 { x: rect.left - borderWidth - offsetX, y: rect.bottom + borderWidth - cornerSize - offsetY } 255 ]; 256 257 isActiveRef.current = true; 258 gsap.ticker.add(tickerFnRef.current); 259 260 gsap.to(activeStrengthRef, { 261 current: 1, 262 duration: hoverDuration, 263 ease: 'power2.out' 264 }); 265 266 corners.forEach((corner, i) => { 267 gsap.to(corner, { 268 x: targetCornerPositionsRef.current[i].x - cursorX, 269 y: targetCornerPositionsRef.current[i].y - cursorY, 270 duration: 0.2, 271 ease: 'power2.out' 272 }); 273 }); 274 275 const leaveHandler = () => { 276 gsap.ticker.remove(tickerFnRef.current); 277 278 isActiveRef.current = false; 279 targetCornerPositionsRef.current = null; 280 gsap.set(activeStrengthRef, { current: 0, overwrite: true }); 281 activeTarget = null; 282 283 if (cursorColorOnTarget && cornersRef.current) { 284 gsap.to(Array.from(cornersRef.current), { 285 borderColor: cursorColor, 286 duration: 0.15, 287 ease: 'power2.out' 288 }); 289 if (dotRef.current) { 290 gsap.to(dotRef.current, { 291 backgroundColor: cursorColor, 292 duration: 0.15, 293 ease: 'power2.out' 294 }); 295 } 296 } 297 298 if (cornersRef.current) { 299 const corners = Array.from(cornersRef.current); 300 gsap.killTweensOf(corners, 'x,y'); 301 const { cornerSize } = constants; 302 const positions = [ 303 { x: -cornerSize * 1.5, y: -cornerSize * 1.5 },
304 { x: cornerSize * 0.5, y: -cornerSize * 1.5 }, 305 { x: cornerSize * 0.5, y: cornerSize * 0.5 }, 306 { x: -cornerSize * 1.5, y: cornerSize * 0.5 } 307 ]; 308 const tl = gsap.timeline(); 309 corners.forEach((corner, index) => { 310 tl.to( 311 corner, 312 { 313 x: positions[index].x, 314 y: positions[index].y, 315 duration: 0.3, 316 ease: 'power3.out' 317 }, 318 0 319 ); 320 }); 321 } 322 323 resumeTimeout = setTimeout(() => { 324 if (!activeTarget && cursorRef.current && spinTl.current) { 325 const currentRotation = gsap.getProperty(cursorRef.current, 'rotation'); 326 const normalizedRotation = currentRotation % 360; 327 spinTl.current.kill(); 328 spinTl.current = gsap 329 .timeline({ repeat: -1 }) 330 .to(cursorRef.current, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 331 gsap.to(cursorRef.current, { 332 rotation: normalizedRotation + 360, 333 duration: spinDuration * (1 - normalizedRotation / 360), 334 ease: 'none', 335 onComplete: () => { 336 spinTl.current?.restart(); 337 } 338 }); 339 } 340 resumeTimeout = null; 341 }, 50); 342 343 cleanupTarget(target); 344 }; 345 346 currentLeaveHandler = leaveHandler; 347 target.addEventListener('mouseleave', leaveHandler); 348 }; 349 350 window.addEventListener('mouseover', enterHandler, { passive: true }); 351 352 const resizeHandler = () => { 353 containingBlockRef.current = getContainingBlock(cursor); 354 }; 355 window.addEventListener('resize', resizeHandler); 356 357 return () => { 358 if (tickerFnRef.current) { 359 gsap.ticker.remove(tickerFnRef.current); 360 } 361 362 window.removeEventListener('mousemove', moveHandler); 363 window.removeEventListener('mouseover', enterHandler); 364 window.removeEventListener('scroll', scrollHandler); 365 window.removeEventListener('resize', resizeHandler); 366 window.removeEventListener('mousedown', mouseDownHandler); 367 window.removeEventListener('mouseup', mouseUpHandler); 368 369 if (activeTarget) { 370 cleanupTarget(activeTarget); 371 } 372 373 spinTl.current?.kill(); 374 document.body.style.cursor = originalCursor; 375 376 isActiveRef.current = false; 377 targetCornerPositionsRef.current = null; 378 activeStrengthRef.current = 0; 379 }; 380 }, [ 381 targetSelector, 382 spinDuration, 383 moveCursor, 384 constants, 385 hideDefaultCursor, 386 isMobile, 387 hoverDuration, 388 parallaxOn, 389 cursorColor, 390 cursorColorOnTarget 391 ]); 392 393 useEffect(() => { 394 if (isMobile || !cursorRef.current || !spinTl.current) return; 395 if (spinTl.current.isActive()) { 396 spinTl.current.kill(); 397 spinTl.current = gsap 398 .timeline({ repeat: -1 }) 399 .to(cursorRef.current, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 400 } 401 }, [spinDuration, isMobile]); 402 403 if (isMobile || typeof document === 'undefined') { 404 return null; 405 } 406 407 return createPortal( 408 <div ref={cursorRef} className="target-cursor-wrapper"> 409 <div ref={dotRef} className="target-cursor-dot" style={{ backgroundColor: cursorColor }} /> 410 <div className="target-cursor-corner corner-tl" style={{ borderColor: cursorColor }} /> 411 <div className="target-cursor-corner corner-tr" style={{ borderColor: cursorColor }} /> 412 <div className="target-cursor-corner corner-br" style={{ borderColor: cursorColor }} /> 413 <div className="target-cursor-corner corner-bl" style={{ borderColor: cursorColor }} /> 414 </div>, 415 document.body 416 ); 417}; 418 419export default TargetCursor; 420`,Oe=`.target-cursor-wrapper { 421 position: fixed; 422 top: 0; 423 left: 0; 424 width: 0; 425 height: 0; 426 pointer-events: none; 427 z-index: 2147483647; 428 mix-blend-mode: difference; 429 transform: translate(-50%, -50%); 430} 431 432.target-cursor-dot { 433 position: absolute; 434 left: 50%; 435 top: 50%; 436 width: 4px; 437 height: 4px; 438 background: #fff; 439 border-radius: 50%; 440 transform: translate(-50%, -50%); 441 will-change: transform; 442} 443 444.target-cursor-corner { 445 position: absolute; 446 left: 50%; 447 top: 50%; 448 width: 12px; 449 height: 12px; 450 border: 3px solid #fff; 451 will-change: transform; 452} 453 454.corner-tl { 455 transform: translate(-150%, -150%);
456 border-right: none; 457 border-bottom: none; 458} 459 460.corner-tr { 461 transform: translate(50%, -150%); 462 border-left: none; 463 border-bottom: none; 464} 465 466.corner-br { 467 transform: translate(50%, 50%); 468 border-left: none; 469 border-top: none; 470} 471 472.corner-bl { 473 transform: translate(-150%, 50%); 474 border-right: none; 475 border-top: none; 476} 477`,ze=`import { useEffect, useRef, useCallback, useMemo } from 'react'; 478import { createPortal } from 'react-dom'; 479import { gsap } from 'gsap'; 480 481// A position: fixed element is positioned relative to the viewport UNLESS an 482// ancestor establishes a containing block (transform, perspective, filter, 483// will-change of those, or contain). When that happens, the cursor's translate 484// no longer maps to viewport coordinates, so we measure and compensate for it. 485const getContainingBlock = element => { 486 let node = element?.parentElement; 487 while (node && node !== document.documentElement) { 488 const style = getComputedStyle(node); 489 if ( 490 style.transform !== 'none' || 491 style.perspective !== 'none' || 492 style.filter !== 'none' || 493 style.willChange.includes('transform') || 494 style.willChange.includes('perspective') || 495 style.willChange.includes('filter') || 496 /paint|layout|strict|content/.test(style.contain) 497 ) { 498 return node; 499 } 500 node = node.parentElement; 501 } 502 return null; 503}; 504 505const getContainingBlockOffset = block => { 506 if (!block) return { x: 0, y: 0 }; 507 const rect = block.getBoundingClientRect(); 508 return { x: rect.left + block.clientLeft, y: rect.top + block.clientTop }; 509}; 510 511const TargetCursor = ({ 512 targetSelector = '.cursor-target', 513 spinDuration = 2, 514 hideDefaultCursor = true, 515 hoverDuration = 0.2, 516 parallaxOn = true, 517 cursorColor = '#ffffff', 518 cursorColorOnTarget 519}) => { 520 const cursorRef = useRef(null); 521 const cornersRef = useRef(null); 522 const spinTl = useRef(null); 523 const dotRef = useRef(null); 524 const containingBlockRef = useRef(null); 525 526 const isActiveRef = useRef(false); 527 const targetCornerPositionsRef = useRef(null); 528 const tickerFnRef = useRef(null); 529 const activeStrengthRef = useRef(0); 530 531 const isMobile = useMemo(() => { 532 if (typeof window === 'undefined') return false; 533 const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0; 534 const isSmallScreen = window.innerWidth <= 768; 535 const userAgent = navigator.userAgent || navigator.vendor || window.opera; 536 const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i; 537 const isMobileUserAgent = mobileRegex.test(userAgent.toLowerCase()); 538 return (hasTouchScreen && isSmallScreen) || isMobileUserAgent; 539 }, []); 540 541 const constants = useMemo( 542 () => ({ 543 borderWidth: 3, 544 cornerSize: 12 545 }), 546 [] 547 ); 548 549 const moveCursor = useCallback((x, y) => { 550 if (!cursorRef.current) return; 551 const { x: offsetX, y: offsetY } = getContainingBlockOffset(containingBlockRef.current); 552 gsap.to(cursorRef.current, { 553 x: x - offsetX, 554 y: y - offsetY, 555 duration: 0.1, 556 ease: 'power3.out' 557 }); 558 }, []); 559 560 useEffect(() => { 561 if (isMobile || !cursorRef.current) return; 562 563 const originalCursor = document.body.style.cursor; 564 if (hideDefaultCursor) { 565 document.body.style.cursor = 'none'; 566 } 567 568 const cursor = cursorRef.current; 569 cornersRef.current = cursor.querySelectorAll('.target-cursor-corner'); 570 571 containingBlockRef.current = getContainingBlock(cursor); 572 const getOffset = () => getContainingBlockOffset(containingBlockRef.current); 573 574 let activeTarget = null; 575 let currentLeaveHandler = null; 576 let resumeTimeout = null; 577 578 const cleanupTarget = target => { 579 if (currentLeaveHandler) { 580 target.removeEventListener('mouseleave', currentLeaveHandler); 581 } 582 currentLeaveHandler = null; 583 }; 584 585 const initialOffset = getOffset(); 586 gsap.set(cursor, { 587 xPercent: -50, 588 yPercent: -50, 589 x: window.innerWidth / 2 - initialOffset.x, 590 y: window.innerHeight / 2 - initialOffset.y 591 }); 592 593 const createSpinTimeline = () => { 594 if (spinTl.current) { 595 spinTl.current.kill(); 596 } 597 spinTl.current = gsap 598 .timeline({ repeat: -1 }) 599 .to(cursor, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 600 }; 601 602 createSpinTimeline(); 603 604 const tickerFn = () => { 605 if (!targetCornerPositionsRef.current || !cursorRef.current || !cornersRef.current) { 606 return; 607 } 608 const strength = activeStrengthRef.current; 609 if (strength === 0) return; 610 const cursorX = gsap.getProperty(cursorRef.current, 'x'); 611 const cursorY = gsap.getProperty(cursorRef.current, 'y'); 612 const corners = Array.from(cornersRef.current);
613 corners.forEach((corner, i) => { 614 const currentX = gsap.getProperty(corner, 'x'); 615 const currentY = gsap.getProperty(corner, 'y'); 616 const targetX = targetCornerPositionsRef.current[i].x - cursorX; 617 const targetY = targetCornerPositionsRef.current[i].y - cursorY; 618 const finalX = currentX + (targetX - currentX) * strength; 619 const finalY = currentY + (targetY - currentY) * strength; 620 const duration = strength >= 0.99 ? (parallaxOn ? 0.2 : 0) : 0.05; 621 gsap.to(corner, { 622 x: finalX, 623 y: finalY, 624 duration: duration, 625 ease: duration === 0 ? 'none' : 'power1.out', 626 overwrite: 'auto' 627 }); 628 }); 629 }; 630 631 tickerFnRef.current = tickerFn; 632 633 const moveHandler = e => moveCursor(e.clientX, e.clientY); 634 window.addEventListener('mousemove', moveHandler); 635 636 const scrollHandler = () => { 637 if (!activeTarget || !cursorRef.current) return; 638 const { x: offsetX, y: offsetY } = getOffset(); 639 const mouseX = gsap.getProperty(cursorRef.current, 'x') + offsetX; 640 const mouseY = gsap.getProperty(cursorRef.current, 'y') + offsetY; 641 const elementUnderMouse = document.elementFromPoint(mouseX, mouseY); 642 const isStillOverTarget = 643 elementUnderMouse && 644 (elementUnderMouse === activeTarget || elementUnderMouse.closest(targetSelector) === activeTarget); 645 if (!isStillOverTarget) { 646 if (currentLeaveHandler) { 647 currentLeaveHandler(); 648 } 649 } 650 }; 651 window.addEventListener('scroll', scrollHandler, { passive: true }); 652 653 const mouseDownHandler = () => { 654 if (!dotRef.current) return; 655 gsap.to(dotRef.current, { scale: 0.7, duration: 0.3 }); 656 gsap.to(cursorRef.current, { scale: 0.9, duration: 0.2 }); 657 }; 658 659 const mouseUpHandler = () => { 660 if (!dotRef.current) return; 661 gsap.to(dotRef.current, { scale: 1, duration: 0.3 }); 662 gsap.to(cursorRef.current, { scale: 1, duration: 0.2 }); 663 }; 664 665 window.addEventListener('mousedown', mouseDownHandler); 666 window.addEventListener('mouseup', mouseUpHandler); 667 668 const enterHandler = e => { 669 const directTarget = e.target; 670 const allTargets = []; 671 let current = directTarget; 672 while (current && current !== document.body) { 673 if (current.matches(targetSelector)) { 674 allTargets.push(current); 675 } 676 current = current.parentElement; 677 } 678 const target = allTargets[0] || null; 679 if (!target || !cursorRef.current || !cornersRef.current) return; 680 if (activeTarget === target) return; 681 if (activeTarget) { 682 cleanupTarget(activeTarget); 683 } 684 if (resumeTimeout) { 685 clearTimeout(resumeTimeout); 686 resumeTimeout = null; 687 } 688 689 activeTarget = target; 690 const corners = Array.from(cornersRef.current); 691 corners.forEach(corner => gsap.killTweensOf(corner, 'x,y')); 692 gsap.killTweensOf(cursorRef.current, 'rotation'); 693 spinTl.current?.pause(); 694 gsap.set(cursorRef.current, { rotation: 0 }); 695 696 if (cursorColorOnTarget) { 697 gsap.to(corners, { 698 borderColor: cursorColorOnTarget, 699 duration: 0.15, 700 ease: 'power2.out' 701 }); 702 if (dotRef.current) { 703 gsap.to(dotRef.current, { 704 backgroundColor: cursorColorOnTarget, 705 duration: 0.15, 706 ease: 'power2.out' 707 }); 708 } 709 } 710 711 const rect = target.getBoundingClientRect(); 712 const { borderWidth, cornerSize } = constants; 713 const { x: offsetX, y: offsetY } = getOffset(); 714 const cursorX = gsap.getProperty(cursorRef.current, 'x'); 715 const cursorY = gsap.getProperty(cursorRef.current, 'y'); 716 717 targetCornerPositionsRef.current = [ 718 { x: rect.left - borderWidth - offsetX, y: rect.top - borderWidth - offsetY }, 719 { x: rect.right + borderWidth - cornerSize - offsetX, y: rect.top - borderWidth - offsetY }, 720 { x: rect.right + borderWidth - cornerSize - offsetX, y: rect.bottom + borderWidth - cornerSize - offsetY }, 721 { x: rect.left - borderWidth - offsetX, y: rect.bottom + borderWidth - cornerSize - offsetY } 722 ]; 723 724 isActiveRef.current = true; 725 gsap.ticker.add(tickerFnRef.current); 726 727 gsap.to(activeStrengthRef, { current: 1, duration: hoverDuration, ease: 'power2.out' }); 728
729 corners.forEach((corner, i) => { 730 gsap.to(corner, { 731 x: targetCornerPositionsRef.current[i].x - cursorX, 732 y: targetCornerPositionsRef.current[i].y - cursorY, 733 duration: 0.2, 734 ease: 'power2.out' 735 }); 736 }); 737 738 const leaveHandler = () => { 739 gsap.ticker.remove(tickerFnRef.current); 740 isActiveRef.current = false; 741 targetCornerPositionsRef.current = null; 742 gsap.set(activeStrengthRef, { current: 0, overwrite: true }); 743 activeTarget = null; 744 745 if (cursorColorOnTarget && cornersRef.current) { 746 gsap.to(Array.from(cornersRef.current), { 747 borderColor: cursorColor, 748 duration: 0.15, 749 ease: 'power2.out' 750 }); 751 if (dotRef.current) { 752 gsap.to(dotRef.current, { 753 backgroundColor: cursorColor, 754 duration: 0.15, 755 ease: 'power2.out' 756 }); 757 } 758 } 759 760 if (cornersRef.current) { 761 const corners = Array.from(cornersRef.current); 762 gsap.killTweensOf(corners, 'x,y'); 763 const { cornerSize } = constants; 764 const positions = [ 765 { x: -cornerSize * 1.5, y: -cornerSize * 1.5 }, 766 { x: cornerSize * 0.5, y: -cornerSize * 1.5 }, 767 { x: cornerSize * 0.5, y: cornerSize * 0.5 }, 768 { x: -cornerSize * 1.5, y: cornerSize * 0.5 } 769 ]; 770 const tl = gsap.timeline(); 771 corners.forEach((corner, index) => { 772 tl.to(corner, { x: positions[index].x, y: positions[index].y, duration: 0.3, ease: 'power3.out' }, 0); 773 }); 774 } 775 resumeTimeout = setTimeout(() => { 776 if (!activeTarget && cursorRef.current && spinTl.current) { 777 const currentRotation = gsap.getProperty(cursorRef.current, 'rotation'); 778 const normalizedRotation = currentRotation % 360; 779 spinTl.current.kill(); 780 spinTl.current = gsap 781 .timeline({ repeat: -1 }) 782 .to(cursorRef.current, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 783 gsap.to(cursorRef.current, { 784 rotation: normalizedRotation + 360, 785 duration: spinDuration * (1 - normalizedRotation / 360), 786 ease: 'none', 787 onComplete: () => { 788 spinTl.current?.restart(); 789 } 790 }); 791 } 792 resumeTimeout = null; 793 }, 50); 794 cleanupTarget(target); 795 }; 796 currentLeaveHandler = leaveHandler; 797 target.addEventListener('mouseleave', leaveHandler); 798 }; 799 800 window.addEventListener('mouseover', enterHandler, { passive: true }); 801 802 const resizeHandler = () => { 803 containingBlockRef.current = getContainingBlock(cursor); 804 }; 805 window.addEventListener('resize', resizeHandler); 806 807 return () => { 808 if (tickerFnRef.current) { 809 gsap.ticker.remove(tickerFnRef.current); 810 } 811 window.removeEventListener('mousemove', moveHandler); 812 window.removeEventListener('mouseover', enterHandler); 813 window.removeEventListener('scroll', scrollHandler); 814 window.removeEventListener('resize', resizeHandler); 815 window.removeEventListener('mousedown', mouseDownHandler); 816 window.removeEventListener('mouseup', mouseUpHandler); 817 if (activeTarget) { 818 cleanupTarget(activeTarget); 819 } 820 spinTl.current?.kill(); 821 document.body.style.cursor = originalCursor; 822 isActiveRef.current = false; 823 targetCornerPositionsRef.current = null; 824 activeStrengthRef.current = 0; 825 }; 826 }, [ 827 targetSelector, 828 spinDuration, 829 moveCursor, 830 constants, 831 hideDefaultCursor, 832 isMobile, 833 hoverDuration, 834 parallaxOn, 835 cursorColor, 836 cursorColorOnTarget 837 ]); 838 839 useEffect(() => { 840 if (isMobile || !cursorRef.current || !spinTl.current) return; 841 if (spinTl.current.isActive()) { 842 spinTl.current.kill(); 843 spinTl.current = gsap 844 .timeline({ repeat: -1 }) 845 .to(cursorRef.current, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 846 } 847 }, [spinDuration, isMobile]); 848 849 if (isMobile || typeof document === 'undefined') { 850 return null; 851 } 852 853 return createPortal( 854 <div 855 ref={cursorRef} 856 className="fixed top-0 left-0 w-0 h-0 pointer-events-none z-[2147483647]" 857 style={{ willChange: 'transform' }} 858 > 859 <div 860 ref={dotRef} 861 className="absolute top-1/2 left-1/2 w-1 h-1 rounded-full -translate-x-1/2 -translate-y-1/2" 862 style={{ willChange: 'transform', backgroundColor: cursorColor }} 863 /> 864 <div 865 className="target-cursor-corner absolute top-1/2 left-1/2 w-3 h-3 border-[3px] -translate-x-[150%]
865-translate-y-[150%] border-r-0 border-b-0" 866 style={{ willChange: 'transform', borderColor: cursorColor }} 867 /> 868 <div 869 className="target-cursor-corner absolute top-1/2 left-1/2 w-3 h-3 border-[3px] translate-x-1/2 -translate-y-[150%] border-l-0 border-b-0" 870 style={{ willChange: 'transform', borderColor: cursorColor }} 871 /> 872 <div 873 className="target-cursor-corner absolute top-1/2 left-1/2 w-3 h-3 border-[3px] translate-x-1/2 translate-y-1/2 border-l-0 border-t-0" 874 style={{ willChange: 'transform', borderColor: cursorColor }} 875 /> 876 <div 877 className="target-cursor-corner absolute top-1/2 left-1/2 w-3 h-3 border-[3px] -translate-x-[150%] translate-y-1/2 border-r-0 border-t-0" 878 style={{ willChange: 'transform', borderColor: cursorColor }} 879 /> 880 </div>, 881 document.body 882 ); 883}; 884 885export default TargetCursor; 886`,Ye=`import React, { useEffect, useRef, useCallback, useMemo } from 'react'; 887import { createPortal } from 'react-dom'; 888import { gsap } from 'gsap'; 889import './TargetCursor.css'; 890 891// A position: fixed element is positioned relative to the viewport UNLESS an 892// ancestor establishes a containing block (transform, perspective, filter, 893// will-change of those, or contain). When that happens, the cursor's translate 894// no longer maps to viewport coordinates, so we measure and compensate for it. 895const getContainingBlock = (element: HTMLElement | null): HTMLElement | null => { 896 let node = element?.parentElement ?? null; 897 while (node && node !== document.documentElement) { 898 const style = getComputedStyle(node); 899 if ( 900 style.transform !== 'none' || 901 style.perspective !== 'none' || 902 style.filter !== 'none' || 903 style.willChange.includes('transform') || 904 style.willChange.includes('perspective') || 905 style.willChange.includes('filter') || 906 /paint|layout|strict|content/.test(style.contain) 907 ) { 908 return node; 909 } 910 node = node.parentElement; 911 } 912 return null; 913}; 914 915const getContainingBlockOffset = (block: HTMLElement | null): { x: number; y: number } => { 916 if (!block) return { x: 0, y: 0 }; 917 const rect = block.getBoundingClientRect(); 918 return { x: rect.left + block.clientLeft, y: rect.top + block.clientTop }; 919}; 920 921export interface TargetCursorProps { 922 targetSelector?: string; 923 spinDuration?: number; 924 hideDefaultCursor?: boolean; 925 hoverDuration?: number; 926 parallaxOn?: boolean; 927 cursorColor?: string; 928 cursorColorOnTarget?: string; 929} 930 931const TargetCursor: React.FC<TargetCursorProps> = ({ 932 targetSelector = '.cursor-target', 933 spinDuration = 2, 934 hideDefaultCursor = true, 935 hoverDuration = 0.2, 936 parallaxOn = true, 937 cursorColor = '#ffffff', 938 cursorColorOnTarget 939}) => { 940 const cursorRef = useRef<HTMLDivElement>(null); 941 const cornersRef = useRef<NodeListOf<HTMLDivElement> | null>(null); 942 const spinTl = useRef<gsap.core.Timeline | null>(null); 943 const dotRef = useRef<HTMLDivElement>(null); 944 const containingBlockRef = useRef<HTMLElement | null>(null); 945 946 const isActiveRef = useRef(false); 947 const targetCornerPositionsRef = useRef<{ x: number; y: number }[] | null>(null); 948 const tickerFnRef = useRef<(() => void) | null>(null); 949 const activeStrengthRef = useRef({ current: 0 }); 950 951 const isMobile = useMemo(() => { 952 if (typeof window === 'undefined') return false; 953 const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0; 954 const isSmallScreen = window.innerWidth <= 768; 955 const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; 956 const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i; 957 const isMobileUserAgent = mobileRegex.test(userAgent.toLowerCase()); 958 return (hasTouchScreen && isSmallScreen) || isMobileUserAgent; 959 }, []); 960 961 const constants = useMemo( 962 () => ({ 963 borderWidth: 3, 964 cornerSize: 12 965 }), 966 [] 967 ); 968 969 const moveCursor = useCallback((x: number, y: number) => { 970 if (!cursorRef.current) return; 971 const { x: offsetX, y: offsetY } = getContainingBlockOffset(containingBlockRef.current); 972 gsap.to(cursorRef.current, { 973 x: x - offsetX, 974 y: y - offsetY, 975 duration: 0.1, 976 ease: 'power3.out' 977 }); 978 }, []); 979 980 useEffect(() => { 981 if (isMobile || !cursorRef.current) return; 982 983 const originalCursor = document.body.style.cursor; 984 if (hideDefaultCursor) { 985 document.body.style.cursor = 'none'; 986 } 987
988 const cursor = cursorRef.current; 989 cornersRef.current = cursor.querySelectorAll<HTMLDivElement>('.target-cursor-corner'); 990 991 containingBlockRef.current = getContainingBlock(cursor); 992 const getOffset = () => getContainingBlockOffset(containingBlockRef.current); 993 994 let activeTarget: Element | null = null; 995 let currentLeaveHandler: (() => void) | null = null; 996 let resumeTimeout: ReturnType<typeof setTimeout> | null = null; 997 998 const cleanupTarget = (target: Element) => { 999 if (currentLeaveHandler) { 1000 target.removeEventListener('mouseleave', currentLeaveHandler); 1001 } 1002 currentLeaveHandler = null; 1003 }; 1004 1005 const initialOffset = getOffset(); 1006 gsap.set(cursor, { 1007 xPercent: -50, 1008 yPercent: -50, 1009 x: window.innerWidth / 2 - initialOffset.x, 1010 y: window.innerHeight / 2 - initialOffset.y 1011 }); 1012 1013 const createSpinTimeline = () => { 1014 if (spinTl.current) { 1015 spinTl.current.kill(); 1016 } 1017 spinTl.current = gsap 1018 .timeline({ repeat: -1 }) 1019 .to(cursor, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 1020 }; 1021 1022 createSpinTimeline(); 1023 1024 const tickerFn = () => { 1025 if (!targetCornerPositionsRef.current || !cursorRef.current || !cornersRef.current) { 1026 return; 1027 } 1028 1029 const strength = activeStrengthRef.current.current; 1030 if (strength === 0) return; 1031 1032 const cursorX = gsap.getProperty(cursorRef.current, 'x') as number; 1033 const cursorY = gsap.getProperty(cursorRef.current, 'y') as number; 1034 1035 const corners = Array.from(cornersRef.current); 1036 corners.forEach((corner, i) => { 1037 const currentX = gsap.getProperty(corner, 'x') as number; 1038 const currentY = gsap.getProperty(corner, 'y') as number; 1039 1040 const targetX = targetCornerPositionsRef.current![i].x - cursorX; 1041 const targetY = targetCornerPositionsRef.current![i].y - cursorY; 1042 1043 const finalX = currentX + (targetX - currentX) * strength; 1044 const finalY = currentY + (targetY - currentY) * strength; 1045 1046 const duration = strength >= 0.99 ? (parallaxOn ? 0.2 : 0) : 0.05; 1047 1048 gsap.to(corner, { 1049 x: finalX, 1050 y: finalY, 1051 duration: duration, 1052 ease: duration === 0 ? 'none' : 'power1.out', 1053 overwrite: 'auto' 1054 }); 1055 }); 1056 }; 1057 1058 tickerFnRef.current = tickerFn; 1059 1060 const moveHandler = (e: MouseEvent) => moveCursor(e.clientX, e.clientY); 1061 window.addEventListener('mousemove', moveHandler); 1062 1063 const scrollHandler = () => { 1064 if (!activeTarget || !cursorRef.current) return; 1065 const { x: offsetX, y: offsetY } = getOffset(); 1066 const mouseX = (gsap.getProperty(cursorRef.current, 'x') as number) + offsetX; 1067 const mouseY = (gsap.getProperty(cursorRef.current, 'y') as number) + offsetY; 1068 const elementUnderMouse = document.elementFromPoint(mouseX, mouseY); 1069 const isStillOverTarget = 1070 elementUnderMouse && 1071 (elementUnderMouse === activeTarget || elementUnderMouse.closest(targetSelector) === activeTarget); 1072 if (!isStillOverTarget) { 1073 currentLeaveHandler?.(); 1074 } 1075 }; 1076 window.addEventListener('scroll', scrollHandler, { passive: true }); 1077 1078 const mouseDownHandler = () => { 1079 if (!dotRef.current) return; 1080 gsap.to(dotRef.current, { scale: 0.7, duration: 0.3 }); 1081 gsap.to(cursorRef.current, { scale: 0.9, duration: 0.2 }); 1082 }; 1083 1084 const mouseUpHandler = () => { 1085 if (!dotRef.current) return; 1086 gsap.to(dotRef.current, { scale: 1, duration: 0.3 }); 1087 gsap.to(cursorRef.current, { scale: 1, duration: 0.2 }); 1088 }; 1089 1090 window.addEventListener('mousedown', mouseDownHandler); 1091 window.addEventListener('mouseup', mouseUpHandler); 1092 1093 const enterHandler = (e: MouseEvent) => { 1094 const directTarget = e.target as Element; 1095 const allTargets: Element[] = []; 1096 let current: Element | null = directTarget; 1097 while (current && current !== document.body) { 1098 if (current.matches(targetSelector)) { 1099 allTargets.push(current); 1100 } 1101 current = current.parentElement; 1102 } 1103 const target = allTargets[0] || null; 1104 if (!target || !cursorRef.current || !cornersRef.current) return; 1105 if (activeTarget === target) return; 1106 if (activeTarget) { 1107 cleanupTarget(activeTarget); 1108 } 1109 if (resumeTimeout) { 1110 clearTimeout(resumeTimeout); 1111 resumeTimeout = null; 1112 } 1113 1114 activeTarget = target; 1115 const corners = Array.from(cornersRef.current);
1116 corners.forEach(corner => gsap.killTweensOf(corner, 'x,y')); 1117 1118 gsap.killTweensOf(cursorRef.current, 'rotation'); 1119 spinTl.current?.pause(); 1120 gsap.set(cursorRef.current, { rotation: 0 }); 1121 1122 if (cursorColorOnTarget) { 1123 gsap.to(corners, { 1124 borderColor: cursorColorOnTarget, 1125 duration: 0.15, 1126 ease: 'power2.out' 1127 }); 1128 if (dotRef.current) { 1129 gsap.to(dotRef.current, { 1130 backgroundColor: cursorColorOnTarget, 1131 duration: 0.15, 1132 ease: 'power2.out' 1133 }); 1134 } 1135 } 1136 1137 const rect = target.getBoundingClientRect(); 1138 const { borderWidth, cornerSize } = constants; 1139 const { x: offsetX, y: offsetY } = getOffset(); 1140 const cursorX = gsap.getProperty(cursorRef.current, 'x') as number; 1141 const cursorY = gsap.getProperty(cursorRef.current, 'y') as number; 1142 1143 targetCornerPositionsRef.current = [ 1144 { x: rect.left - borderWidth - offsetX, y: rect.top - borderWidth - offsetY }, 1145 { x: rect.right + borderWidth - cornerSize - offsetX, y: rect.top - borderWidth - offsetY }, 1146 { x: rect.right + borderWidth - cornerSize - offsetX, y: rect.bottom + borderWidth - cornerSize - offsetY }, 1147 { x: rect.left - borderWidth - offsetX, y: rect.bottom + borderWidth - cornerSize - offsetY } 1148 ]; 1149 1150 isActiveRef.current = true; 1151 gsap.ticker.add(tickerFnRef.current!); 1152 1153 gsap.to(activeStrengthRef.current, { 1154 current: 1, 1155 duration: hoverDuration, 1156 ease: 'power2.out' 1157 }); 1158 1159 corners.forEach((corner, i) => { 1160 gsap.to(corner, { 1161 x: targetCornerPositionsRef.current![i].x - cursorX, 1162 y: targetCornerPositionsRef.current![i].y - cursorY, 1163 duration: 0.2, 1164 ease: 'power2.out' 1165 }); 1166 }); 1167 1168 const leaveHandler = () => { 1169 gsap.ticker.remove(tickerFnRef.current!); 1170 1171 isActiveRef.current = false; 1172 targetCornerPositionsRef.current = null; 1173 gsap.set(activeStrengthRef.current, { current: 0, overwrite: true }); 1174 activeTarget = null; 1175 1176 if (cursorColorOnTarget && cornersRef.current) { 1177 gsap.to(Array.from(cornersRef.current), { 1178 borderColor: cursorColor, 1179 duration: 0.15, 1180 ease: 'power2.out' 1181 }); 1182 if (dotRef.current) { 1183 gsap.to(dotRef.current, { 1184 backgroundColor: cursorColor, 1185 duration: 0.15, 1186 ease: 'power2.out' 1187 }); 1188 } 1189 } 1190 1191 if (cornersRef.current) { 1192 const corners = Array.from(cornersRef.current); 1193 gsap.killTweensOf(corners, 'x,y'); 1194 const { cornerSize } = constants; 1195 const positions = [ 1196 { x: -cornerSize * 1.5, y: -cornerSize * 1.5 }, 1197 { x: cornerSize * 0.5, y: -cornerSize * 1.5 }, 1198 { x: cornerSize * 0.5, y: cornerSize * 0.5 }, 1199 { x: -cornerSize * 1.5, y: cornerSize * 0.5 } 1200 ]; 1201 const tl = gsap.timeline(); 1202 corners.forEach((corner, index) => { 1203 tl.to( 1204 corner, 1205 { 1206 x: positions[index].x, 1207 y: positions[index].y, 1208 duration: 0.3, 1209 ease: 'power3.out' 1210 }, 1211 0 1212 ); 1213 }); 1214 } 1215 1216 resumeTimeout = setTimeout(() => { 1217 if (!activeTarget && cursorRef.current && spinTl.current) { 1218 const currentRotation = gsap.getProperty(cursorRef.current, 'rotation') as number; 1219 const normalizedRotation = currentRotation % 360; 1220 spinTl.current.kill(); 1221 spinTl.current = gsap 1222 .timeline({ repeat: -1 }) 1223 .to(cursorRef.current, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 1224 gsap.to(cursorRef.current, { 1225 rotation: normalizedRotation + 360, 1226 duration: spinDuration * (1 - normalizedRotation / 360), 1227 ease: 'none', 1228 onComplete: () => { 1229 spinTl.current?.restart(); 1230 } 1231 }); 1232 } 1233 resumeTimeout = null; 1234 }, 50); 1235 1236 cleanupTarget(target); 1237 }; 1238 1239 currentLeaveHandler = leaveHandler; 1240 target.addEventListener('mouseleave', leaveHandler); 1241 }; 1242 1243 window.addEventListener('mouseover', enterHandler as EventListener, { passive: true }); 1244 1245 const resizeHandler = () => { 1246 containingBlockRef.current = getContainingBlock(cursor); 1247 }; 1248 window.addEventListener('resize', resizeHandler); 1249 1250 return () => { 1251 if (tickerFnRef.current) { 1252 gsap.ticker.remove(tickerFnRef.current); 1253 } 1254 1255 window.removeEventListener('mousemove', moveHandler); 1256 window.removeEventListener('mouseover', enterHandler as EventListener); 1257 window.removeEventListener('scroll', scrollHandler); 1258 window.removeEventListener('resize', resizeHandler); 1259 window.removeEventListener('mousedown', mouseDownHandler); 1260 window.removeEventListener('mouseup', mouseUpHandler); 1261 1262 if (activeTarget) { 1263 cleanupTarget(activeTarget); 1264 } 1265 1266 spinTl.current?.kill(); 1267 document.body.style.cursor = originalCursor; 1268 1269 isActiveRef.current = false;
1270 targetCornerPositionsRef.current = null; 1271 activeStrengthRef.current.current = 0; 1272 }; 1273 }, [ 1274 targetSelector, 1275 spinDuration, 1276 moveCursor, 1277 constants, 1278 hideDefaultCursor, 1279 isMobile, 1280 hoverDuration, 1281 parallaxOn, 1282 cursorColor, 1283 cursorColorOnTarget 1284 ]); 1285 1286 useEffect(() => { 1287 if (isMobile || !cursorRef.current || !spinTl.current) return; 1288 if (spinTl.current.isActive()) { 1289 spinTl.current.kill(); 1290 spinTl.current = gsap 1291 .timeline({ repeat: -1 }) 1292 .to(cursorRef.current, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 1293 } 1294 }, [spinDuration, isMobile]); 1295 1296 if (isMobile || typeof document === 'undefined') { 1297 return null; 1298 } 1299 1300 return createPortal( 1301 <div ref={cursorRef} className="target-cursor-wrapper"> 1302 <div ref={dotRef} className="target-cursor-dot" style={{ backgroundColor: cursorColor }} /> 1303 <div className="target-cursor-corner corner-tl" style={{ borderColor: cursorColor }} /> 1304 <div className="target-cursor-corner corner-tr" style={{ borderColor: cursorColor }} /> 1305 <div className="target-cursor-corner corner-br" style={{ borderColor: cursorColor }} /> 1306 <div className="target-cursor-corner corner-bl" style={{ borderColor: cursorColor }} /> 1307 </div>, 1308 document.body 1309 ); 1310}; 1311 1312export default TargetCursor; 1313`,Xe=`import React, { useEffect, useRef, useCallback, useMemo } from 'react'; 1314import { createPortal } from 'react-dom'; 1315import { gsap } from 'gsap'; 1316 1317// A position: fixed element is positioned relative to the viewport UNLESS an 1318// ancestor establishes a containing block (transform, perspective, filter, 1319// will-change of those, or contain). When that happens, the cursor's translate 1320// no longer maps to viewport coordinates, so we measure and compensate for it. 1321const getContainingBlock = (element: HTMLElement | null): HTMLElement | null => { 1322 let node = element?.parentElement ?? null; 1323 while (node && node !== document.documentElement) { 1324 const style = getComputedStyle(node); 1325 if ( 1326 style.transform !== 'none' || 1327 style.perspective !== 'none' || 1328 style.filter !== 'none' || 1329 style.willChange.includes('transform') || 1330 style.willChange.includes('perspective') || 1331 style.willChange.includes('filter') || 1332 /paint|layout|strict|content/.test(style.contain) 1333 ) { 1334 return node; 1335 } 1336 node = node.parentElement; 1337 } 1338 return null; 1339}; 1340 1341const getContainingBlockOffset = (block: HTMLElement | null): { x: number; y: number } => { 1342 if (!block) return { x: 0, y: 0 }; 1343 const rect = block.getBoundingClientRect(); 1344 return { x: rect.left + block.clientLeft, y: rect.top + block.clientTop }; 1345}; 1346 1347export interface TargetCursorProps { 1348 targetSelector?: string; 1349 spinDuration?: number; 1350 hideDefaultCursor?: boolean; 1351 hoverDuration?: number; 1352 parallaxOn?: boolean; 1353 cursorColor?: string; 1354 cursorColorOnTarget?: string; 1355} 1356 1357const TargetCursor: React.FC<TargetCursorProps> = ({ 1358 targetSelector = '.cursor-target', 1359 spinDuration = 2, 1360 hideDefaultCursor = true, 1361 hoverDuration = 0.2, 1362 parallaxOn = true, 1363 cursorColor = '#ffffff', 1364 cursorColorOnTarget 1365}) => { 1366 const cursorRef = useRef<HTMLDivElement>(null); 1367 const cornersRef = useRef<NodeListOf<HTMLDivElement> | null>(null); 1368 const spinTl = useRef<gsap.core.Timeline | null>(null); 1369 const dotRef = useRef<HTMLDivElement>(null); 1370 const containingBlockRef = useRef<HTMLElement | null>(null); 1371 1372 const isActiveRef = useRef(false); 1373 const targetCornerPositionsRef = useRef<{ x: number; y: number }[] | null>(null); 1374 const tickerFnRef = useRef<(() => void) | null>(null); 1375 const activeStrengthRef = useRef({ current: 0 }); 1376 1377 const isMobile = useMemo(() => { 1378 if (typeof window === 'undefined') return false; 1379 const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0; 1380 const isSmallScreen = window.innerWidth <= 768; 1381 const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; 1382 const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i; 1383 const isMobileUserAgent = mobileRegex.test(userAgent.toLowerCase()); 1384 return (hasTouchScreen && isSmallScreen) || isMobileUserAgent; 1385 }, []); 1386 1387 const constants = useMemo(() => ({ borderWidth: 3, cornerSize: 12 }), []); 1388 1389 const moveCursor = useCallback((x: number, y: number) => { 1390 if (!cursorRef.current) return; 1391 const { x: offsetX, y: offsetY } = getContainingBlockOffset(containingBlockRef.current); 1392 gsap.to(cursorRef.current, { x: x - offsetX, y: y - offsetY, duration: 0.1, ease: 'power3.out' }); 1393 }, []); 1394 1395 useEffect(() => { 1396 if (isMobile || !cursorRef.current) return; 1397 1398 const originalCursor = document.body.style.cursor; 1399 if (hideDefaultCursor) { 1400 document.body.style.cursor = 'none'; 1401 } 1402
1403 const cursor = cursorRef.current; 1404 cornersRef.current = cursor.querySelectorAll<HTMLDivElement>('.target-cursor-corner'); 1405 1406 containingBlockRef.current = getContainingBlock(cursor); 1407 const getOffset = () => getContainingBlockOffset(containingBlockRef.current); 1408 1409 let activeTarget: Element | null = null; 1410 let currentLeaveHandler: (() => void) | null = null; 1411 let resumeTimeout: ReturnType<typeof setTimeout> | null = null; 1412 1413 const cleanupTarget = (target: Element) => { 1414 if (currentLeaveHandler) { 1415 target.removeEventListener('mouseleave', currentLeaveHandler); 1416 } 1417 currentLeaveHandler = null; 1418 }; 1419 1420 const initialOffset = getOffset(); 1421 gsap.set(cursor, { 1422 xPercent: -50, 1423 yPercent: -50, 1424 x: window.innerWidth / 2 - initialOffset.x, 1425 y: window.innerHeight / 2 - initialOffset.y 1426 }); 1427 1428 const createSpinTimeline = () => { 1429 if (spinTl.current) { 1430 spinTl.current.kill(); 1431 } 1432 spinTl.current = gsap 1433 .timeline({ repeat: -1 }) 1434 .to(cursor, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 1435 }; 1436 1437 createSpinTimeline(); 1438 1439 const tickerFn = () => { 1440 if (!targetCornerPositionsRef.current || !cursorRef.current || !cornersRef.current) { 1441 return; 1442 } 1443 const strength = activeStrengthRef.current.current; 1444 if (strength === 0) return; 1445 const cursorX = gsap.getProperty(cursorRef.current, 'x') as number; 1446 const cursorY = gsap.getProperty(cursorRef.current, 'y') as number; 1447 const corners = Array.from(cornersRef.current); 1448 corners.forEach((corner, i) => { 1449 const currentX = gsap.getProperty(corner, 'x') as number; 1450 const currentY = gsap.getProperty(corner, 'y') as number; 1451 const targetX = targetCornerPositionsRef.current![i].x - cursorX; 1452 const targetY = targetCornerPositionsRef.current![i].y - cursorY; 1453 const finalX = currentX + (targetX - currentX) * strength; 1454 const finalY = currentY + (targetY - currentY) * strength; 1455 const duration = strength >= 0.99 ? (parallaxOn ? 0.2 : 0) : 0.05; 1456 gsap.to(corner, { 1457 x: finalX, 1458 y: finalY, 1459 duration: duration, 1460 ease: duration === 0 ? 'none' : 'power1.out', 1461 overwrite: 'auto' 1462 }); 1463 }); 1464 }; 1465 1466 tickerFnRef.current = tickerFn; 1467 1468 const moveHandler = (e: MouseEvent) => moveCursor(e.clientX, e.clientY); 1469 window.addEventListener('mousemove', moveHandler); 1470 1471 const scrollHandler = () => { 1472 if (!activeTarget || !cursorRef.current) return; 1473 const { x: offsetX, y: offsetY } = getOffset(); 1474 const mouseX = (gsap.getProperty(cursorRef.current, 'x') as number) + offsetX; 1475 const mouseY = (gsap.getProperty(cursorRef.current, 'y') as number) + offsetY; 1476 const elementUnderMouse = document.elementFromPoint(mouseX, mouseY); 1477 const isStillOverTarget = 1478 elementUnderMouse && 1479 (elementUnderMouse === activeTarget || elementUnderMouse.closest(targetSelector) === activeTarget); 1480 if (!isStillOverTarget) { 1481 currentLeaveHandler?.(); 1482 } 1483 }; 1484 window.addEventListener('scroll', scrollHandler, { passive: true }); 1485 1486 const mouseDownHandler = () => { 1487 if (!dotRef.current) return; 1488 gsap.to(dotRef.current, { scale: 0.7, duration: 0.3 }); 1489 gsap.to(cursorRef.current, { scale: 0.9, duration: 0.2 }); 1490 }; 1491 1492 const mouseUpHandler = () => { 1493 if (!dotRef.current) return; 1494 gsap.to(dotRef.current, { scale: 1, duration: 0.3 }); 1495 gsap.to(cursorRef.current, { scale: 1, duration: 0.2 }); 1496 }; 1497 1498 window.addEventListener('mousedown', mouseDownHandler); 1499 window.addEventListener('mouseup', mouseUpHandler); 1500 1501 const enterHandler = (e: MouseEvent) => { 1502 const directTarget = e.target as Element; 1503 const allTargets: Element[] = []; 1504 let current: Element | null = directTarget; 1505 while (current && current !== document.body) { 1506 if (current.matches(targetSelector)) { 1507 allTargets.push(current); 1508 } 1509 current = current.parentElement; 1510 } 1511 const target = allTargets[0] || null; 1512 if (!target || !cursorRef.current || !cornersRef.current) return; 1513 if (activeTarget === target) return; 1514 if (activeTarget) { 1515 cleanupTarget(activeTarget); 1516 } 1517 if (resumeTimeout) { 1518 clearTimeout(resumeTimeout); 1519 resumeTimeout = null; 1520 } 1521 1522 activeTarget = target; 1523 const corners = Array.from(cornersRef.current);
1524 corners.forEach(corner => gsap.killTweensOf(corner, 'x,y')); 1525 gsap.killTweensOf(cursorRef.current, 'rotation'); 1526 spinTl.current?.pause(); 1527 gsap.set(cursorRef.current, { rotation: 0 }); 1528 1529 if (cursorColorOnTarget) { 1530 gsap.to(corners, { 1531 borderColor: cursorColorOnTarget, 1532 duration: 0.15, 1533 ease: 'power2.out' 1534 }); 1535 if (dotRef.current) { 1536 gsap.to(dotRef.current, { 1537 backgroundColor: cursorColorOnTarget, 1538 duration: 0.15, 1539 ease: 'power2.out' 1540 }); 1541 } 1542 } 1543 1544 const rect = target.getBoundingClientRect(); 1545 const { borderWidth, cornerSize } = constants; 1546 const { x: offsetX, y: offsetY } = getOffset(); 1547 const cursorX = gsap.getProperty(cursorRef.current, 'x') as number; 1548 const cursorY = gsap.getProperty(cursorRef.current, 'y') as number; 1549 1550 targetCornerPositionsRef.current = [ 1551 { x: rect.left - borderWidth - offsetX, y: rect.top - borderWidth - offsetY }, 1552 { x: rect.right + borderWidth - cornerSize - offsetX, y: rect.top - borderWidth - offsetY }, 1553 { x: rect.right + borderWidth - cornerSize - offsetX, y: rect.bottom + borderWidth - cornerSize - offsetY }, 1554 { x: rect.left - borderWidth - offsetX, y: rect.bottom + borderWidth - cornerSize - offsetY } 1555 ]; 1556 1557 isActiveRef.current = true; 1558 gsap.ticker.add(tickerFnRef.current!); 1559 1560 gsap.to(activeStrengthRef.current, { current: 1, duration: hoverDuration, ease: 'power2.out' }); 1561 1562 corners.forEach((corner, i) => { 1563 gsap.to(corner, { 1564 x: targetCornerPositionsRef.current![i].x - cursorX, 1565 y: targetCornerPositionsRef.current![i].y - cursorY, 1566 duration: 0.2, 1567 ease: 'power2.out' 1568 }); 1569 }); 1570 1571 const leaveHandler = () => { 1572 gsap.ticker.remove(tickerFnRef.current!); 1573 isActiveRef.current = false; 1574 targetCornerPositionsRef.current = null; 1575 gsap.set(activeStrengthRef.current, { current: 0, overwrite: true }); 1576 activeTarget = null; 1577 1578 if (cursorColorOnTarget && cornersRef.current) { 1579 gsap.to(Array.from(cornersRef.current), { 1580 borderColor: cursorColor, 1581 duration: 0.15, 1582 ease: 'power2.out' 1583 }); 1584 if (dotRef.current) { 1585 gsap.to(dotRef.current, { 1586 backgroundColor: cursorColor, 1587 duration: 0.15, 1588 ease: 'power2.out' 1589 }); 1590 } 1591 } 1592 1593 if (cornersRef.current) { 1594 const corners = Array.from(cornersRef.current); 1595 gsap.killTweensOf(corners, 'x,y'); 1596 const { cornerSize } = constants; 1597 const positions = [ 1598 { x: -cornerSize * 1.5, y: -cornerSize * 1.5 }, 1599 { x: cornerSize * 0.5, y: -cornerSize * 1.5 }, 1600 { x: cornerSize * 0.5, y: cornerSize * 0.5 }, 1601 { x: -cornerSize * 1.5, y: cornerSize * 0.5 } 1602 ]; 1603 const tl = gsap.timeline(); 1604 corners.forEach((corner, index) => { 1605 tl.to(corner, { x: positions[index].x, y: positions[index].y, duration: 0.3, ease: 'power3.out' }, 0); 1606 }); 1607 } 1608 resumeTimeout = setTimeout(() => { 1609 if (!activeTarget && cursorRef.current && spinTl.current) { 1610 const currentRotation = gsap.getProperty(cursorRef.current, 'rotation') as number; 1611 const normalizedRotation = currentRotation % 360; 1612 spinTl.current.kill(); 1613 spinTl.current = gsap 1614 .timeline({ repeat: -1 }) 1615 .to(cursorRef.current, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 1616 gsap.to(cursorRef.current, { 1617 rotation: normalizedRotation + 360, 1618 duration: spinDuration * (1 - normalizedRotation / 360), 1619 ease: 'none', 1620 onComplete: () => { 1621 spinTl.current?.restart(); 1622 } 1623 }); 1624 } 1625 resumeTimeout = null; 1626 }, 50); 1627 cleanupTarget(target); 1628 }; 1629 currentLeaveHandler = leaveHandler; 1630 target.addEventListener('mouseleave', leaveHandler); 1631 }; 1632 1633 window.addEventListener('mouseover', enterHandler as EventListener); 1634 1635 const resizeHandler = () => { 1636 containingBlockRef.current = getContainingBlock(cursor); 1637 }; 1638 window.addEventListener('resize', resizeHandler); 1639 1640 return () => { 1641 if (tickerFnRef.current) { 1642 gsap.ticker.remove(tickerFnRef.current); 1643 } 1644 window.removeEventListener('mousemove', moveHandler); 1645 window.removeEventListener('mouseover', enterHandler as EventListener); 1646 window.removeEventListener('scroll', scrollHandler); 1647 window.removeEventListener('resize', resizeHandler); 1648 window.removeEventListener('mousedown', mouseDownHandler); 1649 window.removeEventListener('mouseup', mouseUpHandler); 1650 if (activeTarget) { 1651 cleanupTarget(activeTarget); 1652 } 1653 spinTl.current?.kill(); 1654 document.body.style.cursor = originalCursor; 1655 isActiveRef.current = false;
1656 targetCornerPositionsRef.current = null; 1657 activeStrengthRef.current.current = 0; 1658 }; 1659 }, [ 1660 targetSelector, 1661 spinDuration, 1662 moveCursor, 1663 constants, 1664 hideDefaultCursor, 1665 isMobile, 1666 hoverDuration, 1667 parallaxOn, 1668 cursorColor, 1669 cursorColorOnTarget 1670 ]); 1671 1672 useEffect(() => { 1673 if (isMobile || !cursorRef.current || !spinTl.current) return; 1674 if (spinTl.current.isActive()) { 1675 spinTl.current.kill(); 1676 spinTl.current = gsap 1677 .timeline({ repeat: -1 }) 1678 .to(cursorRef.current, { rotation: '+=360', duration: spinDuration, ease: 'none' }); 1679 } 1680 }, [spinDuration, isMobile]); 1681 1682 if (isMobile || typeof document === 'undefined') { 1683 return null; 1684 } 1685 1686 return createPortal( 1687 <div 1688 ref={cursorRef} 1689 className="fixed top-0 left-0 w-0 h-0 pointer-events-none z-[2147483647]" 1690 style={{ willChange: 'transform' }} 1691 > 1692 <div 1693 ref={dotRef} 1694 className="absolute top-1/2 left-1/2 w-1 h-1 rounded-full -translate-x-1/2 -translate-y-1/2" 1695 style={{ willChange: 'transform', backgroundColor: cursorColor }} 1696 /> 1697 <div 1698 className="target-cursor-corner absolute top-1/2 left-1/2 w-3 h-3 border-[3px] -translate-x-[150%] -translate-y-[150%] border-r-0 border-b-0" 1699 style={{ willChange: 'transform', borderColor: cursorColor }} 1700 /> 1701 <div 1702 className="target-cursor-corner absolute top-1/2 left-1/2 w-3 h-3 border-[3px] translate-x-1/2 -translate-y-[150%] border-l-0 border-b-0" 1703 style={{ willChange: 'transform', borderColor: cursorColor }} 1704 /> 1705 <div 1706 className="target-cursor-corner absolute top-1/2 left-1/2 w-3 h-3 border-[3px] translate-x-1/2 translate-y-1/2 border-l-0 border-t-0" 1707 style={{ willChange: 'transform', borderColor: cursorColor }} 1708 /> 1709 <div 1710 className="target-cursor-corner absolute top-1/2 left-1/2 w-3 h-3 border-[3px] -translate-x-[150%] translate-y-1/2 border-r-0 border-t-0" 1711 style={{ willChange: 'transform', borderColor: cursorColor }} 1712 /> 1713 </div>, 1714 document.body 1715 ); 1716}; 1717 1718export default TargetCursor; 1719`,De={dependencies:"gsap",usage:`import TargetCursor from './TargetCursor'; 1720 1721export default function App() { 1722 return ( 1723 <div> 1724 <TargetCursor 1725 spinDuration={2} 1726 hideDefaultCursor={true} 1727 parallaxOn={true} 1728 /> 1729 1730 <h1>Hover over the elements below</h1> 1731 <button className="cursor-target">Click me!</button> 1732 <div className="cursor-target">Hover target</div> 1733 </div> 1734 ); 1735}`,code:Pe,css:Oe,tailwind:ze,tsCode:Ye,tsTailwind:Xe},I={spinDuration:2,hideDefaultCursor:!0,hoverDuration:.2,parallaxOn:!0,cursorColor:"#ffffff",cursorColorOnTarget:"#B497CF"},er=()=>{const{props:s,updateProp:t,resetProps:u,hasChanges:b}=ye(I),{spinDuration:y,hideDefaultCursor:l,hoverDuration:v,parallaxOn:n,cursorColor:c,cursorColorOnTarget:o}=s,f=Re(c===I.cursorColor?"#18181b":c,c),h=i.useMemo(()=>[{name:"targetSelector",type:"string",default:'".cursor-target"',description:"CSS selector for elements that should trigger the cursor targeting effect"},{name:"spinDuration",type:"number",default:"2",description:"Duration in seconds for the cursor's spinning animation when not targeting"},{name:"hideDefaultCursor",type:"boolean",default:"true",description:"Whether to hide the default browser cursor when the component is active"},{name:"hoverDuration",type:"number",default:"0.2",description:"Duration in seconds for the transition when the cursor locks onto a target"},{name:"parallaxOn",type:"boolean",default:"true",description:"Enables a subtle parallax effect on the corners when moving over a target"},{name:"cursorColor",type:"string",default:"'#ffffff'",description:"Color of the cursor dot and corner brackets at rest"},{name:"cursorColorOnTarget",type:"string",default:"undefined",description:"Optional color the cursor smoothly transitions to while locked onto a target"}],[]);return e.jsxs(e.Fragment,{children:[e.jsx(he,{props:s,defaultProps:I,resetProps:u,hasChanges:b,children:e.jsxs(be,{children:[e.jsxs(Ce,{children:[e.jsxs(we,{position:"relative",className:"demo-container",flexDirection:"column",h:400,overflow:"hidden",children:[e.jsx(W,{className:"demo-instruction",fontSize:"clamp(2rem, 6vw, 3rem)",fontWeight:600,mb:6,children:"Hover Below."}),e.jsxs(He,{templateColumns:"repeat(3, 1fr)",gap:4,mb:2,children:[e.jsx(N,{children:e.jsx(W,{borderRadius:"15p
1735x",color:"#B497CF",border:"1px dashed #B497CF",fontWeight:600,fontSize:"clamp(1rem, 4vw, 2rem)",className:"cursor-target",py:2,px:6,textAlign:"center",children:"THIS"})}),e.jsx(N,{children:e.jsx(W,{borderRadius:"15px",color:"#B497CF",border:"1px dashed #B497CF",fontWeight:600,fontSize:"clamp(1rem, 4vw, 2rem)",className:"cursor-target",py:2,px:6,textAlign:"center",children:"FEELS"})}),e.jsx(N,{children:e.jsx(W,{borderRadius:"15px",color:"#B497CF",border:"1px dashed #B497CF",fontWeight:600,fontSize:"clamp(1rem, 4vw, 2rem)",className:"cursor-target",py:2,px:6,textAlign:"center",children:"QUITE"})}),e.jsx(N,{colSpan:3,children:e.jsx(W,{textAlign:"center",borderRadius:"15px",color:"#B497CF",border:"1px dashed #B497CF",fontWeight:600,fontSize:"clamp(1rem, 4vw, 2rem)",className:"cursor-target",py:2,px:6,children:"SNAPPY!"})})]})]}),e.jsxs(Se,{children:[e.jsx(oe,{title:"Spin Duration",min:.5,max:5,step:.1,value:y,valueUnit:"s",width:200,onChange:a=>t("spinDuration",a)}),e.jsx(oe,{title:"Hover Duration",min:.1,max:1,step:.05,value:v,valueUnit:"s",width:200,onChange:a=>t("hoverDuration",a)}),e.jsx(se,{title:"Hide Default Cursor",isChecked:l,onChange:a=>t("hideDefaultCursor",a)}),e.jsx(se,{title:"Enable Parallax",isChecked:n,onChange:a=>t("parallaxOn",a)}),e.jsx(ce,{title:"Cursor Color",color:f,onChange:a=>t("cursorColor",a)}),e.jsx(ce,{title:"Cursor Color On Target",color:o,onChange:a=>t("cursorColorOnTarget",a)})]}),e.jsx(Te,{data:h}),e.jsx(Ee,{dependencyList:["gsap"]})]}),e.jsx(xe,{children:e.jsx(ke,{codeObject:De,componentName:"TargetCursor"})})]})}),e.jsx(Le,{spinDuration:y,hideDefaultCursor:l,hoverDuration:v,parallaxOn:n,cursorColor:f,cursorColorOnTarget:o})]})};export{er as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.