feat: restyle experience section

This commit is contained in:
Alvalens 2025-05-26 23:09:29 +07:00
parent 661482c405
commit bf31fda249

View file

@ -1,5 +1,70 @@
"use client";
import Hr from "@/components/Hr";
import { motion } from "framer-motion";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
const experiences = [
{
id: 1,
period: "2023 - Present",
startDate: "Jan 2023",
endDate: "Present",
company: "PT Hafdzamedia Teknologi Aplikasi",
position: "Fullstack Web Developer",
type: "Part-time",
description:
"Responsible for building and maintaining scalable web applications using the latest technologies. Collaborated with cross-functional teams to deliver high-quality solutions that meet business requirements.",
skills: ["React", "Next.js", "Laravel", "MySql"],
},
{
id: 2,
period: "2024 - Present",
startDate: "Mar 2024",
endDate: "Present",
company: "Outlier",
position: "AI Trainer",
type: "Freelance",
description:
"Crafted and refined chatbot prompts using prompt engineering and RLHF techniques to improve the AI responses in English and Indonesian, leading to more accurate and contextually relevant interactions.",
skills: ["Generative AI", "Prompt Engineering", "Problem Solving"],
},
{
id: 3,
period: "2023 - Present",
startDate: "Jun 2023",
endDate: "Present",
company: "Self-Employed",
position: "Web Developer",
type: "Freelance",
description:
"Specialized in crafting innovative solutions using Laravel and Next.js, enhancing client satisfaction and project efficiency. Contributed expertise in both frontend and backend development during collaborative team projects.",
skills: ["React", "Next.js", "Laravel", "MySql"],
},
{
id: 4,
period: "2022 - 2023",
startDate: "Sep 2022",
endDate: "Aug 2023",
company: "TechStart Solutions",
position: "Frontend Developer",
type: "Internship",
description:
"Developed responsive user interfaces using React and TypeScript. Worked closely with UX designers to implement pixel-perfect designs and improved website performance by 40%.",
skills: ["React", "TypeScript", "CSS3", "Figma"],
},
{
id: 5,
period: "2021 - 2022",
startDate: "Oct 2021",
endDate: "Sep 2022",
company: "Digital Innovation Labs",
position: "Junior Developer",
type: "Contract",
description:
"Built REST APIs using Node.js and Express. Implemented database schemas and optimized queries for better performance. Participated in code reviews and agile development processes.",
skills: ["Node.js", "Express", "MongoDB", "Git"],
},
];
function Title() {
return (
@ -18,7 +83,6 @@ function Title() {
}}
transition={{
delay: 0.7,
type: "spring",
}}>
Profesional Experience
@ -28,135 +92,194 @@ function Title() {
);
}
function TimelineCard({ experience, index, isEven }) {
return (
<motion.div
initial={{ opacity: 0, y: -20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.15, duration: 0.5 }}
className={`flex ps-5 md:ps-0 ${
isEven
? "md:justify-center md:translate-x-60"
: "md:justify-center md:-translate-x-60"
} justify-center mb-4`}>
<div className="bg-gradient-to-r from-black to-gray-800 text-white px-12 py-3 rounded-xl shadow-lg border border-gray-600 min-w-max">
<div className="flex items-center justify-center gap-6">
<div className="text-center">
<div className="text-sm font-bold">{experience.startDate}</div>
<div className="text-xs text-gray-300">Start</div>
</div>
<div className="w-px h-8 bg-gray-500"></div>
<div className="text-center">
<div className="text-sm font-bold">{experience.endDate}</div>
<div className="text-xs text-gray-300">End</div>
</div>
<div className="w-px h-8 bg-gray-500"></div>
<div className="text-center">
<div className="text-sm font-medium text-gray-400">
{experience.type}
</div>
<div className="text-xs text-gray-300">Type</div>
</div>
</div>
</div>
</motion.div>
);
}
function ExperienceCard({ experience, index, isEven }) {
return (
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.2, duration: 0.6 }}
className={`relative group ${
isEven ? "md:ml-auto md:pl-12" : "md:mr-auto md:pr-12"
} md:w-1/2`}>
{" "}
{/* Card */}
<div
className={`bg-white/20 backdrop-blur-sm border border-gray-300/30 rounded-2xl p-6 shadow-lg
hover:shadow-xl hover:bg-white/30 transition-all duration-300 ml-12 md:ml-0`}>
{/* Company & Position */}
<div className="mb-4">
<h3 className="font-bold text-xl text-black mb-1">
{experience.company}
</h3>
<h4 className="font-medium text-lg text-gray-700">
{experience.position}
<span className="text-sm font-normal text-gray-500 ml-2">
• {experience.type}
</span>
</h4>
</div>
{/* Description */}
<p className="text-gray-600 text-justify leading-relaxed mb-4">
{experience.description}
</p>
{/* Skills */}
<div className="flex flex-wrap gap-2">
{experience.skills.map((skill, idx) => (
<span
key={idx}
className="bg-gray-200/60 hover:bg-gray-300/60 border border-gray-400/40 text-black px-3 py-1 rounded-full text-sm font-medium transition-all duration-300 backdrop-blur-sm hover:scale-105">
{skill}
</span>
))}
</div>
</div>
</motion.div>
);
}
function Wrapper({ children }) {
return (
<div className="mx-auto container gap-10 p-10 grid grid-cols-1 my-10 ">
<motion.div
className="flex justify-center items-start flex-col mb-5 "
initial={{
opacity: 0,
x: -200,
}}
whileInView={{
opacity: 1,
x: 0,
}}
transition={{
delay: 0.5,
type: "spring",
}}>
<section className="grid gap-8 md:gap-12">
<div className="relative after:absolute after:inset-y-2 after:w-1 after:bg-gray-700 after:left-[-4px] md:after:left-1 lg:after:left-2 xl:after:left-3 grid gap-8 md:gap-12 pl-6 md:pl-8">
<div className="mx-auto container px-6 py-10">
<div
className="flex justify-center items-center flex-col">
{children}
</div>
</section>
</motion.div>
</div>
);
}
export default function Experience() {
const [showAll, setShowAll] = useState(false);
const displayedExperiences = showAll ? experiences : experiences.slice(0, 3);
return (
<>
<Title />
<Wrapper>
<div className="grid gap-4 relative">
<div className="aspect-square w-5 bg-black rounded-full absolute left-0 translate-x-[-29.5px] z-10" />
<div className="font-medium text-lg">2023 - Present</div>
<div>
<h3 className="font-semibold text-xl text-black">
PT Hafdzamedia Teknologi Aplikasi
</h3>
<h4 className=" font-light text-md mb-4">
Fullstack Web Developer | Part-time
</h4>
<p className="text-justify">
Responsible for building and maintaining scalable
web applications using the latest technologies.
Collaborated with cross-functional teams to deliver
high-quality solutions that meet business
requirements.
</p>
<div className="flex flex-wrap gap-2 mt-4 text-sm">
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
React
</div>
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
Next.js
</div>
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
Laravel
</div>
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
MySql
</div>
</div>
</div>
</div>
<div className="grid gap-4 relative">
<div className="aspect-square w-5 bg-black rounded-full absolute left-0 translate-x-[-29.5px] z-10" />
<div className="font-medium text-lg">2024 - Present</div>
<div>
<h3 className="font-semibold text-xl text-black">
Outlier
</h3>
<h4 className=" font-light text-md mb-4">
AI Trainer | Freelance
</h4>
<p className="text-justify">
Crafted and refined chatbot prompts using prompt
engineering and RLHF techniques to improve the AI
responses in English and Indonesian, leading to more
accurate and contextually relevant interactions.
</p>
{" "}
<div className="relative w-full max-w-6xl mx-auto">
{" "}
{/* Timeline line - hidden on mobile, visible on md+ */}
<div className="hidden md:block absolute left-1/2 transform -translate-x-1/2 w-1 bg-gradient-to-b from-black via-gray-400 to-transparent h-full"></div>
{/* Mobile timeline line */}
<div className="md:hidden absolute left-0 w-1 bg-gradient-to-b from-black via-gray-400 to-transparent h-full"></div>{" "}
{/* Experience cards */}
<div className="space-y-12 md:space-y-16 relative">
<AnimatePresence>
{displayedExperiences.map((experience, index) => (
<div key={experience.id} className="relative">
{/* Timeline period card - flows naturally above content */}
<TimelineCard
experience={experience}
index={index}
isEven={index % 2 === 1}
/>
<div className="flex flex-wrap gap-2 mt-4 text-sm">
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
Generative AI
</div>
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
Prompt Engineering
</div>
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
Problem Solving
</div>
</div>
</div>
</div>
<div className="grid gap-4 relative">
<div className="aspect-square w-5 bg-black rounded-full absolute left-0 translate-x-[-29.5px] z-10" />
<div className="font-medium text-lg">2023 - Present</div>
<div>
<h3 className="font-semibold text-xl text-black">
Self-Employed
</h3>
<h4 className=" font-light text-md mb-4">
Web Developer | Freelance
</h4>
<p className="text-justify">
Specialized in crafting innovative solutions using
Laravel and Next.js, enhancing client satisfaction
and project efficiency. Contributed expertise in
both frontend and backend development during
collaborative team projects, leading to successful
project completions
</p>
<div className="flex flex-wrap gap-2 mt-4 text-sm">
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
React
</div>
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
Next.js
</div>
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
Laravel
</div>
<div className="bg-gray-300 text-black px-2 py-1 rounded-2xl">
MySql
</div>
{/* Timeline dot - positioned at the start of the experience card */}
<div
className={`absolute w-6 h-6 bg-black rounded-full border-4 border-white shadow-lg z-30
md:left-1/2 md:-translate-x-1/2 md:top-4
left-0 -translate-x-1/2 top-2`}
/>
{/* Experience content card */}
<ExperienceCard
experience={experience}
index={index}
isEven={index % 2 === 1}
/>
</div>
))}
</AnimatePresence>
</div>
{/* Expand/Collapse button */}
{experiences.length > 3 && (
<motion.div
className="flex justify-center mt-12"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.5 }}>
<button
onClick={() => setShowAll(!showAll)}
className="bg-black hover:bg-gray-800 text-white px-8 py-3 rounded-full font-medium
transition-all duration-300 hover:scale-105 shadow-lg flex items-center gap-2">
{showAll ? (
<>
Show Less
<svg
className="w-4 h-4 transform rotate-180"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</>
) : (
<>
View More Experience
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</>
)}
</button>
</motion.div>
)}{" "}
{/* Gradient fade effect at bottom */}
{!showAll && (
<div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-stale-300 to-transparent pointer-events-none"></div>
)}
</div>
</Wrapper>
</>