hover-effects-ts
Version:
A collection of beautiful hover effects for images using canvas
893 lines • 52.4 kB
JavaScript
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)