414 lines
18 KiB
TypeScript
414 lines
18 KiB
TypeScript
"use client";
|
||
|
||
import SafeImage from "./SafeImage";
|
||
|
||
interface Task {
|
||
name: string;
|
||
description: string;
|
||
status: string;
|
||
technologies: string[];
|
||
}
|
||
|
||
interface ProjectLink {
|
||
name: string;
|
||
url: string;
|
||
}
|
||
|
||
interface Project {
|
||
name: string;
|
||
description: string;
|
||
logo?: string;
|
||
links?: ProjectLink[];
|
||
tasks: Task[];
|
||
}
|
||
|
||
interface Experience {
|
||
company: string;
|
||
position: string;
|
||
startDate: string;
|
||
endDate: string;
|
||
logo?: string;
|
||
}
|
||
|
||
interface Education {
|
||
degree: string;
|
||
school: string;
|
||
startDate: string;
|
||
endDate: string;
|
||
}
|
||
|
||
interface HeroProps {
|
||
name: string;
|
||
lastName: string;
|
||
title: string;
|
||
description: string;
|
||
skills: string[];
|
||
projects: Project[];
|
||
experiences: Experience[];
|
||
education: Education[];
|
||
languages: { name: string; level: string }[];
|
||
interests: string[];
|
||
location: string;
|
||
}
|
||
|
||
// ─── BentoCard ───────────────────────────────────────────────────────────────
|
||
|
||
function BentoCard({
|
||
num,
|
||
label,
|
||
href,
|
||
children,
|
||
accent = false,
|
||
delay = 0,
|
||
}: {
|
||
num: string;
|
||
label: string;
|
||
href: string;
|
||
children: React.ReactNode;
|
||
accent?: boolean;
|
||
delay?: number;
|
||
}) {
|
||
return (
|
||
<a
|
||
href={href}
|
||
style={{
|
||
animationName: "fadeUp",
|
||
animationDuration: "0.7s",
|
||
animationTimingFunction: "ease",
|
||
animationFillMode: "both",
|
||
animationDelay: `${delay}ms`,
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
padding: "1.75rem",
|
||
border: "1px solid #1c1f26",
|
||
background: accent ? "rgba(200,169,110,0.03)" : "#0e1014",
|
||
textDecoration: "none",
|
||
color: "inherit",
|
||
position: "relative",
|
||
overflow: "hidden",
|
||
cursor: "pointer",
|
||
transition: "border-color 0.3s ease, background 0.3s ease, transform 0.3s ease",
|
||
minHeight: "200px",
|
||
}}
|
||
onMouseEnter={(e) => {
|
||
const el = e.currentTarget as HTMLElement;
|
||
el.style.borderColor = "#6b5730";
|
||
el.style.background = accent ? "rgba(200,169,110,0.07)" : "rgba(200,169,110,0.03)";
|
||
el.style.transform = "translateY(-2px)";
|
||
const arrow = el.querySelector(".card-arrow") as HTMLElement | null;
|
||
if (arrow) arrow.style.opacity = "1";
|
||
const shine = el.querySelector(".card-shine") as HTMLElement | null;
|
||
if (shine) shine.style.opacity = "1";
|
||
}}
|
||
onMouseLeave={(e) => {
|
||
const el = e.currentTarget as HTMLElement;
|
||
el.style.borderColor = "#1c1f26";
|
||
el.style.background = accent ? "rgba(200,169,110,0.03)" : "#0e1014";
|
||
el.style.transform = "translateY(0)";
|
||
const arrow = el.querySelector(".card-arrow") as HTMLElement | null;
|
||
if (arrow) arrow.style.opacity = "0";
|
||
const shine = el.querySelector(".card-shine") as HTMLElement | null;
|
||
if (shine) shine.style.opacity = "0";
|
||
}}
|
||
>
|
||
<div
|
||
className="card-shine"
|
||
style={{
|
||
position: "absolute",
|
||
inset: 0,
|
||
background: "linear-gradient(135deg, rgba(200,169,110,0.06) 0%, transparent 50%)",
|
||
opacity: 0,
|
||
transition: "opacity 0.3s",
|
||
pointerEvents: "none",
|
||
}}
|
||
/>
|
||
|
||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "auto" }}>
|
||
<div style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.58rem", letterSpacing: "0.2em", color: "#c8a96e", opacity: 0.7 }}>
|
||
{num} · {label}
|
||
</div>
|
||
<div className="card-arrow" style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.75rem", color: "#c8a96e", opacity: 0, transition: "opacity 0.2s" }}>
|
||
↗
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ marginTop: "1rem", flex: 1 }}>{children}</div>
|
||
</a>
|
||
);
|
||
}
|
||
|
||
// ─── Hero ─────────────────────────────────────────────────────────────────────
|
||
|
||
export default function Hero({
|
||
name,
|
||
lastName,
|
||
title,
|
||
description,
|
||
skills,
|
||
projects,
|
||
experiences,
|
||
education,
|
||
languages,
|
||
interests,
|
||
location,
|
||
}: HeroProps) {
|
||
|
||
const feat1 = projects[2]; // FOON
|
||
const feat2 = projects[0]; // Gazelle
|
||
const topSkills = skills.slice(0, 10);
|
||
|
||
const levelColor = (level: string) => {
|
||
if (level === "Native") return "#c8a96e";
|
||
if (level === "C2") return "#3a7a5a";
|
||
return "#4a6050";
|
||
};
|
||
|
||
return (
|
||
<section
|
||
id="hero"
|
||
className="hero-section"
|
||
style={{ minHeight: "100vh", position: "relative" }}
|
||
>
|
||
<div style={{ maxWidth: "1200px", margin: "0 auto", position: "relative", zIndex: 3 }}>
|
||
|
||
{/* ── Headline ─────────────────────────────────────────────────── */}
|
||
<div
|
||
className="animate-fade-up delay-1"
|
||
style={{ marginBottom: "2.5rem" }}
|
||
>
|
||
<h1
|
||
style={{
|
||
fontFamily: "var(--font-lora), Georgia, serif",
|
||
fontStyle: "italic",
|
||
fontWeight: 400,
|
||
fontSize: "clamp(2.6rem, 7vw, 7rem)",
|
||
lineHeight: 1.05,
|
||
letterSpacing: "-0.01em",
|
||
color: "#e2e4e9",
|
||
margin: 0,
|
||
userSelect: "none",
|
||
}}
|
||
>
|
||
Let's bend
|
||
<br />
|
||
<span style={{ color: "#c8a96e" }}>spacetime</span>
|
||
</h1>
|
||
</div>
|
||
|
||
{/* ── Name + description strip ──────────────────────────────── */}
|
||
<div className="hero-header-strip animate-fade-up delay-2">
|
||
<div>
|
||
<div
|
||
className="section-label"
|
||
style={{ marginBottom: "0.5rem", display: "flex", alignItems: "center", gap: "0.6rem" }}
|
||
>
|
||
<span style={{ display: "inline-block", width: "24px", height: "1px", background: "#c8a96e" }} />
|
||
{title}
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontFamily: "var(--font-bebas), Impact, sans-serif",
|
||
fontSize: "clamp(1.6rem, 4vw, 3rem)",
|
||
letterSpacing: "0.04em",
|
||
color: "#6b7280",
|
||
lineHeight: 1,
|
||
}}
|
||
>
|
||
{name}{" "}
|
||
<span style={{ color: "#4a5060" }}>{lastName}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ maxWidth: "360px" }}>
|
||
<p style={{ fontFamily: "var(--font-lora), serif", fontSize: "0.88rem", lineHeight: 1.75, color: "#6b7280", margin: 0 }}>
|
||
{description}
|
||
</p>
|
||
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem", marginTop: "0.6rem" }}>
|
||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#4a5060" strokeWidth="2">
|
||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
|
||
<circle cx="12" cy="10" r="3" />
|
||
</svg>
|
||
<span style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.62rem", letterSpacing: "0.1em", color: "#4a5060" }}>
|
||
{location}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── Divider ───────────────────────────────────────────────── */}
|
||
<div
|
||
className="animate-fade-up delay-3"
|
||
style={{ height: "1px", background: "#1c1f26", marginBottom: "2rem" }}
|
||
/>
|
||
|
||
{/* ── 3 × 2 Bento grid ──────────────────────────────────────── */}
|
||
<div className="hero-bento-grid">
|
||
|
||
{/* 01 — FOON */}
|
||
<BentoCard num="01" label="TypeScript SDK" href="#projects" accent delay={500}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem", marginBottom: "0.75rem" }}>
|
||
{feat1.logo && (
|
||
<SafeImage src={feat1.logo} alt={feat1.name} width={28} height={28}
|
||
style={{ width: "22px", height: "22px", objectFit: "contain", filter: "brightness(0.9)" }}
|
||
/>
|
||
)}
|
||
<h3 style={{ fontFamily: "var(--font-bebas), sans-serif", fontSize: "1.8rem", letterSpacing: "0.04em", color: "#e2e4e9", lineHeight: 1 }}>
|
||
{feat1.name}
|
||
</h3>
|
||
</div>
|
||
<p style={{ fontFamily: "var(--font-lora), serif", fontSize: "0.82rem", lineHeight: 1.65, color: "#6b7280", marginBottom: "1rem" }}>
|
||
{feat1.description}
|
||
</p>
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: "0.35rem", marginBottom: "0.75rem" }}>
|
||
{feat1.tasks.flatMap((t) => t.technologies).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).map((tech) => (
|
||
<span key={tech} className="tech-tag">{tech}</span>
|
||
))}
|
||
</div>
|
||
{feat1.links && (
|
||
<div style={{ display: "flex", gap: "0.6rem", flexWrap: "wrap", marginTop: "auto" }}>
|
||
{feat1.links.slice(0, 2).map((l) => (
|
||
<span key={l.name} style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.6rem", letterSpacing: "0.1em", color: "#c8a96e", textTransform: "uppercase" }}>
|
||
↗ {l.name}
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
</BentoCard>
|
||
|
||
{/* 02 — Gazelle */}
|
||
<BentoCard num="02" label="Java · Healthcare" href="#projects" delay={600}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem", marginBottom: "0.75rem" }}>
|
||
{feat2.logo && (
|
||
<SafeImage src={feat2.logo} alt={feat2.name} width={28} height={28}
|
||
fallbackLabel={feat2.name.slice(0, 2).toUpperCase()}
|
||
style={{ width: "22px", height: "22px", objectFit: "contain", filter: "brightness(0.75) grayscale(0.3)" }}
|
||
/>
|
||
)}
|
||
<h3 style={{ fontFamily: "var(--font-bebas), sans-serif", fontSize: "1.8rem", letterSpacing: "0.04em", color: "#e2e4e9", lineHeight: 1 }}>
|
||
{feat2.name}
|
||
</h3>
|
||
</div>
|
||
<p style={{ fontFamily: "var(--font-lora), serif", fontSize: "0.82rem", lineHeight: 1.65, color: "#6b7280", marginBottom: "1rem" }}>
|
||
{feat2.description}
|
||
</p>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "0.4rem" }}>
|
||
{feat2.tasks.map((task) => (
|
||
<div key={task.name} style={{ display: "flex", alignItems: "center", gap: "0.6rem" }}>
|
||
<span style={{ width: "5px", height: "5px", borderRadius: "50%", background: task.status === "Done" ? "#3a7a5a" : "#c8954a", flexShrink: 0 }} />
|
||
<span style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.62rem", color: "#6b7280", letterSpacing: "0.04em" }}>
|
||
{task.name}
|
||
</span>
|
||
<span className={task.status === "Done" ? "status-done" : "status-progress"}>{task.status}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</BentoCard>
|
||
|
||
{/* 03 — Experience */}
|
||
<BentoCard num="03" label="Career" href="#experience" delay={700}>
|
||
<h3 style={{ fontFamily: "var(--font-bebas), sans-serif", fontSize: "1.8rem", letterSpacing: "0.04em", color: "#e2e4e9", lineHeight: 1, marginBottom: "1rem" }}>
|
||
Experience
|
||
</h3>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "0.65rem" }}>
|
||
{experiences.map((exp, i) => (
|
||
<div key={exp.company + exp.position} style={{ display: "flex", alignItems: "flex-start", gap: "0.75rem", opacity: i === 0 ? 1 : i === 1 ? 0.6 : 0.35 }}>
|
||
<div style={{ width: "6px", height: "6px", borderRadius: "50%", background: i === 0 ? "#c8a96e" : "#1c1f26", border: i === 0 ? "none" : "1px solid #4a5060", marginTop: "5px", flexShrink: 0 }} />
|
||
<div>
|
||
<div style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.7rem", color: i === 0 ? "#e2e4e9" : "#6b7280", letterSpacing: "0.04em", lineHeight: 1.3 }}>
|
||
{exp.position}
|
||
</div>
|
||
<div style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.58rem", letterSpacing: "0.12em", color: i === 0 ? "#c8a96e" : "#4a5060", textTransform: "uppercase", marginTop: "1px" }}>
|
||
{exp.company} · {exp.endDate === "current" ? "Present" : exp.endDate.split("-")[2]}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</BentoCard>
|
||
|
||
{/* 04 — Education */}
|
||
<BentoCard num="04" label="Academic" href="#education" delay={800}>
|
||
<h3 style={{ fontFamily: "var(--font-bebas), sans-serif", fontSize: "1.8rem", letterSpacing: "0.04em", color: "#e2e4e9", lineHeight: 1, marginBottom: "1rem" }}>
|
||
Education
|
||
</h3>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "0.85rem" }}>
|
||
{education.map((edu, i) => (
|
||
<div key={edu.degree} style={{ borderLeft: "2px solid #1c1f26", paddingLeft: "0.75rem" }}>
|
||
<div style={{ fontFamily: "var(--font-bebas), sans-serif", fontSize: "0.88rem", letterSpacing: "0.06em", color: i === 0 ? "#e2e4e9" : "#6b7280", lineHeight: 1.3, marginBottom: "0.2rem" }}>
|
||
{edu.degree}
|
||
</div>
|
||
<div style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.58rem", letterSpacing: "0.1em", color: "#4a5060", textTransform: "uppercase" }}>
|
||
{edu.school.split(" ").slice(-2).join(" ")} · {edu.endDate}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</BentoCard>
|
||
|
||
{/* 05 — Skills */}
|
||
<BentoCard num="05" label="Stack" href="#skills" delay={900}>
|
||
<h3 style={{ fontFamily: "var(--font-bebas), sans-serif", fontSize: "1.8rem", letterSpacing: "0.04em", color: "#e2e4e9", lineHeight: 1, marginBottom: "1rem" }}>
|
||
{skills.length} Skills
|
||
</h3>
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: "0.35rem" }}>
|
||
{topSkills.map((skill, i) => (
|
||
<span key={skill} className="tech-tag" style={{ opacity: 1 - i * 0.06, fontSize: i < 3 ? "0.68rem" : "0.6rem" }}>
|
||
{skill}
|
||
</span>
|
||
))}
|
||
{skills.length > 10 && (
|
||
<span className="tech-tag" style={{ color: "#c8a96e", borderColor: "#6b5730" }}>
|
||
+{skills.length - 10}
|
||
</span>
|
||
)}
|
||
</div>
|
||
</BentoCard>
|
||
|
||
{/* 06 — About */}
|
||
<BentoCard num="06" label="Profile" href="#contact" accent delay={1000}>
|
||
<h3 style={{ fontFamily: "var(--font-bebas), sans-serif", fontSize: "1.8rem", letterSpacing: "0.04em", color: "#e2e4e9", lineHeight: 1, marginBottom: "1rem" }}>
|
||
About
|
||
</h3>
|
||
<div style={{ marginBottom: "0.85rem" }}>
|
||
<div style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.56rem", letterSpacing: "0.18em", color: "#4a5060", textTransform: "uppercase", marginBottom: "0.45rem" }}>
|
||
Languages
|
||
</div>
|
||
<div style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap" }}>
|
||
{languages.map((lang) => (
|
||
<span key={lang.name} style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.62rem", padding: "2px 8px", border: `1px solid ${levelColor(lang.level)}`, color: levelColor(lang.level), letterSpacing: "0.06em" }}>
|
||
{lang.name} {lang.level}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.56rem", letterSpacing: "0.18em", color: "#4a5060", textTransform: "uppercase", marginBottom: "0.45rem" }}>
|
||
Interests
|
||
</div>
|
||
<div style={{ display: "flex", flexWrap: "wrap", gap: "0.35rem" }}>
|
||
{interests.map((interest, i) => (
|
||
<span key={interest} style={{ fontFamily: "var(--font-lora), serif", fontSize: "0.78rem", fontStyle: "italic", color: "#6b7280" }}>
|
||
{interest}{i < interests.length - 1 && <span style={{ color: "#1c1f26", marginLeft: "0.35rem" }}>·</span>}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</BentoCard>
|
||
</div>
|
||
|
||
{/* ── Scroll hint ───────────────────────────────────────────── */}
|
||
<div
|
||
className="animate-fade-up delay-6"
|
||
style={{ marginTop: "2rem", display: "flex", alignItems: "center", justifyContent: "center", gap: "0.75rem" }}
|
||
>
|
||
<div style={{ height: "1px", width: "40px", background: "#1c1f26" }} />
|
||
<span style={{ fontFamily: "var(--font-jetbrains), monospace", fontSize: "0.58rem", letterSpacing: "0.2em", color: "#4a5060", textTransform: "uppercase" }}>
|
||
Scroll to explore
|
||
</span>
|
||
<div style={{ height: "1px", width: "40px", background: "#1c1f26" }} />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|