CI / verify (push) Failing after 5m9s
Align the notebook browser with the physical LCD, refresh real project captures and navigation, simplify service content, and stabilize hero hydration.
508 lines
21 KiB
TypeScript
508 lines
21 KiB
TypeScript
"use client";
|
|
|
|
import { motion, type Variants } from "motion/react";
|
|
import dynamic from "next/dynamic";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { useReducedMotion } from "@/app/hooks/useReducedMotion";
|
|
import { shouldLoadHeroScene } from "@/app/lib/should-load-hero-scene";
|
|
import { CV_URL } from "@/lib/site-navigation";
|
|
import { buildWhatsAppUrl } from "@/lib/whatsapp";
|
|
import { ControlRoomGlobe } from "./cinematic/ControlRoomGlobe";
|
|
import { HeroStarfield } from "./cinematic/HeroStarfield";
|
|
import MagneticButton from "./cinematic/MagneticButton";
|
|
import { TelemetryStrip } from "./cinematic/TelemetryStrip";
|
|
|
|
const HeroScene = dynamic(() => import("./cinematic/HeroScene"), {
|
|
ssr: false,
|
|
});
|
|
|
|
const SOCIAL_LINKS = [
|
|
{
|
|
href: "https://github.com/evandrodevbr",
|
|
label: "GitHub",
|
|
icon: (
|
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
|
),
|
|
},
|
|
{
|
|
href: "https://www.linkedin.com/in/evandro-fonseca-junior-0969aa1b0/",
|
|
label: "LinkedIn",
|
|
icon: (
|
|
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
|
),
|
|
},
|
|
{
|
|
href: "mailto:[email protected]",
|
|
label: "Email",
|
|
icon: (
|
|
<>
|
|
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
|
|
<polyline points="22,6 12,13 2,6" />
|
|
</>
|
|
),
|
|
stroke: true,
|
|
},
|
|
];
|
|
|
|
const HERO_CONTENT = {
|
|
status: "Disponível para novas oportunidades",
|
|
chip: "Garuva · Joinville · Curitiba · Remoto",
|
|
eyebrow:
|
|
"Evandro Fonseca Junior | Engenheiro da Computação e Desenvolvedor de Software",
|
|
headline:
|
|
"Engenharia de software para sistemas que precisam operar de verdade.",
|
|
subtitle:
|
|
"Profissional disponível para oportunidades CLT e PJ em Garuva, Joinville, Norte de Santa Catarina e Curitiba; também atuo em consultoria para produtos, integrações e sistemas críticos.",
|
|
cta: "Conversar sobre uma oportunidade",
|
|
whatsappMessage:
|
|
"Olá! Quero conversar sobre uma oportunidade profissional em engenharia de software.",
|
|
sideLabel: "Contato profissional direto",
|
|
sideTitle: "Procurando um engenheiro da computação para o seu time?",
|
|
sideText:
|
|
"Vamos conversar sobre o contexto técnico, o desafio da vaga e como posso contribuir da arquitetura à entrega.",
|
|
points: [
|
|
"Engenharia full-stack com base em sistemas e integrações.",
|
|
"Next.js, TypeScript, Rust, IoT e arquitetura de software.",
|
|
"Disponível para times do Norte de SC, Curitiba e atuação remota.",
|
|
],
|
|
proof: [
|
|
{
|
|
label: "Operação",
|
|
value: "Fluxo pensado para discovery, build, deploy e sustentação.",
|
|
},
|
|
{
|
|
label: "Clareza",
|
|
value: "Menos ruído entre produto, engenharia e decisão técnica.",
|
|
},
|
|
{
|
|
label: "Resultado",
|
|
value: "Base pronta para operar, evoluir e receber próximo ciclo.",
|
|
},
|
|
],
|
|
} as const;
|
|
|
|
function getGreeting(message: string) {
|
|
const hour = new Date().getHours();
|
|
if (hour >= 8 && hour < 12) return `Bom dia! ${message}`;
|
|
if (hour >= 13 && hour < 18) return `Boa tarde! ${message}`;
|
|
return message;
|
|
}
|
|
|
|
export default function Hero() {
|
|
const heroRef = useRef<HTMLElement>(null);
|
|
const shouldReduceMotion = useReducedMotion();
|
|
const [shouldRenderHeroScene, setShouldRenderHeroScene] = useState(false);
|
|
const [isHeroVisible, setIsHeroVisible] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const reducedMotion = window.matchMedia(
|
|
"(prefers-reduced-motion: reduce)",
|
|
).matches;
|
|
const coarsePointer = window.matchMedia("(pointer: coarse)").matches;
|
|
|
|
if (
|
|
!shouldLoadHeroScene({
|
|
reducedMotion,
|
|
coarsePointer,
|
|
width: window.innerWidth,
|
|
})
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const timer = window.setTimeout(() => setShouldRenderHeroScene(true), 1500);
|
|
return () => window.clearTimeout(timer);
|
|
}, []);
|
|
useEffect(() => {
|
|
const hero = heroRef.current;
|
|
if (!hero || !("IntersectionObserver" in window)) return undefined;
|
|
|
|
const observer = new IntersectionObserver(
|
|
([entry]) => setIsHeroVisible(entry?.isIntersecting ?? false),
|
|
{ threshold: 0 },
|
|
);
|
|
observer.observe(hero);
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
const handleWhatsAppClick = () => {
|
|
window.open(
|
|
buildWhatsAppUrl(getGreeting(HERO_CONTENT.whatsappMessage)),
|
|
"_blank",
|
|
"noopener,noreferrer",
|
|
);
|
|
};
|
|
|
|
const heroContainer: Variants = {
|
|
hidden: shouldReduceMotion ? { opacity: 1 } : { opacity: 0 },
|
|
visible: {
|
|
opacity: 1,
|
|
transition: shouldReduceMotion
|
|
? { duration: 0 }
|
|
: { staggerChildren: 0.1, delayChildren: 0.3 },
|
|
},
|
|
};
|
|
|
|
const easeOut: [number, number, number, number] = [0.16, 1, 0.3, 1];
|
|
|
|
const heroItem: Variants = {
|
|
hidden: shouldReduceMotion
|
|
? { opacity: 1, y: 0, filter: "blur(0px)" }
|
|
: { opacity: 0, y: 40, filter: "blur(10px)" },
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
filter: "blur(0px)",
|
|
transition: shouldReduceMotion
|
|
? { duration: 0 }
|
|
: { duration: 1, ease: easeOut },
|
|
},
|
|
};
|
|
|
|
const heroItemSlow: Variants = {
|
|
hidden: shouldReduceMotion
|
|
? { opacity: 1, y: 0, filter: "blur(0px)" }
|
|
: { opacity: 0, y: 60, filter: "blur(12px)" },
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
filter: "blur(0px)",
|
|
transition: shouldReduceMotion
|
|
? { duration: 0 }
|
|
: { duration: 1.2, ease: easeOut },
|
|
},
|
|
};
|
|
|
|
return (
|
|
<section
|
|
ref={heroRef}
|
|
id="home"
|
|
className="relative flex min-h-[100svh] w-full items-center overflow-hidden bg-[var(--ink)] text-[#f4f2ed]"
|
|
>
|
|
{shouldRenderHeroScene && <HeroScene isVisible={isHeroVisible} />}
|
|
|
|
<div className="pointer-events-none absolute inset-0 z-[1] bg-[radial-gradient(ellipse_80%_60%_at_50%_40%,transparent_0%,rgba(10,10,10,0.4)_50%,var(--ink)_100%)]" />
|
|
<div className="pointer-events-none absolute inset-0 z-[1] bg-gradient-to-b from-transparent via-transparent to-[var(--ink)]" />
|
|
<div
|
|
className="pointer-events-none absolute inset-0 z-[2] opacity-[0.015]"
|
|
style={{
|
|
backgroundImage:
|
|
"repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,255,255,0.03) 2px, rgba(255,255,255,0.03) 4px)",
|
|
}}
|
|
/>
|
|
<div className="pointer-events-none absolute inset-0 z-[2] bg-[radial-gradient(circle_at_50%_50%,transparent_40%,rgba(10,10,10,0.6)_100%)]" />
|
|
|
|
{/* pt >= altura do header fixo (52px): conteúdo do hero nunca nasce sob a barra */}
|
|
<div className="relative z-10 mx-auto flex w-full max-w-[1240px] flex-col px-sp-6 pt-sp-16 pb-sp-16 lg:max-h-none lg:px-sp-16 lg:pt-sp-14 lg:pb-sp-12">
|
|
<motion.div
|
|
initial={shouldReduceMotion ? false : "hidden"}
|
|
animate="visible"
|
|
variants={heroContainer}
|
|
className="grid grid-cols-[minmax(0,1fr)] items-start gap-sp-8 lg:grid-cols-[minmax(0,1.1fr)_minmax(340px,0.9fr)] lg:gap-sp-10"
|
|
>
|
|
<div className="max-w-[800px]">
|
|
<motion.div
|
|
className="mb-sp-5 flex flex-wrap items-center gap-sp-3"
|
|
variants={heroItem}
|
|
>
|
|
<div className="cinematic-glass inline-flex items-center gap-sp-3 px-4 py-2">
|
|
<motion.span
|
|
className="h-2 w-2 rounded-sm bg-[var(--slash)]"
|
|
animate={
|
|
shouldReduceMotion
|
|
? { opacity: 1 }
|
|
: {
|
|
opacity: [1, 0.25, 1],
|
|
boxShadow: [
|
|
"0 0 4px rgba(204,255,0,0.6)",
|
|
"0 0 12px rgba(204,255,0,0.2)",
|
|
"0 0 4px rgba(204,255,0,0.6)",
|
|
],
|
|
}
|
|
}
|
|
transition={
|
|
shouldReduceMotion
|
|
? { duration: 0 }
|
|
: {
|
|
duration: 2,
|
|
ease: "easeInOut",
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
}
|
|
}
|
|
/>
|
|
<span className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.82)]">
|
|
{HERO_CONTENT.status}
|
|
</span>
|
|
</div>
|
|
<div className="cinematic-glass inline-flex items-center gap-sp-2 px-4 py-2 text-[11px] text-[rgba(244,242,237,.72)]">
|
|
{HERO_CONTENT.chip}
|
|
</div>
|
|
</motion.div>
|
|
|
|
<motion.p
|
|
className="mb-sp-4 max-w-[560px] font-mono text-[10px] uppercase tracking-[0.12em] text-[var(--slash)]"
|
|
variants={heroItem}
|
|
>
|
|
{HERO_CONTENT.eyebrow}
|
|
</motion.p>
|
|
|
|
<motion.h1
|
|
className="max-w-[940px] font-display text-[clamp(1.9rem,5.2vw,4.75rem)] font-extrabold leading-[0.95] tracking-[-0.055em] lg:leading-[0.9] lg:text-balance"
|
|
variants={heroItemSlow}
|
|
>
|
|
{HERO_CONTENT.headline}
|
|
</motion.h1>
|
|
|
|
<motion.p
|
|
className="mt-sp-6 max-w-[700px] text-[16px] leading-8 text-[rgba(244,242,237,.88)] lg:text-[17px]"
|
|
variants={heroItem}
|
|
>
|
|
{HERO_CONTENT.subtitle}
|
|
</motion.p>
|
|
|
|
<motion.ul
|
|
className="mt-sp-8 grid gap-sp-3 sm:grid-cols-3"
|
|
variants={heroItem}
|
|
>
|
|
{HERO_CONTENT.points.map((point, i) => (
|
|
<motion.li
|
|
key={point}
|
|
className="cinematic-glass flex min-w-0 gap-sp-3 px-sp-4 py-sp-4 text-[13px] leading-6 text-[rgba(244,242,237,.82)]"
|
|
initial={
|
|
shouldReduceMotion
|
|
? false
|
|
: { opacity: 0, y: 30, scale: 0.95 }
|
|
}
|
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
transition={
|
|
shouldReduceMotion
|
|
? { duration: 0 }
|
|
: {
|
|
duration: 0.8,
|
|
delay: 0.8 + i * 0.1,
|
|
ease: [0.16, 1, 0.3, 1],
|
|
}
|
|
}
|
|
>
|
|
<span className="mt-[8px] h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--slash)] shadow-[0_0_8px_rgba(204,255,0,0.5)]" />
|
|
<span>{point}</span>
|
|
</motion.li>
|
|
))}
|
|
</motion.ul>
|
|
|
|
<motion.div
|
|
className="mt-sp-8 flex flex-wrap items-center gap-sp-4"
|
|
variants={heroItem}
|
|
>
|
|
<MagneticButton
|
|
as="button"
|
|
type="button"
|
|
onClick={handleWhatsAppClick}
|
|
className="cinematic-cta-primary inline-flex min-h-12 items-center justify-center px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#0A0A0A] no-underline"
|
|
strength={0.25}
|
|
>
|
|
<span className="relative z-10">{HERO_CONTENT.cta}</span>
|
|
</MagneticButton>
|
|
|
|
<MagneticButton
|
|
as="a"
|
|
href="/cases"
|
|
className="cinematic-cta-secondary inline-flex min-h-12 items-center justify-center px-sp-8 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[#f4f2ed] no-underline"
|
|
strength={0.25}
|
|
>
|
|
Ver casos selecionados
|
|
</MagneticButton>
|
|
|
|
<MagneticButton
|
|
as="a"
|
|
href={CV_URL}
|
|
download
|
|
className="inline-flex min-h-12 items-center justify-center px-sp-2 text-[12px] font-medium text-[rgba(244,242,237,.76)] no-underline transition-colors duration-300 hover:text-[var(--slash)]"
|
|
strength={0.2}
|
|
>
|
|
Download CV
|
|
</MagneticButton>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
className="mt-sp-10 flex flex-wrap items-center gap-x-sp-6 gap-y-sp-3 border-t border-[rgba(255,255,255,.06)] pt-sp-6"
|
|
variants={heroItem}
|
|
>
|
|
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.08em] text-[rgba(244,242,237,.68)]">
|
|
<span className="h-2 w-2 rounded-full bg-[var(--slash)] shadow-[0_0_8px_var(--slash)]" />
|
|
Garuva, SC / Brasil
|
|
</div>
|
|
<div className="inline-flex items-center gap-sp-3 text-[11px] uppercase tracking-[0.08em] text-[rgba(244,242,237,.68)]">
|
|
<span className="h-[1px] w-6 bg-[rgba(255,255,255,.2)]" />
|
|
Next.js, Tauri, Rust, Node.js
|
|
</div>
|
|
<div className="flex items-center gap-sp-2">
|
|
{SOCIAL_LINKS.map((link) => (
|
|
<MagneticButton
|
|
key={link.label}
|
|
as="a"
|
|
href={link.href}
|
|
aria-label={link.label}
|
|
target={link.href.startsWith("http") ? "_blank" : undefined}
|
|
rel={
|
|
link.href.startsWith("http")
|
|
? "noopener noreferrer"
|
|
: undefined
|
|
}
|
|
className="cinematic-social-link inline-flex h-11 w-11 items-center justify-center rounded-full"
|
|
strength={0.4}
|
|
>
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill={link.stroke ? "none" : "currentColor"}
|
|
stroke={link.stroke ? "currentColor" : "none"}
|
|
strokeWidth={link.stroke ? "2" : undefined}
|
|
aria-hidden="true"
|
|
>
|
|
{link.icon}
|
|
</svg>
|
|
</MagneticButton>
|
|
))}
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
|
|
<motion.aside
|
|
className="cinematic-aside-card relative overflow-hidden p-sp-6 lg:p-sp-8"
|
|
initial={
|
|
shouldReduceMotion
|
|
? false
|
|
: { opacity: 0, x: 60, rotateY: -8, transformPerspective: 1000 }
|
|
}
|
|
animate={{ opacity: 1, x: 0, rotateY: 0 }}
|
|
transition={
|
|
shouldReduceMotion
|
|
? { duration: 0 }
|
|
: {
|
|
duration: 1.2,
|
|
delay: 0.5,
|
|
ease: [0.16, 1, 0.3, 1],
|
|
}
|
|
}
|
|
>
|
|
<motion.div
|
|
className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(204,255,0,0.8),transparent)]"
|
|
initial={shouldReduceMotion ? false : { scaleX: 0 }}
|
|
animate={{ scaleX: 1 }}
|
|
transition={
|
|
shouldReduceMotion
|
|
? { duration: 0 }
|
|
: {
|
|
duration: 1.5,
|
|
delay: 1,
|
|
ease: [0.16, 1, 0.3, 1],
|
|
}
|
|
}
|
|
/>
|
|
|
|
<div className="mb-sp-6 space-y-sp-4">
|
|
<HeroStarfield />
|
|
<ControlRoomGlobe autoRotate={!shouldReduceMotion} />
|
|
<TelemetryStrip reducedMotion={shouldReduceMotion} />
|
|
</div>
|
|
|
|
<div className="border-b border-[rgba(255,255,255,0.08)] pb-sp-5">
|
|
<div className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--slash)]">
|
|
{HERO_CONTENT.sideLabel}
|
|
</div>
|
|
<h2 className="mt-sp-3 font-display text-[clamp(1.35rem,3vw,2rem)] font-extrabold leading-[1.04] tracking-[-0.03em] text-[#f7f4ee]">
|
|
{HERO_CONTENT.sideTitle}
|
|
</h2>
|
|
<p className="mt-sp-4 text-[14px] leading-7 text-[rgba(244,242,237,.78)]">
|
|
{HERO_CONTENT.sideText}
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={handleWhatsAppClick}
|
|
className="mt-sp-5 inline-flex w-full items-center justify-center bg-[var(--slash)] px-sp-5 py-sp-4 font-mono text-[11px] font-bold uppercase tracking-[0.1em] text-[var(--ink)] transition hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--slash)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ink)]"
|
|
>
|
|
{/* Ícone WhatsApp inline: lucide não tem brand icons */}
|
|
<svg
|
|
aria-hidden="true"
|
|
viewBox="0 0 24 24"
|
|
fill="currentColor"
|
|
className="h-4 w-4"
|
|
>
|
|
<path d="M12.04 2a9.88 9.88 0 0 0-8.41 4.7A9.88 9.88 0 0 0 2.1 12.6a9.8 9.8 0 0 0 1.34 5.02L2 22l4.53-1.38a9.86 9.86 0 0 0 5.51 1.66h.01a9.87 9.87 0 0 0 7.02-2.9 9.79 9.79 0 0 0 2.89-6.98 9.73 9.73 0 0 0-.29-2.35A9.88 9.88 0 0 0 12.04 2Zm5.77 13.94c-.24.68-1.42 1.3-1.96 1.35-.5.05-1.13.24-3.8-.98-3.18-1.44-5.15-4.72-5.31-4.93-.15-.22-1.25-1.75-1.16-3.29.09-1.54.92-2.28 1.23-2.59.31-.31.66-.37.88-.36h.63c.2 0 .47-.03.71.58.26.65.88 2.25.96 2.41.08.17.13.36.02.57-.11.22-.17.35-.33.54-.17.19-.35.43-.5.58-.16.16-.33.34-.15.66.18.32.81 1.42 1.75 2.3 1.2 1.14 2.21 1.49 2.52 1.66.31.17.5.14.68-.09.18-.22.78-.99.99-1.33.21-.34.42-.27.7-.16.29.11 1.82.95 2.13 1.12.31.17.52.26.6.4.07.15.07.83-.17 1.51Z" />
|
|
</svg>
|
|
Agendar no WhatsApp
|
|
</button>
|
|
</div>
|
|
|
|
<div className="mt-sp-6 space-y-sp-5">
|
|
{HERO_CONTENT.proof.map((item, index) => (
|
|
<motion.article
|
|
key={item.label}
|
|
className="border-b border-[rgba(255,255,255,0.08)] pb-sp-5 last:border-b-0 last:pb-0"
|
|
initial={
|
|
shouldReduceMotion
|
|
? false
|
|
: { opacity: 0, y: 20, filter: "blur(6px)" }
|
|
}
|
|
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
|
transition={
|
|
shouldReduceMotion
|
|
? { duration: 0 }
|
|
: {
|
|
duration: 0.8,
|
|
delay: 0.8 + index * 0.12,
|
|
ease: [0.16, 1, 0.3, 1],
|
|
}
|
|
}
|
|
>
|
|
<div className="font-mono text-[10px] uppercase tracking-[0.12em] text-[rgba(244,242,237,.56)]">
|
|
{item.label}
|
|
</div>
|
|
<p className="mt-sp-2 text-[14px] leading-7 text-[rgba(244,242,237,.92)]">
|
|
{item.value}
|
|
</p>
|
|
</motion.article>
|
|
))}
|
|
</div>
|
|
</motion.aside>
|
|
</motion.div>
|
|
</div>
|
|
|
|
<motion.div
|
|
className="absolute bottom-8 left-1/2 z-10 -translate-x-1/2"
|
|
initial={shouldReduceMotion ? false : { opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={
|
|
shouldReduceMotion ? { duration: 0 } : { delay: 2, duration: 1 }
|
|
}
|
|
>
|
|
{shouldReduceMotion ? (
|
|
<div className="flex flex-col items-center gap-2" aria-hidden="true">
|
|
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-[rgba(244,242,237,.68)]">
|
|
Scroll
|
|
</span>
|
|
<div className="h-8 w-px bg-gradient-to-b from-[var(--slash)] to-transparent opacity-50" />
|
|
</div>
|
|
) : (
|
|
<motion.div
|
|
className="flex flex-col items-center gap-2"
|
|
animate={{ y: [0, 8, 0] }}
|
|
transition={{
|
|
duration: 2,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: "easeInOut",
|
|
}}
|
|
>
|
|
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-[rgba(244,242,237,.68)]">
|
|
Scroll
|
|
</span>
|
|
<div className="h-8 w-px bg-gradient-to-b from-[var(--slash)] to-transparent opacity-50" />
|
|
</motion.div>
|
|
)}
|
|
</motion.div>
|
|
</section>
|
|
);
|
|
}
|