PageSourceSearch

https://reactbits.dev/assets/TargetCursorDemo-B66G_d5b.js

js reactbits.dev collected 2026-09-25 14:13:25 UTC 70,802 bytes, 1,735 lines download raw bytes

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.