revamp
This commit is contained in:
+326
-110
@@ -1,6 +1,16 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Github, Linkedin, Mail, ChevronDown, Code, Database, BarChart3, Globe, Award, GraduationCap, Image, ExternalLink as LinkIcon } from 'lucide-react';
|
import { Github, Linkedin, Mail, ChevronDown, Code, Database, BarChart3, Globe, Award, GraduationCap, Image, ExternalLink as LinkIcon, Server, Shield, Settings, Briefcase, Calendar } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Timeline,
|
||||||
|
TimelineContent,
|
||||||
|
TimelineDate,
|
||||||
|
TimelineHeader,
|
||||||
|
TimelineIndicator,
|
||||||
|
TimelineItem,
|
||||||
|
TimelineSeparator,
|
||||||
|
TimelineTitle,
|
||||||
|
} from "@/components/ui/timeline"
|
||||||
|
|
||||||
const Portfolio = () => {
|
const Portfolio = () => {
|
||||||
const [activeSection, setActiveSection] = useState('home');
|
const [activeSection, setActiveSection] = useState('home');
|
||||||
@@ -35,81 +45,143 @@ const Portfolio = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const skills = {
|
const skills = {
|
||||||
'Data & Analytics': ['Python', 'R', 'SQL', 'Statistical Analysis', 'Data Visualization', 'Business Intelligence'],
|
'System Administration': ['Linux/Windows Server', 'Active Directory', 'Group Policy', 'Office 365', 'PowerShell', 'Bash Scripting'],
|
||||||
'Web Development': ['JavaScript', 'TypeScript', 'React', 'Next.js', 'HTML5', 'CSS3', 'Tailwind CSS'],
|
'Virtualization & Cloud': ['Proxmox VE', 'Hyper-V', 'Docker', 'LXC', 'Infrastructure as Code', 'NGINX'],
|
||||||
'Backend & Tools': ['Flask', 'Express', 'MongoDB', 'SQLite', 'Git', 'Linux'],
|
'Networking & Security': ['VLAN Configuration', 'Firewall Management', 'VPN (Tailscale)', 'Network Troubleshooting', 'DDNS'],
|
||||||
'Other': ['Java', 'LaTeX', 'Project Planning', 'Financial Analysis']
|
'Development & Analytics': ['Python', 'JavaScript', 'React', 'Next.js', 'SQL', 'Data Analysis'],
|
||||||
|
'Monitoring & Tools': ['Grafana', 'Prometheus', 'Git', 'Backup & Recovery', 'RAID Systems']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const experience = [
|
||||||
|
{
|
||||||
|
title: 'Systems Engineering Consultant',
|
||||||
|
company: 'Freelance',
|
||||||
|
period: 'January 2020 - Present',
|
||||||
|
location: 'Ticino, Switzerland',
|
||||||
|
description: 'Delivered end-to-end IT infrastructure and systems engineering services to multiple clients, with a focus on server deployment, virtualization platforms, and network architecture.',
|
||||||
|
achievements: [
|
||||||
|
'Built and maintained 6-10 production-grade servers with 90%+ uptime',
|
||||||
|
'Designed VLAN-based network topologies and firewall policies',
|
||||||
|
'Implemented secure remote access via VPN (Tailscale)',
|
||||||
|
'Created automation scripts for routine maintenance and monitoring',
|
||||||
|
'Managed Windows Server environments with Active Directory integration'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const projects = [
|
const projects = [
|
||||||
{
|
{
|
||||||
title: 'Valgo - Algorithm Visualizer',
|
title: 'Enterprise Proxmox Infrastructure',
|
||||||
period: 'Jun 2024 - Present',
|
period: 'Professional Client Project',
|
||||||
description: 'Interactive web application designed to help users understand and visualize various sorting algorithms. Built with Next.js and React to make learning algorithms more engaging and intuitive.',
|
description: 'Deployed a full single-node virtualization environment using Proxmox VE, with structured rack cabling, DDNS, and NGINX reverse proxy for secure service exposure. Integrated Tailscale VPN for remote management.',
|
||||||
tech: ['Next.js', 'React', 'JavaScript', 'Algorithm Visualization'],
|
tech: ['Proxmox VE', 'NGINX', 'Docker', 'LXC', 'Grafana', 'Tailscale', 'DDNS', 'TrueNAS', 'Linux'],
|
||||||
|
icon: <Server className="w-6 h-6" />,
|
||||||
|
status: 'Production',
|
||||||
|
images: [
|
||||||
|
'https://via.placeholder.com/600x400/434C5E/D8DEE9?text=Proxmox+Dashboard',
|
||||||
|
],
|
||||||
|
features: ['Centralized infrastructure visibility using Grafana', 'Docker containers and LXC instances', 'Nextcloud, Jellyfin, and TrueNAS integration', 'Secure remote access via VPN'],
|
||||||
|
category: 'Infrastructure'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Multi-User NAS Solution',
|
||||||
|
period: 'Professional Client Project',
|
||||||
|
description: 'Designed and deployed a RAID 10-based NAS system supporting 9 users as a self-hosted cloud storage alternative. Configured SMB/NFS shares, access control lists (ACLs), and failover mechanisms.',
|
||||||
|
tech: ['RAID', 'TrueNAS', 'rsync', 'SMB/NFS', 'systemd timers', 'ACLs', 'Linux'],
|
||||||
|
icon: <Database className="w-6 h-6" />,
|
||||||
|
status: 'Production',
|
||||||
|
images: [
|
||||||
|
'https://via.placeholder.com/600x400/434C5E/D8DEE9?text=NAS+Dashboard',
|
||||||
|
],
|
||||||
|
features: ['Automated snapshotting and rsync-based backups', 'High-availability storage solution', 'Multi-user access control', 'Failover mechanisms'],
|
||||||
|
category: 'Storage'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Custom Gaming Systems',
|
||||||
|
period: 'Professional Client Project',
|
||||||
|
description: 'Engineered and delivered 12+ custom PC builds, including high-performance gaming rigs and workstation-grade machines. Focused on component compatibility analysis, PSU sizing, and thermal management.',
|
||||||
|
tech: ['Hardware Engineering', 'BIOS Configuration', 'Thermal Management', 'Performance Tuning'],
|
||||||
|
icon: <Settings className="w-6 h-6" />,
|
||||||
|
status: 'Delivered',
|
||||||
|
images: [
|
||||||
|
'https://via.placeholder.com/600x400/434C5E/D8DEE9?text=Custom+PC+Build',
|
||||||
|
],
|
||||||
|
features: ['Component compatibility analysis', 'PSU sizing and airflow optimization', 'BIOS/driver optimization', 'System benchmarking'],
|
||||||
|
category: 'Hardware'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Personal Homelab Infrastructure',
|
||||||
|
period: 'Personal Project',
|
||||||
|
description: 'Replicated enterprise-level infrastructure architecture in a personal homelab, using Proxmox, Docker, and LXC. Focused on Infrastructure as Code (IaC) principles and container orchestration.',
|
||||||
|
tech: ['Proxmox VE', 'Docker', 'LXC', 'Bash', 'Tailscale', 'Grafana', 'IaC'],
|
||||||
icon: <Code className="w-6 h-6" />,
|
icon: <Code className="w-6 h-6" />,
|
||||||
|
status: 'Ongoing',
|
||||||
|
images: [
|
||||||
|
'https://via.placeholder.com/600x400/434C5E/D8DEE9?text=Homelab+Setup',
|
||||||
|
],
|
||||||
|
features: ['Infrastructure as Code (IaC) principles', 'Virtual machine provisioning', 'Monitoring and container orchestration', 'Standardized deployment procedures'],
|
||||||
|
category: 'Infrastructure'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Valgo - Algorithm Visualizer',
|
||||||
|
period: 'Personal Development Project',
|
||||||
|
description: 'Interactive web application designed to help users understand and visualize various sorting algorithms. Built with Next.js and React to make learning algorithms more engaging and intuitive.',
|
||||||
|
tech: ['Next.js', 'React', 'TypeScript', 'Tailwind CSS'],
|
||||||
|
icon: <Globe className="w-6 h-6" />,
|
||||||
status: 'Live',
|
status: 'Live',
|
||||||
images: [
|
images: [
|
||||||
'valgo.png',
|
'https://via.placeholder.com/600x400/434C5E/D8DEE9?text=Algorithm+Visualizer',
|
||||||
],
|
],
|
||||||
features: ['Real-time algorithm visualization', 'Multiple sorting algorithms', 'Step-by-step breakdown', 'Performance comparison'],
|
features: ['Real-time algorithm visualization', 'Multiple sorting algorithms', 'Step-by-step breakdown', 'Performance comparison'],
|
||||||
github: 'https://github.com/Quinta0/valgo',
|
github: 'https://github.com/Quinta0/valgo',
|
||||||
demo: 'https://quinta0.github.io/valgo/'
|
demo: 'https://quinta0.github.io/valgo/',
|
||||||
|
category: 'Development'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Boston Housing Price Prediction',
|
title: 'Finance Tracker Application',
|
||||||
period: 'May 2024 - Present',
|
period: 'In Development',
|
||||||
description: 'Machine learning model to predict housing prices in the Boston area based on various features such as crime rate, average number of rooms per dwelling, and accessibility to radial highways.',
|
description: 'Designing a simple and intuitive personal finance app built with Django (Python) and SQLite, focused on clarity, control, and peace of mind. Features category-based expense tracking and budget summaries.',
|
||||||
tech: ['Python', 'Machine Learning', 'Data Analysis', 'Statistical Modeling'],
|
tech: ['Django', 'Python', 'SQLite', 'Chart.js', 'HTML/CSS', 'JavaScript'],
|
||||||
icon: <BarChart3 className="w-6 h-6" />,
|
icon: <BarChart3 className="w-6 h-6" />,
|
||||||
status: 'Academic Project',
|
status: 'In Development',
|
||||||
images:[
|
|
||||||
'Housing.jpeg',
|
|
||||||
],
|
|
||||||
features: ['Predictive modeling', 'Feature engineering', 'Data visualization', 'Statistical analysis'],
|
|
||||||
github: 'https://github.com/Quinta0/BostonHousing'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Forest Fire Simulation',
|
|
||||||
period: 'Mar 2024 - Present',
|
|
||||||
description: 'Probability & Statistics course project modeling the spread of forest fires over a grid. Accounts for vegetation types, terrain elevation, wind patterns, humidity levels, and spontaneous ignition.',
|
|
||||||
tech: ['Python', 'Statistical Modeling', 'Simulation', 'Data Science'],
|
|
||||||
icon: <Database className="w-6 h-6" />,
|
|
||||||
status: 'Academic Project',
|
|
||||||
images: [
|
images: [
|
||||||
'forestfire.jpg',
|
'https://via.placeholder.com/600x400/434C5E/D8DEE9?text=Finance+Tracker',
|
||||||
],
|
],
|
||||||
features: ['Monte Carlo simulation', 'Environmental factors modeling', 'Statistical analysis', 'Visualization tools'],
|
features: ['Category-based expense tracking', 'Budget summaries', 'Data visualizations', '50/30/20 budgeting framework'],
|
||||||
github: 'https://github.com/Quinta0/ForestFire'
|
category: 'Development'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const education = [
|
const education = [
|
||||||
{
|
{
|
||||||
institution: 'USI Università della Svizzera italiana',
|
id: 1,
|
||||||
degree: "Bachelor's degree Economics",
|
institution: 'Scuola cantonale di commercio, Bellinzona',
|
||||||
period: 'Sep 2024 - Jun 2027',
|
degree: 'Maturità Cantonale & AFC Economics',
|
||||||
status: 'Current'
|
period: 'September 2018 - June 2022',
|
||||||
|
status: '4.5/6 (Maturità), 5/6 (AFC)',
|
||||||
|
description: 'Completed secondary education with focus on economics and business administration.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
institution: 'USI Università della Svizzera italiana',
|
id: 2,
|
||||||
degree: 'Bachelor of Science - BS Informatics',
|
institution: 'USI Università della Svizzera italiana, Lugano',
|
||||||
period: 'Jan 2022 - Jan 2024',
|
degree: 'Bachelor of Science in Informatics (English Stream)',
|
||||||
status: 'Completed'
|
period: 'September 2022 - June 2024',
|
||||||
|
status: '2 years completed',
|
||||||
|
description: 'Completed comprehensive coursework in software engineering, AGILE methodologies, data structures and algorithms, and database systems.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
institution: 'Scuola cantonale di commercio',
|
id: 3,
|
||||||
degree: 'Maturità Cantonale e AFC Economics',
|
institution: 'USI Università della Svizzera italiana, Lugano',
|
||||||
period: 'Sep 2017 - Jun 2021',
|
degree: "Bachelor's Degree in Economics (English Stream)",
|
||||||
status: 'GPA: 4.5/5'
|
period: 'September 2024 - Present',
|
||||||
|
status: 'Current',
|
||||||
|
description: 'Pursuing coursework in microeconomics, macroeconomics, statistics, accounting, and quantitative methods with particular interest in finance.'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const certificates = [
|
const certificates = [
|
||||||
'Google Data Analytics Professional Certificate',
|
'Google Data Analytics Professional Certificate',
|
||||||
'Google IT Support Professional Certificate',
|
'Google IT Support Professional Certificate'
|
||||||
'IT Security: Defense against the digital dark arts',
|
|
||||||
'Python for Data Science, AI & Development'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const scrollToSection = (sectionId: string) => {
|
const scrollToSection = (sectionId: string) => {
|
||||||
@@ -122,6 +194,7 @@ const Portfolio = () => {
|
|||||||
project: Project;
|
project: Project;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectGallery: React.FC<ProjectGalleryProps> = ({ project, onClose }) => (
|
const ProjectGallery: React.FC<ProjectGalleryProps> = ({ project, onClose }) => (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
@@ -152,6 +225,15 @@ const Portfolio = () => {
|
|||||||
>
|
>
|
||||||
{project.status}
|
{project.status}
|
||||||
</span>
|
</span>
|
||||||
|
<span
|
||||||
|
className="px-2 py-1 rounded text-xs"
|
||||||
|
style={{
|
||||||
|
backgroundColor: nordColors.accent,
|
||||||
|
color: nordColors.bg
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{project.category}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -270,7 +352,7 @@ const Portfolio = () => {
|
|||||||
Pietro Quintavalle
|
Pietro Quintavalle
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden md:flex space-x-8">
|
<div className="hidden md:flex space-x-8">
|
||||||
{['Home', 'About', 'Projects', 'Education', 'Contact'].map((item) => (
|
{['Home', 'About', 'Experience', 'Projects', 'Education', 'Contact'].map((item) => (
|
||||||
<button
|
<button
|
||||||
key={item}
|
key={item}
|
||||||
onClick={() => scrollToSection(item.toLowerCase())}
|
onClick={() => scrollToSection(item.toLowerCase())}
|
||||||
@@ -296,10 +378,12 @@ const Portfolio = () => {
|
|||||||
<span className="ml-4" style={{ color: nordColors.accent }}>Quintavalle</span>
|
<span className="ml-4" style={{ color: nordColors.accent }}>Quintavalle</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div className="text-xl md:text-2xl mb-6" style={{ color: nordColors.blue }}>
|
<div className="text-xl md:text-2xl mb-6" style={{ color: nordColors.blue }}>
|
||||||
Economics Student • Data Analytics • Web Development
|
IT Systems Engineer • Economics Student • Infrastructure Specialist
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg max-w-2xl mx-auto leading-relaxed" style={{ color: nordColors.textMuted }}>
|
<p className="text-lg max-w-2xl mx-auto leading-relaxed" style={{ color: nordColors.textMuted }}>
|
||||||
Passionate about the intersection of economics and technology, exploring how data and code can solve real-world problems.
|
Certified IT Support professional with 5+ years of hands-on experience in system administration,
|
||||||
|
virtualization, and network infrastructure. Seeking Junior SysAdmin roles to contribute reliable,
|
||||||
|
scalable solutions in enterprise systems.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -325,13 +409,6 @@ const Portfolio = () => {
|
|||||||
>
|
>
|
||||||
<Mail className="w-6 h-6" style={{ color: nordColors.text }} />
|
<Mail className="w-6 h-6" style={{ color: nordColors.text }} />
|
||||||
</a>
|
</a>
|
||||||
<a
|
|
||||||
href="https://quinta0.github.io/whoami/"
|
|
||||||
className="p-3 rounded-full transition-colors hover:opacity-80"
|
|
||||||
style={{ backgroundColor: nordColors.bgLight }}
|
|
||||||
>
|
|
||||||
<Globe className="w-6 h-6" style={{ color: nordColors.text }} />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -350,31 +427,42 @@ const Portfolio = () => {
|
|||||||
<div className="grid md:grid-cols-2 gap-12 items-center">
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-lg leading-relaxed mb-6" style={{ color: nordColors.text }}>
|
<p className="text-lg leading-relaxed mb-6" style={{ color: nordColors.text }}>
|
||||||
I'm an Economics student at USI with extensive experience in data analytics and web development.
|
I'm an IT Support–certified Economics student with 5+ years of hands-on experience in system
|
||||||
My journey combines analytical thinking with technical skills to create meaningful solutions.
|
administration, virtualization (Proxmox, Hyper-V), and network infrastructure.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg leading-relaxed mb-6" style={{ color: nordColors.text }}>
|
<p className="text-lg leading-relaxed mb-6" style={{ color: nordColors.text }}>
|
||||||
Google-certified in Data Analytics and IT Support, I bring proven expertise in Python, R, SQL,
|
Skilled in scripting, automation, and server deployment across Windows and Linux environments.
|
||||||
and modern web technologies like Next.js and React.
|
I've built and maintained 6-10 production-grade servers with 90%+ uptime using automation tools
|
||||||
|
and Infrastructure as Code (IaC) principles.
|
||||||
|
</p>
|
||||||
|
<p className="text-lg leading-relaxed mb-6" style={{ color: nordColors.text }}>
|
||||||
|
Currently pursuing Economics at USI while continuing to deliver scalable, secure, and
|
||||||
|
high-availability infrastructure solutions to clients.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
<span
|
<span
|
||||||
className="px-3 py-1 rounded-full text-sm"
|
className="px-3 py-1 rounded-full text-sm"
|
||||||
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
|
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
|
||||||
>
|
>
|
||||||
Italian (Native)
|
🇮🇹 Italian (Native)
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="px-3 py-1 rounded-full text-sm"
|
className="px-3 py-1 rounded-full text-sm"
|
||||||
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
|
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
|
||||||
>
|
>
|
||||||
English (C2)
|
🇺🇸 English (Fluent)
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="px-3 py-1 rounded-full text-sm"
|
className="px-3 py-1 rounded-full text-sm"
|
||||||
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
|
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
|
||||||
>
|
>
|
||||||
Switzerland 🇨🇭
|
🇩🇪 German (Conversational)
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="px-3 py-1 rounded-full text-sm"
|
||||||
|
style={{ backgroundColor: nordColors.green, color: nordColors.bg }}
|
||||||
|
>
|
||||||
|
📍 Switzerland 🇨🇭
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -413,8 +501,79 @@ const Portfolio = () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Experience Section */}
|
||||||
|
<section id="experience" className="py-20">
|
||||||
|
<div className="max-w-6xl mx-auto px-6">
|
||||||
|
<h2 className="text-4xl font-bold text-center mb-16" style={{ color: nordColors.accent }}>
|
||||||
|
Professional Experience
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
{experience.map((exp, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="p-8 rounded-lg border mb-8"
|
||||||
|
style={{
|
||||||
|
backgroundColor: nordColors.bgLight,
|
||||||
|
borderColor: nordColors.bgLighter
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-4">
|
||||||
|
<div className="flex items-center mb-4">
|
||||||
|
<div
|
||||||
|
className="p-3 rounded-lg mr-4"
|
||||||
|
style={{ backgroundColor: nordColors.darkBlue }}
|
||||||
|
>
|
||||||
|
<Briefcase className="w-6 h-6" style={{ color: nordColors.text }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-bold" style={{ color: nordColors.text }}>
|
||||||
|
{exp.title}
|
||||||
|
</h3>
|
||||||
|
<div className="flex items-center gap-4 mt-1">
|
||||||
|
<span className="text-lg" style={{ color: nordColors.blue }}>
|
||||||
|
{exp.company}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm" style={{ color: nordColors.textMuted }}>
|
||||||
|
{exp.location}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-sm" style={{ color: nordColors.blue }}>
|
||||||
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
|
{exp.period}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-lg leading-relaxed mb-6" style={{ color: nordColors.textMuted }}>
|
||||||
|
{exp.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="text-lg font-semibold mb-3" style={{ color: nordColors.blue }}>
|
||||||
|
Key Achievements:
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{exp.achievements.map((achievement, idx) => (
|
||||||
|
<div key={idx} className="flex items-start">
|
||||||
|
<div
|
||||||
|
className="w-2 h-2 rounded-full mt-2 mr-3 flex-shrink-0"
|
||||||
|
style={{ backgroundColor: nordColors.accent }}
|
||||||
|
></div>
|
||||||
|
<span style={{ color: nordColors.text }}>{achievement}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Projects Section */}
|
{/* Projects Section */}
|
||||||
<section id="projects" className="py-20">
|
<section id="projects" className="py-20" style={{ backgroundColor: 'rgba(59, 66, 82, 0.3)' }}>
|
||||||
<div className="max-w-6xl mx-auto px-6">
|
<div className="max-w-6xl mx-auto px-6">
|
||||||
<h2 className="text-4xl font-bold text-center mb-16" style={{ color: nordColors.accent }}>
|
<h2 className="text-4xl font-bold text-center mb-16" style={{ color: nordColors.accent }}>
|
||||||
Featured Projects
|
Featured Projects
|
||||||
@@ -438,22 +597,33 @@ const Portfolio = () => {
|
|||||||
>
|
>
|
||||||
{project.icon}
|
{project.icon}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex-1">
|
||||||
<h3 className="text-xl font-semibold" style={{ color: nordColors.text }}>
|
<h3 className="text-xl font-semibold" style={{ color: nordColors.text }}>
|
||||||
{project.title}
|
{project.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
<div className="flex items-center gap-2 mt-1">
|
||||||
<p className="text-sm" style={{ color: nordColors.blue }}>
|
<p className="text-sm" style={{ color: nordColors.blue }}>
|
||||||
{project.period}
|
{project.period}
|
||||||
</p>
|
</p>
|
||||||
|
<span
|
||||||
|
className="px-2 py-0.5 rounded text-xs"
|
||||||
|
style={{
|
||||||
|
backgroundColor: nordColors.accent,
|
||||||
|
color: nordColors.bg
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{project.category}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="leading-relaxed mb-4" style={{ color: nordColors.textMuted }}>
|
<p className="leading-relaxed mb-4 text-sm" style={{ color: nordColors.textMuted }}>
|
||||||
{project.description}
|
{project.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2 mb-4">
|
<div className="flex flex-wrap gap-1 mb-4">
|
||||||
{project.tech.map((tech) => (
|
{project.tech.slice(0, 3).map((tech) => (
|
||||||
<span
|
<span
|
||||||
key={tech}
|
key={tech}
|
||||||
className="px-2 py-1 rounded text-xs"
|
className="px-2 py-1 rounded text-xs"
|
||||||
@@ -465,6 +635,17 @@ const Portfolio = () => {
|
|||||||
{tech}
|
{tech}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
{project.tech.length > 3 && (
|
||||||
|
<span
|
||||||
|
className="px-2 py-1 rounded text-xs"
|
||||||
|
style={{
|
||||||
|
backgroundColor: nordColors.bgLighter,
|
||||||
|
color: nordColors.textMuted
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
+{project.tech.length - 3} more
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -473,13 +654,14 @@ const Portfolio = () => {
|
|||||||
style={{ color: nordColors.blue }}
|
style={{ color: nordColors.blue }}
|
||||||
>
|
>
|
||||||
<Image className="w-4 h-4" />
|
<Image className="w-4 h-4" />
|
||||||
View Gallery
|
View Details
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className="px-2 py-1 rounded text-xs"
|
className="px-2 py-1 rounded text-xs"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: nordColors.darkBlue,
|
backgroundColor: project.status === 'Production' ? nordColors.green :
|
||||||
color: nordColors.text
|
project.status === 'Live' ? nordColors.blue : nordColors.yellow,
|
||||||
|
color: nordColors.bg
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{project.status}
|
{project.status}
|
||||||
@@ -492,50 +674,37 @@ const Portfolio = () => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Education & Certificates Section */}
|
{/* Education & Certificates Section */}
|
||||||
<section id="education" className="py-20" style={{ backgroundColor: 'rgba(59, 66, 82, 0.3)' }}>
|
<section id="education" className="py-20">
|
||||||
<div className="max-w-6xl mx-auto px-6">
|
<div className="max-w-6xl mx-auto px-6">
|
||||||
<h2 className="text-4xl font-bold text-center mb-16" style={{ color: nordColors.accent }}>
|
<h2 className="text-4xl font-bold text-center mb-16" style={{ color: nordColors.accent }}>
|
||||||
Education & Certifications
|
Education & Certifications
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-12">
|
<div className="grid md:grid-cols-2 gap-12">
|
||||||
{/* Education */}
|
{/* Education Timeline */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl font-semibold mb-8 flex items-center" style={{ color: nordColors.blue }}>
|
<h3 className="text-2xl font-semibold mb-8 flex items-center" style={{ color: nordColors.blue }}>
|
||||||
<GraduationCap className="w-6 h-6 mr-2" />
|
<GraduationCap className="w-6 h-6 mr-2" />
|
||||||
Education
|
Education Timeline
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<Timeline defaultValue={3} orientation="horizontal">
|
||||||
{education.map((edu, index) => (
|
{education.map((edu) => ( // ← Changed from 'education' to 'edu'
|
||||||
<div
|
<TimelineItem
|
||||||
key={index}
|
key={edu.id} // ← Now using 'edu' consistently
|
||||||
className="p-6 rounded-lg border"
|
step={edu.id}
|
||||||
style={{
|
className="group-data-[orientation=horizontal]/timeline:mt-0"
|
||||||
backgroundColor: nordColors.bg,
|
|
||||||
borderColor: nordColors.bgLight
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<h4 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>
|
<TimelineHeader>
|
||||||
{edu.degree}
|
<TimelineSeparator className="group-data-[orientation=horizontal]/timeline:top-8" />
|
||||||
</h4>
|
<TimelineDate className="mb-10">{edu.period}</TimelineDate>
|
||||||
<p className="mb-2" style={{ color: nordColors.blue }}>
|
<TimelineTitle>{edu.degree}</TimelineTitle>
|
||||||
{edu.institution}
|
<TimelineIndicator className="group-data-[orientation=horizontal]/timeline:top-8" />
|
||||||
</p>
|
</TimelineHeader>
|
||||||
<div className="flex justify-between items-center">
|
<TimelineContent>{edu.description}</TimelineContent>
|
||||||
<span className="text-sm" style={{ color: nordColors.textMuted }}>
|
</TimelineItem>
|
||||||
{edu.period}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="px-2 py-1 rounded text-xs"
|
|
||||||
style={{ backgroundColor: nordColors.darkBlue, color: nordColors.text }}
|
|
||||||
>
|
|
||||||
{edu.status}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</Timeline>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Certificates */}
|
{/* Certificates */}
|
||||||
@@ -572,10 +741,10 @@ const Portfolio = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h4 className="text-lg font-semibold mb-3" style={{ color: nordColors.blue }}>
|
<h4 className="text-lg font-semibold mb-3" style={{ color: nordColors.blue }}>
|
||||||
Interests
|
Interests & Specialties
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{['Computer Hardware', 'Web Development', 'Finance', 'Economics', 'Literature'].map((interest) => (
|
{['Computer Hardware', 'Economics & Finance', 'Web Development', 'Literature', 'Photography', 'Virtualization', 'Network Security'].map((interest) => (
|
||||||
<span
|
<span
|
||||||
key={interest}
|
key={interest}
|
||||||
className="px-3 py-1 rounded text-sm"
|
className="px-3 py-1 rounded text-sm"
|
||||||
@@ -589,19 +758,22 @@ const Portfolio = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Contact Section */}
|
{/* Contact Section */}
|
||||||
<section id="contact" className="py-20">
|
<section id="contact" className="py-20" style={{ backgroundColor: 'rgba(59, 66, 82, 0.3)' }}>
|
||||||
<div className="max-w-4xl mx-auto px-6 text-center">
|
<div className="max-w-4xl mx-auto px-6 text-center">
|
||||||
<h2 className="text-4xl font-bold mb-8" style={{ color: nordColors.accent }}>
|
<h2 className="text-4xl font-bold mb-8" style={{ color: nordColors.accent }}>
|
||||||
Let's Connect
|
Let's Connect
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg mb-12 max-w-2xl mx-auto" style={{ color: nordColors.textMuted }}>
|
<p className="text-lg mb-12 max-w-2xl mx-auto" style={{ color: nordColors.textMuted }}>
|
||||||
I'm always interested in discussing new opportunities, collaborations, or just having a chat about technology and economics.
|
I'm always interested in discussing new opportunities, collaborations, or Junior SysAdmin positions.
|
||||||
|
Let's connect and explore how my infrastructure expertise can benefit your organization.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-3 gap-8 mb-12">
|
<div className="grid md:grid-cols-3 gap-8 mb-12">
|
||||||
@@ -612,7 +784,7 @@ const Portfolio = () => {
|
|||||||
>
|
>
|
||||||
<Mail className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
|
<Mail className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
|
||||||
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>Email</h3>
|
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>Email</h3>
|
||||||
<p className="text-sm" style={{ color: nordColors.textMuted }}>Drop me a line</p>
|
<p className="text-sm" style={{ color: nordColors.textMuted }}>Professional inquiries</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
@@ -622,7 +794,7 @@ const Portfolio = () => {
|
|||||||
>
|
>
|
||||||
<Linkedin className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
|
<Linkedin className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
|
||||||
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>LinkedIn</h3>
|
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>LinkedIn</h3>
|
||||||
<p className="text-sm" style={{ color: nordColors.textMuted }}>Let's connect professionally</p>
|
<p className="text-sm" style={{ color: nordColors.textMuted }}>Professional network</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
@@ -632,18 +804,62 @@ const Portfolio = () => {
|
|||||||
>
|
>
|
||||||
<Github className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
|
<Github className="w-8 h-8 mx-auto mb-4" style={{ color: nordColors.blue }} />
|
||||||
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>GitHub</h3>
|
<h3 className="text-lg font-semibold mb-2" style={{ color: nordColors.text }}>GitHub</h3>
|
||||||
<p className="text-sm" style={{ color: nordColors.textMuted }}>Check out my code</p>
|
<p className="text-sm" style={{ color: nordColors.textMuted }}>Code repositories</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Download CV Button */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<button
|
||||||
|
className="inline-flex items-center px-6 py-3 rounded-lg font-medium transition-colors hover:opacity-90"
|
||||||
|
style={{
|
||||||
|
backgroundColor: nordColors.accent,
|
||||||
|
color: nordColors.bg
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
Download CV
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="py-8 border-t" style={{ borderColor: nordColors.bgLight }}>
|
<footer className="py-8 border-t" style={{ borderColor: nordColors.bgLight }}>
|
||||||
<div className="max-w-6xl mx-auto px-6 text-center">
|
<div className="max-w-6xl mx-auto px-6 text-center">
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="flex justify-center items-center gap-6 mb-4">
|
||||||
|
<a
|
||||||
|
href="https://github.com/Quinta0"
|
||||||
|
className="text-sm hover:opacity-80"
|
||||||
|
style={{ color: nordColors.blue }}
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://www.linkedin.com/in/pietro-quintavalle-996b96267/"
|
||||||
|
className="text-sm hover:opacity-80"
|
||||||
|
style={{ color: nordColors.blue }}
|
||||||
|
>
|
||||||
|
LinkedIn
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="mailto:0pietroquintavalle0@gmail.com"
|
||||||
|
className="text-sm hover:opacity-80"
|
||||||
|
style={{ color: nordColors.blue }}
|
||||||
|
>
|
||||||
|
Email
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p style={{ color: nordColors.textMuted }}>
|
<p style={{ color: nordColors.textMuted }}>
|
||||||
Built with ❄️ using React & Nord theme • Pietro Quintavalle © 2025
|
Built with ❄️ using React & Nord theme • Pietro Quintavalle © 2025
|
||||||
</p>
|
</p>
|
||||||
|
<p className="text-sm mt-2" style={{ color: nordColors.textMuted }}>
|
||||||
|
IT Support Professional | Systems Engineer | Economics Student
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
Generated
+1730
-1
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.441.0",
|
"lucide-react": "^0.441.0",
|
||||||
"next": "14.2.13",
|
"next": "14.2.13",
|
||||||
|
"radix-ui": "^1.4.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
Timeline,
|
||||||
|
TimelineContent,
|
||||||
|
TimelineDate,
|
||||||
|
TimelineHeader,
|
||||||
|
TimelineIndicator,
|
||||||
|
TimelineItem,
|
||||||
|
TimelineSeparator,
|
||||||
|
TimelineTitle,
|
||||||
|
} from "@/components/ui/timeline"
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
date: "Mar 15, 2024",
|
||||||
|
title: "Project Kickoff",
|
||||||
|
description: "Initial team meeting.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
date: "Mar 22, 2024",
|
||||||
|
title: "Design Phase",
|
||||||
|
description: "Completed wireframes.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
date: "Apr 5, 2024",
|
||||||
|
title: "Development Sprint",
|
||||||
|
description: "Backend development.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
date: "Apr 19, 2024",
|
||||||
|
title: "Testing & Deployment",
|
||||||
|
description: "Performance optimization.",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function Component() {
|
||||||
|
return (
|
||||||
|
<Timeline defaultValue={3} orientation="horizontal">
|
||||||
|
{items.map((item) => (
|
||||||
|
<TimelineItem
|
||||||
|
key={item.id}
|
||||||
|
step={item.id}
|
||||||
|
className="group-data-[orientation=horizontal]/timeline:mt-0"
|
||||||
|
>
|
||||||
|
<TimelineHeader>
|
||||||
|
<TimelineSeparator className="group-data-[orientation=horizontal]/timeline:top-8" />
|
||||||
|
<TimelineDate className="mb-10">{item.date}</TimelineDate>
|
||||||
|
<TimelineTitle>{item.title}</TimelineTitle>
|
||||||
|
<TimelineIndicator className="group-data-[orientation=horizontal]/timeline:top-8" />
|
||||||
|
</TimelineHeader>
|
||||||
|
<TimelineContent>{item.description}</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
))}
|
||||||
|
</Timeline>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
// Types
|
||||||
|
type TimelineContextValue = {
|
||||||
|
activeStep: number
|
||||||
|
setActiveStep: (step: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context
|
||||||
|
const TimelineContext = React.createContext<TimelineContextValue | undefined>(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
const useTimeline = () => {
|
||||||
|
const context = React.useContext(TimelineContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useTimeline must be used within a Timeline")
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Components
|
||||||
|
interface TimelineProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
defaultValue?: number
|
||||||
|
value?: number
|
||||||
|
onValueChange?: (value: number) => void
|
||||||
|
orientation?: "horizontal" | "vertical"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Timeline({
|
||||||
|
defaultValue = 1,
|
||||||
|
value,
|
||||||
|
onValueChange,
|
||||||
|
orientation = "vertical",
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: TimelineProps) {
|
||||||
|
const [activeStep, setInternalStep] = React.useState(defaultValue)
|
||||||
|
|
||||||
|
const setActiveStep = React.useCallback(
|
||||||
|
(step: number) => {
|
||||||
|
if (value === undefined) {
|
||||||
|
setInternalStep(step)
|
||||||
|
}
|
||||||
|
onValueChange?.(step)
|
||||||
|
},
|
||||||
|
[value, onValueChange]
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentStep = value ?? activeStep
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TimelineContext.Provider
|
||||||
|
value={{ activeStep: currentStep, setActiveStep }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-slot="timeline"
|
||||||
|
className={cn(
|
||||||
|
"group/timeline flex data-[orientation=horizontal]:w-full data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
data-orientation={orientation}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</TimelineContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimelineContent
|
||||||
|
function TimelineContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="timeline-content"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimelineDate
|
||||||
|
interface TimelineDateProps extends React.HTMLAttributes<HTMLTimeElement> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function TimelineDate({
|
||||||
|
asChild = false,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: TimelineDateProps) {
|
||||||
|
const Comp = asChild ? Slot.Root : "time"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="timeline-date"
|
||||||
|
className={cn(
|
||||||
|
"text-muted-foreground mb-1 block text-xs font-medium group-data-[orientation=vertical]/timeline:max-sm:h-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimelineHeader
|
||||||
|
function TimelineHeader({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div data-slot="timeline-header" className={cn(className)} {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimelineIndicator
|
||||||
|
interface TimelineIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function TimelineIndicator({
|
||||||
|
asChild = false,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: TimelineIndicatorProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="timeline-indicator"
|
||||||
|
className={cn(
|
||||||
|
"border-primary/20 group-data-completed/timeline-item:border-primary absolute size-4 rounded-full border-2 group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:left-0 group-data-[orientation=horizontal]/timeline:-translate-y-1/2 group-data-[orientation=vertical]/timeline:top-0 group-data-[orientation=vertical]/timeline:-left-6 group-data-[orientation=vertical]/timeline:-translate-x-1/2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimelineItem
|
||||||
|
interface TimelineItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
step: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function TimelineItem({ step, className, ...props }: TimelineItemProps) {
|
||||||
|
const { activeStep } = useTimeline()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="timeline-item"
|
||||||
|
className={cn(
|
||||||
|
"group/timeline-item has-[+[data-completed]]:[&_[data-slot=timeline-separator]]:bg-primary relative flex flex-1 flex-col gap-0.5 group-data-[orientation=horizontal]/timeline:mt-8 group-data-[orientation=horizontal]/timeline:not-last:pe-8 group-data-[orientation=vertical]/timeline:ms-8 group-data-[orientation=vertical]/timeline:not-last:pb-12",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
data-completed={step <= activeStep || undefined}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimelineSeparator
|
||||||
|
function TimelineSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="timeline-separator"
|
||||||
|
className={cn(
|
||||||
|
"bg-primary/10 absolute self-start group-last/timeline-item:hidden group-data-[orientation=horizontal]/timeline:-top-6 group-data-[orientation=horizontal]/timeline:h-0.5 group-data-[orientation=horizontal]/timeline:w-[calc(100%-1rem-0.25rem)] group-data-[orientation=horizontal]/timeline:translate-x-4.5 group-data-[orientation=horizontal]/timeline:-translate-y-1/2 group-data-[orientation=vertical]/timeline:-left-6 group-data-[orientation=vertical]/timeline:h-[calc(100%-1rem-0.25rem)] group-data-[orientation=vertical]/timeline:w-0.5 group-data-[orientation=vertical]/timeline:-translate-x-1/2 group-data-[orientation=vertical]/timeline:translate-y-4.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimelineTitle
|
||||||
|
function TimelineTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLHeadingElement>) {
|
||||||
|
return (
|
||||||
|
<h3
|
||||||
|
data-slot="timeline-title"
|
||||||
|
className={cn("text-sm font-medium", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Timeline,
|
||||||
|
TimelineContent,
|
||||||
|
TimelineDate,
|
||||||
|
TimelineHeader,
|
||||||
|
TimelineIndicator,
|
||||||
|
TimelineItem,
|
||||||
|
TimelineSeparator,
|
||||||
|
TimelineTitle,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user