- Updated multiple widget components to change the maximum width from `max-w-7xl` to `max-w-6xl`, ensuring a more uniform appearance across the application. - Adjusted layout in various pages to enhance responsiveness and maintain design integrity on different screen sizes.
279 lines
8.7 KiB
Plaintext
279 lines
8.7 KiB
Plaintext
---
|
|
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-6xl 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-pointer 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>
|
|
<!-- Mobile expand indicator -->
|
|
<div class="mobile-expand-hint md:hidden text-xs text-gray-400 dark:text-gray-500 mt-2 opacity-70">
|
|
Tap to read more
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<!-- Shimmer effect -->
|
|
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-black/10 to-transparent dark:via-white/10 /* Smart theme-aware shimmer */ 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;
|
|
}
|
|
|
|
/* Mobile-specific description styling */
|
|
@media (max-width: 768px) {
|
|
.work-description {
|
|
max-height: 100px;
|
|
}
|
|
|
|
.work-card:hover .work-description,
|
|
.work-card:active .work-description,
|
|
.work-card.expanded .work-description {
|
|
max-height: 400px; /* More room on mobile */
|
|
}
|
|
|
|
.work-card.expanded .mobile-expand-hint {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 1024px) {
|
|
.work-card {
|
|
margin-bottom: 1rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.work-card {
|
|
padding: 1.5rem; /* More padding on mobile */
|
|
}
|
|
|
|
.work-card .flex {
|
|
gap: 1rem; /* More gap on mobile */
|
|
}
|
|
|
|
.work-card .w-12.h-12 {
|
|
width: 2.75rem;
|
|
height: 2.75rem;
|
|
}
|
|
|
|
.work-card .w-6.h-6 {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
}
|
|
|
|
/* Better mobile text sizing */
|
|
.work-card h3 {
|
|
font-size: 1rem;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.work-card .text-sm {
|
|
font-size: 0.8rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script is:inline>
|
|
// Add click-to-expand functionality for mobile
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const workCards = document.querySelectorAll('.work-card');
|
|
|
|
workCards.forEach(card => {
|
|
card.addEventListener('click', function() {
|
|
// Only toggle on mobile/tablet (screens smaller than md breakpoint)
|
|
if (window.innerWidth < 768) {
|
|
this.classList.toggle('expanded');
|
|
}
|
|
});
|
|
|
|
card.addEventListener('touchend', function(e) {
|
|
// Prevent double-firing and only on small screens
|
|
if (window.innerWidth < 768) {
|
|
e.preventDefault();
|
|
this.classList.toggle('expanded');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script> |