feat: harden portfolio and add SEO foundations
This commit is contained in:
1 parent
b079afdd73
commit
62bcfbd8fa
121 files changed
+7769
-10469
No files matched your search
@@ -1,114 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
interface SectionDividerProps {
|
||||
variant?: "gradient" | "pulse" | "waveform";
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const PULSE_DOTS = ["dot-1", "dot-2", "dot-3", "dot-4", "dot-5"] as const;
|
||||
|
||||
export default function SectionDivider({
|
||||
variant = "gradient",
|
||||
className = "",
|
||||
}: SectionDividerProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (variant !== "waveform") return;
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
let animId: number;
|
||||
|
||||
const resize = () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = 60;
|
||||
};
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
|
||||
const render = () => {
|
||||
animId = requestAnimationFrame(render);
|
||||
const { width, height } = canvas;
|
||||
const t = performance.now() * 0.001;
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Draw multiple waveforms
|
||||
for (let wave = 0; wave < 3; wave++) {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = `rgba(204, 255, 0, ${0.1 - wave * 0.03})`;
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
for (let x = 0; x < width; x++) {
|
||||
const frequency = 0.005 + wave * 0.002;
|
||||
const amplitude = 8 + wave * 4;
|
||||
const speed = t * (0.5 + wave * 0.2);
|
||||
const y =
|
||||
height / 2 +
|
||||
Math.sin(x * frequency + speed) * amplitude +
|
||||
Math.sin(x * frequency * 2.3 + speed * 1.5) * (amplitude * 0.4);
|
||||
|
||||
if (x === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
}
|
||||
};
|
||||
|
||||
animId = requestAnimationFrame(render);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animId);
|
||||
window.removeEventListener("resize", resize);
|
||||
};
|
||||
}, [variant]);
|
||||
const rootClassName =
|
||||
`section-divider section-divider-${variant} ${className}`.trim();
|
||||
|
||||
if (variant === "gradient") {
|
||||
return (
|
||||
<div className={`relative h-px w-full overflow-hidden ${className}`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-[var(--slash)] to-transparent opacity-30" />
|
||||
<div
|
||||
className="absolute inset-0 bg-gradient-to-r from-transparent via-[var(--slash)] to-transparent opacity-60"
|
||||
style={{
|
||||
animation: "shimmerDivider 3s ease-in-out infinite",
|
||||
}}
|
||||
/>
|
||||
<div aria-hidden="true" className={rootClassName}>
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === "pulse") {
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-center gap-2 py-8 ${className}`}
|
||||
>
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="h-1 w-1 rounded-full bg-[var(--slash)]"
|
||||
style={{
|
||||
animation: `pulseDot 2s ease-in-out ${i * 0.15}s infinite`,
|
||||
}}
|
||||
/>
|
||||
<div aria-hidden="true" className={rootClassName}>
|
||||
{PULSE_DOTS.map((dot) => (
|
||||
<span key={dot} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// waveform
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="w-full opacity-60"
|
||||
style={{ height: 60 }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div aria-hidden="true" className={rootClassName}>
|
||||
<svg viewBox="0 0 1200 60" preserveAspectRatio="none" focusable="false">
|
||||
<title>Divisor decorativo</title>
|
||||
<path d="M0 30 C100 8 180 52 280 30 S460 8 560 30 740 52 840 30 1020 8 1200 30" />
|
||||
<path d="M0 30 C120 48 220 12 340 30 S560 48 680 30 900 12 1020 30 1140 48 1200 30" />
|
||||
<path d="M0 30 C80 20 160 40 240 30 S400 20 480 30 640 40 720 30 880 20 960 30 1120 40 1200 30" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user