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:
@@ -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)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
45
backend/templates/email/contact_me.html
Normal file
45
backend/templates/email/contact_me.html
Normal 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>
|
||||||
@@ -1,35 +1,50 @@
|
|||||||
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 = () => {
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
|
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
|
||||||
if (opened && e.propertyName === "transform") {
|
if (opened && e.propertyName === "transform") {
|
||||||
setContentMoveUp(true)
|
setContentMoveUp(true)
|
||||||
setTimeout(() => setOpeningBehind(true), 10)
|
setTimeout(() => setOpeningBehind(true), 10)
|
||||||
}
|
}
|
||||||
if (!opened && e.propertyName === "transform") {
|
if (!opened && e.propertyName === "transform") {
|
||||||
@@ -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(' ')}
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit}>
|
{success ? (
|
||||||
<input
|
<div style={{
|
||||||
type="email"
|
display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
|
||||||
name="email"
|
height: "100%", gap: "0.75rem", padding: "1.5rem", textAlign: "center",
|
||||||
placeholder="Váš email"
|
}}>
|
||||||
required
|
<div style={{ fontSize: "2.5rem" }}>✓</div>
|
||||||
/>
|
<p style={{ color: "var(--c-other)", fontWeight: 700, margin: 0 }}>Zpráva odeslána!</p>
|
||||||
<textarea
|
<p style={{ color: "color-mix(in hsl, var(--c-text), transparent 35%)", fontSize: "0.85rem", margin: 0 }}>
|
||||||
name="message"
|
Ozvu se vám do 24 hodin.
|
||||||
placeholder="Vaše zpráva"
|
</p>
|
||||||
required
|
<button
|
||||||
/>
|
onClick={() => setSuccess(false)}
|
||||||
<input type="hidden" name="state" />
|
style={{
|
||||||
<input type="submit"/>
|
marginTop: "0.5rem", background: "none", border: "1px solid var(--c-lines)",
|
||||||
</form>
|
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}>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="Váš email"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={e => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
<textarea
|
||||||
|
name="message"
|
||||||
|
placeholder="Vaše zpráva"
|
||||||
|
required
|
||||||
|
value={message}
|
||||||
|
onChange={e => setMessage(e.target.value)}
|
||||||
|
/>
|
||||||
|
{error && (
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.cover}></div>
|
<div className={styles.cover}></div>
|
||||||
|
|||||||
@@ -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" }} />
|
||||||
<ContactMeForm />
|
<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>
|
|
||||||
|
|
||||||
{/* Mobile: simple card version without envelope */}
|
<div className="container" style={{ position: "relative", zIndex: 1 }}>
|
||||||
<div className="md:hidden">
|
|
||||||
<div className="card p-5">
|
{/* Header */}
|
||||||
<form className="space-y-3">
|
<motion.div {...fadeUp(0)} style={{ textAlign: "center", marginBottom: "4rem" }}>
|
||||||
<div>
|
<span style={{
|
||||||
<label className="block text-sm text-brand-lines mb-1">Your email</label>
|
display: "inline-block", padding: "0.3em 0.9em", borderRadius: "9999px",
|
||||||
<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" />
|
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>
|
</div>
|
||||||
<div>
|
</motion.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?" />
|
{/* Contact details */}
|
||||||
</div>
|
{CONTACT_ITEMS.map(({ icon, label, value, href, color }, i) => (
|
||||||
<button type="submit" className="px-4 py-2 rounded-lg font-semibold text-white bg-brandGradient shadow-glow">Send</button>
|
<motion.a
|
||||||
</form>
|
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>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="glass p-6 max-w-lg mt-8">
|
{/* Right — envelope form (untouched) */}
|
||||||
<p><strong>Email:</strong> <a href="mailto:brunovontor@gmail.com" className="hover:text-[var(--c-other)]">brunovontor@gmail.com</a></p>
|
<motion.div {...fadeUp(0.2)} style={{ display: "flex", justifyContent: "center" }}>
|
||||||
<p className="mt-2"><strong>Phone:</strong> <a href="tel:+420605512624" className="hover:text-[var(--c-other)]">+420 605 512 624</a></p>
|
<ContactMeForm />
|
||||||
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Responsive: stack on mobile */}
|
||||||
|
<style>{`
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.contact-grid { grid-template-columns: 1fr !important; gap: 2.5rem !important; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" />
|
||||||
<ContactMeForm />
|
|
||||||
|
{/* 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 />
|
||||||
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user