Enhance contact UI and email handling

Add an email template and improve contact flow: create backend/templates/email/contact_me.html and update backend/advertisement/tasks.py to use SiteConfiguration.contact_email with a fallback to brunovontor@gmail.com when sending contact form emails. Revamp frontend contact experience: ContactMeForm is now open by default, uses controlled email/message inputs, shows loading/success/error states and posts to /api/advertisement/contact-me/ via publicApi. Update ContactPage and Home to include richer contact sections (framer-motion animations, icons, social links and responsive layouts). Also add a PowerShell helper entry to .claude/settings.local.json.
This commit is contained in:
2026-06-07 23:17:10 +02:00
parent 3766b033bc
commit ad7f0fbe55
6 changed files with 385 additions and 59 deletions

View File

@@ -15,7 +15,8 @@
"Bash(node -e \"const r = require\\('react-icons/fa'\\); const keys = Object.keys\\(r\\).filter\\(k => k.toLowerCase\\(\\).includes\\('migrat'\\) || k.toLowerCase\\(\\).includes\\('sync'\\) || k.toLowerCase\\(\\).includes\\('exchange'\\) || k.toLowerCase\\(\\).includes\\('arrow'\\)\\).slice\\(0,15\\); console.log\\(keys.join\\('\\\\n'\\)\\);\")", "Bash(node -e \"const r = require\\('react-icons/fa'\\); const keys = Object.keys\\(r\\).filter\\(k => k.toLowerCase\\(\\).includes\\('migrat'\\) || k.toLowerCase\\(\\).includes\\('sync'\\) || k.toLowerCase\\(\\).includes\\('exchange'\\) || k.toLowerCase\\(\\).includes\\('arrow'\\)\\).slice\\(0,15\\); console.log\\(keys.join\\('\\\\n'\\)\\);\")",
"Bash(node -e \"const r = require\\('react-icons/fa'\\); console.log\\('FaExchangeAlt' in r, 'FaSyncAlt' in r, 'FaCloudUploadAlt' in r, 'FaRandom' in r, 'FaDatabase' in r\\);\")", "Bash(node -e \"const r = require\\('react-icons/fa'\\); console.log\\('FaExchangeAlt' in r, 'FaSyncAlt' in r, 'FaCloudUploadAlt' in r, 'FaRandom' in r, 'FaDatabase' in r\\);\")",
"Bash(node -e \"const r = require\\('react-icons/gi'\\); console.log\\('GiStabilizer' in r, 'GiDroneBoy' in r, 'GiCctvCamera' in r, 'GiFilmProjector' in r, 'GiGyroscope' in r\\);\")", "Bash(node -e \"const r = require\\('react-icons/gi'\\); console.log\\('GiStabilizer' in r, 'GiDroneBoy' in r, 'GiCctvCamera' in r, 'GiFilmProjector' in r, 'GiGyroscope' in r\\);\")",
"Bash(node -e \"const r = require\\('react-icons/si'\\); const celery = Object.keys\\(r\\).filter\\(k => k.toLowerCase\\(\\).includes\\('celery'\\) || k.toLowerCase\\(\\).includes\\('worker'\\) || k.toLowerCase\\(\\).includes\\('task'\\)\\).slice\\(0,10\\); console.log\\(celery\\);\")" "Bash(node -e \"const r = require\\('react-icons/si'\\); const celery = Object.keys\\(r\\).filter\\(k => k.toLowerCase\\(\\).includes\\('celery'\\) || k.toLowerCase\\(\\).includes\\('worker'\\) || k.toLowerCase\\(\\).includes\\('task'\\)\\).slice\\(0,10\\); console.log\\(celery\\);\")",
"Bash(Get-ChildItem -Path \"c:\\\\Users\\\\bruno\\\\Documents\\\\GitHub\\\\vontor-cz\\\\backend\\\\\" -Directory | Select-Object -ExpandProperty Name)"
] ]
} }
} }

View File

