- Updated ImageModal styles to center the modal and adjust image container dimensions for better visual presentation. - Refactored CompactSteps to include an icon component for better visual consistency and adjusted layout for improved readability. - Removed outdated WorkExperience item rendering to streamline the component structure. - Updated aboutme page to conditionally set icons based on title content for better context.
189 lines
6.4 KiB
Plaintext
189 lines
6.4 KiB
Plaintext
---
|
|
// ImageModal.astro - A reusable modal component for displaying enlarged images
|
|
---
|
|
|
|
<div
|
|
id="image-modal"
|
|
class="fixed inset-0 z-50 flex items-center justify-center opacity-0 pointer-events-none transition-opacity duration-300 ease-in-out"
|
|
>
|
|
<!-- Backdrop overlay -->
|
|
<div
|
|
id="modal-backdrop"
|
|
class="absolute inset-0 bg-black bg-opacity-75 backdrop-blur-sm transition-opacity duration-300"
|
|
>
|
|
</div>
|
|
|
|
<!-- Modal container with animation -->
|
|
<div
|
|
id="modal-container"
|
|
class="fixed top-1/2 left-1/2 z-60 max-w-4xl w-full p-4 transform -translate-x-1/2 -translate-y-1/2 scale-95 transition-all duration-300 ease-in-out flex flex-col items-center"
|
|
>
|
|
<!-- Close button -->
|
|
<button
|
|
id="modal-close"
|
|
class="absolute top-2 right-2 z-10 bg-white dark:bg-gray-800 rounded-full p-2 shadow-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-200"
|
|
aria-label="Close modal"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-gray-600 dark:text-gray-300"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Image container -->
|
|
<div
|
|
class="bg-white dark:bg-gray-800 rounded-lg shadow-xl overflow-hidden flex flex-col justify-center items-center"
|
|
style="min-height: 350px; max-height: 75vh; min-width: 320px;"
|
|
>
|
|
<div class="flex flex-1 items-center justify-center w-full min-h-[250px]" style="height: 100%;">
|
|
<img
|
|
id="modal-image"
|
|
src=""
|
|
alt="Enlarged certificate"
|
|
class="w-auto object-contain max-h-[60vh]"
|
|
style="vertical-align: middle;"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Caption -->
|
|
<div id="modal-caption" class="mt-2 text-center text-white text-lg font-medium"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Declare the global function type
|
|
declare global {
|
|
interface Window {
|
|
openImageModal: (imgSrc: string, imgAlt: string) => void;
|
|
}
|
|
}
|
|
|
|
// Initialize modal functionality when the DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const modal = document.getElementById('image-modal');
|
|
const modalBackdrop = document.getElementById('modal-backdrop');
|
|
const modalContainer = document.getElementById('modal-container');
|
|
const modalImage = document.getElementById('modal-image') as HTMLImageElement;
|
|
const modalCaption = document.getElementById('modal-caption');
|
|
const closeButton = document.getElementById('modal-close');
|
|
|
|
// Function to open the modal with a specific image
|
|
function openModal(imgSrc, imgAlt) {
|
|
if (!modal || !modalImage || !modalCaption) return;
|
|
|
|
// Create a temporary image to get the natural dimensions
|
|
const tempImg = new Image();
|
|
tempImg.onload = function () {
|
|
// Calculate the certification section height (75% of viewport height as defined in the container)
|
|
const certSectionHeight = window.innerHeight * 0.75;
|
|
const maxHeight = certSectionHeight * 0.75; // 75% of the certification section height
|
|
|
|
// If the natural image height is smaller than the max height, use the natural height
|
|
if (tempImg.height < maxHeight) {
|
|
modalImage.style.setProperty('--cert-max-height', `${tempImg.height}px`);
|
|
} else {
|
|
modalImage.style.setProperty('--cert-max-height', `${maxHeight}px`);
|
|
}
|
|
|
|
// Set the image source and alt text
|
|
modalImage.src = imgSrc;
|
|
modalCaption.textContent = imgAlt;
|
|
|
|
// Make the modal visible
|
|
modal.classList.remove('opacity-0', 'pointer-events-none');
|
|
modal.classList.add('opacity-100', 'pointer-events-auto');
|
|
|
|
// Animate the container
|
|
if (modalContainer) {
|
|
modalContainer.classList.remove('scale-95');
|
|
modalContainer.classList.add('scale-100');
|
|
}
|
|
|
|
// Set focus to the close button for accessibility
|
|
if (closeButton) {
|
|
setTimeout(() => closeButton.focus(), 100);
|
|
}
|
|
};
|
|
|
|
// Start loading the image
|
|
tempImg.src = imgSrc;
|
|
|
|
// Prevent scrolling on the body
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
// Function to close the modal
|
|
function closeModal() {
|
|
if (!modal || !modalContainer) return;
|
|
|
|
// Hide the modal with animation
|
|
modal.classList.remove('opacity-100', 'pointer-events-auto');
|
|
modal.classList.add('opacity-0', 'pointer-events-none');
|
|
|
|
// Animate the container
|
|
modalContainer.classList.remove('scale-100');
|
|
modalContainer.classList.add('scale-95');
|
|
|
|
// Re-enable scrolling
|
|
document.body.style.overflow = '';
|
|
|
|
// Clear the image source after animation completes
|
|
setTimeout(() => {
|
|
if (modalImage) modalImage.src = '';
|
|
if (modalCaption) modalCaption.textContent = '';
|
|
}, 300);
|
|
}
|
|
|
|
// Register the openImageModal function globally so it can be called from anywhere
|
|
window.openImageModal = openModal;
|
|
|
|
// Close modal when clicking the close button
|
|
if (closeButton) {
|
|
closeButton.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
closeModal();
|
|
});
|
|
}
|
|
|
|
// Close modal when clicking outside the image container
|
|
if (modal) {
|
|
modal.addEventListener('click', (e) => {
|
|
// Check if the click was on the modal backdrop or the modal itself (not on its children)
|
|
if (e.target === modal || e.target === modalBackdrop) {
|
|
closeModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Close modal when pressing Escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape') {
|
|
closeModal();
|
|
}
|
|
});
|
|
|
|
// Handle window resize to recalculate image dimensions
|
|
let resizeTimeout;
|
|
window.addEventListener('resize', () => {
|
|
clearTimeout(resizeTimeout);
|
|
resizeTimeout = setTimeout(() => {
|
|
if (modalImage && modalImage.src && modal && modal.classList.contains('opacity-100')) {
|
|
// Get current image source and recalculate dimensions
|
|
const currentSrc = modalImage.src;
|
|
const currentAlt = modalCaption?.textContent || '';
|
|
|
|
// Close and reopen modal to trigger recalculation
|
|
closeModal();
|
|
setTimeout(() => openModal(currentSrc, currentAlt), 350);
|
|
}
|
|
}, 200); // Debounce resize events
|
|
});
|
|
});
|
|
</script>
|