- Introduced DrawLine component for drawing animations. - Added LazyRevealImage component for lazy loading images with reveal animations. - Implemented TextDiffuseReveal for animated text reveals with staggered effects. - Created TopProgressBar for a dynamic loading indicator. - Added PointCloud mock for testing animations. - Developed useReducedMotion hook to handle user preferences for reduced motion. - Centralized animation configurations in config.ts for easier management. - Defined animation variants in variants.ts for consistent animation effects. - Established AnimationProvider to supply global animation settings. - Created tests for AnimationProvider and useReducedMotion hook. - Added SVG assets for branding and UI elements. - Documented frontend review plan and hero circuit board animation plan.
204 lines
7.2 KiB
TypeScript
204 lines
7.2 KiB
TypeScript
"use client";
|
|
|
|
import type React from "react";
|
|
import { useState } from "react";
|
|
import { motion, type Variants, useReducedMotion } from "framer-motion";
|
|
import { Section } from "./Section";
|
|
import { DrawLine } from "./animations/DrawLine";
|
|
|
|
const WHATSAPP_NUMBER = "5547992742365";
|
|
|
|
export default function Contact() {
|
|
const shouldReduceMotion = useReducedMotion();
|
|
const [formData, setFormData] = useState({
|
|
name: "",
|
|
email: "",
|
|
subject: "full-stack",
|
|
message: "",
|
|
});
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
const messageTemplate = `*NOVA SOLICITACAO DE PROJETO*\n\n*Origem:* Portfolio Profissional\n*Prioridade:* Alta\n\n---\n*CLIENTE:* ${formData.name}\n*EMAIL:* ${formData.email}\n*AREA:* ${formData.subject.toUpperCase()}\n\n*DESCRICAO:*\n"${formData.message}"\n---\n_Mensagem gerada via Evandro.dev | Garuva, SC_`;
|
|
|
|
const encodedMessage = encodeURIComponent(messageTemplate);
|
|
const whatsappUrl = `https://wa.me/${WHATSAPP_NUMBER}?text=${encodedMessage}`;
|
|
|
|
window.open(whatsappUrl, "_blank", "noopener,noreferrer");
|
|
};
|
|
|
|
const gridVariants: Variants = {
|
|
hidden: {},
|
|
visible: {
|
|
transition: {
|
|
delayChildren: 0.05,
|
|
staggerChildren: 0.08,
|
|
},
|
|
},
|
|
};
|
|
|
|
const cardVariants: Variants = {
|
|
hidden: shouldReduceMotion
|
|
? { opacity: 0 }
|
|
: { opacity: 0, y: 18, clipPath: "inset(100% 0 0 0)" },
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
clipPath: "inset(0% 0 0 0)",
|
|
transition: shouldReduceMotion
|
|
? { duration: 0.25 }
|
|
: { duration: 0.75, ease: [0.16, 1, 0.3, 1] },
|
|
},
|
|
};
|
|
|
|
return (
|
|
<Section
|
|
id="contact"
|
|
eyebrow="CONTACT_PROTOCOL.init"
|
|
title="Vamos discutir o proximo sistema"
|
|
description="Descreva o contexto, o gargalo ou o produto. O contato abre direto no WhatsApp com briefing estruturado."
|
|
dark
|
|
disableChildrenAnimation
|
|
>
|
|
<motion.div
|
|
className="ds-grid-2"
|
|
initial="hidden"
|
|
whileInView="visible"
|
|
viewport={{ once: true, amount: 0.2 }}
|
|
variants={gridVariants}
|
|
>
|
|
<motion.div
|
|
className="ds-card"
|
|
variants={cardVariants}
|
|
whileHover={
|
|
shouldReduceMotion
|
|
? undefined
|
|
: {
|
|
y: -4,
|
|
boxShadow: "0 20px 42px rgba(0, 0, 0, 0.4)",
|
|
}
|
|
}
|
|
whileTap={shouldReduceMotion ? undefined : { scale: 0.995 }}
|
|
transition={{ type: "spring", stiffness: 180, damping: 18 }}
|
|
>
|
|
<div className="ds-kicker">Response SLA</div>
|
|
<DrawLine delay={0.04} />
|
|
<h3 className="ds-card-title">Retorno inicial em ate 24h</h3>
|
|
<p className="ds-copy">
|
|
Ideal para discovery tecnico, revisao de arquitetura, rescue de
|
|
projeto, otimizacao de performance ou construcao de produto do zero.
|
|
</p>
|
|
<div className="mt-sp-6 space-y-sp-4 text-[12px] leading-7 text-[rgba(255,255,255,.7)]">
|
|
<div>WhatsApp: +55 47 99274-2365</div>
|
|
<div>E-mail: evandrofonsecajunior@gmail.com</div>
|
|
<div>Base: Garuva, Santa Catarina | atuacao remota</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
<motion.form
|
|
onSubmit={handleSubmit}
|
|
className="ds-card space-y-sp-5"
|
|
variants={cardVariants}
|
|
whileHover={
|
|
shouldReduceMotion
|
|
? undefined
|
|
: {
|
|
y: -4,
|
|
boxShadow: "0 20px 42px rgba(0, 0, 0, 0.4)",
|
|
}
|
|
}
|
|
transition={{ type: "spring", stiffness: 180, damping: 18 }}
|
|
>
|
|
<div>
|
|
<label htmlFor="contact-name" className="ds-kicker block">
|
|
Nome completo
|
|
</label>
|
|
<input
|
|
id="contact-name"
|
|
required
|
|
type="text"
|
|
value={formData.name}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, name: e.target.value })
|
|
}
|
|
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
|
|
style={{
|
|
color: "#fff",
|
|
backgroundColor: "rgba(255,255,255,0.06)",
|
|
}}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="contact-email" className="ds-kicker block">
|
|
E-mail corporativo
|
|
</label>
|
|
<input
|
|
id="contact-email"
|
|
required
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, email: e.target.value })
|
|
}
|
|
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
|
|
style={{
|
|
color: "#fff",
|
|
backgroundColor: "rgba(255,255,255,0.06)",
|
|
}}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="contact-subject" className="ds-kicker block">
|
|
Tipo de projeto
|
|
</label>
|
|
<select
|
|
id="contact-subject"
|
|
value={formData.subject}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, subject: e.target.value })
|
|
}
|
|
className="mt-sp-2 w-full border border-[rgba(255,255,255,.12)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
|
|
style={{ color: "#fff", backgroundColor: "#1a1a1a" }}
|
|
>
|
|
<option value="full-stack">Desenvolvimento Full-Stack</option>
|
|
<option value="cloud">Cloud & Infrastructure</option>
|
|
<option value="iot">Internet of Things (IoT)</option>
|
|
<option value="consultancy">Consultoria Tecnica Senior</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="contact-message" className="ds-kicker block">
|
|
Detalhes do projeto
|
|
</label>
|
|
<textarea
|
|
id="contact-message"
|
|
required
|
|
rows={6}
|
|
value={formData.message}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, message: e.target.value })
|
|
}
|
|
className="mt-sp-2 w-full resize-none border border-[rgba(255,255,255,.12)] bg-[rgba(255,255,255,.06)] px-sp-4 py-[14px] text-[12px] outline-none transition-colors focus:border-slash"
|
|
style={{
|
|
color: "#fff",
|
|
backgroundColor: "rgba(255,255,255,0.06)",
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<motion.button
|
|
type="submit"
|
|
className="ds-link ds-link-accent border-0"
|
|
whileHover={shouldReduceMotion ? undefined : { scale: 1.03 }}
|
|
whileTap={shouldReduceMotion ? undefined : { scale: 0.97 }}
|
|
transition={{ type: "spring", stiffness: 260, damping: 20 }}
|
|
>
|
|
Iniciar protocolo de contato
|
|
</motion.button>
|
|
</motion.form>
|
|
</motion.div>
|
|
</Section>
|
|
);
|
|
}
|