Enhance About Me page with modern components and animations

- Replaced existing widgets with modern versions for Work Experience, Certifications, Skills, and Education sections to improve visual appeal and interactivity.
- Introduced an animated hero background and enhanced call-to-action buttons for better user engagement.
- Implemented staggered animations for content sections to create a more dynamic user experience.
- Updated styles for a cohesive glassmorphism effect across all sections, enhancing the overall aesthetic of the page.
This commit is contained in:
becarta
2025-06-15 18:07:20 +02:00
parent 290505f96e
commit 7f6578ceb1
6 changed files with 1613 additions and 128 deletions

View File

@@ -0,0 +1,224 @@
---
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
import Headline from '~/components/ui/Headline.astro';
import { Icon } from 'astro-icon/components';
import type { Widget } from '~/types';
export interface Props extends Widget {
title?: string;
subtitle?: string;
tagline?: string;
compact?: boolean;
items?: Array<{
title: string;
description?: string;
company?: string;
date?: string;
location?: string;
icon?: string;
classes?: Record<string, string>;
}>;
}
const {
title = 'Work Experience',
subtitle = 'My professional journey',
tagline = '',
compact = false,
items = [],
id,
isDark = false,
classes = {},
bg = '',
} = Astro.props as Props;
// Function to get gradient based on company or role
const getWorkGradient = (company: string = '', title: string = '') => {
const companyLower = company.toLowerCase();
const titleLower = title.toLowerCase();
if (companyLower.includes('cofra')) return 'from-blue-600 to-indigo-700';
if (companyLower.includes('hyva')) return 'from-green-600 to-teal-700';
if (companyLower.includes('bergsma')) return 'from-purple-600 to-pink-700';
if (companyLower.includes('allseas')) return 'from-orange-600 to-red-700';
if (companyLower.includes('oz export')) return 'from-cyan-600 to-blue-700';
// Fallback based on role level
if (titleLower.includes('manager')) return 'from-indigo-600 to-purple-700';
if (titleLower.includes('professional')) return 'from-blue-600 to-cyan-700';
if (titleLower.includes('engineer')) return 'from-green-600 to-blue-700';
if (titleLower.includes('consultant')) return 'from-purple-600 to-indigo-700';
if (titleLower.includes('administrator')) return 'from-gray-600 to-slate-700';
return 'from-gray-600 to-gray-700';
};
// Function to get appropriate icon
const getWorkIcon = (title: string = '') => {
const titleLower = title.toLowerCase();
if (titleLower.includes('manager')) return 'tabler:user-star';
if (titleLower.includes('professional')) return 'tabler:certificate';
if (titleLower.includes('engineer')) return 'tabler:code';
if (titleLower.includes('consultant')) return 'tabler:user-check';
if (titleLower.includes('administrator')) return 'tabler:settings';
return 'tabler:briefcase';
};
// Function to extract years from date range
const getYearRange = (dateStr: string = '') => {
if (!dateStr) return '';
// Handle formats like "02-2025 - Present" or "04-2018 - 09-2018"
const match = dateStr.match(/(\d{2})-(\d{4})\s*-\s*(.+)/);
if (match) {
const startYear = match[2];
const endPart = match[3].trim();
if (endPart.toLowerCase().includes('present') || endPart.toLowerCase().includes('heden')) {
return `${startYear} - Present`;
} else {
const endMatch = endPart.match(/(\d{2})-(\d{4})/);
if (endMatch) {
return `${startYear} - ${endMatch[2]}`;
}
}
}
return dateStr;
};
---
<WidgetWrapper id={id} isDark={isDark} containerClass={`max-w-7xl mx-auto ${classes?.container ?? ''}`} bg={bg}>
<Headline
title={title}
subtitle={subtitle}
tagline={tagline}
classes={{
container: 'max-w-3xl',
title: 'text-3xl lg:text-4xl',
}}
/>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8">
{items.map((item, index) => {
const gradient = getWorkGradient(item.company, item.title);
const icon = getWorkIcon(item.title);
const yearRange = getYearRange(item.date);
return (
<div
class="work-card group bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm rounded-2xl p-6 transition-all duration-300 cursor-default border border-gray-100 dark:border-slate-800 hover:transform hover:scale-[1.02] hover:shadow-xl relative overflow-hidden"
>
<!-- Top gradient bar -->
<div class={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${gradient}`}></div>
<!-- Header with Icon, Title & Company -->
<div class="flex items-start gap-4 mb-4">
<div class={`flex-shrink-0 w-12 h-12 rounded-xl bg-gradient-to-r ${gradient} flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-lg`}>
<Icon name={icon} class="w-6 h-6 text-white" />
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-lg text-gray-900 dark:text-white mb-1 group-hover:text-transparent group-hover:bg-clip-text group-hover:bg-gradient-to-r group-hover:from-blue-600 group-hover:to-purple-600 transition-all duration-300 leading-snug">
{item.title}
</h3>
{item.company && (
<p class={`font-medium text-transparent bg-clip-text bg-gradient-to-r ${gradient} text-sm mb-1`}>
{item.company}
</p>
)}
<div class="flex flex-wrap gap-2 text-xs text-gray-500 dark:text-gray-400">
{yearRange && (
<span class="flex items-center gap-1">
<Icon name="tabler:calendar" class="w-3 h-3" />
{yearRange}
</span>
)}
{item.location && (
<span class="flex items-center gap-1">
<Icon name="tabler:map-pin" class="w-3 h-3" />
{item.location}
</span>
)}
</div>
</div>
</div>
<!-- Description -->
{item.description && (
<div class="work-description">
<p class="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">
{item.description}
</p>
</div>
)}
<!-- Shimmer effect -->
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-700 ease-out"></div>
</div>
);
})}
</div>
</WidgetWrapper>
<style>
.work-card {
animation: fadeInUp 0.6s ease-out forwards;
opacity: 0;
transform: translateY(20px);
}
.work-card:nth-child(1) { animation-delay: 0.1s; }
.work-card:nth-child(2) { animation-delay: 0.15s; }
.work-card:nth-child(3) { animation-delay: 0.2s; }
.work-card:nth-child(4) { animation-delay: 0.25s; }
.work-card:nth-child(5) { animation-delay: 0.3s; }
.work-card:nth-child(6) { animation-delay: 0.35s; }
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Enhanced description styling */
.work-description {
max-height: 120px;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.work-card:hover .work-description {
max-height: 300px;
}
/* Responsive adjustments */
@media (max-width: 1024px) {
.work-card {
margin-bottom: 1rem;
}
}
@media (max-width: 640px) {
.work-card {
padding: 1.25rem;
}
.work-card .flex {
gap: 0.75rem;
}
.work-card .w-12.h-12 {
width: 2.75rem;
height: 2.75rem;
}
.work-card .w-6.h-6 {
width: 1.25rem;
height: 1.25rem;
}
}
</style>