Adds dynamic case study and curriculo routes, project health monitoring for live demo status, and strengthens local SEO (structured data, service-area targeting for Garuva/Joinville/Curitiba, a dedicated "criação de sites" service page) so search results better match the professional title (Engenheiro da Computação) and experience (5 anos). Co-Authored-By: Claude Sonnet 5 <[email protected]>
106 lines
4.0 KiB
TypeScript
106 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { createPortal } from "react-dom";
|
|
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
|
|
|
const EXIT_EASE: [number, number, number, number] = [0.76, 0, 0.24, 1];
|
|
|
|
export default function PageIntro() {
|
|
const [mounted, setMounted] = useState(false);
|
|
const [visible, setVisible] = useState(true);
|
|
const reduceMotion = useReducedMotion();
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
const timeout = window.setTimeout(
|
|
() => setVisible(false),
|
|
reduceMotion ? 150 : 1350,
|
|
);
|
|
|
|
return () => window.clearTimeout(timeout);
|
|
}, [reduceMotion]);
|
|
|
|
if (!mounted) return null;
|
|
|
|
return createPortal(
|
|
<AnimatePresence>
|
|
{visible && (
|
|
<motion.div
|
|
className="fixed inset-0 z-[99999] grid overflow-hidden bg-[var(--ink)] text-[#f4f2ed]"
|
|
initial={{ opacity: 1 }}
|
|
exit={
|
|
reduceMotion
|
|
? { opacity: 0, transition: { duration: 0.15 } }
|
|
: {
|
|
clipPath: "inset(0 0 100% 0)",
|
|
transition: { duration: 0.65, ease: EXIT_EASE },
|
|
}
|
|
}
|
|
aria-live="polite"
|
|
aria-label="Preparando o portfólio"
|
|
>
|
|
<div className="pointer-events-none absolute inset-0 opacity-30 [background-image:linear-gradient(rgba(255,255,255,.06)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,.06)_1px,transparent_1px)] [background-size:48px_48px]" />
|
|
<div className="pointer-events-none absolute inset-x-0 top-[42%] h-px bg-[var(--slash)] opacity-70" />
|
|
|
|
<div className="relative m-auto w-[min(88vw,680px)]">
|
|
<motion.p
|
|
className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--slash)]"
|
|
initial={reduceMotion ? false : { opacity: 0, y: 8 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.28, ease: [0.16, 1, 0.3, 1] }}
|
|
>
|
|
SINAL DE ENTRADA
|
|
</motion.p>
|
|
<motion.h1
|
|
className="mt-sp-4 font-display text-[clamp(3rem,12vw,8rem)] font-extrabold leading-[0.82] tracking-[-0.08em]"
|
|
initial={reduceMotion ? false : { opacity: 0, y: 28 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{
|
|
duration: 0.48,
|
|
delay: 0.06,
|
|
ease: [0.16, 1, 0.3, 1],
|
|
}}
|
|
>
|
|
EVANDRO<span className="text-[var(--slash)]">.</span>DEV
|
|
</motion.h1>
|
|
<motion.div
|
|
className="mt-sp-8 flex items-center gap-sp-4"
|
|
initial={reduceMotion ? false : { opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
transition={{ duration: 0.28, delay: 0.18 }}
|
|
>
|
|
<span className="relative block h-2 w-2" aria-hidden="true">
|
|
<span className="absolute inset-0 bg-[var(--slash)]" />
|
|
{!reduceMotion && (
|
|
<motion.span
|
|
className="absolute inset-0 bg-[var(--slash)]"
|
|
animate={{ scale: [1, 2.8], opacity: [0.8, 0] }}
|
|
transition={{
|
|
duration: 0.8,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
}}
|
|
/>
|
|
)}
|
|
</span>
|
|
<p className="font-mono text-[11px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.72)]">
|
|
Preparando o portfólio
|
|
</p>
|
|
<span
|
|
className="h-px flex-1 bg-[rgba(244,242,237,.22)]"
|
|
aria-hidden="true"
|
|
/>
|
|
</motion.div>
|
|
</div>
|
|
|
|
<div className="absolute bottom-8 left-6 right-6 flex justify-between font-mono text-[9px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.45)] lg:bottom-10 lg:left-12 lg:right-12">
|
|
<span>Engenharia de software</span>
|
|
<span>Garuva · Joinville · Curitiba</span>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>,
|
|
document.body,
|
|
);
|
|
}
|