feat: consolidate homelab projects, add skills dropdown, update timeline animations
Build & Push Portfolio Image / build-push (push) Successful in 2m33s
Build & Push Portfolio Image / build-push (push) Successful in 2m33s
- Consolidate 3 homelab projects into single 'Self-Hosted Cloud' card - Add /homelab deep dive page with full tech stack - Update to 4 nodes (3 control plane + 1 worker) - Add organized skills dropdown (6 categories) - Update timeline skills for riotpiao.com experience - Add 'Learn more' links for all major experiences - Change timeline animation to scroll-reveal (y-axis)
This commit is contained in:
@@ -12,7 +12,7 @@ const colors = [
|
||||
]
|
||||
|
||||
const skills = [
|
||||
['LangGraph', 'Temporal', 'LLM Ops', 'PyTorch', 'Kafka', 'Observability', 'Go-Flink'],
|
||||
['Kubernetes', 'Talos', 'ArgoCD', 'Terraform', 'Authentik', 'Prometheus', 'Grafana', 'vLLM', 'Temporal', 'Kafka', 'PostgreSQL', 'Go'],
|
||||
['Golang', 'Terraform', 'IaC', 'OpenShift', 'Docker', 'Distributed Systems', 'Grafana'],
|
||||
['Java', 'AWS', 'DynamoDB', 'CloudWatch', 'gRPC', 'Distributed Systems', 'Ownership', 'Disaster Recovery', 'Observability'],
|
||||
['C++', 'CMake', 'Golang', 'Docker'],
|
||||
@@ -20,7 +20,7 @@ const skills = [
|
||||
]
|
||||
|
||||
const links = [
|
||||
null,
|
||||
'#project-homelab', // Homelab - link to homelab project showcase
|
||||
'#project-rbc', // RBC - link to RBC project showcase
|
||||
'#project-aws', // AWS - link to AWS project showcase
|
||||
null,
|
||||
@@ -28,29 +28,19 @@ const links = [
|
||||
]
|
||||
|
||||
const linkTexts = [
|
||||
'',
|
||||
'Learn more →',
|
||||
'Learn more →',
|
||||
'Learn more →',
|
||||
'',
|
||||
'View Product →',
|
||||
]
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, x: -20 },
|
||||
hidden: { opacity: 0, y: 40 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: { duration: 0.6, ease: 'easeOut' },
|
||||
y: 0,
|
||||
transition: { duration: 0.5, ease: 'easeOut' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -73,13 +63,7 @@ export function ExperienceTimeline() {
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="relative">
|
||||
{/* Timeline line */}
|
||||
<div className="absolute left-0 md:left-1/2 top-0 bottom-0 w-1 bg-gradient-to-b from-blue-500 via-purple-500 to-green-500 md:transform md:-translate-x-1/2" />
|
||||
|
||||
@@ -89,6 +73,9 @@ export function ExperienceTimeline() {
|
||||
<motion.div
|
||||
key={item.company}
|
||||
variants={itemVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
className="relative pl-8 md:pl-0"
|
||||
>
|
||||
{/* Timeline dot */}
|
||||
@@ -138,7 +125,7 @@ export function ExperienceTimeline() {
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
+30
-4
@@ -70,8 +70,21 @@ export default function Header() {
|
||||
{t.header.nav.skills}
|
||||
</button>
|
||||
{skillsOpen && (
|
||||
<div className="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg p-4 space-y-2">
|
||||
<div className="text-xs font-semibold text-gray-600 dark:text-gray-400">{t.header.skillsPlaceholder}</div>
|
||||
<div className="absolute right-0 mt-2 w-80 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg p-4">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{Object.entries(t.header.skills).map(([key, category]) => (
|
||||
<div key={key}>
|
||||
<div className="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1">{(category as {title: string}).title}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{(category as {items: string[]}).items.map((skill) => (
|
||||
<span key={skill} className="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -234,8 +247,21 @@ export default function Header() {
|
||||
{t.header.nav.skills}
|
||||
</button>
|
||||
{skillsOpen && (
|
||||
<div className="bg-gray-50 dark:bg-gray-800 rounded p-3 mt-2 space-y-2">
|
||||
<div className="text-xs font-semibold text-gray-600 dark:text-gray-400">{t.header.skillsPlaceholder}</div>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 rounded p-3 mt-2">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{Object.entries(t.header.skills).map(([key, category]) => (
|
||||
<div key={key}>
|
||||
<div className="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1">{(category as {title: string}).title}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{(category as {items: string[]}).items.map((skill) => (
|
||||
<span key={skill} className="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user