@@ -14,8 +14,10 @@ def send_contact_me_email_task(client_email, message_content):
"client_email": client_email, "client_email": client_email,
"message_content": message_content "message_content": message_content
} }
config_email = SiteConfiguration.get_solo().contact_email
recipient = config_email if config_email else "brunovontor@gmail.com"
send_email_with_context( send_email_with_context(
recipients=SiteConfiguration.get_solo().contact_email, recipients=recipient,
subject="Poptávka z kontaktního formuláře!!!", subject="Poptávka z kontaktního formuláře!!!",
template_path="email/contact_me.html", template_path="email/contact_me.html",
context=context, context=context,

View File

@@ -0,0 +1,45 @@
<!-- Contact form submission notification -->
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="padding: 8px 0 20px;">
<h2 style="margin: 0 0 6px; font-size: 20px; color: #031D44;">
📬 Nová zpráva z kontaktního formuláře
</h2>
<p style="margin: 0; font-size: 13px; color: #666;">
Přišla poptávka přes vontor.cz
</p>
</td>
</tr>
<!-- Sender email -->
<tr>
<td style="padding: 12px 16px; background: #f7f9fc; border-left: 4px solid #70A288; border-radius: 4px; margin-bottom: 12px;">
<p style="margin: 0 0 2px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #888;">Od</p>
<p style="margin: 0; font-size: 15px; font-weight: 600; color: #031D44;">
<a href="mailto:{{ client_email }}" style="color: #24719f; text-decoration: none;">{{ client_email }}</a>
</p>
</td>
</tr>
<tr><td style="height: 12px;"></td></tr>
<!-- Message body -->
<tr>
<td style="padding: 16px; background: #f0f4f8; border-radius: 6px;">
<p style="margin: 0 0 8px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #888;">Zpráva</p>
<p style="margin: 0; font-size: 14px; line-height: 1.7; color: #222; white-space: pre-wrap;">{{ message_content }}</p>
</td>
</tr>
<tr><td style="height: 20px;"></td></tr>
<!-- Reply button -->
<tr>
<td>
<a href="mailto:{{ client_email }}"
style="display: inline-block; padding: 10px 24px; background: #70A288; color: #ffffff; font-weight: 600; font-size: 14px; border-radius: 6px; text-decoration: none;">
Odpovědět
</a>
</td>
</tr>
</table>

View File

@@ -1,16 +1,33 @@
import React, { useState, useRef } from "react" import React, { useState, useRef } from "react"
import styles from "./contact-me.module.css" import styles from "./contact-me.module.css"
import { LuMousePointerClick } from "react-icons/lu"; import { LuMousePointerClick } from "react-icons/lu";
import { publicApi } from "@/api/publicClient";
export default function ContactMeForm() { export default function ContactMeForm() {
const [opened, setOpened] = useState(false) const [opened, setOpened] = useState(true)
const [contentMoveUp, setContentMoveUp] = useState(false) const [contentMoveUp, setContentMoveUp] = useState(true)
const [openingBehind, setOpeningBehind] = useState(false) const [openingBehind, setOpeningBehind] = useState(true)
// const [success, setSuccess] = useState(false) const [email, setEmail] = useState("")
const [message, setMessage] = useState("")
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const [error, setError] = useState("")
const openingRef = useRef<HTMLDivElement>(null) const openingRef = useRef<HTMLDivElement>(null)
function handleSubmit() { async function handleSubmit(e: React.FormEvent) {
// form submission logic here e.preventDefault()
setLoading(true)
setError("")
try {
await publicApi.post("/api/advertisement/contact-me/", { email, message, hp: "" })
setSuccess(true)
setEmail("")
setMessage("")
} catch {
setError("Nepodařilo se odeslat zprávu. Zkuste to prosím znovu.")
} finally {
setLoading(false)
}
} }
const toggleOpen = () => { const toggleOpen = () => {
@@ -18,12 +35,10 @@ export default function ContactMeForm() {
setOpened(true) setOpened(true)
setOpeningBehind(false) setOpeningBehind(false)
setContentMoveUp(false) setContentMoveUp(false)
// Wait for the rotate-opening animation to finish before moving content up
// The actual moveUp will be handled in onTransitionEnd
} else { } else {
setContentMoveUp(false) setContentMoveUp(false)
setOpeningBehind(false) setOpeningBehind(false)
setTimeout(() => setOpened(false), 1000) // match transition duration setTimeout(() => setOpened(false), 1000)
} }
} }
@@ -38,7 +53,6 @@ export default function ContactMeForm() {
} }
return ( return (
<div className={styles["contact-me"]}> <div className={styles["contact-me"]}>
<div <div
ref={openingRef} ref={openingRef}
@@ -52,7 +66,7 @@ export default function ContactMeForm() {
onClick={toggleOpen} onClick={toggleOpen}
onTransitionEnd={handleTransitionEnd} onTransitionEnd={handleTransitionEnd}
> >
<LuMousePointerClick/> <LuMousePointerClick />
</div> </div>
<div <div
@@ -61,21 +75,50 @@ export default function ContactMeForm() {
contentMoveUp ? styles["content-moveup"] : '' contentMoveUp ? styles["content-moveup"] : ''
].filter(Boolean).join(' ')} ].filter(Boolean).join(' ')}
> >
{success ? (
<div style={{
display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
height: "100%", gap: "0.75rem", padding: "1.5rem", textAlign: "center",
}}>
<div style={{ fontSize: "2.5rem" }}></div>
<p style={{ color: "var(--c-other)", fontWeight: 700, margin: 0 }}>Zpráva odeslána!</p>
<p style={{ color: "color-mix(in hsl, var(--c-text), transparent 35%)", fontSize: "0.85rem", margin: 0 }}>
Ozvu se vám do 24 hodin.
</p>
<button
onClick={() => setSuccess(false)}
style={{
marginTop: "0.5rem", background: "none", border: "1px solid var(--c-lines)",
color: "var(--c-text)", padding: "0.4em 1.2em", borderRadius: "0.5em",
cursor: "pointer", fontSize: "0.82rem",
}}
>
Nová zpráva
</button>
</div>
) : (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<input <input
type="email" type="email"
name="email" name="email"
placeholder="Váš email" placeholder="Váš email"
required required
value={email}
onChange={e => setEmail(e.target.value)}
/> />
<textarea <textarea
name="message" name="message"
placeholder="Vaše zpráva" placeholder="Vaše zpráva"
required required
value={message}
onChange={e => setMessage(e.target.value)}
/> />
<input type="hidden" name="state" /> {error && (
<input type="submit"/> <p style={{ color: "#ff6b6b", fontSize: "0.8rem", margin: "0", textAlign: "center" }}>{error}</p>
)}
<input type="submit" value={loading ? "Odesílám…" : "Odeslat"} disabled={loading} />
</form> </form>
)}
</div> </div>
<div className={styles.cover}></div> <div className={styles.cover}></div>

View File

@@ -1,39 +1,154 @@
import { motion } from "framer-motion";
import { FaEnvelope, FaPhone, FaGithub, FaLinkedin, FaInstagram } from "react-icons/fa";
import ContactMeForm from "../../components/home/ContactMe/ContactMeForm"; import ContactMeForm from "../../components/home/ContactMe/ContactMeForm";
export default function ContactPage(){ const fadeUp = (delay = 0) => ({
initial: { opacity: 0, y: 28 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.6, ease: "easeOut", delay },
});
const CONTACT_ITEMS = [
{ icon: <FaEnvelope />, label: "Email", value: "brunovontor@gmail.com", href: "mailto:brunovontor@gmail.com", color: "var(--c-other)" },
{ icon: <FaPhone />, label: "Phone", value: "+420 605 512 624", href: "tel:+420605512624", color: "var(--c-lines)" },
];
const SOCIAL_LINKS = [
{ icon: <FaGithub />, href: "https://github.com/Brunobrno", label: "GitHub", color: "#e6edf3" },
{ icon: <FaLinkedin />, href: "https://linkedin.com", label: "LinkedIn", color: "#0a66c2" },
{ icon: <FaInstagram />,href: "https://instagram.com", label: "Instagram",color: "#e1306c" },
];
export default function ContactPage() {
return ( return (
<section className="section"> <section style={{ minHeight: "100svh", background: "var(--c-background)", padding: "5rem 1.5rem 4rem", position: "relative", overflow: "hidden" }}>
<div className="container">
<h2 className="text-2xl md:text-3xl font-bold mb-2 text-rainbow">Get in Touch</h2>
<p className="text-brand-lines mb-6">Reach out via the form or use the details below.</p>
{/* Desktop/tablet: envelope animation + slide-out form */} {/* Background glow blobs */}
<div className="hidden md:block"> <div style={{ position: "absolute", top: "10%", left: "-10%", width: "500px", height: "500px", borderRadius: "50%", background: "color-mix(in hsl, var(--c-other), transparent 88%)", filter: "blur(80px)", zIndex: 0, pointerEvents: "none" }} />
<div style={{ position: "absolute", bottom: "5%", right: "-5%", width: "400px", height: "400px", borderRadius: "50%", background: "color-mix(in hsl, var(--c-boxes), transparent 85%)", filter: "blur(80px)", zIndex: 0, pointerEvents: "none" }} />
<div className="container" style={{ position: "relative", zIndex: 1 }}>
{/* Header */}
<motion.div {...fadeUp(0)} style={{ textAlign: "center", marginBottom: "4rem" }}>
<span style={{
display: "inline-block", padding: "0.3em 0.9em", borderRadius: "9999px",
background: "color-mix(in hsl, var(--c-other), transparent 82%)",
border: "1px solid color-mix(in hsl, var(--c-other), transparent 50%)",
color: "var(--c-other)", fontSize: "0.78rem", fontWeight: 700,
letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: "1rem",
}}>
Contact
</span>
<h1 style={{ fontSize: "clamp(2rem, 5vw, 3.5rem)", fontWeight: 900, margin: "0 0 0.8rem", lineHeight: 1.1 }}>
Let's{" "}
<span style={{
background: "linear-gradient(135deg, var(--c-other), var(--c-lines))",
WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent", backgroundClip: "text",
}}>
Work Together
</span>
</h1>
<p style={{ color: "color-mix(in hsl, var(--c-text), transparent 40%)", maxWidth: "480px", margin: "0 auto", lineHeight: 1.7, fontSize: "1rem" }}>
Have a project in mind? Click the envelope to open a message, or reach out directly below.
</p>
</motion.div>
{/* Two-column layout */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1.4fr", gap: "4rem", alignItems: "start" }}>
{/* Left — info */}
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
{/* Available badge */}
<motion.div {...fadeUp(0.1)}>
<div className="glass" style={{ padding: "1.2rem 1.5rem", display: "flex", alignItems: "center", gap: "1rem" }}>
<div style={{ width: "10px", height: "10px", borderRadius: "50%", background: "var(--c-other)", boxShadow: "0 0 8px var(--c-other)", flexShrink: 0, animation: "pulse-glow 2s ease-in-out infinite" }} />
<div>
<div style={{ fontWeight: 700, fontSize: "0.95rem" }}>Available for new projects</div>
<div style={{ fontSize: "0.8rem", color: "color-mix(in hsl, var(--c-text), transparent 45%)" }}>Usually responds within 24 hours</div>
</div>
</div>
</motion.div>
{/* Contact details */}
{CONTACT_ITEMS.map(({ icon, label, value, href, color }, i) => (
<motion.a
key={label}
{...fadeUp(0.15 + i * 0.1)}
href={href}
className="glass"
style={{
padding: "1.2rem 1.5rem", display: "flex", alignItems: "center", gap: "1rem",
textDecoration: "none", color: "inherit", transition: "transform 0.2s ease, box-shadow 0.2s ease",
}}
whileHover={{ y: -3, boxShadow: `0 8px 24px color-mix(in hsl, ${color}, transparent 70%)` }}
>
<div style={{
width: "2.6rem", height: "2.6rem", borderRadius: "0.65rem", flexShrink: 0,
background: `color-mix(in hsl, ${color}, transparent 82%)`,
border: `1px solid color-mix(in hsl, ${color}, transparent 55%)`,
display: "flex", alignItems: "center", justifyContent: "center",
fontSize: "1.1rem", color,
}}>
{icon}
</div>
<div>
<div style={{ fontSize: "0.72rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.06em", color: "color-mix(in hsl, var(--c-text), transparent 50%)", marginBottom: "0.15rem" }}>{label}</div>
<div style={{ fontWeight: 600, fontSize: "0.95rem" }}>{value}</div>
</div>
</motion.a>
))}
{/* Social links */}
<motion.div {...fadeUp(0.35)} style={{ display: "flex", gap: "0.75rem", paddingTop: "0.5rem" }}>
{SOCIAL_LINKS.map(({ icon, href, label, color }) => (
<a
key={label}
href={href}
target="_blank"
rel="noopener noreferrer"
title={label}
style={{
width: "2.8rem", height: "2.8rem", borderRadius: "0.75rem",
background: "color-mix(in hsl, var(--c-background-light), transparent 20%)",
border: "1px solid color-mix(in hsl, var(--c-lines), transparent 60%)",
display: "flex", alignItems: "center", justifyContent: "center",
fontSize: "1.2rem", color: "color-mix(in hsl, var(--c-text), transparent 30%)",
textDecoration: "none", transition: "color 0.2s ease, border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease",
}}
onMouseEnter={e => {
e.currentTarget.style.color = color;
e.currentTarget.style.borderColor = color;
e.currentTarget.style.transform = "translateY(-3px)";
e.currentTarget.style.boxShadow = `0 4px 16px color-mix(in hsl, ${color}, transparent 55%)`;
}}
onMouseLeave={e => {
e.currentTarget.style.color = "color-mix(in hsl, var(--c-text), transparent 30%)";
e.currentTarget.style.borderColor = "color-mix(in hsl, var(--c-lines), transparent 60%)";
e.currentTarget.style.transform = "translateY(0)";
e.currentTarget.style.boxShadow = "none";
}}
>
{icon}
</a>
))}
</motion.div>
</div>
{/* Right — envelope form (untouched) */}
<motion.div {...fadeUp(0.2)} style={{ display: "flex", justifyContent: "center" }}>
<ContactMeForm /> <ContactMeForm />
</div> </motion.div>
{/* Mobile: simple card version without envelope */}
<div className="md:hidden">
<div className="card p-5">
<form className="space-y-3">
<div>
<label className="block text-sm text-brand-lines mb-1">Your email</label>
<input type="email" required className="w-full rounded-lg bg-brand-bgLight/40 border border-brand-lines/40 focus:outline-none focus:ring-2 focus:ring-brand-accent px-3 py-2 text-brand-text placeholder:text-brand-lines/70" placeholder="Enter your email" />
</div>
<div>
<label className="block text-sm text-brand-lines mb-1">Message</label>
<textarea required rows={5} className="w-full rounded-lg bg-brand-bgLight/40 border border-brand-lines/40 focus:outline-none focus:ring-2 focus:ring-brand-accent px-3 py-2 text-brand-text placeholder:text-brand-lines/70" placeholder="How can I help?" />
</div>
<button type="submit" className="px-4 py-2 rounded-lg font-semibold text-white bg-brandGradient shadow-glow">Send</button>
</form>
</div> </div>
</div> </div>
<div className="glass p-6 max-w-lg mt-8"> {/* Responsive: stack on mobile */}
<p><strong>Email:</strong> <a href="mailto:brunovontor@gmail.com" className="hover:text-[var(--c-other)]">brunovontor@gmail.com</a></p> <style>{`
<p className="mt-2"><strong>Phone:</strong> <a href="tel:+420605512624" className="hover:text-[var(--c-other)]">+420 605 512 624</a></p> @media (max-width: 768px) {
</div> .contact-grid { grid-template-columns: 1fr !important; gap: 2.5rem !important; }
</div> }
`}</style>
</section> </section>
); );
} }

View File

@@ -1,4 +1,7 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { motion } from "framer-motion";
import { FaEnvelope, FaPhone } from "react-icons/fa";
import { Link } from "react-router-dom";
import HeroSection from "@/components/home/hero/HeroSection"; import HeroSection from "@/components/home/hero/HeroSection";
import DroneSection from "@/components/home/drone/DroneSection"; import DroneSection from "@/components/home/drone/DroneSection";
import WebDevSection from "@/components/home/webdev/WebDevSection"; import WebDevSection from "@/components/home/webdev/WebDevSection";
@@ -7,7 +10,6 @@ import TechMarquee from "@/components/home/tech/TechMarquee";
import ContactMeForm from "@/components/home/ContactMe/ContactMeForm"; import ContactMeForm from "@/components/home/ContactMe/ContactMeForm";
export default function Home() { export default function Home() {
// Spark cursor on click
useEffect(() => { useEffect(() => {
const handleClick = (event: MouseEvent) => { const handleClick = (event: MouseEvent) => {
const spark = document.createElement("div"); const spark = document.createElement("div");
@@ -43,7 +45,125 @@ export default function Home() {
<div className="divider" /> <div className="divider" />
<TechMarquee /> <TechMarquee />
<div className="divider" /> <div className="divider" />
{/* Contact section */}
<section id="contact" style={{ background: "color-mix(in hsl, var(--c-background), black 10%)", padding: "5rem 1.5rem 6rem", position: "relative", overflow: "hidden" }}>
{/* Background glow */}
<div style={{ position: "absolute", bottom: "-10%", left: "50%", transform: "translateX(-50%)", width: "600px", height: "300px", borderRadius: "50%", background: "color-mix(in hsl, var(--c-other), transparent 90%)", filter: "blur(80px)", zIndex: 0, pointerEvents: "none" }} />
<div className="container" style={{ position: "relative", zIndex: 1 }}>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 28 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
style={{ textAlign: "center", marginBottom: "3.5rem" }}
>
<span style={{
display: "inline-block", padding: "0.3em 0.9em", borderRadius: "9999px",
background: "color-mix(in hsl, var(--c-other), transparent 82%)",
border: "1px solid color-mix(in hsl, var(--c-other), transparent 50%)",
color: "var(--c-other)", fontSize: "0.78rem", fontWeight: 700,
letterSpacing: "0.08em", textTransform: "uppercase", marginBottom: "1rem",
}}>
Contact
</span>
<h2 style={{ fontSize: "clamp(1.9rem, 4.5vw, 3rem)", fontWeight: 800, margin: "0 0 0.8rem" }}>
Let's{" "}
<span style={{
background: "linear-gradient(135deg, var(--c-other), var(--c-lines))",
WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent", backgroundClip: "text",
}}>
Work Together
</span>
</h2>
<p style={{ color: "color-mix(in hsl, var(--c-text), transparent 40%)", maxWidth: "440px", margin: "0 auto", lineHeight: 1.7 }}>
Click the envelope to open it or reach out directly.
</p>
</motion.div>
{/* Two-column: info left, form right */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1.5fr", gap: "4rem", alignItems: "center" }}>
{/* Left — contact info */}
<motion.div
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6 }}
viewport={{ once: true }}
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
{[
{ icon: <FaEnvelope />, label: "Email", value: "brunovontor@gmail.com", href: "mailto:brunovontor@gmail.com", color: "var(--c-other)" },
{ icon: <FaPhone />, label: "Phone", value: "+420 605 512 624", href: "tel:+420605512624", color: "var(--c-lines)" },
].map(({ icon, label, value, href, color }) => (
<a
key={label}
href={href}
className="glass"
style={{
padding: "1rem 1.3rem", display: "flex", alignItems: "center", gap: "1rem",
textDecoration: "none", color: "inherit", borderRadius: "1rem",
transition: "transform 0.2s ease",
}}
onMouseEnter={e => (e.currentTarget.style.transform = "translateY(-3px)")}
onMouseLeave={e => (e.currentTarget.style.transform = "translateY(0)")}
>
<div style={{
width: "2.4rem", height: "2.4rem", borderRadius: "0.6rem", flexShrink: 0,
background: `color-mix(in hsl, ${color}, transparent 82%)`,
border: `1px solid color-mix(in hsl, ${color}, transparent 55%)`,
display: "flex", alignItems: "center", justifyContent: "center",
fontSize: "1rem", color,
}}>
{icon}
</div>
<div>
<div style={{ fontSize: "0.7rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.06em", color: "color-mix(in hsl, var(--c-text), transparent 50%)", marginBottom: "0.1rem" }}>{label}</div>
<div style={{ fontWeight: 600, fontSize: "0.9rem" }}>{value}</div>
</div>
</a>
))}
<Link
to="/contact"
style={{
marginTop: "0.5rem",
display: "inline-flex", alignItems: "center", gap: "0.5rem",
padding: "0.7em 1.6em", borderRadius: "9999px",
background: "color-mix(in hsl, var(--c-other), transparent 80%)",
border: "1px solid color-mix(in hsl, var(--c-other), transparent 50%)",
color: "var(--c-other)", fontWeight: 600, fontSize: "0.88rem",
textDecoration: "none", transition: "background 0.2s ease, transform 0.2s ease",
width: "fit-content",
}}
onMouseEnter={e => { e.currentTarget.style.background = "color-mix(in hsl, var(--c-other), transparent 60%)"; e.currentTarget.style.transform = "scale(1.04)"; }}
onMouseLeave={e => { e.currentTarget.style.background = "color-mix(in hsl, var(--c-other), transparent 80%)"; e.currentTarget.style.transform = "scale(1)"; }}
>
Full Contact Page
</Link>
</motion.div>
{/* Right — envelope form */}
<motion.div
initial={{ opacity: 0, x: 30 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
viewport={{ once: true }}
style={{ display: "flex", justifyContent: "center" }}
>
<ContactMeForm /> <ContactMeForm />
</motion.div>
</div>
</div>
<style>{`
@media (max-width: 768px) {
#contact .container > div:last-child { grid-template-columns: 1fr !important; gap: 2rem !important; }
}
`}</style>
</section>
</main> </main>
); );
} }