Files
portifolio-evandrodevbr/app/components/cinematic/PageIntro.tsx
T
Evandro Fonseca Junior b791b7de9e feat: control room hero, SEO/AEO optimization and design system fixes
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).
2026-08-27 10:55:21 -03:00

108 lines
4.3 KiB
TypeScript

"use client";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
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-[68%] 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>
{/* Wordmark decorativo do overlay: NÃO é heading — o h1 real vive na página */}
<motion.p
className="mt-sp-4 font-display text-[clamp(2rem,12vw,8rem)] font-extrabold leading-[0.82] tracking-[-0.04em] lg: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.p>
<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-[8px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.32)] lg:bottom-10 lg:left-12 lg:right-12 lg:text-[9px] lg:text-[rgba(244,242,237,.45)]">
<span>Engenharia de software</span>
<span>Garuva · Joinville · Curitiba</span>
</div>
</motion.div>
)}
</AnimatePresence>,
document.body,
);
}