CoolFace
Apppublic

bsateeshk/techfolio-nexus

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes
script.js492 linesDownload Raw Back to root
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});