class Cursor { constructor(options) { this.options = $.extend(true, { container: "body", speed: 0.7, ease: "expo.out", visibleTimeout: 300 }, options); this.body = $(this.options.container); this.el = $('
'); this.text = $('
'); this.init(); } init() { this.el.append(this.text); this.body.append(this.el); this.bind(); this.move(-window.innerWidth, -window.innerHeight, 0); } bind() { const self = this; this.body.on('mouseleave', () => { self.hide(); }).on('mouseenter', () => { self.show(); }).on('mousemove', (e) => { if (this.lastMove && Date.now() - this.lastMove < 16) return; // Throttle to ~60fps this.lastMove = Date.now(); this.pos = { x: this.stick ? this.stick.x - ((this.stick.x - e.clientX) * 0.15) : e.clientX, y: this.stick ? this.stick.y - ((this.stick.y - e.clientY) * 0.15) : e.clientY }; this.update(); }).on('mousedown', () => { self.setState('-active'); }).on('mouseup', () => { self.removeState('-active'); }).on('mouseenter', 'a,input,textarea,button', () => { self.setState('-pointer'); }).on('mouseleave', 'a,input,textarea,button', () => { self.removeState('-pointer'); }).on('mouseenter', 'iframe', () => { self.hide(); }).on('mouseleave', 'iframe', () => { self.show(); }).on('mouseenter', '[data-cursor]', function () { self.setState(this.dataset.cursor); }).on('mouseleave', '[data-cursor]', function () { self.removeState(this.dataset.cursor); }).on('mouseenter', '[data-cursor-text]', function () { self.setText(this.dataset.cursorText); }).on('mouseleave', '[data-cursor-text]', function () { self.removeText(); }).on('mouseenter', '[data-cursor-stick]', function () { self.setStick(this.dataset.cursorStick); }).on('mouseleave', '[data-cursor-stick]', function () { self.removeStick(); }); } setState(state) { this.el.addClass(state); } removeState(state) { this.el.removeClass(state); } toggleState(state) { this.el.toggleClass(state); } setText(text) { this.text.html(text); this.el.addClass('-text'); } removeText() { this.el.removeClass('-text'); } setStick(el) { const target = $(el); const bound = target.get(0).getBoundingClientRect(); this.stick = { y: bound.top + (target.height() / 2), x: bound.left + (target.width() / 2) }; this.move(this.stick.x, this.stick.y, 5); } removeStick() { this.stick = false; } update() { if (!this.visible) return; this.move(); this.show(); } move(x, y, duration) { if (!this.el.length) return; gsap.to(this.el, { x: x || this.pos.x, y: y || this.pos.y, force3D: true, overwrite: "auto", ease: this.options.ease, duration: this.visible ? (duration || this.options.speed) : 0 }); } show() { if (this.visible || !this.el.length) return; this.el.addClass('-visible'); this.visible = true; } hide() { if (!this.el.length) return; this.el.removeClass('-visible'); this.visible = false; } } // Init cursor only on desktop and after load $(window).on('load', function() { if (window.innerWidth > 1024 && !('ontouchstart' in window)) { const cursor = new Cursor(); } });