bsateeshk/techfolio-nexus
0
1// SketchVerse - Main Application Logic2 3// Data: Gallery Items4const galleryData = [5 {6 id: 1,7 title: "Ethereal Gaze",8 category: "portrait",9 image: "http://static.photos/people/640x800/1",10 description: "Hyper-realistic portrait exploring human emotion"11 },12 {13 id: 2,14 title: "Mountain Solitude",15 category: "landscape",16 image: "http://static.photos/nature/640x480/2",17 description: "Dramatic mountain landscape in graphite"18 },19 {20 id: 3,21 title: "Timeless Wisdom",22 category: "portrait",23 image: "http://static.photos/people/640x800/3",24 description: "Elder portrait capturing years of stories"25 },26 {27 id: 4,28 title: "Urban Dreams",29 category: "conceptual",30 image: "http://static.photos/cityscape/640x480/4",31 description: "Surreal cityscape blending reality and imagination"32 },33 {34 id: 5,35 title: "Silent Observer",36 category: "portrait",37 image: "http://static.photos/people/640x800/5",38 description: "Contemplative portrait study"39 },40 {41 id: 6,42 title: "Forest Whispers",43 category: "landscape",44 image: "http://static.photos/nature/640x800/6",45 description: "Detailed forest scene with dramatic lighting"46 },47 {48 id: 7,49 title: "The Musician",50 category: "portrait",51 image: "http://static.photos/people/640x800/7",52 description: "Portrait of a classical guitarist"53 },54 {55 id: 8,56 title: "Abstract Thoughts",57 category: "conceptual",58 image: "http://static.photos/abstract/640x640/8",59 description: "Conceptual piece exploring consciousness"60 },61 {62 id: 9,63 title: "Coastal Serenity",64 category: "landscape",65 image: "http://static.photos/nature/640x480/9",66 description: "Seascape capturing the calm of the ocean"67 }68];69 70// State Management71const state = {72 currentFilter: 'all',73 modalOpen: false,74 currentImageIndex: 0,75 zoomLevel: 1,76 darkMode: false,77 displayedItems: 678};79 80// DOM Elements81const elements = {82 galleryGrid: document.getElementById('gallery-grid'),83 filterButtons: document.querySelectorAll('.filter-btn'),84 modal: document.getElementById('image-modal'),85 modalImage: document.getElementById('modal-image'),86 modalTitle: document.getElementById('modal-title'),87 modalCategory: document.getElementById('modal-category'),88 modalClose: document.getElementById('modal-close'),89 modalBackdrop: document.getElementById('modal-backdrop'),90 modalZoomIn: document.getElementById('modal-zoom-in'),91 modalZoomOut: document.getElementById('modal-zoom-out'),92 modalShare: document.getElementById('modal-share'),93 loadMoreBtn: document.getElementById('load-more'),94 themeToggle: document.getElementById('theme-toggle'),95 contactForm: document.getElementById('contact-form'),96 toast: document.getElementById('toast'),97 toastMessage: document.getElementById('toast-message'),98 installBtn: document.getElementById('install-btn'),99 header: document.getElementById('main-header')100};101 102// Initialize App103document.addEventListener('DOMContentLoaded', () => {104 initGallery();105 initEventListeners();106 initTheme();107 initScrollEffects();108 initLazyLoading();109});110 111// Gallery Functions112function initGallery() {113 renderGallery();114}115 116function renderGallery() {117 const filtered = state.currentFilter === 'all' 118 ? galleryData 119 : galleryData.filter(item => item.category === state.currentFilter);120 121 const toShow = filtered.slice(0, state.displayedItems);122 123 elements.galleryGrid.innerHTML = toShow.map((item, index) => `124 <article class="gallery-item group relative bg-white dark:bg-primary-800 rounded-2xl overflow-hidden shadow-md hover:shadow-2xl transition-all duration-500 animate-fade-in stagger-${(index % 6) + 1}" data-category="${item.category}">125 <div class="aspect-w-4 aspect-h-5 md:aspect-w-3 md:aspect-h-4 overflow-hidden bg-primary-200 dark:bg-primary-700">126 <img 127 src="${item.image}" 128 alt="${item.title}" 129 class="w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-700"130 loading="lazy"131 >132 <div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>133 </div>134 <div class="absolute bottom-0 left-0 right-0 p-6 transform translate-y-full group-hover:translate-y-0 transition-transform duration-300">135 <h3 class="text-white font-serif text-xl font-bold mb-1">${item.title}</h3>136 <p class="text-white/80 text-sm capitalize">${item.category}</p>137 </div>138 <button class="absolute top-4 right-4 w-10 h-10 bg-white/90 dark:bg-primary-900/90 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-all duration-300 hover:scale-110 focus:outline-none focus:ring-2 focus:ring-secondary-500" onclick="openModal(${item.id})" aria-label="View ${item.title}">139 <i data-feather="maximize-2" class="w-5 h-5 text-primary-900 dark:text-primary-100"></i>140 </button>141 </article>142 `).join('');143 144 // Update load more button visibility145 if (state.displayedItems >= filtered.length) {146 elements.loadMoreBtn.style.display = 'none';147 } else {148 elements.loadMoreBtn.style.display = 'inline-block';149 }150 151 // Re-initialize feather icons for new content152 if (typeof feather !== 'undefined') {153 feather.replace();154 }155}156 157// Filter Functionality158function initEventListeners() {159 // Filter buttons160 elements.filterButtons.forEach(btn => {161 btn.addEventListener('click', (e) => {162 // Update active state163 elements.filterButtons.forEach(b => {164 b.classList.remove('active', 'bg-primary-900', 'dark:bg-primary-100', 'text-primary-50', 'dark:text-primary-900');165 b.classList.add('bg-white', 'dark:bg-primary-800', 'text-primary-600', 'dark:text-primary-300');166 b.setAttribute('aria-selected', 'false');167 });168 169 e.target.classList.remove('bg-white', 'dark:bg-primary-800', 'text-primary-600', 'dark:text-primary-300');170 e.target.classList.add('active', 'bg-primary-900', 'dark:bg-primary-100', 'text-primary-50', 'dark:text-primary-900');171 e.target.setAttribute('aria-selected', 'true');172 173 // Update state and render174 state.currentFilter = e.target.dataset.filter;175 state.displayedItems = 6;176 renderGallery();177 });178 });179 180 // Load more181 elements.loadMoreBtn.addEventListener('click', () => {182 state.displayedItems += 3;183 renderGallery();184 });185 186 // Modal controls187 elements.modalClose.addEventListener('click', closeModal);188 elements.modalBackdrop.addEventListener('click', closeModal);189 elements.modalZoomIn.addEventListener('click', () => adjustZoom(0.25));190 elements.modalZoomOut.addEventListener('click', () => adjustZoom(-0.25));191 elements.modalShare.addEventListener('click', shareImage);192 193 // Keyboard navigation194 document.addEventListener('keydown', (e) => {195 if (!state.modalOpen) return;196 197 if (e.key === 'Escape') closeModal();198 if (e.key === 'ArrowLeft') navigateModal(-1);199 if (e.key === 'ArrowRight') navigateModal(1);200 });201 202 // Touch gestures for modal203 let touchStartX = 0;204 let touchEndX = 0;205 206 elements.modal.addEventListener('touchstart', (e) => {207 touchStartX = e.changedTouches[0].screenX;208 }, {passive: true});209 210 elements.modal.addEventListener('touchend', (e) => {211 touchEndX = e.changedTouches[0].screenX;212 handleSwipe();213 }, {passive: true});214 215 function handleSwipe() {216 const diff = touchStartX - touchEndX;217 if (Math.abs(diff) > 50) {218 if (diff > 0) navigateModal(1);219 else navigateModal(-1);220 }221 }222 223 // Theme toggle224 elements.themeToggle.addEventListener('click', toggleTheme);225 226 // Contact form227 elements.contactForm.addEventListener('submit', handleFormSubmit);228 229 // Install button230 let deferredPrompt;231 window.addEventListener('beforeinstallprompt', (e) => {232 e.preventDefault();233 deferredPrompt = e;234 elements.installBtn.classList.remove('hidden');235 elements.installBtn.classList.add('flex');236 });237 238 elements.installBtn.addEventListener('click', async () => {239 if (!deferredPrompt) return;240 deferredPrompt.prompt();241 const { outcome } = await deferredPrompt.userChoice;242 if (outcome === 'accepted') {243 showToast('App installed successfully!');244 }245 deferredPrompt = null;246 elements.installBtn.classList.add('hidden');247 elements.installBtn.classList.remove('flex');248 });249 250 // Mobile nav active state251 const mobileNavLinks = document.querySelectorAll('nav[aria-label="Mobile navigation"] a');252 const sections = document.querySelectorAll('section[id]');253 254 window.addEventListener('scroll', () => {255 let current = '';256 sections.forEach(section => {257 const sectionTop = section.offsetTop;258 const sectionHeight = section.clientHeight;259 if (scrollY >= sectionTop - 200) {260 current = section.getAttribute('id');261 }262 });263 264 mobileNavLinks.forEach(link => {265 link.classList.remove('text-secondary-600', 'dark:text-secondary-400');266 link.classList.add('text-primary-400', 'dark:text-primary-500');267 if (link.getAttribute('href') === `#${current}`) {268 link.classList.remove('text-primary-400', 'dark:text-primary-500');269 link.classList.add('text-secondary-600', 'dark:text-secondary-400');270 }271 });272 });273}274 275// Modal Functions276function openModal(id) {277 const item = galleryData.find(i => i.id === id);278 if (!item) return;279 280 state.currentImageIndex = galleryData.indexOf(item);281 state.zoomLevel = 1;282 283 elements.modalImage.src = item.image;284 elements.modalImage.style.transform = `scale(${state.zoomLevel})`;285 elements.modalTitle.textContent = item.title;286 elements.modalCategory.textContent = item.category.charAt(0).toUpperCase() + item.category.slice(1);287 288 elements.modal.classList.remove('hidden');289 document.body.style.overflow = 'hidden';290 state.modalOpen = true;291 292 // Preload adjacent images293 preloadAdjacentImages();294}295 296function closeModal() {297 elements.modal.classList.add('hidden');298 document.body.style.overflow = '';299 state.modalOpen = false;300 state.zoomLevel = 1;301}302 303function adjustZoom(delta) {304 state.zoomLevel = Math.max(0.5, Math.min(3, state.zoomLevel + delta));305 elements.modalImage.style.transform = `scale(${state.zoomLevel})`;306}307 308function navigateModal(direction) {309 let newIndex = state.currentImageIndex + direction;310 311 // Filter logic for navigation312 const filtered = state.currentFilter === 'all' 313 ? galleryData 314 : galleryData.filter(item => item.category === state.currentFilter);315 316 if (newIndex < 0) newIndex = filtered.length - 1;317 if (newIndex >= filtered.length) newIndex = 0;318 319 const item = filtered[newIndex];320 state.currentImageIndex = galleryData.indexOf(item);321 322 // Animate transition323 elements.modalImage.style.opacity = '0';324 setTimeout(() => {325 elements.modalImage.src = item.image;326 elements.modalTitle.textContent = item.title;327 elements.modalCategory.textContent = item.category.charAt(0).toUpperCase() + item.category.slice(1);328 state.zoomLevel = 1;329 elements.modalImage.style.transform = `scale(${state.zoomLevel})`;330 elements.modalImage.style.opacity = '1';331 }, 200);332}333 334function preloadAdjacentImages() {335 const filtered = state.currentFilter === 'all' 336 ? galleryData 337 : galleryData.filter(item => item.category === state.currentFilter);338 339 [-1, 1].forEach(offset => {340 let index = state.currentImageIndex + offset;341 if (index < 0) index = filtered.length - 1;342 if (index >= filtered.length) index = 0;343 344 const img = new Image();345 img.src = filtered[index].image;346 });347}348 349async function shareImage() {350 const item = galleryData[state.currentImageIndex];351 352 if (navigator.share) {353 try {354 await navigator.share({355 title: item.title,356 text: `Check out this amazing pencil sketch: ${item.title}`,357 url: window.location.href358 });359 } catch (err) {360 console.log('Share cancelled');361 }362 } else {363 // Fallback: copy to clipboard364 navigator.clipboard.writeText(window.location.href);365 showToast('Link copied to clipboard!');366 }367}368 369// Theme Functions370function initTheme() {371 const savedTheme = localStorage.getItem('theme');372 const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;373 374 if (savedTheme === 'dark' || (!savedTheme && systemDark)) {375 document.documentElement.classList.add('dark');376 state.darkMode = true;377 }378}379 380function toggleTheme() {381 state.darkMode = !state.darkMode;382 document.documentElement.classList.toggle('dark');383 localStorage.setItem('theme', state.darkMode ? 'dark' : 'light');384 385 // Update icon386 setTimeout(() => {387 if (typeof feather !== 'undefined') {388 feather.replace();389 }390 }, 100);391}392 393// Form Handling394function handleFormSubmit(e) {395 e.preventDefault();396 397 const formData = new FormData(e.target);398 const data = Object.fromEntries(formData);399 400 // Simulate API call401 const submitBtn = e.target.querySelector('button[type="submit"]');402 const originalText = submitBtn.textContent;403 submitBtn.disabled = true;404 submitBtn.innerHTML = '<i data-feather="loader" class="w-5 h-5 animate-spin mx-auto"></i>';405 feather.replace();406 407 setTimeout(() => {408 submitBtn.disabled = false;409 submitBtn.textContent = originalText;410 e.target.reset();411 showToast('Message sent successfully! I\'ll get back to you soon.');412 }, 1500);413}414 415function showToast(message) {416 elements.toastMessage.textContent = message;417 elements.toast.classList.remove('translate-y-20', 'opacity-0');418 419 setTimeout(() => {420 elements.toast.classList.add('translate-y-20', 'opacity-0');421 }, 3000);422}423 424// Scroll Effects425function initScrollEffects() {426 let lastScroll = 0;427 428 window.addEventListener('scroll', () => {429 const currentScroll = window.pageYOffset;430 431 // Header hide/show on mobile432 if (window.innerWidth < 768) {433 if (currentScroll > lastScroll && currentScroll > 100) {434 elements.header.style.transform = 'translateY(-100%)';435 } else {436 elements.header.style.transform = 'translateY(0)';437 }438 }439 440 lastScroll = currentScroll;441 });442 443 // Intersection Observer for animations444 const observerOptions = {445 threshold: 0.1,446 rootMargin: '0px 0px -50px 0px'447 };448 449 const observer = new IntersectionObserver((entries) => {450 entries.forEach(entry => {451 if (entry.isIntersecting) {452 entry.target.classList.add('animate-slide-up');453 entry.target.style.opacity = '1';454 }455 });456 }, observerOptions);457 458 document.querySelectorAll('section h2, section p').forEach(el => {459 el.style.opacity = '0';460 observer.observe(el);461 });462}463 464// Lazy Loading465function initLazyLoading() {466 const imageObserver = new IntersectionObserver((entries, observer) => {467 entries.forEach(entry => {468 if (entry.isIntersecting) {469 const img = entry.target;470 img.src = img.dataset.src || img.src;471 img.classList.remove('img-skeleton');472 observer.unobserve(img);473 }474 });475 });476 477 document.querySelectorAll('img[loading="lazy"]').forEach(img => {478 imageObserver.observe(img);479 });480}481 482// Performance: Debounce resize483let resizeTimer;484window.addEventListener('resize', () => {485 clearTimeout(resizeTimer);486 resizeTimer = setTimeout(() => {487 // Recalculate layout if needed488 if (state.modalOpen) {489 closeModal();490 }491 }, 250);492});