Build portfolio site with real CV content

Single-page scrolling portfolio using Next.js 16, Tailwind CSS v4, and
Framer Motion. Sections: Hero, About, Skills, Projects, Contact. Dark
theme with orange/amber accents, scroll-reveal animations, and a sticky
blurring navbar. All content populated from Khaalid's CV — real bio,
skills, work context, and personal projects (server hosting, game dev,
AI). Contact form wired to khaalidmnbaccar@gmail.com via mailto.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Baccar
2026-05-25 09:53:29 +04:00
parent fcdecaf1d1
commit debbb64f20
13 changed files with 679 additions and 78 deletions

155
src/components/Contact.tsx Normal file
View File

@@ -0,0 +1,155 @@
"use client";
import { useState } from "react";
import SectionWrapper from "./SectionWrapper";
function GitHubIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" />
</svg>
);
}
function LinkedInIcon() {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<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.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 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" />
</svg>
);
}
function EmailIcon() {
return (
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="2" y="4" width="20" height="16" rx="2" />
<path d="M22 7L13.03 12.7a2 2 0 01-2.06 0L2 7" />
</svg>
);
}
const socials = [
{ label: "GitHub", href: "https://github.com", Icon: GitHubIcon },
{ label: "LinkedIn", href: "https://linkedin.com", Icon: LinkedInIcon },
{ label: "Email", href: "mailto:khaalidmnbaccar@gmail.com", Icon: EmailIcon },
];
const CONTACT_EMAIL = "khaalidmnbaccar@gmail.com";
const inputClass =
"w-full bg-zinc-900/60 border border-zinc-800 rounded-lg px-4 py-3 text-white text-sm placeholder-zinc-600 focus:outline-none focus:border-orange-500/60 transition-colors";
export default function Contact() {
const [form, setForm] = useState({ name: "", email: "", message: "" });
const [status, setStatus] = useState<"idle" | "opening">("idle");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setStatus("opening");
const subject = `Portfolio contact from ${form.name}`;
const body = `${form.message}\n\nReply to: ${form.email}`;
window.location.href = `mailto:${CONTACT_EMAIL}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
setTimeout(() => setStatus("idle"), 1500);
};
return (
<SectionWrapper id="contact" className="py-28 px-6">
<div className="max-w-2xl mx-auto">
<div className="flex items-center gap-4 mb-16">
<span className="font-mono text-orange-400 text-sm">04.</span>
<h2 className="text-3xl font-bold text-white">Get In Touch</h2>
<div className="flex-1 h-px bg-zinc-800" />
</div>
<p className="text-zinc-400 text-base leading-relaxed text-center mb-12">
I&apos;m open to new opportunities and collaborations. Whether you have a project in
mind or just want to connect feel free to reach out.
</p>
<form onSubmit={handleSubmit} className="space-y-5 mb-12">
<div className="grid sm:grid-cols-2 gap-5">
<div>
<label htmlFor="name" className="block text-sm text-zinc-400 mb-2 font-medium">
Name
</label>
<input
id="name"
type="text"
required
value={form.name}
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
placeholder="Your name"
className={inputClass}
/>
</div>
<div>
<label htmlFor="email" className="block text-sm text-zinc-400 mb-2 font-medium">
Email
</label>
<input
id="email"
type="email"
required
value={form.email}
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
placeholder="your@email.com"
className={inputClass}
/>
</div>
</div>
<div>
<label htmlFor="message" className="block text-sm text-zinc-400 mb-2 font-medium">
Message
</label>
<textarea
id="message"
required
rows={5}
value={form.message}
onChange={(e) => setForm((f) => ({ ...f, message: e.target.value }))}
placeholder="What's on your mind?"
className={`${inputClass} resize-none`}
/>
</div>
<button
type="submit"
disabled={status !== "idle"}
className="w-full py-3.5 bg-orange-500 hover:bg-orange-400 disabled:opacity-60 text-white font-semibold rounded-lg transition-all duration-200 hover:shadow-[0_0_25px_rgba(249,115,22,0.3)] active:scale-[0.99]"
>
{status === "opening" ? "Opening mail client…" : "Send Message"}
</button>
</form>
<div className="flex justify-center gap-8">
{socials.map(({ label, href, Icon }) => (
<a
key={label}
href={href}
target={href.startsWith("mailto") ? undefined : "_blank"}
rel="noopener noreferrer"
className="flex items-center gap-2 text-zinc-500 hover:text-orange-400 transition-colors text-sm"
>
<Icon />
<span>{label}</span>
</a>
))}
</div>
</div>
<div className="mt-20 pt-8 border-t border-zinc-900 text-center">
<p className="font-mono text-zinc-600 text-xs">
Built with Next.js &amp; Tailwind CSS Designed &amp; Developed by Khaalid.M.N Baccar
</p>
</div>
</SectionWrapper>
);
}