// cursor.js document.addEventListener("DOMContentLoaded", function () { if (window.matchMedia("(pointer: coarse)").matches) return; // --- SETTINGS --- const CONFIG = { tentacleCount: 8, triggerDist: 10, maxLength: 300, connectionDist: 150, thickness: 1, color: "rgba(20, 20, 20, 1)", prediction: 3.5 }; const toggleBtn = document.getElementById('cursorToggle'); const body = document.body; // Default to system cursor (disabled custom cursor) let isCursorDisabled = localStorage.getItem('venomCursorDisabled') !== 'false'; function updateCursorState() { if (isCursorDisabled) { // System cursor (default) - show spidy icon body.classList.add('system-cursor'); if (toggleBtn) { toggleBtn.classList.remove('active'); let icon = toggleBtn.querySelector('.cursor-icon'); if (icon) { // Replace img with spidy.png if needed if (icon.tagName !== 'IMG') { const newIcon = document.createElement('img'); newIcon.className = 'cursor-icon'; newIcon.alt = 'Spider Cursor'; // Check if we're on a page in the sites/ folder const currentPath = window.location.pathname; const isInSitesFolder = currentPath.includes('/sites/'); const imagePath = isInSitesFolder ? '../images/additional/spidy.png' : 'images/additional/spidy.png'; newIcon.src = imagePath; icon.parentNode.replaceChild(newIcon, icon); } else { // Update existing img const currentPath = window.location.pathname; const isInSitesFolder = currentPath.includes('/sites/'); const imagePath = isInSitesFolder ? '../images/additional/spidy.png' : 'images/additional/spidy.png'; icon.src = imagePath; } } } } else { // Custom cursor (secondary) - show standard cursor icon body.classList.remove('system-cursor'); if (toggleBtn) { toggleBtn.classList.add('active'); let icon = toggleBtn.querySelector('.cursor-icon'); if (icon) { // Replace img with cursor.png if needed if (icon.tagName !== 'IMG') { const newIcon = document.createElement('img'); newIcon.className = 'cursor-icon'; newIcon.alt = 'Custom Cursor'; // Check if we're on a page in the sites/ folder const currentPath = window.location.pathname; const isInSitesFolder = currentPath.includes('/sites/'); const imagePath = isInSitesFolder ? '../images/additional/cursor.png' : 'images/additional/cursor.png'; newIcon.src = imagePath; icon.parentNode.replaceChild(newIcon, icon); } else { // Update existing img const currentPath = window.location.pathname; const isInSitesFolder = currentPath.includes('/sites/'); const imagePath = isInSitesFolder ? '../images/additional/cursor.png' : 'images/additional/cursor.png'; icon.src = imagePath; } } } } } updateCursorState(); if (toggleBtn) { toggleBtn.addEventListener('click', () => { isCursorDisabled = !isCursorDisabled; localStorage.setItem('venomCursorDisabled', isCursorDisabled); updateCursorState(); }); } function initCursor() { const canvas = document.createElement("canvas"); canvas.id = "venom-cursor"; document.body.appendChild(canvas); const ctx = canvas.getContext("2d"); const width = window.innerWidth; const height = window.innerHeight; canvas.width = width; canvas.height = height; const tentacles = []; const mouse = { x: 0, y: 0 }; const oldMouse = { x: 0, y: 0 }; document.addEventListener("mousemove", (e) => { mouse.x = e.clientX; mouse.y = e.clientY; }); window.addEventListener("resize", () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }); return { canvas, ctx, tentacles, mouse, oldMouse }; } const cursorElements = initCursor(); const canvas = cursorElements.canvas; const ctx = cursorElements.ctx; const tentacles = cursorElements.tentacles; const mouse = cursorElements.mouse; const oldMouse = cursorElements.oldMouse; class Tentacle { constructor(mx, my, targetX, targetY) { this.dead = false; this.anchor = { x: targetX, y: targetY }; this.currentDist = 0; // For calculating connection opacity } update(currentMouse) { const dx = currentMouse.x - this.anchor.x; const dy = currentMouse.y - this.anchor.y; this.currentDist = Math.sqrt(dx*dx + dy*dy); if (this.currentDist > CONFIG.maxLength) { this.dead = true; } } draw(ctx, currentMouse) { if (this.dead) return; // Tension (0..1) const tension = Math.min(this.currentDist / CONFIG.maxLength, 1); const dynamicThickness = CONFIG.thickness * (1 - tension * 0.9); // Draw main line (Cursor -> Anchor) ctx.beginPath(); ctx.moveTo(currentMouse.x, currentMouse.y); ctx.lineTo(this.anchor.x, this.anchor.y); ctx.lineWidth = Math.max(0.2, dynamicThickness); ctx.strokeStyle = CONFIG.color; ctx.lineCap = "butt"; ctx.stroke(); // Draw anchor point ctx.beginPath(); ctx.arc(this.anchor.x, this.anchor.y, 1.5 * (1 - tension), 0, Math.PI * 2); ctx.fillStyle = CONFIG.color; ctx.fill(); } } function render() { if (isCursorDisabled) { requestAnimationFrame(render); return; } ctx.clearRect(0, 0, canvas.width, canvas.height); // 1. Create new tentacles on movement const distMoved = Math.hypot(mouse.x - oldMouse.x, mouse.y - oldMouse.y); if (distMoved > CONFIG.triggerDist) { const vx = mouse.x - oldMouse.x; const vy = mouse.y - oldMouse.y; // "Spread" of shots increased slightly for better geometry const targetX = mouse.x + vx * CONFIG.prediction + (Math.random() - 0.5) * 60; const targetY = mouse.y + vy * CONFIG.prediction + (Math.random() - 0.5) * 60; tentacles.push(new Tentacle(mouse.x, mouse.y, targetX, targetY)); oldMouse.x = mouse.x; oldMouse.y = mouse.y; } // Remove old ones (FIFO) if (tentacles.length > CONFIG.tentacleCount) { tentacles.shift(); } // 2. Draw main tentacles for (let i = tentacles.length - 1; i >= 0; i--) { const t = tentacles[i]; t.update(mouse); if (t.dead) { tentacles.splice(i, 1); } else { t.draw(ctx, mouse); } } // 3. DRAW CONNECTIONS BETWEEN ANCHORS (New logic) // Iterate through all pairs of active tentacles ctx.beginPath(); // Begin common path for optimization ctx.lineWidth = 0.5; // Connections are always thin for (let i = 0; i < tentacles.length; i++) { for (let j = i + 1; j < tentacles.length; j++) { const t1 = tentacles[i]; const t2 = tentacles[j]; // Calculate distance between ends of two tentacles const dx = t1.anchor.x - t2.anchor.x; const dy = t1.anchor.y - t2.anchor.y; const dist = Math.sqrt(dx*dx + dy*dy); // If they are close to each other — connect if (dist < CONFIG.connectionDist) { // Opacity depends on how far apart they are // And how much the tentacles themselves are stretched const alpha = (1 - dist / CONFIG.connectionDist) * 0.6; ctx.beginPath(); // New path for each to control opacity ctx.strokeStyle = `rgba(20, 20, 20, ${alpha})`; ctx.moveTo(t1.anchor.x, t1.anchor.y); ctx.lineTo(t2.anchor.x, t2.anchor.y); ctx.stroke(); } } } // 4. Cursor (Rhombus) ctx.beginPath(); ctx.fillStyle = CONFIG.color; ctx.moveTo(mouse.x, mouse.y - 5); ctx.lineTo(mouse.x + 5, mouse.y); ctx.lineTo(mouse.x, mouse.y + 5); ctx.lineTo(mouse.x - 5, mouse.y); ctx.fill(); requestAnimationFrame(render); } render(); });