- Changed shimmer effect gradient from white to black for better visibility in light mode. - Ensured consistent styling across all three components to enhance user experience and visual appeal.
344 lines
11 KiB
Plaintext
344 lines
11 KiB
Plaintext
---
|
|
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
|
|
import Headline from '~/components/ui/Headline.astro';
|
|
import { Icon } from 'astro-icon/components';
|
|
import type { Steps as Props } from '~/types';
|
|
|
|
const {
|
|
title = await Astro.slots.render('title'),
|
|
subtitle = await Astro.slots.render('subtitle'),
|
|
tagline = await Astro.slots.render('tagline'),
|
|
items = [],
|
|
|
|
id,
|
|
isDark = false,
|
|
classes = {},
|
|
bg = await Astro.slots.render('bg'),
|
|
} = Astro.props;
|
|
|
|
// Function to get the appropriate icon and gradient based on education level
|
|
const getEducationStyle = (title: string) => {
|
|
const titleLower = title.toLowerCase();
|
|
|
|
if (titleLower.includes('bachelor')) {
|
|
return {
|
|
icon: 'tabler:school',
|
|
gradient: 'from-blue-500 to-indigo-600',
|
|
badgeIcon: 'tabler:book-2',
|
|
completed: false
|
|
};
|
|
} else if (titleLower.includes('associate')) {
|
|
return {
|
|
icon: 'tabler:certificate',
|
|
gradient: 'from-green-500 to-teal-600',
|
|
badgeIcon: 'tabler:check',
|
|
completed: true
|
|
};
|
|
} else {
|
|
return {
|
|
icon: 'tabler:graduation',
|
|
gradient: 'from-purple-500 to-pink-600',
|
|
badgeIcon: 'tabler:check',
|
|
completed: true
|
|
};
|
|
}
|
|
};
|
|
---
|
|
|
|
<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',
|
|
...((classes?.headline as object) ?? {}),
|
|
}}
|
|
/>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-8">
|
|
{items.map((item, index) => {
|
|
const educationStyle = getEducationStyle(item.title || '');
|
|
|
|
return (
|
|
<div
|
|
class="education-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-105 hover:shadow-xl relative overflow-hidden"
|
|
data-education-title={item.title || ''}
|
|
data-education-description={item.description || ''}
|
|
>
|
|
<!-- Top gradient bar -->
|
|
<div class={`absolute top-0 left-0 right-0 h-1 bg-gradient-to-r ${educationStyle.gradient}`}></div>
|
|
|
|
<!-- Education Icon -->
|
|
<div class={`w-12 h-12 rounded-xl bg-gradient-to-r ${educationStyle.gradient} flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-300 shadow-lg`}>
|
|
<Icon name={educationStyle.icon} class="w-6 h-6 text-white" />
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="education-content">
|
|
<div
|
|
class="font-semibold text-sm leading-relaxed text-gray-900 dark:text-white 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"
|
|
set:html={item.title}
|
|
/>
|
|
</div>
|
|
|
|
<!-- Achievement Badge (bottom right) -->
|
|
<div class={`absolute bottom-4 right-4 w-6 h-6 rounded-full bg-gradient-to-r ${educationStyle.gradient} flex items-center justify-center opacity-60 group-hover:opacity-100 transition-opacity duration-300`}>
|
|
<Icon name={educationStyle.badgeIcon} class="w-3 h-3 text-white" />
|
|
</div>
|
|
|
|
<!-- Shimmer effect -->
|
|
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-black/10 to-transparent dark:via-white/10 -translate-x-full group-hover:translate-x-full transition-transform duration-700 ease-out"></div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</WidgetWrapper>
|
|
|
|
<!-- Education Details Modal -->
|
|
<div id="educationModal" class="modal fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden items-center justify-center p-4">
|
|
<div class="modal-content bg-white dark:bg-slate-800 rounded-2xl p-6 max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto transform scale-90 opacity-0 transition-all duration-300">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<div>
|
|
<h3 id="educationModalTitle" class="text-2xl font-bold text-gray-900 dark:text-white mb-1"></h3>
|
|
<p id="educationModalStatus" class="text-sm font-medium"></p>
|
|
</div>
|
|
<button onclick="closeEducationModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
|
|
<Icon name="tabler:x" class="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
<div id="educationModalDescription" class="text-gray-700 dark:text-gray-300 leading-relaxed"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.education-card {
|
|
animation: fadeInUp 0.6s ease-out forwards;
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
|
|
.education-card:nth-child(1) { animation-delay: 0.1s; }
|
|
.education-card:nth-child(2) { animation-delay: 0.2s; }
|
|
.education-card:nth-child(3) { animation-delay: 0.3s; }
|
|
.education-card:nth-child(4) { animation-delay: 0.4s; }
|
|
|
|
@keyframes fadeInUp {
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.modal {
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.modal.active {
|
|
display: flex !important;
|
|
}
|
|
|
|
.modal.active .modal-content {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Enhanced styling for education content */
|
|
.education-content :global(span.font-normal) {
|
|
font-weight: 500;
|
|
color: #6b7280;
|
|
font-size: 0.8rem;
|
|
display: block;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.education-content :global(span.text-sm) {
|
|
font-size: 0.75rem;
|
|
opacity: 0.8;
|
|
font-weight: 500;
|
|
display: block;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
:global(.dark) .education-content :global(span.font-normal) {
|
|
color: #9ca3af;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 640px) {
|
|
.education-card {
|
|
padding: 1.25rem;
|
|
}
|
|
|
|
.education-card .flex {
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.education-card .w-12.h-12 {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
}
|
|
|
|
.education-card .w-6.h-6 {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script is:inline>
|
|
// Education widget touch handling - namespaced to avoid conflicts
|
|
(function() {
|
|
let eduTouchStart = { x: 0, y: 0, time: 0 };
|
|
let eduIsScrolling = false;
|
|
let eduOriginalScrollPosition = 0;
|
|
let eduClickedElement = null;
|
|
|
|
function eduHandleTouchStart(e) {
|
|
const touch = e.touches[0];
|
|
eduTouchStart = {
|
|
x: touch.clientX,
|
|
y: touch.clientY,
|
|
time: Date.now()
|
|
};
|
|
eduIsScrolling = false;
|
|
}
|
|
|
|
function eduHandleTouchMove(e) {
|
|
const touch = e.touches[0];
|
|
const deltaX = Math.abs(touch.clientX - eduTouchStart.x);
|
|
const deltaY = Math.abs(touch.clientY - eduTouchStart.y);
|
|
|
|
if (deltaX > 10 || deltaY > 10) {
|
|
eduIsScrolling = true;
|
|
}
|
|
}
|
|
|
|
function eduHandleTouchEnd(e, element) {
|
|
const touch = e.changedTouches[0];
|
|
const touchEndTime = Date.now();
|
|
|
|
if (eduIsScrolling) return;
|
|
|
|
const touchDuration = touchEndTime - eduTouchStart.time;
|
|
if (touchDuration > 300) return;
|
|
|
|
const deltaX = Math.abs(touch.clientX - eduTouchStart.x);
|
|
const deltaY = Math.abs(touch.clientY - eduTouchStart.y);
|
|
if (deltaX > 15 || deltaY > 15) return;
|
|
|
|
e.preventDefault();
|
|
showEducationModal(element);
|
|
}
|
|
|
|
window.showEducationModal = function(element) {
|
|
// Store the clicked element and current scroll position
|
|
eduClickedElement = element;
|
|
eduOriginalScrollPosition = window.pageYOffset || document.documentElement.scrollTop;
|
|
|
|
const title = element.dataset.educationTitle;
|
|
const description = element.dataset.educationDescription;
|
|
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = title;
|
|
const cleanTitle = tempDiv.textContent || tempDiv.innerText || title;
|
|
|
|
const titleLower = cleanTitle.toLowerCase();
|
|
let statusText = '';
|
|
let statusClass = '';
|
|
|
|
if (titleLower.includes('bachelor')) {
|
|
statusText = 'Studies Undertaken';
|
|
statusClass = 'text-blue-600 dark:text-blue-400';
|
|
} else if (titleLower.includes('associate')) {
|
|
statusText = 'Completed';
|
|
statusClass = 'text-green-600 dark:text-green-400';
|
|
} else {
|
|
statusText = 'Completed';
|
|
statusClass = 'text-purple-600 dark:text-purple-400';
|
|
}
|
|
|
|
document.getElementById('educationModalTitle').innerHTML = title;
|
|
document.getElementById('educationModalDescription').textContent = description || 'No additional information available.';
|
|
document.getElementById('educationModalStatus').textContent = statusText;
|
|
document.getElementById('educationModalStatus').className = `text-sm font-medium ${statusClass}`;
|
|
|
|
const modal = document.getElementById('educationModal');
|
|
modal.classList.add('active');
|
|
|
|
setTimeout(() => {
|
|
const modalContent = modal.querySelector('.modal-content');
|
|
if (modalContent) {
|
|
modalContent.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
}, 100);
|
|
};
|
|
|
|
window.closeEducationModal = function() {
|
|
const modal = document.getElementById('educationModal');
|
|
modal.classList.remove('active');
|
|
|
|
// Restore scroll position to the original card
|
|
if (eduClickedElement) {
|
|
setTimeout(() => {
|
|
// First, scroll to the original position
|
|
window.scrollTo({
|
|
top: eduOriginalScrollPosition,
|
|
behavior: 'smooth'
|
|
});
|
|
|
|
// Then, ensure the clicked card is visible with a slight offset
|
|
setTimeout(() => {
|
|
const elementRect = eduClickedElement.getBoundingClientRect();
|
|
const isElementVisible = elementRect.top >= 0 && elementRect.bottom <= window.innerHeight;
|
|
|
|
if (!isElementVisible) {
|
|
eduClickedElement.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'center'
|
|
});
|
|
}
|
|
}, 300);
|
|
}, 100);
|
|
}
|
|
|
|
// Clear the reference
|
|
eduClickedElement = null;
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const educationCards = document.querySelectorAll('.education-card');
|
|
|
|
educationCards.forEach(card => {
|
|
card.addEventListener('click', function(e) {
|
|
if (!('ontouchstart' in window)) {
|
|
e.preventDefault();
|
|
showEducationModal(this);
|
|
}
|
|
});
|
|
|
|
card.addEventListener('touchstart', eduHandleTouchStart, { passive: true });
|
|
card.addEventListener('touchmove', eduHandleTouchMove, { passive: true });
|
|
card.addEventListener('touchend', function(e) {
|
|
eduHandleTouchEnd(e, this);
|
|
}, { passive: false });
|
|
});
|
|
|
|
const educationModal = document.getElementById('educationModal');
|
|
if (educationModal) {
|
|
educationModal.addEventListener('click', function(e) {
|
|
if (e.target === this) {
|
|
closeEducationModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
closeEducationModal();
|
|
}
|
|
});
|
|
});
|
|
})();
|
|
</script> |