Hero: replace particle field with cobe globe (Capricorn view, multi-axis drag, reduced-motion safe), telemetry HUD with pointer parallax, particle text build effect, Space Grotesk display font. SEO: short titles with template, PT-BR accents, twitter cards on dynamic cases, escaped JSON-LD via lib/jsonld.ts, CSP report-only, 25 service FAQs answer-first. Design: fix TW4 layered reset killing spacing utilities, literal @theme tokens, mobile hero overflow, header overlap, empty case cards. Remove framer-motion (unified on motion/react), header version badge, phone number from hero CTA. Production build verified (24 static pages).
115 lines
2.9 KiB
TypeScript
115 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
interface SectionDividerProps {
|
|
variant?: "gradient" | "pulse" | "waveform";
|
|
className?: string;
|
|
}
|
|
|
|
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]);
|
|
|
|
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>
|
|
);
|
|
}
|
|
|
|
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>
|
|
);
|
|
}
|
|
|
|
// waveform
|
|
return (
|
|
<div className={`relative ${className}`}>
|
|
<canvas
|
|
ref={canvasRef}
|
|
className="w-full opacity-60"
|
|
style={{ height: 60 }}
|
|
aria-hidden="true"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|