add challenge 11(portifolio)

This commit is contained in:
vista-man
2025-06-19 01:03:59 +02:00
parent 8faefd26ae
commit 5245c6f9b1
27 changed files with 1937 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
// Dark mode functionality with enhanced animations
function initDarkMode() {
// Check for saved theme preference or use system preference
const savedTheme = localStorage.getItem('theme');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme === 'dark' || (!savedTheme && systemPrefersDark)) {
document.documentElement.classList.add('dark');
} else {
}
// Remove existing toggle button if it exists
const existingButton = document.getElementById('theme-toggle');
if (existingButton) {
existingButton.remove();
}
// Create and add the theme toggle button with fallback styling
const toggleButton = document.createElement('button');
toggleButton.id = 'theme-toggle';
toggleButton.setAttribute('aria-label', 'Toggle dark mode');
// Use inline styles as fallback
const isDark = document.documentElement.classList.contains('dark');
toggleButton.style.cssText = `
position: fixed;
bottom: 1rem;
left: 1rem;
z-index: 9999;
background: ${isDark ? '#334155' : '#ffffff'};
border: 2px solid ${isDark ? '#475569' : '#e2e8f0'};
border-radius: 50%;
padding: 0.75rem;
box-shadow: 0 4px 12px ${isDark ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.1)'};
transition: all 0.3s ease;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
`;
toggleButton.innerHTML = `
<svg class="w-6 h-6 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="color: ${isDark ? '#f1f5f9' : '#1e293b'}">
<path class="sun-icon" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
<path class="moon-icon" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" style="display: ${isDark ? 'block' : 'none'}" />
</svg>
`;
document.body.appendChild(toggleButton);
// Add click handler with enhanced animation
toggleButton.addEventListener('click', () => {
// Add click animation
toggleButton.style.transform = 'scale(0.9)';
setTimeout(() => {
toggleButton.style.transform = '';
}, 150);
// Toggle theme
const newIsDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', newIsDark ? 'dark' : 'light');
// Update button styles
toggleButton.style.background = newIsDark ? '#334155' : '#ffffff';
toggleButton.style.borderColor = newIsDark ? '#475569' : '#e2e8f0';
toggleButton.style.boxShadow = newIsDark ? '0 4px 12px rgba(0,0,0,0.3)' : '0 4px 12px rgba(0,0,0,0.1)';
const svg = toggleButton.querySelector('svg');
svg.style.color = newIsDark ? '#f1f5f9' : '#1e293b';
const sunIcon = toggleButton.querySelector('.sun-icon');
const moonIcon = toggleButton.querySelector('.moon-icon');
if (newIsDark) {
sunIcon.style.display = 'none';
moonIcon.style.display = 'block';
} else {
sunIcon.style.display = 'block';
moonIcon.style.display = 'none';
}
// Add theme transition effect
addThemeTransitionEffect();
});
// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
if (e.matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
});
}
function addThemeTransitionEffect() {
// Create a temporary overlay for smooth transition
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: ${document.documentElement.classList.contains('dark') ? '#0f172a' : '#ffffff'};
z-index: 9998;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
`;
document.body.appendChild(overlay);
// Fade in and out
requestAnimationFrame(() => {
overlay.style.opacity = '0.1';
setTimeout(() => {
overlay.style.opacity = '0';
setTimeout(() => {
if (document.body.contains(overlay)) {
document.body.removeChild(overlay);
}
}, 300);
}, 150);
});
}
// Initialize dark mode when the DOM is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDarkMode);
} else {
// DOM is already loaded
initDarkMode();
}
// Also try to initialize after a short delay to ensure everything is ready
setTimeout(initDarkMode, 100);

View File

@@ -0,0 +1,188 @@
document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById('projects');
// Add loading state with enhanced spinner
container.innerHTML = '<div class="col-span-full text-center p-8"><div class="spinner mx-auto mb-4"></div><p class="text-gray-500 dark:text-gray-400">Loading projects...</p></div>';
// Create modal element with enhanced styling
const modal = document.createElement('div');
modal.className = 'modal-overlay fixed inset-0 hidden items-center justify-center z-50 transition-opacity duration-300';
modal.innerHTML = `
<div class="modal-content max-w-4xl w-full mx-4 max-h-[90vh] overflow-y-auto transform transition-all duration-300 scale-95 opacity-0">
<div class="p-8">
<div class="flex justify-between items-start mb-6">
<h2 class="text-3xl font-bold"></h2>
<button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700" onclick="closeModal()">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div class="modal-content">
<div class="modal-image mb-6"></div>
<div class="modal-description text-gray-700 dark:text-gray-300 mb-6 text-lg"></div>
<div class="modal-tags mb-6"></div>
<div class="modal-links"></div>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// Add modal functions to window
window.openModal = (project) => {
const modalTitle = modal.querySelector('h2');
const modalImage = modal.querySelector('.modal-image');
const modalDescription = modal.querySelector('.modal-description');
const modalTags = modal.querySelector('.modal-tags');
const modalLinks = modal.querySelector('.modal-links');
const modalContent = modal.querySelector('.modal-content');
modalTitle.textContent = project.title;
if (project.image_url) {
modalImage.innerHTML = `<img src="${project.image_url}" alt="${project.title}" class="project-image w-full h-96 object-cover shadow-lg">`;
} else {
modalImage.innerHTML = '<div class="w-full h-96 bg-gradient-to-br from-blue-100 to-purple-100 dark:from-blue-900 dark:to-purple-900 rounded-lg flex items-center justify-center"><svg class="w-24 h-24 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg></div>';
}
modalDescription.innerHTML = `
<p class="text-lg mb-4 leading-relaxed">${project.description || ''}</p>
${project.long_description ? `<p class="mt-6 leading-relaxed text-gray-600 dark:text-gray-400">${project.long_description}</p>` : ''}
`;
if (project.tags) {
modalTags.innerHTML = `
<div class="flex flex-wrap gap-2">
${project.tags.split(',').map(tag =>
`<span class="tag">${tag.trim()}</span>`
).join('')}
</div>
`;
} else {
modalTags.innerHTML = '';
}
modalLinks.innerHTML = project.project_url ?
`<a href="${project.project_url}" target="_blank" class="btn-primary inline-block">View Project →</a>` : '';
// Show modal with animation
modal.classList.remove('hidden');
modal.classList.add('flex');
document.body.style.overflow = 'hidden';
// Trigger animation
requestAnimationFrame(() => {
modalContent.classList.remove('scale-95', 'opacity-0');
modalContent.classList.add('scale-100', 'opacity-100');
});
};
window.closeModal = () => {
const modalContent = modal.querySelector('.modal-content');
// Start closing animation
modalContent.classList.remove('scale-100', 'opacity-100');
modalContent.classList.add('scale-95', 'opacity-0');
// Wait for animation to finish before hiding
setTimeout(() => {
modal.classList.add('hidden');
modal.classList.remove('flex');
document.body.style.overflow = '';
}, 300);
};
// Close modal when clicking outside
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
});
// Close modal with Escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !modal.classList.contains('hidden')) {
closeModal();
}
});
// Enhanced project fetching with error handling
fetch('php/get_projects.php')
.then(res => {
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
return res.json();
})
.then(projects => {
if (!Array.isArray(projects) || projects.length === 0) {
container.innerHTML = `
<div class="col-span-full text-center p-8">
<div class="w-24 h-24 mx-auto mb-4 text-gray-400">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
</div>
<p class="text-gray-500 dark:text-gray-400 text-lg">No projects found.</p>
<p class="text-sm text-gray-400 dark:text-gray-500 mt-2">Projects will appear here once added.</p>
</div>`;
return;
}
container.innerHTML = '';
// Add projects with CSS animations
projects.forEach((project) => {
const card = document.createElement('div');
card.className = 'project-card p-5 flex flex-col cursor-pointer hover-lift';
card.onclick = () => openModal(project);
if (project.image_url) {
card.innerHTML += `<img src="${project.image_url}" alt="${project.title}" class="project-image mb-4 h-40 object-cover w-full">`;
} else {
card.innerHTML += `<div class="project-image mb-4 h-40 bg-gradient-to-br from-blue-100 to-purple-100 dark:from-blue-900 dark:to-purple-900 rounded-lg flex items-center justify-center"><svg class="w-16 h-16 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg></div>`;
}
card.innerHTML += `
<h2 class="text-xl font-semibold mb-2">${project.title}</h2>
<p class="text-gray-700 dark:text-gray-300 mb-3 flex-grow">${project.description || ''}</p>
${project.tags ? `<div class="mb-3 flex flex-wrap gap-1">${project.tags.split(',').map(tag => `<span class="tag text-xs">${tag.trim()}</span>`).join('')}</div>` : ''}
${project.project_url ? `<a href="${project.project_url}" target="_blank" class="mt-auto inline-block text-blue-600 dark:text-blue-400 hover:underline font-medium transition-colors" onclick="event.stopPropagation()">View Project →</a>` : ''}
`;
container.appendChild(card);
});
})
.catch(err => {
console.error('Error fetching projects:', err);
container.innerHTML = `
<div class="col-span-full text-center p-8">
<div class="w-24 h-24 mx-auto mb-4 text-red-400">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<p class="text-red-500 dark:text-red-400 mb-2 text-lg">Failed to load projects.</p>
<p class="text-sm text-gray-500 dark:text-gray-400">Error: ${err.message}</p>
<button onclick="location.reload()" class="btn-primary mt-4">Try Again</button>
</div>`;
});
});
// Add fadeInUp animation
const style = document.createElement('style');
style.textContent = `
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`;
document.head.appendChild(style);