continue re-design
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
"use client";
|
||||
|
||||
import { animate } from "motion";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
import initCanvas from "@/utils/init-canvas";
|
||||
|
||||
export default function EndpointsCrawl({
|
||||
active,
|
||||
alwaysHeat = false,
|
||||
triggerOnHover = false,
|
||||
size = 20,
|
||||
}: {
|
||||
active?: boolean;
|
||||
alwaysHeat?: boolean;
|
||||
triggerOnHover?: boolean;
|
||||
size?: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const fnRefs = useRef<{
|
||||
activate: () => void;
|
||||
deactivate: () => void;
|
||||
}>({ activate: () => {}, deactivate: () => {} });
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = initCanvas(canvas);
|
||||
|
||||
let isRunning = false;
|
||||
let isActive = false;
|
||||
|
||||
let activeGroup = 0;
|
||||
const rowAlphas = [0.2, 0.4, 1, 0.04];
|
||||
|
||||
const grid = [
|
||||
[24],
|
||||
[16, 18, 30, 32],
|
||||
[8, 12, 36, 40],
|
||||
[0, 3, 6, 21, 27, 42, 45, 48],
|
||||
];
|
||||
|
||||
const scaler = size / 20;
|
||||
|
||||
const render = () => {
|
||||
ctx.fillStyle = "#FF4C00";
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (const group of grid.slice(0, 4)) {
|
||||
const groupIndex = grid.indexOf(group);
|
||||
ctx.globalAlpha = rowAlphas[groupIndex];
|
||||
|
||||
for (const index of group) {
|
||||
ctx.fillRect(
|
||||
(3 + (index % 7) * 2) * scaler,
|
||||
(3 + Math.floor(index / 7) * 2) * scaler,
|
||||
2 * scaler,
|
||||
2 * scaler,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isRunning) {
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
};
|
||||
|
||||
const timeouts: number[] = [];
|
||||
|
||||
let runCount = 0;
|
||||
|
||||
const cycle = () => {
|
||||
isRunning = true;
|
||||
activeGroup = (activeGroup + 1) % 5;
|
||||
|
||||
rowAlphas.forEach((alpha, index) => {
|
||||
let targetAlpha = alpha;
|
||||
|
||||
if (index === activeGroup) targetAlpha = 1;
|
||||
else if (index === (activeGroup + 1) % 4) targetAlpha = 0.12;
|
||||
else if (index === (activeGroup + 2) % 4) targetAlpha = 0.2;
|
||||
else if (index === (activeGroup + 3) % 4) targetAlpha = 0.4;
|
||||
|
||||
animate(alpha, targetAlpha, {
|
||||
duration: 0.05,
|
||||
onUpdate: (value) => {
|
||||
rowAlphas[index] = value;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
timeouts.forEach((timeout) => {
|
||||
window.clearTimeout(timeout);
|
||||
});
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
isRunning = false;
|
||||
}, 300),
|
||||
);
|
||||
|
||||
if (activeGroup === 3) runCount += 1;
|
||||
|
||||
if ((runCount === 2 || !isActive) && activeGroup === 2) return;
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
cycle();
|
||||
}, 50),
|
||||
);
|
||||
};
|
||||
|
||||
fnRefs.current = {
|
||||
activate: () => {
|
||||
if (isActive) return;
|
||||
|
||||
isActive = true;
|
||||
|
||||
runCount = 0;
|
||||
|
||||
cycle();
|
||||
render();
|
||||
},
|
||||
deactivate: () => {
|
||||
if (!isActive) return;
|
||||
|
||||
isActive = false;
|
||||
},
|
||||
};
|
||||
|
||||
render();
|
||||
canvas.addEventListener("resize", render);
|
||||
|
||||
if (triggerOnHover) {
|
||||
const group = canvasRef.current!.closest(".group");
|
||||
|
||||
if (group) {
|
||||
group.addEventListener("mouseenter", fnRefs.current.activate);
|
||||
group.addEventListener("mouseleave", fnRefs.current.deactivate);
|
||||
|
||||
return () => {
|
||||
group.removeEventListener("mouseenter", fnRefs.current.activate);
|
||||
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [triggerOnHover, size]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerOnHover) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && active) {
|
||||
fnRefs.current.activate();
|
||||
} else {
|
||||
fnRefs.current.deactivate();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 },
|
||||
);
|
||||
|
||||
observer.observe(canvasRef.current!);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [active, triggerOnHover]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
className={cn(
|
||||
alwaysHeat
|
||||
? ""
|
||||
: [
|
||||
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
|
||||
!active && "grayscale",
|
||||
],
|
||||
)}
|
||||
ref={canvasRef}
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
"use client";
|
||||
|
||||
import { animate } from "motion";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
import initCanvas from "@/utils/init-canvas";
|
||||
|
||||
export default function EndpointsExtract({
|
||||
active,
|
||||
disabledCells,
|
||||
alwaysHeat = false,
|
||||
triggerOnHover = false,
|
||||
size = 20,
|
||||
}: {
|
||||
active?: boolean;
|
||||
disabledCells?: number[];
|
||||
alwaysHeat?: boolean;
|
||||
triggerOnHover?: boolean;
|
||||
size?: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const fnRefs = useRef<{
|
||||
activate: () => void;
|
||||
deactivate: () => void;
|
||||
}>({ activate: () => {}, deactivate: () => {} });
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = initCanvas(canvas);
|
||||
|
||||
let isRunning = false;
|
||||
let isActive = false;
|
||||
|
||||
let activeCol = 0;
|
||||
const colAlphas = [1, 0.4, 0.2, 0.12];
|
||||
|
||||
const scaler = size / 20;
|
||||
|
||||
const render = () => {
|
||||
ctx.fillStyle = "#FF4C00";
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Draw Extract pattern - represents structured data extraction
|
||||
// Draw columns to represent data fields
|
||||
for (let col = 0; col < 4; col++) {
|
||||
ctx.globalAlpha = colAlphas[col];
|
||||
|
||||
// Draw vertical bars of different heights to represent extracted data
|
||||
const heights = [3, 2, 3, 1];
|
||||
const startY = [1, 2, 1, 3];
|
||||
|
||||
for (let row = 0; row < heights[col]; row++) {
|
||||
ctx.fillRect(
|
||||
(3 + col * 4) * scaler,
|
||||
(3 + startY[col] * 2 + row * 4) * scaler,
|
||||
2 * scaler,
|
||||
2 * scaler,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isRunning) {
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
};
|
||||
|
||||
const timeouts: number[] = [];
|
||||
|
||||
let runCount = 0;
|
||||
|
||||
const cycle = () => {
|
||||
isRunning = true;
|
||||
activeCol = (activeCol + 1) % 4;
|
||||
|
||||
colAlphas.forEach((alpha, index) => {
|
||||
let targetAlpha = alpha;
|
||||
|
||||
if (index === activeCol) targetAlpha = 1;
|
||||
else if (index === (activeCol + 1) % 4) targetAlpha = 0.12;
|
||||
else if (index === (activeCol + 2) % 4) targetAlpha = 0.2;
|
||||
else if (index === (activeCol + 3) % 4) targetAlpha = 0.4;
|
||||
|
||||
animate(alpha, targetAlpha, {
|
||||
duration: 0.05,
|
||||
onUpdate: (value) => {
|
||||
colAlphas[index] = value;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
timeouts.forEach((timeout) => {
|
||||
window.clearTimeout(timeout);
|
||||
});
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
isRunning = false;
|
||||
}, 400),
|
||||
);
|
||||
|
||||
if (activeCol === 3) runCount += 1;
|
||||
|
||||
if ((runCount === 2 || !isActive) && activeCol === 0) return;
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
cycle();
|
||||
}, 50),
|
||||
);
|
||||
};
|
||||
|
||||
fnRefs.current = {
|
||||
activate: () => {
|
||||
if (isActive) return;
|
||||
|
||||
isActive = true;
|
||||
|
||||
runCount = 0;
|
||||
cycle();
|
||||
render();
|
||||
},
|
||||
deactivate: () => {
|
||||
if (!isActive) return;
|
||||
|
||||
isActive = false;
|
||||
},
|
||||
};
|
||||
|
||||
render();
|
||||
canvas.addEventListener("resize", render);
|
||||
|
||||
if (triggerOnHover) {
|
||||
const group = canvasRef.current!.closest(".group");
|
||||
|
||||
if (group) {
|
||||
group.addEventListener("mouseenter", fnRefs.current.activate);
|
||||
group.addEventListener("mouseleave", fnRefs.current.deactivate);
|
||||
|
||||
return () => {
|
||||
group.removeEventListener("mouseenter", fnRefs.current.activate);
|
||||
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [disabledCells, size, triggerOnHover]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerOnHover) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && active) {
|
||||
fnRefs.current.activate();
|
||||
} else {
|
||||
fnRefs.current.deactivate();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 },
|
||||
);
|
||||
|
||||
observer.observe(canvasRef.current!);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [active, triggerOnHover]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
className={cn(
|
||||
alwaysHeat
|
||||
? ""
|
||||
: [
|
||||
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
|
||||
!active && "grayscale",
|
||||
],
|
||||
)}
|
||||
ref={canvasRef}
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { ComponentProps } from "react";
|
||||
|
||||
import EndpointsScrape from "@/components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape";
|
||||
|
||||
export default function EndpointsMap(
|
||||
props: ComponentProps<typeof EndpointsScrape>,
|
||||
) {
|
||||
return <EndpointsScrape {...props} disabledCells={[1, 2, 3, 7, 9, 12, 15]} />;
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
"use client";
|
||||
|
||||
import { animate } from "motion";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
import initCanvas from "@/utils/init-canvas";
|
||||
|
||||
export default function EndpointsScrape({
|
||||
active,
|
||||
disabledCells,
|
||||
alwaysHeat = false,
|
||||
triggerOnHover = false,
|
||||
size = 20,
|
||||
}: {
|
||||
active?: boolean;
|
||||
disabledCells?: number[];
|
||||
alwaysHeat?: boolean;
|
||||
triggerOnHover?: boolean;
|
||||
size?: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const fnRefs = useRef<{
|
||||
activate: () => void;
|
||||
deactivate: () => void;
|
||||
}>({ activate: () => {}, deactivate: () => {} });
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = initCanvas(canvas);
|
||||
|
||||
let isRunning = false;
|
||||
let isActive = false;
|
||||
|
||||
let activeRow = 2;
|
||||
const rowAlphas = [0.2, 0.4, 1, 0.12];
|
||||
|
||||
const scaler = size / 20;
|
||||
|
||||
const render = () => {
|
||||
ctx.fillStyle = "#FF4C00";
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
if (disabledCells && disabledCells.includes(i)) continue;
|
||||
|
||||
ctx.globalAlpha = rowAlphas[Math.floor(i / 4)];
|
||||
|
||||
ctx.fillRect(
|
||||
(3 + (i % 4) * 4) * scaler,
|
||||
(3 + Math.floor(i / 4) * 4) * scaler,
|
||||
2 * scaler,
|
||||
2 * scaler,
|
||||
);
|
||||
}
|
||||
|
||||
if (isRunning) {
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
};
|
||||
|
||||
const timeouts: number[] = [];
|
||||
|
||||
let runCount = 0;
|
||||
|
||||
const cycle = () => {
|
||||
isRunning = true;
|
||||
activeRow = (activeRow + 1) % 5;
|
||||
|
||||
rowAlphas.forEach((alpha, index) => {
|
||||
let targetAlpha = alpha;
|
||||
|
||||
if (index === activeRow) targetAlpha = 1;
|
||||
else if (index === (activeRow + 1) % 4) targetAlpha = 0.12;
|
||||
else if (index === (activeRow + 2) % 4) targetAlpha = 0.2;
|
||||
else if (index === (activeRow + 3) % 4) targetAlpha = 0.4;
|
||||
|
||||
animate(alpha, targetAlpha, {
|
||||
duration: 0.05,
|
||||
onUpdate: (value) => {
|
||||
rowAlphas[index] = value;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
timeouts.forEach((timeout) => {
|
||||
window.clearTimeout(timeout);
|
||||
});
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
isRunning = false;
|
||||
}, 400),
|
||||
);
|
||||
|
||||
if (activeRow === 3) runCount += 1;
|
||||
|
||||
if ((runCount === 2 || !isActive) && activeRow === 2) return;
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
cycle();
|
||||
}, 50),
|
||||
);
|
||||
};
|
||||
|
||||
fnRefs.current = {
|
||||
activate: () => {
|
||||
if (isActive) return;
|
||||
|
||||
isActive = true;
|
||||
|
||||
runCount = 0;
|
||||
cycle();
|
||||
render();
|
||||
},
|
||||
deactivate: () => {
|
||||
if (!isActive) return;
|
||||
|
||||
isActive = false;
|
||||
},
|
||||
};
|
||||
|
||||
render();
|
||||
canvas.addEventListener("resize", render);
|
||||
|
||||
if (triggerOnHover) {
|
||||
const group = canvasRef.current!.closest(".group");
|
||||
|
||||
if (group) {
|
||||
group.addEventListener("mouseenter", fnRefs.current.activate);
|
||||
group.addEventListener("mouseleave", fnRefs.current.deactivate);
|
||||
|
||||
return () => {
|
||||
group.removeEventListener("mouseenter", fnRefs.current.activate);
|
||||
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [disabledCells, size, triggerOnHover]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerOnHover) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && active) {
|
||||
fnRefs.current.activate();
|
||||
} else {
|
||||
fnRefs.current.deactivate();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 },
|
||||
);
|
||||
|
||||
observer.observe(canvasRef.current!);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [active, triggerOnHover]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
className={cn(
|
||||
alwaysHeat
|
||||
? ""
|
||||
: [
|
||||
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
|
||||
!active && "grayscale",
|
||||
],
|
||||
)}
|
||||
ref={canvasRef}
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/* eslint-disable @stylistic/array-element-newline */
|
||||
"use client";
|
||||
|
||||
import initCanvas from "@/utils/init-canvas";
|
||||
import { animate } from "motion";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function EndpointsSearch({
|
||||
alwaysHeat,
|
||||
size = 20,
|
||||
}: {
|
||||
alwaysHeat?: boolean;
|
||||
size?: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = initCanvas(canvas);
|
||||
|
||||
let isRunning = false;
|
||||
let isActive = false;
|
||||
|
||||
let diff = 0;
|
||||
const defaultRowAlphas = [
|
||||
0, 0.2, 0.4, 0, 0.4, 1, 0.4, 0.2, 0.2, 0.4, 1, 0.4, 0, 0.4, 0.2, 0,
|
||||
];
|
||||
|
||||
const differs = Array.from({ length: 16 }, () => 0.2 + Math.random() * 0.2);
|
||||
|
||||
differs[5] = 0.6;
|
||||
differs[6] = 0.6;
|
||||
differs[9] = 0.6;
|
||||
differs[10] = 0.6;
|
||||
|
||||
const scaler = size / 20;
|
||||
|
||||
const render = () => {
|
||||
ctx.fillStyle = "#FF4C00";
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
if ([0, 3, 12, 15].includes(i)) continue;
|
||||
|
||||
const maxAlpha = [5, 6, 9, 10].includes(i) ? 1 : 0.4;
|
||||
|
||||
const alpha = defaultRowAlphas[i] + diff * differs[i];
|
||||
ctx.globalAlpha = Math.min(
|
||||
Math.min(alpha, maxAlpha) - Math.max(alpha - maxAlpha, 0),
|
||||
1,
|
||||
);
|
||||
|
||||
ctx.fillRect(
|
||||
(3 + (i % 4) * 4) * scaler,
|
||||
(3 + Math.floor(i / 4) * 4) * scaler,
|
||||
2 * scaler,
|
||||
2 * scaler,
|
||||
);
|
||||
}
|
||||
|
||||
if (isRunning) {
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
};
|
||||
|
||||
const timeouts: number[] = [];
|
||||
|
||||
let runCount = 0;
|
||||
|
||||
const duration = 300;
|
||||
|
||||
const cycle = () => {
|
||||
isRunning = true;
|
||||
|
||||
animate(diff, 1, {
|
||||
duration: duration / 1000,
|
||||
onUpdate: (value) => {
|
||||
diff = value < 0.5 ? value * 2 : 1 - (value - 0.5) * 2;
|
||||
},
|
||||
});
|
||||
|
||||
timeouts.forEach((timeout) => {
|
||||
window.clearTimeout(timeout);
|
||||
});
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(
|
||||
() => {
|
||||
isRunning = false;
|
||||
},
|
||||
Math.max(duration, 300),
|
||||
),
|
||||
);
|
||||
|
||||
runCount += 1;
|
||||
|
||||
if (runCount === 3 || !isActive) return;
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
cycle();
|
||||
}, duration),
|
||||
);
|
||||
};
|
||||
|
||||
const activate = () => {
|
||||
if (isActive) return;
|
||||
|
||||
isActive = true;
|
||||
|
||||
runCount = 0;
|
||||
cycle();
|
||||
render();
|
||||
};
|
||||
|
||||
const deactivate = () => {
|
||||
if (!isActive) return;
|
||||
|
||||
isActive = false;
|
||||
};
|
||||
|
||||
render();
|
||||
canvas.addEventListener("resize", render);
|
||||
|
||||
const group = canvasRef.current!.closest(".group");
|
||||
|
||||
if (group) {
|
||||
group.addEventListener("mouseenter", activate);
|
||||
group.addEventListener("mouseleave", deactivate);
|
||||
|
||||
return () => {
|
||||
group.removeEventListener("mouseenter", activate);
|
||||
group.removeEventListener("mouseleave", deactivate);
|
||||
};
|
||||
}
|
||||
}, [size]);
|
||||
|
||||
return <canvas ref={canvasRef} style={{ width: size, height: size }} />;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/* eslint-disable @stylistic/array-element-newline */
|
||||
"use client";
|
||||
|
||||
import { animate } from "motion";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import initCanvas from "@/utils/init-canvas";
|
||||
|
||||
export default function EndpointsExtract({ size = 20 }: { size?: number }) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = initCanvas(canvas);
|
||||
|
||||
let isRunning = false;
|
||||
let isActive = false;
|
||||
|
||||
let diff = 0;
|
||||
const defaultRowAlphas = [
|
||||
0.4, 0.04, 0.2, 0.4, 0.2, 0, 0, 0.04, 0.04, 0, 0, 0.2, 0.4, 0.2, 0.04,
|
||||
0.4,
|
||||
];
|
||||
|
||||
const differs = Array.from({ length: 16 }, () => 0.2 + Math.random() * 0.2);
|
||||
|
||||
const scaler = size / 20;
|
||||
|
||||
const render = () => {
|
||||
ctx.fillStyle = "#FF4C00";
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
if ([5, 6, 9, 10].includes(i)) continue;
|
||||
|
||||
ctx.globalAlpha = defaultRowAlphas[i] + diff * differs[i];
|
||||
ctx.globalAlpha =
|
||||
Math.min(ctx.globalAlpha, 0.4) - Math.max(ctx.globalAlpha - 0.4, 0);
|
||||
|
||||
ctx.fillRect(
|
||||
(3 + (i % 4) * 4) * scaler,
|
||||
(3 + Math.floor(i / 4) * 4) * scaler,
|
||||
2 * scaler,
|
||||
2 * scaler,
|
||||
);
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillRect(7, 7, 6, 2);
|
||||
ctx.globalAlpha = 0.4;
|
||||
ctx.fillRect(7, 11, 2 + diff * 4, 2);
|
||||
|
||||
if (isRunning) {
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
};
|
||||
|
||||
const timeouts: number[] = [];
|
||||
|
||||
let runCount = 0;
|
||||
|
||||
const duration = 300;
|
||||
|
||||
const cycle = () => {
|
||||
isRunning = true;
|
||||
|
||||
animate(diff, 1, {
|
||||
duration: duration / 1000,
|
||||
onUpdate: (value) => {
|
||||
diff = value < 0.5 ? value * 2 : 1 - (value - 0.5) * 2;
|
||||
},
|
||||
});
|
||||
|
||||
timeouts.forEach((timeout) => {
|
||||
window.clearTimeout(timeout);
|
||||
});
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(
|
||||
() => {
|
||||
isRunning = false;
|
||||
},
|
||||
Math.max(duration, 300),
|
||||
),
|
||||
);
|
||||
|
||||
runCount += 1;
|
||||
|
||||
if (runCount === 3 || !isActive) return;
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
cycle();
|
||||
}, duration),
|
||||
);
|
||||
};
|
||||
|
||||
const activate = () => {
|
||||
if (isActive) return;
|
||||
|
||||
isActive = true;
|
||||
|
||||
runCount = 0;
|
||||
cycle();
|
||||
render();
|
||||
};
|
||||
|
||||
const deactivate = () => {
|
||||
if (!isActive) return;
|
||||
|
||||
isActive = false;
|
||||
};
|
||||
|
||||
render();
|
||||
canvas.addEventListener("resize", render);
|
||||
|
||||
const group = canvasRef.current!.closest(".group");
|
||||
|
||||
if (group) {
|
||||
group.addEventListener("mouseenter", activate);
|
||||
group.addEventListener("mouseleave", deactivate);
|
||||
|
||||
return () => {
|
||||
group.removeEventListener("mouseenter", activate);
|
||||
group.removeEventListener("mouseleave", deactivate);
|
||||
};
|
||||
}
|
||||
}, [size]);
|
||||
|
||||
return <canvas ref={canvasRef} style={{ width: size, height: size }} />;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
"use client";
|
||||
|
||||
import { animate } from "motion";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
import initCanvas from "@/utils/init-canvas";
|
||||
|
||||
export default function EndpointsMcp({
|
||||
active,
|
||||
alwaysHeat = false,
|
||||
triggerOnHover = false,
|
||||
size = 20,
|
||||
}: {
|
||||
active?: boolean;
|
||||
alwaysHeat?: boolean;
|
||||
triggerOnHover?: boolean;
|
||||
size?: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const fnRefs = useRef<{
|
||||
activate: () => void;
|
||||
deactivate: () => void;
|
||||
}>({ activate: () => {}, deactivate: () => {} });
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = initCanvas(canvas);
|
||||
|
||||
let isRunning = false;
|
||||
let isActive = false;
|
||||
|
||||
let activeIndex = 5;
|
||||
const rowAlphas = [0.12, 0.2, 0.4, 0.4, 1, 1, 1, 0.4, 0.2];
|
||||
|
||||
const scaler = size / 20;
|
||||
|
||||
const render = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = "#FF4C00";
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
ctx.globalAlpha = rowAlphas[i];
|
||||
|
||||
ctx.fillRect(
|
||||
(5 + (i % 3) * 4) * scaler,
|
||||
(5 + Math.floor(i / 3) * 4) * scaler,
|
||||
2 * scaler,
|
||||
2 * scaler,
|
||||
);
|
||||
}
|
||||
|
||||
if (isRunning) {
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
};
|
||||
|
||||
const timeouts: number[] = [];
|
||||
|
||||
let runCount = 0;
|
||||
|
||||
const cycle = () => {
|
||||
isRunning = true;
|
||||
activeIndex = (activeIndex + 1) % 9;
|
||||
|
||||
rowAlphas.forEach((alpha, index) => {
|
||||
let targetAlpha = alpha;
|
||||
|
||||
if (index === activeIndex) targetAlpha = 1;
|
||||
else if (index === (activeIndex - 1 + 9) % 9) targetAlpha = 1;
|
||||
else if (index === (activeIndex - 2 + 9) % 9) targetAlpha = 1;
|
||||
else if (index === (activeIndex - 3 + 9) % 9) targetAlpha = 0.4;
|
||||
else if (index === (activeIndex - 4 + 9) % 9) targetAlpha = 0.2;
|
||||
else if (index === (activeIndex - 5 + 9) % 9) targetAlpha = 0.2;
|
||||
else if (index === (activeIndex - 6 + 9) % 9) targetAlpha = 0.12;
|
||||
else if (index === (activeIndex - 7 + 9) % 9) targetAlpha = 0.12;
|
||||
else if (index === (activeIndex - 8 + 9) % 9) targetAlpha = 0.4;
|
||||
|
||||
animate(alpha, targetAlpha, {
|
||||
duration: 30 / 1000,
|
||||
onUpdate: (value) => {
|
||||
rowAlphas[index] = value;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
timeouts.forEach((timeout) => {
|
||||
window.clearTimeout(timeout);
|
||||
});
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
isRunning = false;
|
||||
}, 300),
|
||||
);
|
||||
|
||||
if (activeIndex === 7) runCount += 1;
|
||||
|
||||
if ((runCount === 2 || !isActive) && activeIndex === 6) return;
|
||||
|
||||
timeouts.push(
|
||||
window.setTimeout(() => {
|
||||
cycle();
|
||||
}, 30),
|
||||
);
|
||||
};
|
||||
|
||||
fnRefs.current = {
|
||||
activate: () => {
|
||||
if (isActive) return;
|
||||
|
||||
isActive = true;
|
||||
|
||||
runCount = 0;
|
||||
|
||||
cycle();
|
||||
render();
|
||||
},
|
||||
deactivate: () => {
|
||||
if (!isActive) return;
|
||||
|
||||
isActive = false;
|
||||
},
|
||||
};
|
||||
|
||||
render();
|
||||
canvas.addEventListener("resize", render);
|
||||
|
||||
if (triggerOnHover) {
|
||||
const group = canvasRef.current!.closest(".group");
|
||||
|
||||
if (group) {
|
||||
group.addEventListener("mouseenter", fnRefs.current.activate);
|
||||
group.addEventListener("mouseleave", fnRefs.current.deactivate);
|
||||
|
||||
return () => {
|
||||
group.removeEventListener("mouseenter", fnRefs.current.activate);
|
||||
group.removeEventListener("mouseleave", fnRefs.current.deactivate);
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [size, triggerOnHover]);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerOnHover) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && active) {
|
||||
fnRefs.current.activate();
|
||||
} else {
|
||||
fnRefs.current.deactivate();
|
||||
}
|
||||
},
|
||||
{ threshold: 0.5 },
|
||||
);
|
||||
|
||||
observer.observe(canvasRef.current!);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [active, triggerOnHover]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
className={cn(
|
||||
alwaysHeat
|
||||
? ""
|
||||
: [
|
||||
"[&.grayscale]:opacity-60 transition-[filter,opacity]",
|
||||
!active && "grayscale",
|
||||
],
|
||||
)}
|
||||
ref={canvasRef}
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user