Files
365devnet/src/components/widgets/ModernWorkExperience.astro
Richard Bergsma 97f9faa8b2 Refactor widget container widths for improved layout consistency
- 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.
2025-11-06 13:28:20 +01:00

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>