UNPKG

hover-effects-ts

Version:

A collection of beautiful hover effects for images using canvas

893 lines 52.4 kB
function L(c) { if (typeof c == "string") { const t = document.querySelectorAll(c); return Array.from(t); } else { if (c instanceof HTMLElement) return [c]; if (c instanceof NodeList) return Array.from(c); } return []; } class C { constructor(t = {}) { this.element = null, this.canvas = null, this.ctx = null, this.tempCanvas = null, this.tempCtx = null, this.isHovering = !1, this.mousePos = { x: 0, y: 0 }, this.animationFrame = null, this.imageData = null, this.lastRenderTime = 0, this.FPS = 30, this.frameInterval = 1e3 / 30, this.frameCount = 0, this.glitchOffsets = [], this.nonColoredChars = ["█", "@", "%", "#", "*", "+", "=", "^", " "], this.charMetrics = { width: 14, height: 16.8 }, this.autoSize = !1, this.baseImageSize = 0, this.onMouseEnter = (i) => { if (!this.element || !this.canvas) return; this.isHovering = !0; const e = this.element.getBoundingClientRect(); this.mousePos = { x: i.clientX - e.left, y: i.clientY - e.top }, this.canvas.style.opacity = "1", this.lastRenderTime = 0, this.render(); }, this.onMouseLeave = () => { !this.canvas || !this.ctx || (this.isHovering = !1, this.canvas.style.opacity = "0", this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), setTimeout(() => { !this.isHovering && this.ctx && this.canvas && this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); }, 300)); }, this.onMouseMove = (i) => { if (!this.element || !this.canvas) return; const e = this.canvas.getBoundingClientRect(), s = this.canvas.width / e.width, a = this.canvas.height / e.height; this.mousePos = { x: (i.clientX - e.left) * s, y: (i.clientY - e.top) * a }, console.log(`MousePos: (${this.mousePos.x}, ${this.mousePos.y}), Radius: ${this.radius}`); }, this.render = (i = 0) => { if (!this.element || !this.canvas || !this.ctx || !this.imageData || !this.isHovering) return; if (this.autoSize) { const d = this.size; this.calculateAutoSize(), Math.abs(this.size - d) > 1 && (this.measureChars(), this.updateImageData()); } if (i - this.lastRenderTime < this.frameInterval) { this.animationFrame = requestAnimationFrame(this.render); return; } this.lastRenderTime = i, this.frameCount++; const s = this.canvas.width, a = this.canvas.height, h = Math.floor(s * this.scale), n = Math.floor(a * this.scale); this.ctx.clearRect(0, 0, s, a), this.ctx.globalCompositeOperation = "source-over", this.ctx.font = `bold ${this.size}px monospace`, this.ctx.textAlign = "center", this.ctx.textBaseline = "middle"; const m = this.imageData.data, r = this.radius; for (let d = 0; d < n; d++) for (let u = 0; u < h; u++) { const o = u / this.scale, v = d / this.scale, f = Math.hypot(o - this.mousePos.x, v - this.mousePos.y); if (f < r) { const l = (d * h + u) * 4, g = (m[l] + m[l + 1] + m[l + 2]) / 3, p = Date.now() / (300 / this.glitchSpeed), x = Math.sin(p + u * d / 1e3) * this.glitchIntensity, M = 255 - g + x, w = Math.min( Math.max( Math.floor(M / 255 * (this.chars.length - 1)), 0 ), this.chars.length - 1 ), y = 1 - f / r; this.ctx.fillStyle = `rgba(0,0,0,${y * 0.8})`, this.ctx.fillRect(o - this.size / 2, v - this.size / 2, this.size, this.size), this.ctx.fillStyle = `rgba(255,255,255,${y})`, this.ctx.fillText(this.chars[w], o, v); } } this.isHovering && (this.animationFrame = requestAnimationFrame(this.render)); }, this.id = Math.random().toString(36).substring(2, 9), this.radius = t.radius ?? 100, this.size = t.size ?? 16, this.scale = 0.15, this.chars = this.nonColoredChars, this.glitchIntensity = t.glitchIntensity ?? 3, this.glitchSpeed = t.glitchSpeed ?? 0.5, t.chars && (this.chars = t.chars); } // Default values for 14px size // Cell calculations using metrics get cellW() { return this.charMetrics.width; } get cellH() { return this.charMetrics.height; } // Helper to convert hex colors to rgba hexToRgba(t, i) { const e = parseInt(t.slice(1, 3), 16), s = parseInt(t.slice(3, 5), 16), a = parseInt(t.slice(5, 7), 16); return `rgba(${e}, ${s}, ${a}, ${i})`; } // Helper to convert RGB to HSL rgbToHsl(t, i, e) { t /= 255, i /= 255, e /= 255; const s = Math.max(t, i, e), a = Math.min(t, i, e); let h = 0, n = 0, m = (s + a) / 2; if (s !== a) { const r = s - a; switch (n = m > 0.5 ? r / (2 - s - a) : r / (s + a), s) { case t: h = (i - e) / r + (i < e ? 6 : 0); break; case i: h = (e - t) / r + 2; break; case e: h = (t - i) / r + 4; break; } h /= 6; } return { h, s: n, l: m }; } // Helper to convert HSL to CSS color string hslToCss({ h: t, s: i, l: e }) { return `hsl(${Math.round(t * 360)}, ${Math.round(i * 100)}%, ${Math.round(e * 100)}%)`; } initGlitchOffsets(t, i, e, s) { this.glitchOffsets = []; for (let a = 0; a < i; a += s) for (let h = 0; h < t; h += e) this.glitchOffsets.push({ x: 0, y: 0 }); } updateGlitchOffsets() { const t = this.glitchIntensity; this.glitchOffsets.forEach((i) => { Math.random() < 0.1 && (i.x = (Math.random() * 2 - 1) * t, i.y = (Math.random() * 2 - 1) * t); }); } updateImageData() { if (!(!this.element || !this.tempCanvas || !this.tempCtx) && this.canvas) { const t = this.canvas.width, i = this.canvas.height, e = Math.floor(t * this.scale), s = Math.floor(i * this.scale); this.tempCanvas.width = e, this.tempCanvas.height = s, this.tempCtx.drawImage(this.element, 0, 0, e, s), this.imageData = this.tempCtx.getImageData(0, 0, e, s), console.log(`Canvas size: ${t}x${i}, Scale: ${this.scale}, Sampled size: ${e}x${s}`); } } attach(t) { if (!(t instanceof HTMLImageElement)) { console.error("ASCII effect can only be applied to img elements"); return; } this.element = t; const i = () => { const e = document.createElement("canvas"), s = t.getBoundingClientRect(); e.width = s.width, e.height = s.height, e.style.position = "absolute", e.style.top = "0", e.style.left = "0", e.style.width = "100%", e.style.height = "100%", e.style.opacity = "0", e.style.transition = "opacity 0.3s ease", e.style.pointerEvents = "none", e.style.backgroundColor = "transparent", e.dataset.asciiId = this.id, this.canvas = e, this.ctx = e.getContext("2d", { alpha: !0, willReadFrequently: !0 }), this.ctx && (this.ctx.globalCompositeOperation = "source-over", this.ctx.imageSmoothingEnabled = !1); const a = document.getElementById("ascii-size"), h = document.getElementById("ascii-radius"), n = document.getElementById("ascii-glitch-intensity"), m = document.getElementById("ascii-glitch-speed"); this.autoSize = !1, a && (this.size = parseFloat(a.value)), h && (this.radius = parseFloat(h.value)), n && (this.glitchIntensity = parseFloat(n.value)), m && (this.glitchSpeed = parseFloat(m.value) / 10), this.size <= 8 ? this.scale = 0.12 : this.size <= 12 ? this.scale = 0.14 : this.size <= 16 ? this.scale = 0.15 : this.size <= 20 ? this.scale = 0.12 : this.scale = 0.1, this.measureChars(), this.tempCanvas = document.createElement("canvas"), this.tempCtx = this.tempCanvas.getContext("2d", { alpha: !0 }), this.updateImageData(); let r = this.element.parentElement; (!r || !r.classList.contains("ascii-wrapper")) && (r = document.createElement("div"), r.className = "ascii-wrapper", r.style.position = "relative", r.style.display = "inline-block", this.element.replaceWith(r), r.appendChild(this.element)), r.appendChild(e), r.addEventListener("mouseenter", this.onMouseEnter), r.addEventListener("mouseleave", this.onMouseLeave), r.addEventListener("mousemove", this.onMouseMove), console.log(`ASCII effect setup with radius: ${this.radius}, size: ${this.size}`); }; t.complete ? i() : t.onload = i; } detach() { var t; this.element && (this.element.removeEventListener("mouseenter", this.onMouseEnter), this.element.removeEventListener("mouseleave", this.onMouseLeave), this.element.removeEventListener("mousemove", this.onMouseMove), this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), (t = this.canvas) == null || t.remove()); } destroy() { if (this.canvas && this.canvas.remove(), this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), this.element) { const t = this.element.parentElement; t && t.classList.contains("ascii-wrapper") && (t.removeEventListener("mouseenter", this.onMouseEnter), t.removeEventListener("mouseleave", this.onMouseLeave), t.removeEventListener("mousemove", this.onMouseMove)); } this.element = null, this.canvas = null, this.ctx = null, this.tempCanvas = null, this.tempCtx = null, this.imageData = null, this.glitchOffsets = []; } setGlitchIntensity(t) { this.glitchIntensity = t; } setGlitchSpeed(t) { this.glitchSpeed = t; } setRadius(t) { this.radius = t, this.isHovering && this.canvas && this.ctx && (this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.render()); } setSize(t) { this.autoSize = !1, this.size = t, t <= 8 ? this.scale = 0.12 : t <= 12 ? this.scale = 0.14 : t <= 16 ? this.scale = 0.15 : t <= 20 ? this.scale = 0.12 : this.scale = 0.1, this.measureChars(), this.element && (this.updateImageData(), this.isHovering && this.render()); } setChars(t) { t && t.length > 0 && (this.chars = t); } // Add method to calculate auto-size based on image dimensions calculateAutoSize() { if (!this.element) return; const t = this.element.getBoundingClientRect(); this.baseImageSize = Math.sqrt(t.width * t.height); const i = Math.max(8, Math.min(24, this.baseImageSize / 150)); this.size = i, this.size <= 8 ? this.scale = 0.12 : this.size <= 12 ? this.scale = 0.14 : this.size <= 16 ? this.scale = 0.15 : this.size <= 20 ? this.scale = 0.12 : this.scale = 0.1; } // Add method to toggle auto-size setAutoSize(t) { this.autoSize = t, t && this.element && (this.calculateAutoSize(), this.measureChars(), this.updateImageData(), this.isHovering && this.render()); } // Simplified character measurement for stability measureChars() { const t = this.size; let i = 0.6, e = 1.2; t <= 8 ? (i = 0.5, e = 1) : t <= 12 && (i = 0.55, e = 1.1), this.charMetrics = { width: t * i, height: t * e }; } } window.AsciiHover = C; class F { constructor(t = {}) { this.element = null, this.canvas = null, this.ctx = null, this.isHovering = !1, this.mousePos = { x: 0, y: 0 }, this.animationFrame = null, this.aspectRatio = 1, this.onMouseEnter = (i) => { !this.element || !this.canvas || (this.isHovering = !0, this.canvas && (this.canvas.style.opacity = "1"), this.render()); }, this.onMouseLeave = () => { !this.element || !this.canvas || (this.isHovering = !1, this.canvas && (this.canvas.style.opacity = "0"), this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null)); }, this.onMouseMove = (i) => { if (!this.element || !this.canvas) return; const e = this.canvas.getBoundingClientRect(), s = this.canvas.width / e.width, a = this.canvas.height / e.height; this.mousePos = { x: (i.clientX - e.left) * s, y: (i.clientY - e.top) * a }, this.isHovering && !this.animationFrame && (this.animationFrame = requestAnimationFrame(() => { this.render(), this.animationFrame = null; })); }, this.radius = t.radius ?? 70, this.scale = t.scale ?? 1.1; } render() { if (!this.element || !this.canvas || !this.ctx) return; const t = this.canvas.getBoundingClientRect(), i = t.width / t.height, e = this.canvas.width / this.canvas.height, s = i / e; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.ctx.drawImage(this.element, 0, 0, this.canvas.width, this.canvas.height), this.ctx.save(), s !== 1 && (this.ctx.setTransform( 1, 0, 0, 1, this.mousePos.x, this.mousePos.y ), s > 1 ? this.ctx.scale(1 / s, 1) : this.ctx.scale(1, s), this.ctx.translate(-this.mousePos.x, -this.mousePos.y)), this.ctx.beginPath(), this.ctx.arc(this.mousePos.x, this.mousePos.y, this.radius, 0, Math.PI * 2), this.ctx.closePath(), this.ctx.clip(), s !== 1 && this.ctx.setTransform(1, 0, 0, 1, 0, 0), this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); const a = this.mousePos.x - this.mousePos.x * this.scale, h = this.mousePos.y - this.mousePos.y * this.scale; this.ctx.translate(a, h), this.ctx.scale(this.scale, this.scale), this.ctx.drawImage(this.element, 0, 0, this.canvas.width, this.canvas.height), this.ctx.restore(), this.ctx.save(), s !== 1 && (this.ctx.setTransform( 1, 0, 0, 1, this.mousePos.x, this.mousePos.y ), s > 1 ? this.ctx.scale(1 / s, 1) : this.ctx.scale(1, s), this.ctx.translate(-this.mousePos.x, -this.mousePos.y)), this.ctx.beginPath(), this.ctx.arc(this.mousePos.x, this.mousePos.y, this.radius, 0, Math.PI * 2), this.ctx.strokeStyle = "rgba(255, 255, 255, 0.2)", this.ctx.lineWidth = 1.5, this.ctx.stroke(), this.ctx.restore(); } attach(t) { if (!(t instanceof HTMLImageElement)) return; this.element = t; const i = () => { const e = document.createElement("canvas"); e.width = this.element.width, e.height = this.element.height, e.style.position = "absolute", e.style.top = "0", e.style.left = "0", e.style.width = "100%", e.style.height = "100%", e.style.pointerEvents = "none", e.style.opacity = "0", e.style.transition = "opacity 0.3s ease", this.canvas = e, this.ctx = e.getContext("2d", { alpha: !0 }), this.ctx && (this.ctx.imageSmoothingEnabled = !0, this.ctx.imageSmoothingQuality = "high"); let s = this.element.parentElement; (!s || !s.classList.contains("zoom-wrapper")) && (s = document.createElement("div"), s.className = "zoom-wrapper", s.style.position = "relative", s.style.display = "inline-block", s.style.overflow = "hidden", this.element.replaceWith(s), s.appendChild(this.element)), s.appendChild(e), s.addEventListener("mouseenter", this.onMouseEnter), s.addEventListener("mouseleave", this.onMouseLeave), s.addEventListener("mousemove", this.onMouseMove); }; t.complete ? i() : t.onload = i; } detach() { var i; if (!this.element) return; const t = this.element.parentElement; t && (t.removeEventListener("mouseenter", this.onMouseEnter), t.removeEventListener("mouseleave", this.onMouseLeave), t.removeEventListener("mousemove", this.onMouseMove), t.classList.contains("zoom-wrapper") && t.replaceWith(this.element)), (i = this.canvas) == null || i.remove(), this.canvas = null, this.ctx = null; } destroy() { this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), this.detach(), this.element = null; } setRadius(t) { this.radius = t; } setScale(t) { this.scale = t; } } class P { constructor(t = {}) { this.element = null, this.canvas = null, this.ctx = null, this.particles = [], this.cursor = { x: 0, y: 0, active: !1 }, this.animationFrame = null, this.isSetup = !1, this.render = (i) => { if (!this.canvas || !this.ctx || !this.isSetup || !this.element) return; const e = i * 1e-3; if (this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.ctx.drawImage(this.element, 0, 0), this.cursor.active) { this.ctx.save(), this.ctx.beginPath(); const s = 3, a = 40, h = this.radius + this.softEdge; for (let n = 0; n <= a; n++) { const m = n / a * Math.PI * 2, r = 1 + (Math.sin(e * 0.5 + m * 5) * 0.5 + 0.5) * (s / h), d = this.cursor.x + Math.cos(m) * h * r, u = this.cursor.y + Math.sin(m) * h * r; n === 0 ? this.ctx.moveTo(d, u) : this.ctx.lineTo(d, u); } this.ctx.closePath(), this.ctx.clip(), this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.particles.forEach((n) => { const m = Math.hypot(n.homeX - this.cursor.x, n.homeY - this.cursor.y), r = this.radius; if (m < r + this.softEdge) { const d = m < r ? 1 : 1 - Math.pow((m - r) / this.softEdge, 2), u = Math.pow(d, this.fadeExp), o = u * this.maxDrift; let v = n.homeX + n.dir.dx * o, f = n.homeY + n.dir.dy * o; const l = Math.sin(e * n.wobSpeed + n.phase) * n.wobAmp * u; v += n.wobVec.dx * l, f += n.wobVec.dy * l, this.ctx.globalAlpha = 0.15 + 0.85 * u, this.ctx.fillStyle = n.color, this.ctx.fillRect(v, f, this.spacing, this.spacing); } }), this.ctx.restore(); } this.animationFrame = requestAnimationFrame(this.render); }, this.onMouseMove = (i) => { if (!this.element || !this.canvas) return; const e = this.canvas.getBoundingClientRect(), s = this.canvas.width / e.width, a = this.canvas.height / e.height; this.cursor.x = (i.clientX - e.left) * s, this.cursor.y = (i.clientY - e.top) * a, this.cursor.active = !0, Math.random() < 0.01 && console.log("Cursor active:", { x: this.cursor.x, y: this.cursor.y, maxDrift: this.maxDrift, radius: this.radius }); }, this.onMouseLeave = () => { this.cursor.active = !1; }, this.spacing = t.spacing ?? 2, this.radius = t.radius ?? 110, this.maxDrift = t.maxDrift ?? 28, this.homeJitter = this.spacing / 2, this.softEdge = Math.min(40, this.radius / 3), this.fadeExp = 1.2, this.wobbleAmpMin = 1, this.wobbleAmpMax = 2, this.wobbleSpeedMin = 0.4, this.wobbleSpeedMax = 0.8; } createParticles() { if (!this.element || !this.canvas || !this.ctx) return; const t = document.createElement("canvas"); t.width = this.canvas.width, t.height = this.canvas.height; const i = t.getContext("2d"); if (!i) return; i.drawImage(this.element, 0, 0, t.width, t.height); const { data: e } = i.getImageData(0, 0, t.width, t.height); this.particles = []; for (let s = 0; s < t.height; s += this.spacing) for (let a = 0; a < t.width; a += this.spacing) { const h = (s * t.width + a) * 4; if (e[h + 3] < 120) continue; const n = `rgb(${e[h]},${e[h + 1]},${e[h + 2]})`, m = a + (Math.random() * 2 - 1) * this.homeJitter, r = s + (Math.random() * 2 - 1) * this.homeJitter, d = Math.random() * Math.PI * 2, u = { dx: Math.cos(d), dy: Math.sin(d) }, o = Math.random() * Math.PI * 2, v = { dx: Math.cos(o), dy: Math.sin(o) }, f = this.wobbleAmpMin + Math.random() * (this.wobbleAmpMax - this.wobbleAmpMin), l = this.wobbleSpeedMin + Math.random() * (this.wobbleSpeedMax - this.wobbleSpeedMin), g = Math.random() * Math.PI * 2; this.particles.push({ homeX: m, homeY: r, dir: u, color: n, wobVec: v, wobAmp: f, wobSpeed: l, phase: g }); } console.log(`Created ${this.particles.length} particles`); } attach(t) { if (!(t instanceof HTMLImageElement)) { console.error("ParticleDust effect can only be applied to img elements"); return; } this.element = t; const i = () => { const e = document.createElement("canvas"); e.width = t.naturalWidth, e.height = t.naturalHeight, e.style.position = "absolute", e.style.top = "0", e.style.left = "0", e.style.width = "100%", e.style.height = "100%", e.style.pointerEvents = "none", this.canvas = e, this.ctx = e.getContext("2d"); let s = t.parentElement; (!s || !s.classList.contains("particle-wrapper")) && (s = document.createElement("div"), s.className = "particle-wrapper", s.style.position = "relative", s.style.display = "inline-block", t.replaceWith(s), s.appendChild(t)), s.appendChild(e), this.createParticles(), this.isSetup = !0, this.animationFrame = requestAnimationFrame(this.render), s.addEventListener("mousemove", this.onMouseMove), s.addEventListener("mouseleave", this.onMouseLeave); }; t.complete ? i() : t.onload = i; } detach() { var i; if (!this.element) return; const t = this.element.parentElement; t && (t.removeEventListener("mousemove", this.onMouseMove), t.removeEventListener("mouseleave", this.onMouseLeave)), this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), (i = this.canvas) == null || i.remove(), this.particles = [], this.isSetup = !1; } destroy() { this.detach(), this.element = null, this.canvas = null, this.ctx = null; } setSpacing(t) { this.spacing = t, this.homeJitter = t / 2, this.isSetup && this.createParticles(); } setMaxDrift(t) { console.log(`[CRITICAL] ParticleDust.setMaxDrift: ${this.maxDrift} → ${t}`), this.maxDrift = t; } setRadius(t) { this.radius = t, this.softEdge = Math.min(40, this.radius / 3); } } class R { constructor(t = {}) { this.element = null, this.canvas = null, this.ctx = null, this.samples = [], this.cursor = { x: 0, y: 0, active: !1 }, this.animationFrame = null, this.isSetup = !1, this.render = () => { !this.canvas || !this.ctx || !this.isSetup || (this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.cursor.active && this.samples.forEach((i) => { const e = Math.hypot(i.x - this.cursor.x, i.y - this.cursor.y); if (e < this.radius + this.softEdge) { const s = e < this.radius ? 1 : 1 - (e - this.radius) / this.softEdge, a = Math.pow(s, this.fadeExp); this.ctx.fillStyle = i.color, this.ctx.globalAlpha = a, this.ctx.fillRect(i.x, i.y, this.blockSize, this.blockSize); } }), this.animationFrame = requestAnimationFrame(this.render)); }, this.onMouseMove = (i) => { if (!this.element || !this.canvas) return; const e = this.canvas.getBoundingClientRect(), s = this.canvas.width / e.width, a = this.canvas.height / e.height; this.cursor.x = (i.clientX - e.left) * s, this.cursor.y = (i.clientY - e.top) * a, this.cursor.active = !0, this.canvas.style.opacity || (this.canvas.style.opacity = "1"); }, this.onMouseEnter = () => { this.canvas && (this.canvas.style.opacity = "1"); }, this.onMouseLeave = () => { this.canvas && (this.canvas.style.opacity = "0", this.cursor.active = !1); }, this.blockSize = 16, this.radius = 130, t.blockSize !== void 0 && (this.blockSize = t.blockSize), t.radius !== void 0 && (this.radius = t.radius), this.softEdge = Math.min(70, this.radius / 2), this.fadeExp = 1.4; } sampleImage() { if (!this.element || !this.canvas || !this.ctx) return; const t = document.createElement("canvas"); t.width = this.canvas.width, t.height = this.canvas.height; const i = t.getContext("2d"); if (!i) return; i.drawImage(this.element, 0, 0, t.width, t.height); const { data: e } = i.getImageData(0, 0, t.width, t.height); this.samples = []; for (let s = 0; s < t.height; s += this.blockSize) for (let a = 0; a < t.width; a += this.blockSize) { const h = (s * t.width + a) * 4; h >= e.length || e[h + 3] < 120 || this.samples.push({ x: a, y: s, color: `rgb(${e[h]},${e[h + 1]},${e[h + 2]})` }); } } attach(t) { if (!(t instanceof HTMLImageElement)) return; this.element = t; const i = () => { const e = document.createElement("canvas"); e.width = t.naturalWidth, e.height = t.naturalHeight, e.style.position = "absolute", e.style.top = "0", e.style.left = "0", e.style.width = "100%", e.style.height = "100%", e.style.pointerEvents = "none", e.style.opacity = "0", e.style.transition = "opacity 0.25s ease", this.canvas = e, this.ctx = e.getContext("2d"); let s = t.parentElement; (!s || !s.classList.contains("pixel-wrapper")) && (s = document.createElement("div"), s.className = "pixel-wrapper", s.style.position = "relative", s.style.display = "inline-block", t.replaceWith(s), s.appendChild(t)), s.appendChild(e), this.sampleImage(), this.isSetup = !0, this.animationFrame = requestAnimationFrame(this.render), s.addEventListener("mousemove", this.onMouseMove), s.addEventListener("mouseenter", this.onMouseEnter), s.addEventListener("mouseleave", this.onMouseLeave); }; t.complete ? i() : t.onload = i; } detach() { var i; if (!this.element) return; const t = this.element.parentElement; t && (t.removeEventListener("mousemove", this.onMouseMove), t.removeEventListener("mouseenter", this.onMouseEnter), t.removeEventListener("mouseleave", this.onMouseLeave)), this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), (i = this.canvas) == null || i.remove(), this.samples = [], this.isSetup = !1; } destroy() { this.detach(), this.element = null, this.canvas = null, this.ctx = null; } setBlockSize(t) { if (t !== this.blockSize && (this.blockSize = t, this.isSetup)) { this.ctx && this.canvas && this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); const i = this.cursor.active; this.cursor.active = !1, this.sampleImage(), this.cursor.active = i, this.cursor.active && this.render(); } } setRadius(t) { this.radius = t; } // Getter methods for debugging getBlockSize() { return this.blockSize; } getRadius() { return this.radius; } getSamplesCount() { return this.samples.length; } getDebugInfo() { return { blockSize: this.blockSize, radius: this.radius, samplesCount: this.samples.length, isSetup: this.isSetup, cursorActive: this.cursor.active, canvasSize: this.canvas ? { width: this.canvas.width, height: this.canvas.height } : null }; } } class k { constructor(t = {}) { this.element = null, this.canvas = null, this.ctx = null, this.samples = [], this.cursor = { x: 0, y: 0, active: !1 }, this.animationFrame = null, this.isSetup = !1, this.render = () => { !this.canvas || !this.ctx || !this.isSetup || (this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.cursor.active && (this.blockSize, this.samples.forEach((i) => { const e = Math.hypot(i.x - this.cursor.x, i.y - this.cursor.y); if (e < this.radius + this.softEdge) { const s = e < this.radius ? 1 : 1 - (e - this.radius) / this.softEdge, a = Math.pow(s, this.fadeExp); this.drawVoxel(i, a); } })), this.animationFrame = requestAnimationFrame(this.render)); }, this.onMouseEnter = () => { this.canvas && (this.canvas.style.opacity = "1"); }, this.onMouseLeave = () => { this.canvas && (this.canvas.style.opacity = "0", this.cursor.active = !1); }, this.onMouseMove = (i) => { if (!this.element || !this.canvas) return; const e = this.canvas.getBoundingClientRect(), s = this.canvas.width / e.width, a = this.canvas.height / e.height; this.cursor.x = (i.clientX - e.left) * s, this.cursor.y = (i.clientY - e.top) * a, this.cursor.active = !0; }, this.blockSize = t.blockSize ?? 28, this.radius = t.radius ?? 130, this.softEdge = Math.min(70, this.radius / 2), this.fadeExp = 1.4; } shadeColor(t, i) { const e = t.slice(4, t.length - 1).split(","), s = parseInt(e[0], 10), a = parseInt(e[1], 10), h = parseInt(e[2], 10), n = i < 0 ? 0 : 255, m = i < 0 ? i * -1 : i, r = s + Math.round((n - s) * m), d = a + Math.round((n - a) * m), u = h + Math.round((n - h) * m); return `rgb(${r}, ${d}, ${u})`; } sampleImage() { if (!this.element || !this.canvas || !this.ctx) return; const t = document.createElement("canvas"); t.width = this.canvas.width, t.height = this.canvas.height; const i = t.getContext("2d"); if (!i) return; i.drawImage(this.element, 0, 0, t.width, t.height); const { data: e } = i.getImageData(0, 0, t.width, t.height); this.samples = []; for (let s = 0; s < t.height; s += this.blockSize) for (let a = 0; a < t.width; a += this.blockSize) { const h = (s * t.width + a) * 4; h >= e.length || e[h + 3] < 120 || this.samples.push({ x: a, y: s, color: `rgb(${e[h]},${e[h + 1]},${e[h + 2]})` }); } } drawVoxel(t, i) { if (!this.ctx) return; const e = this.blockSize, s = e / 2, a = i * e * 0.7; this.ctx.fillStyle = t.color, this.ctx.globalAlpha = i, this.ctx.fillRect(t.x, t.y - a, e, e), this.ctx.fillStyle = this.shadeColor(t.color, 0.25), this.ctx.beginPath(), this.ctx.moveTo(t.x, t.y - a), this.ctx.lineTo(t.x + s, t.y - s - a), this.ctx.lineTo(t.x + e + s, t.y - s - a), this.ctx.lineTo(t.x + e, t.y - a), this.ctx.closePath(), this.ctx.fill(), this.ctx.fillStyle = this.shadeColor(t.color, -0.25), this.ctx.beginPath(), this.ctx.moveTo(t.x + e, t.y - a), this.ctx.lineTo(t.x + e + s, t.y - s - a), this.ctx.lineTo(t.x + e + s, t.y + e - s - a), this.ctx.lineTo(t.x + e, t.y + e - a), this.ctx.closePath(), this.ctx.fill(); } attach(t) { if (!(t instanceof HTMLImageElement)) return; this.element = t; const i = () => { const e = document.createElement("canvas"); e.width = this.element.naturalWidth || this.element.width, e.height = this.element.naturalHeight || this.element.height, e.style.position = "absolute", e.style.top = "0", e.style.left = "0", e.style.width = "100%", e.style.height = "100%", e.style.pointerEvents = "none", e.style.opacity = "0", e.style.transition = "opacity 0.25s ease", this.canvas = e, this.ctx = e.getContext("2d"); let s = this.element.parentElement; (!s || !s.classList.contains("minecraft-wrapper")) && (s = document.createElement("div"), s.className = "minecraft-wrapper", s.style.position = "relative", s.style.display = "inline-block", this.element.replaceWith(s), s.appendChild(this.element)), s.appendChild(e), this.sampleImage(), this.isSetup = !0, this.animationFrame = requestAnimationFrame(this.render), s.addEventListener("mouseenter", this.onMouseEnter), s.addEventListener("mouseleave", this.onMouseLeave), s.addEventListener("mousemove", this.onMouseMove); }; t.complete ? i() : t.onload = i; } detach() { var i; if (!this.element) return; const t = this.element.parentElement; t && (t.removeEventListener("mouseenter", this.onMouseEnter), t.removeEventListener("mouseleave", this.onMouseLeave), t.removeEventListener("mousemove", this.onMouseMove)), this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), (i = this.canvas) == null || i.remove(), this.samples = [], this.isSetup = !1; } destroy() { this.detach(), this.element = null, this.canvas = null, this.ctx = null; } setBlockSize(t) { if (t !== this.blockSize && (this.blockSize = t, this.isSetup)) { this.ctx && this.canvas && this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); const i = this.cursor.active; this.cursor.active = !1, this.sampleImage(), this.cursor.active = i, this.cursor.active && this.render(); } } setRadius(t) { this.radius = t; } // Getter methods for debugging getBlockSize() { return this.blockSize; } getRadius() { return this.radius; } getSamplesCount() { return this.samples.length; } getDebugInfo() { return { blockSize: this.blockSize, radius: this.radius, samplesCount: this.samples.length, isSetup: this.isSetup, cursorActive: this.cursor.active, canvasSize: this.canvas ? { width: this.canvas.width, height: this.canvas.height } : null }; } } class T { constructor(t = {}) { this.element = null, this.canvas = null, this.ctx = null, this.isHovering = !1, this.mousePos = { x: 0, y: 0 }, this.animationFrame = null, this.bricks = [], this.render = () => { !this.element || !this.canvas || !this.ctx || !this.isHovering || (this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.isHovering && this.bricks.forEach((i) => { const e = Math.hypot(i.x - this.mousePos.x, i.y - this.mousePos.y); if (e < this.radius + this.softEdge) { let s; if (e < this.radius) s = 1; else { const h = (e - this.radius) / this.softEdge; s = 1 - Math.pow(h, 0.8); } const a = Math.pow(s, this.fadeExp); this.drawBrick(i, a); } }), this.animationFrame = requestAnimationFrame(this.render)); }, this.onMouseEnter = (i) => { if (!this.element || !this.canvas) return; this.isHovering = !0, this.canvas.style.opacity = "1"; const e = this.element.getBoundingClientRect(), s = this.canvas.width / e.width, a = this.canvas.height / e.height; this.mousePos = { x: (i.clientX - e.left) * s, y: (i.clientY - e.top) * a }, this.animationFrame || this.render(); }, this.onMouseLeave = () => { this.canvas && (this.isHovering = !1, this.canvas.style.opacity = "0", this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null)); }, this.onMouseMove = (i) => { if (!this.element || !this.canvas) return; const e = this.element.getBoundingClientRect(), s = this.canvas.width / e.width, a = this.canvas.height / e.height; this.mousePos = { x: (i.clientX - e.left) * s, y: (i.clientY - e.top) * a }; }, this.blockSize = t.blockSize ?? 20, this.gap = t.gap ?? 2, this.studScale = t.studScale ?? 0.4, this.depth = t.depth ?? 0.3, this.radius = t.radius ?? 140, this.softEdge = t.softEdge ?? 90, this.fadeExp = t.fadeExp ?? 1.4; } // Helper function to shade colors shade(t, i) { return `rgb(${t.map((e) => Math.max(0, Math.min(255, e * (1 + i))) | 0).join(",")})`; } // Sample colors from the source image sample() { if (!this.element || !this.canvas) return; this.canvas.width = this.element.naturalWidth, this.canvas.height = this.element.naturalHeight, this.bricks = []; const t = document.createElement("canvas"); t.width = this.element.naturalWidth, t.height = this.element.naturalHeight; const i = t.getContext("2d"); if (!i) return; i.drawImage(this.element, 0, 0); const s = i.getImageData(0, 0, t.width, t.height).data; for (let a = 0; a < t.height; a += this.blockSize) for (let h = 0; h < t.width; h += this.blockSize) { const n = (a * t.width + h) * 4; s[n + 3] < 50 || this.bricks.push({ x: h, y: a, rgb: [s[n], s[n + 1], s[n + 2]] }); } } // Draw a single LEGO brick drawBrick(t, i) { if (!this.ctx) return; const e = this.blockSize, { x: s, y: a, rgb: h } = t, n = this.studScale * e * 0.5, m = this.depth * 1.5, r = this.shade(h, m), d = this.shade(h, -m), u = this.shade(h, -m * 1.5), o = this.gap; this.ctx.save(), this.ctx.globalAlpha = i; const v = (this.softEdge - 30) / (150 - 30), f = e / 4, l = Math.sqrt(v) * f; this.ctx.fillStyle = r, this.ctx.beginPath(), this.ctx.moveTo(s + o + l, a + o), this.ctx.lineTo(s + e - o - l, a + o), this.ctx.arcTo(s + e - o, a + o, s + e - o, a + o + l, l), this.ctx.lineTo(s + e - o, a + e - o - l), this.ctx.arcTo(s + e - o, a + e - o, s + e - o - l, a + e - o, l), this.ctx.lineTo(s + o + l, a + e - o), this.ctx.arcTo(s + o, a + e - o, s + o, a + e - o - l, l), this.ctx.lineTo(s + o, a + o + l), this.ctx.arcTo(s + o, a + o, s + o + l, a + o, l), this.ctx.closePath(), this.ctx.fill(), this.ctx.fillStyle = d, this.ctx.beginPath(), this.ctx.moveTo(s + e - o, a + o + l), this.ctx.lineTo(s + e, a + o + l), this.ctx.lineTo(s + e, a + e - o), this.ctx.lineTo(s + e - o, a + e - o - l), this.ctx.closePath(), this.ctx.fill(), this.ctx.fillStyle = u, this.ctx.beginPath(), this.ctx.moveTo(s + o + l, a + e - o), this.ctx.lineTo(s + e - o - l, a + e - o), this.ctx.lineTo(s + e - o, a + e), this.ctx.lineTo(s + o, a + e), this.ctx.closePath(), this.ctx.fill(), this.ctx.fillStyle = "rgb(35,35,35)", this.ctx.fillRect(s, a, l, e), this.ctx.fillRect(s, a, e, l); const g = s + e / 2, p = a + e / 2; this.ctx.beginPath(), this.ctx.arc(g, p, n + 1, 0, Math.PI * 2), this.ctx.closePath(), this.ctx.fillStyle = "rgba(0,0,0,0.2)", this.ctx.fill(), this.ctx.beginPath(), this.ctx.arc(g, p, n, 0, Math.PI * 2), this.ctx.closePath(), this.ctx.fillStyle = this.shade(h, -this.depth * 0.3), this.ctx.fill(), this.ctx.lineWidth = Math.max(1, e / 24), this.ctx.strokeStyle = this.shade(h, -this.depth * 0.4), this.ctx.stroke(), this.ctx.beginPath(), this.ctx.arc(g, p, n * 0.8, 0, Math.PI * 2), this.ctx.closePath(), this.ctx.fillStyle = this.shade(h, this.depth * 0.5), this.ctx.fill(); const x = 0.2; this.ctx.beginPath(), this.ctx.arc( g - n * x, p - n * x, n * 0.25, 0, Math.PI * 2 ), this.ctx.closePath(), this.ctx.fillStyle = this.shade(h, this.depth * 2), this.ctx.fill(), this.ctx.restore(); } // Implement HoverEffect interface methods attach(t) { if (!(t instanceof HTMLImageElement)) { console.error("LegoHover effect can only be applied to img elements"); return; } this.element = t; const i = () => { const e = document.createElement("canvas"); e.style.position = "absolute", e.style.top = "0", e.style.left = "0", e.style.width = "100%", e.style.height = "100%", e.style.pointerEvents = "none", e.style.opacity = "0", e.style.transition = "opacity 0.25s ease", this.canvas = e, this.ctx = e.getContext("2d"); let s = this.element.parentElement; (!s || !s.classList.contains("lego-wrapper")) && (s = document.createElement("div"), s.className = "lego-wrapper", s.style.position = "relative", s.style.display = "inline-block", this.element.replaceWith(s), s.appendChild(this.element)), s.appendChild(e), this.sample(), s.addEventListener("mouseenter", this.onMouseEnter), s.addEventListener("mouseleave", this.onMouseLeave), s.addEventListener("mousemove", this.onMouseMove), window.addEventListener("resize", () => { this.sample(); }); }; t.complete ? i() : t.onload = i; } detach() { var i; if (!this.element) return; const t = this.element.parentElement; t && (t.removeEventListener("mouseenter", this.onMouseEnter), t.removeEventListener("mouseleave", this.onMouseLeave), t.removeEventListener("mousemove", this.onMouseMove), t.classList.contains("lego-wrapper") && t.replaceWith(this.element)), window.removeEventListener("resize", this.sample), (i = this.canvas) == null || i.remove(), this.canvas = null, this.ctx = null; } destroy() { this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), this.detach(), this.element = null, this.bricks = []; } // Setters for runtime configuration setBlockSize(t) { this.blockSize = t, this.sample(); } setRadius(t) { this.radius = t; } setGap(t) { this.gap = t, this.sample(); } setStudScale(t) { this.studScale = t; } setDepth(t) { this.depth = t; } setSoftEdge(t) { this.softEdge = t; } setFadeExp(t) { this.fadeExp = t; } } class A { constructor(t = {}) { this.element = null, this.canvas = null, this.ctx = null, this.tempCanvas = null, this.tempCtx = null, this.isHovering = !1, this.mousePos = { x: 0, y: 0 }, this.animationFrame = null, this.imageData = null, this.lastRenderTime = 0, this.FPS = 30, this.frameInterval = 1e3 / 30, this.frameCount = 0, this.ledPath = null, this.driftOffsets = [], this.onMouseEnter = (i) => { if (!this.element || !this.canvas) return; this.isHovering = !0; const e = this.element.getBoundingClientRect(); this.mousePos = { x: i.clientX - e.left, y: i.clientY - e.top }, this.canvas.style.opacity = "1", this.lastRenderTime = 0, this.render(); }, this.onMouseLeave = () => { !this.canvas || !this.ctx || (this.isHovering = !1, this.canvas.style.opacity = "0", this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), setTimeout(() => { !this.isHovering && this.ctx && this.canvas && this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); }, 300)); }, this.onMouseMove = (i) => { if (!this.element || !this.canvas) return; const e = this.canvas.getBoundingClientRect(), s = this.canvas.width / e.width, a = this.canvas.height / e.height; this.mousePos = { x: (i.clientX - e.left) * s, y: (i.clientY - e.top) * a }; }, this.render = (i = 0) => { if (!this.element || !this.canvas || !this.ctx || !this.imageData || !this.isHovering || !this.ledPath) return; if (!this.imageData.data || this.imageData.width <= 0 || this.imageData.height <= 0) { console.warn("Invalid image data, skipping render"); return; } if (i - this.lastRenderTime < this.frameInterval) { this.animationFrame = requestAnimationFrame(this.render); return; } this.lastRenderTime = i, this.frameCount++; const s = this.canvas.width, a = this.canvas.height, h = Math.floor(s / this.ledSpacing), n = Math.floor(a / this.ledSpacing); if (h !== this.imageData.width || n !== this.imageData.height) { console.warn("Grid dimensions mismatch with imageData, updating..."), this.updateImageData(); return; } this.animationType === "drift" && this.driftOffsets.length !== h * n && this.initDriftOffsets(h, n), this.animationType === "drift" && this.updateDriftOffsets(), this.ctx.fillStyle = "#000000", this.ctx.fillRect(0, 0, s, a), this.ctx.globalCompositeOperation = "source-over"; const m = this.imageData.data, r = this.radius; for (let d = 0; d < n; d++) for (let u = 0; u < h; u++) { const o = (u + 0.5) * this.ledSpacing, v = (d + 0.5) * this.ledSpacing, f = this.getAnimationOffset(u, d, h, n), l = o + f.x, g = v + f.y, p = Math.hypot(o - this.mousePos.x, v - this.mousePos.y); if (p < r) { const x = (d * h + u) * 4, M = m[x], w = m[x + 1], y = m[x + 2], z = (M + w + y) / (3 * 255); let E = 1; if (p > r - this.softEdge) { const I = p - (r - this.softEdge); E = 1 - Math.pow(I / this.softEdge, this.fadeExp), E = Math.max(0, E); } const b = z * E; if (b < 0.05) continue; let S; this.colorMode === "rgb" ? S = `rgba(${M}, ${w}, ${y}, ${b})` : S = `rgba(0, 255, 255, ${b})`, this.glow ? (this.ctx.shadowBlur = 4 * f.scale, this.ctx.shadowColor = S) : this.ctx.shadowBlur = 0, this.ctx.fillStyle = S, this.ctx.save(), this.ctx.translate(l, g), f.rotation !== 0 && this.ctx.rotate(f.rotation), f.scale !== 1 && this.ctx.scale(f.scale, f.scale), this.ctx.fill(this.ledPath), this.ctx.restore(); } } this.ctx.shadowBlur = 0, this.isHovering && (this.animationFrame = requestAnimationFrame(this.render)); }, this.id = Math.random().toString(36).substring(2, 9), this.radius = t.radius ?? 120, this.softEdge = t.softEdge ?? 20, this.ledSize = t.ledSize ?? 4, this.ledSpacing = t.ledSpacing ?? 5, this.scale = t.scale ?? 0.12, this.glow = t.glow ?? !0, this.fadeExp = t.fadeExp ?? 2, this.colorMode = t.colorMode ?? "mono", this.animationType = t.animationType ?? "wave", this.animationSpeed = t.animationSpeed ?? 1, this.animationIntensity = t.animationIntensity ?? 3, this.buildLedPath(); } buildLedPath() { this.ledPath = new Path2D(), this.ledPath.arc(0, 0, this.ledSize / 2, 0, Math.PI * 2); } initDriftOffsets(t, i) { this.driftOffsets = []; for (let e = 0; e < i; e++) for (let s = 0; s < t; s++) this.driftOffsets.push({ x: 0, y: 0, vx: (Math.random() - 0.5) * 0.5, vy: (Math.random() - 0.5) * 0.5 }); } updateDriftOffsets() { this.driftOffsets.forEach((t) => { t.x += t.vx * this.animationSpeed, t.y += t.vy * this.animationSpeed, Math.random() < 0.02 && (t.vx += (Math.random() - 0.5) * 0.1, t.vy += (Math.random() - 0.5) * 0.1); const i = 0.8; t.vx = Math.max(-i, Math.min(i, t.vx)), t.vy = Math.max(-i, Math.min(i, t.vy)); const e = this.animationIntensity; t.x = Math.max(-e, Math.min(e, t.x)), t.y = Math.max(-e, Math.min(e, t.y)); }); } getAnimationOffset(t, i, e, s) { const a = this.frameCount * 0.1 * this.animationSpeed, h = this.animationIntensity; switch (this.animationType) { case "wave": return { x: Math.sin(a + t * 0.3) * h, y: Math.cos(a + i * 0.3) * h, scale: 1 + Math.sin(a + (t + i) * 0.2) * 0.3, rotation: 0 }; case "pulse": const n = a + (t + i) * 0.1; return { x: 0, y: 0, scale: 1 + (Math.sin(n) * 0.5 + 0.5) * 0.8, rotation: 0 }; case "drift": const r = i * e + t, d = this.driftOffsets[r] || { x: 0, y: 0, vx: 0, vy: 0 }; return { x: d.x, y: d.y, scale: 1, rotation: 0 }; case "ripple": const u = this.mousePos.x / this.ledSpacing, o = this.mousePos.y / this.ledSpacing, v = Math.hypot(t - u, i - o); return { x: 0, y: 0, scale: 1 + Math.sin(a * 2 - v * 0.5) * h * 0.3, rotation: 0 }; case "rotation": const l = a * 0.5, g = h * 0.5; return { x: Math.cos(l + t * 0.1) * g, y: Math.sin(l + i * 0.1) * g, scale: 1, rotation: l + (t + i) * 0.1 }; default: return { x: 0, y: 0, scale: 1, rotation: 0 }; } } updateImageData() { if (!this.element || !this.tempCanvas || !this.tempCtx || !this.canvas) return; const t = this.canvas.width, i = this.canvas.height; if (t <= 0 || i <= 0) { console.warn("Canvas dimensions are invalid, skipping image data update"); return; } const e = Math.floor(t / this.ledSpacing), s = Math.floor(i / this.ledSpacing); if (e <= 0 || s <= 0) { console.warn("Grid dimensions are invalid, skipping image data update"); return; } this.tempCanvas.width = e, this.tempCanvas.height = s, this.tempCtx.drawImage(this.element, 0, 0, e, s), this.imageData = this.tempCtx.getImageData(0, 0, e, s); } attach(t) { if (!(t instanceof HTMLImageElement)) { console.error("DotMatrix effect can only be applied to img elements"); return; } this.element = t; const i = () => { const e = document.createElement("canvas"), s = t.getBoundingClientRect(); let a = s.width, h = s.height; (a <= 0 || h <= 0) && (a = t.naturalWidth || 300, h = t.naturalHeight || 200, console.warn("Using fallback dimensions for canvas:", a, h)), e.width = a, e.height = h, e.style.position = "absolute", e.style.top = "0", e.style.left = "0", e.style.width = "100%", e.style.height = "100%", e.style.opacity = "0", e.style.transition = "opacity 0.3s ease", e.style.pointerEvents = "none", e.style.backgroundColor = "transparent", e.dataset.dotMatrixId = this.id, this.canvas = e, this.ctx = e.getContext("2d", { alpha: !0, willReadFrequently: !1 }), this.ctx && (this.ctx.globalCompositeOperation = "source-over", this.ctx.imageSmoothingEnabled = !1), this.tempCanvas = document.createElement("canvas"), this.tempCtx = this.tempCanvas.getContext("2d", { alpha: !0 }), a > 0 && h > 0 && this.updateImageData(); let n = this.element.parentElement; (!n || !n.classList.contains("dot-matrix-wrapper")) && (n = document.createElement("div"), n.className = "dot-matrix-wrapper", n.style.position = "relative", n.style.display = "inline-block", this.element.replaceWith(n), n.appendChild(this.element)), n.appendChild(e), n.addEventListener("mouseenter", this.onMouseEnter), n.addEventListener("mouseleave", this.onMouseLeave), n.addEventListener("mousemove", this.onMouseMove); const m = new ResizeObserver(() => { if (this.canvas && this.element) { const r = this.element.getBoundingClientRect(); let d = r.width, u = r.height; (d <= 0 || u <= 0) && (d = this.element.naturalWidth || 300, u = this.element.naturalHeight || 200), this.canvas.width = d, this.canvas.height = u, d > 0 && u > 0 && this.updateImageData(); } }); this.element && m.observe(this.element); }; t.complete ? i() : t.onload = i; } detach() { if (!this.element) return; const t = this.element.parentElement; t && (t.removeEventListener("mouseenter", this.onMouseEnter), t.removeEventListener("mouseleave", this.onMouseLeave), t.removeEventListener("mousemove", this.onMouseMove)), this.animationFrame && (cancelAnimationFrame(this.animationFrame), this.animationFrame = null), this.canvas && (this.canvas.remove(), this.canvas = null, this.ctx = null), this.tempCanvas = null, this.tempCtx = null, this.imageData = null, this.element = null; } destroy() { this.detach(); } // Public setters setRadius(t) { this.radius = Math.max(10, Math.min(500, t)); } setLedSize(t) { this.ledSize = Math.max(2, Math.min(20, t)), this.buildLedPath(); } setLedSpacing(t) { this.ledSpacing = Math.max(this.ledSize + 1, Math.min(50, t)), this.updateImageData(); } setGlow(t) { this.glow = t; } setSoftEdge(t) { this.softEdge = Math.max(0, Math.min(100, t)