-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinline_script.js
More file actions
104 lines (102 loc) Β· 36.2 KB
/
Copy pathinline_script.js
File metadata and controls
104 lines (102 loc) Β· 36.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
(function(){
const canvas=document.getElementById('particle-canvas');
const ctx=canvas.getContext('2d');
let particles=[];
function resize(){canvas.width=innerWidth;canvas.height=innerHeight;}
resize();window.addEventListener('resize',resize);
for(let i=0;i<90;i++){particles.push({x:Math.random()*innerWidth,y:Math.random()*innerHeight,r:Math.random()*1.5+.3,vx:(Math.random()-.5)*.25,vy:(Math.random()-.5)*.25,a:Math.random()*.6+.1});}
function draw(){ctx.clearRect(0,0,canvas.width,canvas.height);particles.forEach(p=>{ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);ctx.fillStyle=`rgba(180,200,255,${p.a})`;ctx.fill();p.x+=p.vx;p.y+=p.vy;if(p.x<0||p.x>canvas.width)p.vx*=-1;if(p.y<0||p.y>canvas.height)p.vy*=-1;});requestAnimationFrame(draw);}draw();
})();
const PEXELS_KEY='fpWfjedYtUsLiEPx4PRat4mjBRo44spbAQY6eIODDp68Q8WYpj5jTrj4';
const PIXABAY_KEY='49779190-56ac3b8e27bb9397dd241c851';
let currentQuery='', currentPage=1, totalResults=0, uploadedImages=[], currentImages=[], currentDetailsImage=null, currentCollections=JSON.parse(localStorage.getItem('imageExplorerCollections')||'[]'), currentLanguage=localStorage.getItem('imageExplorerLang')||detectLanguage(), currentView='grid', threeScene=null, threeRenderer=null, threeGroup=null;
let currentUser=JSON.parse(localStorage.getItem('current_user')||'null');
let trendingSearches=['Nature','Mountains','Ocean','Sunset','Forest','City','Architecture','Animals'];
let searchHistory=JSON.parse(localStorage.getItem('searchHistory')||'[]');
let currentFilters={orientation:'',color:''};
let pendingFiles=[];
let favoriteIds=new Set(JSON.parse(localStorage.getItem('imageExplorerFavorites')||'[]'));
let collectionBuffer={image:null};
let speechRecognition, voiceListening=false;
const translations={
en:{
nav:{signin:'Sign In',logout:'Logout',collections:'Collections'},
hero:{badge:'Powered by <span class="brand-credit">Code with Vishesh</span>',title:'Discover Images<br>Beyond Imagination',subtitle:'Explore millions of stunning, royalty-free photos. Search, upload, and download with one click.',search:'Search',upload:'Upload',orientation:'Orientation',all:'All',landscape:'Landscape',portrait:'Portrait',square:'Square',trending:'π₯ Trending:',saved:'πΎ Saved:',photos:'Photos',artists:'Artists',free:'Free'},
results:{label:'Results',toggleGrid:'3D View',collections:'Collections',grid:'Grid View',loadMore:'Load More'},
modal:{loginTitle:'Welcome Back',signupTitle:'Create Account',email:'Email address',password:'Password',name:'Full name',confirm:'Confirm password',signIn:'Sign In',signUp:'Sign Up',createAccount:'Create Account',noAccount:"Don't have an account?",alreadyHave:'Already have an account?',uploadTitle:'Upload Photos',uploadBrowse:'Click to browse',uploadOr:'or drag & drop',uploadHint:'PNG, JPG, WEBP up to 20MB each',addToGallery:'Add to Gallery',collectionTitle:'Add to Collection',collectionName:'Collection name',collectionDesc:'Description',saveCollection:'Save to Collection'},
details:{title:'Image Details',download:'Download',copy:'Copy Link',share:'Share',favorite:'Favorite',open:'Open Original',info:'Image Information',resolution:'Resolution',width:'Width',height:'Height',aspect:'Aspect Ratio',size:'File Size',format:'Format',colorSpace:'Color Space',orientation:'Orientation',published:'Date Published',license:'License',views:'Views',downloads:'Downloads',likes:'Likes',metadata:'EXIF Metadata',similar:'Similar Images',tags:'Related Tags',colors:'Dominant Colors',zoomIn:'Zoom In',zoomOut:'Zoom Out'},
collections:{title:'Collections',create:'Create Collection',empty:'No collections yet',placeholder:'Collection name',description:'Description',private:'Private',public:'Public',add:'Add to Collection',rename:'Rename',duplicate:'Duplicate',remove:'Remove',delete:'Delete',share:'Share',sort:'Sort',newest:'Newest',oldest:'Oldest',mostDownloaded:'Most Downloaded',mostViewed:'Most Viewed'}
}
};
function detectLanguage(){ const lang=(navigator.language||'en').slice(0,2).toLowerCase(); return ['en','hi','es','fr','ja','de','ar','pt','it','zh'].includes(lang)?lang:'en'; }
function setLanguage(lang){ currentLanguage=lang; localStorage.setItem('imageExplorerLang',lang); document.documentElement.lang=lang; document.documentElement.dir=(lang==='ar'?'rtl':'ltr'); document.body.classList.toggle('rtl',lang==='ar'); document.getElementById('lang-selector').value=lang; applyTranslations(); }
function applyTranslations(){ const dict=translations[currentLanguage]||translations.en; document.querySelectorAll('[data-i18n]').forEach(node=>{ const keys=node.getAttribute('data-i18n').split('.'); let val=dict; keys.forEach(k=>val=val&&val[k]); if(val){ node.innerHTML=val; } }); document.querySelectorAll('[data-i18n-placeholder]').forEach(node=>{ const keys=node.getAttribute('data-i18n-placeholder').split('.'); let val=dict; keys.forEach(k=>val=val&&val[k]); if(val){ node.setAttribute('placeholder',val); } }); }
function setTheme(name,btn){ document.documentElement.setAttribute('data-theme',name==='default'?'':name); document.querySelectorAll('.theme-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); localStorage.setItem('theme',name); showToast('Theme applied','info'); }
(function(){ const saved=localStorage.getItem('theme'); if(saved&&saved!=='default'){ document.documentElement.setAttribute('data-theme',saved); const btn=document.querySelector(`.theme-btn[data-t="${saved}"]`); if(btn){ document.querySelectorAll('.theme-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); } } })();
function updateNavAuth(){ const logoutBtn=document.getElementById('btn-logout'); const loginBtn=document.getElementById('btn-login-nav'); const avatar=document.getElementById('nav-avatar'); const username=document.getElementById('nav-username'); if(currentUser){ logoutBtn.style.display=''; loginBtn.style.display='none'; avatar.style.display=''; username.style.display=''; const initials=currentUser.name.split(' ').map(w=>w[0]).join('').substring(0,2).toUpperCase(); avatar.textContent=initials; username.textContent=`Hi, ${currentUser.name.split(' ')[0]}`; } else { logoutBtn.style.display='none'; loginBtn.style.display=''; avatar.style.display='none'; username.style.display='none'; } }
updateNavAuth();
function doLogin(){ const email=document.getElementById('login-email').value.trim(); const pass=document.getElementById('login-pass').value; if(!email||!pass)return showToast('Please fill all fields','error'); const users=JSON.parse(localStorage.getItem('users')||'[]'); const user=users.find(u=>u.email===email&&u.password===pass); if(!user)return showToast('Invalid credentials','error'); currentUser=user; localStorage.setItem('current_user',JSON.stringify(user)); updateNavAuth(); closeModal('login'); showToast(`Welcome back, ${user.name.split(' ')[0]}!`,'success'); }
function doSignup(){ const name=document.getElementById('signup-name').value.trim(); const email=document.getElementById('signup-email').value.trim(); const pass=document.getElementById('signup-pass').value; const confirm=document.getElementById('signup-confirm').value; if(!name||!email||!pass||!confirm)return showToast('Please fill all fields','error'); if(pass!==confirm)return showToast('Passwords do not match','error'); if(pass.length<6)return showToast('Password must be 6+ characters','error'); const users=JSON.parse(localStorage.getItem('users')||'[]'); if(users.find(u=>u.email===email))return showToast('Email already registered','error'); const user={name,email,password:pass}; users.push(user); localStorage.setItem('users',JSON.stringify(users)); currentUser=user; localStorage.setItem('current_user',JSON.stringify(user)); updateNavAuth(); closeModal('signup'); showToast(`Account created! Welcome, ${name.split(' ')[0]}!`,'success'); }
function logout(){ currentUser=null; localStorage.removeItem('current_user'); updateNavAuth(); showToast('Signed out successfully','info'); }
function openModal(name){ document.getElementById('modal-'+name).classList.add('open'); }
function closeModal(name){ document.getElementById('modal-'+name).classList.remove('open'); }
function switchModal(from,to){ closeModal(from); setTimeout(()=>openModal(to),180); }
document.querySelectorAll('.modal-overlay').forEach(m=>{ m.addEventListener('click',e=>{ if(e.target===m) m.classList.remove('open'); }); });
document.addEventListener('keydown',e=>{ if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='k'){ e.preventDefault(); const search=document.getElementById('search-input'); search.focus(); search.select(); return; } if(e.key==='Escape'){ document.querySelectorAll('.modal-overlay.open').forEach(m=>m.classList.remove('open')); closeLightbox(); } });
function showToast(msg,type='info'){ const c=document.getElementById('toast-container'); const t=document.createElement('div'); t.className=`toast ${type}`; const icons={success:'β
',error:'β',info:'π‘'}; t.innerHTML=`<span>${icons[type]||'βΉοΈ'}</span><span>${msg}</span>`; c.appendChild(t); setTimeout(()=>{t.classList.add('removing');setTimeout(()=>t.remove(),400)},3200);}
function showSuggestions(query){ const dropdown=document.getElementById('suggestions-dropdown'); if(!query){ dropdown.style.display='none'; return; } const matches=trendingSearches.filter(s=>s.toLowerCase().includes(query.toLowerCase())); const history=searchHistory.filter(s=>s.toLowerCase().includes(query.toLowerCase())).slice(0,3); const combined=[...new Set([...matches,...history])].slice(0,6); if(!combined.length){ dropdown.style.display='none'; return; } dropdown.innerHTML=combined.map(s=>`<div class="suggestion-item" onclick="setSuggestion('${s}')">${s}</div>`).join(''); dropdown.style.display='block'; }
function setSuggestion(text){ document.getElementById('search-input').value=text; document.getElementById('suggestions-dropdown').style.display='none'; searchImages(); }
function selectSuggestion(event){ if(event.key==='ArrowDown'){ event.preventDefault(); const dropdown=document.getElementById('suggestions-dropdown'); const first=dropdown.querySelector('.suggestion-item'); if(first){ first.classList.add('active'); } } }
function getVisibleImages(items){ const orientation=currentFilters.orientation; const list=Array.isArray(items)?items:[]; if(!orientation) return list; return list.filter(item=>(item.orientation||'').toLowerCase()===orientation); }
function renderGallery(items,replace=false){ const grid=document.getElementById('img-grid'); if(replace) grid.innerHTML=''; const visible=getVisibleImages(items); if(!visible.length){ grid.innerHTML='<div class="empty-state" style="grid-column:1/-1"><div class="empty-icon">πΌοΈ</div><div class="empty-title">No matching images</div><div class="empty-sub">Try a different search or switch the orientation filter</div></div>'; return; } visible.forEach((item,i)=>grid.insertAdjacentHTML('beforeend',buildCard(item,i))); }
function updateFilters(){ currentFilters.orientation=document.getElementById('filter-orientation').value; if(currentImages.length){ renderGallery(currentImages,true); } }
function showSkeletons(){ const a=document.getElementById('loading-area'); a.innerHTML='<div class="img-grid">'+Array(8).fill('<div class="skeleton"></div>').join('')+'</div>'; }
function optimizeImageUrl(url,width=800){ if(!url) return url; if(url.includes('pexels.com')) return url.replace('auto=compress&cs=tinysrgb','auto=compress&cs=tinysrgb&fm=webp&w='+width); if(url.includes('pixabay.com')) return url.replace(/.jpg|.jpeg|.png/gi,match=>match.includes('jpg')?'.jpg?auto=compress&fm=webp&w='+width:'.png?auto=compress&fm=webp&w='+width); return url; }
function createImageRecord(item, source, index){ const width=item.width||item.imageWidth||1200; const height=item.height||item.imageHeight||800; const orientation=width>height?'landscape':height>width?'portrait':'square'; return {id:item.id||`${source}-${index}` ,src:optimizeImageUrl(item.img||item.largeImageURL||item.src?.large||item.src?.original||item.original),orig:item.orig||item.src?.original||item.largeImageURL||item.img||'',credit:item.credit||item.photographer||item.user||'ImageLens',creditUrl:item.creditUrl||item.photographer_url||`https://pixabay.com/users/${encodeURIComponent(item.user||'pixabay')}/`,source,query:currentQuery, tags:item.tags||[currentQuery,'travel','wallpaper','hd','4k'], colors:['#4f8ef7','#a259ff','#00e5c3','#f5c518'], width,height,aspectRatio:(width/height).toFixed(2),size: `${Math.round((width*height)/1200)} KB`,format:'JPEG',colorSpace:'sRGB',orientation,datePublished:new Date().toISOString().slice(0,10),license:'Royalty-free',views:Math.floor(Math.random()*5000)+200,downloads:Math.floor(Math.random()*1200)+100,likes:Math.floor(Math.random()*800)+50,exif:{camera:'N/A',lens:'N/A',iso:'N/A',aperture:'N/A',shutter:'N/A',flash:'N/A',exposure:'N/A',focalLength:'N/A',gps:'N/A',location:'N/A',device:'N/A',manufacturer:'N/A'},dominantColors:[{hex:'#4f8ef7',rgb:'79, 142, 247',cssVar:'--accent-1'},{hex:'#a259ff',rgb:'162, 89, 255',cssVar:'--accent-2'},{hex:'#00e5c3',rgb:'0, 229, 195',cssVar:'--accent-3'}]}; }
async function searchImages(){ const q=document.getElementById('search-input').value.trim(); if(!q)return showToast('Enter a search term','error'); if(!searchHistory.includes(q)){ searchHistory.unshift(q); if(searchHistory.length>10) searchHistory.pop(); localStorage.setItem('searchHistory',JSON.stringify(searchHistory)); loadSavedSearches(); } currentQuery=q; currentPage=1; currentImages=[]; document.getElementById('results-section').style.display=''; document.getElementById('img-grid').innerHTML=''; document.getElementById('load-more-wrap').style.display='none'; showSkeletons(); await fetchImages(q,1,true); setTimeout(()=>document.getElementById('results-section').scrollIntoView({behavior:'smooth'}),250); }
async function fetchImages(q,page,replace=false){ try { const [pexelsResult,pixabayResult]=await Promise.allSettled([fetch(`https://api.pexels.com/v1/search?query=${encodeURIComponent(q)}&per_page=20&page=${page}`,{headers:{Authorization:PEXELS_KEY}}),fetch(`https://pixabay.com/api/?key=${PIXABAY_KEY}&q=${encodeURIComponent(q)}&image_type=photo&per_page=20&page=${page}`)]); const images=[]; let pexelsNext=false,pixabayMore=false,pexelsTotal=0,pixabayTotal=0; if(pexelsResult.status==='fulfilled'){const r=pexelsResult.value; if(r.ok){const data=await r.json(); if(Array.isArray(data.photos)&&data.photos.length){pexelsTotal=data.total_results||0; pexelsNext=Boolean(data.next_page); images.push(...data.photos.map((p,i)=>createImageRecord({id:`pexels-${p.id}`,img:p.src.large,orig:p.src.original,credit:p.photographer,creditUrl:p.photographer_url,width:p.width||1200,height:p.height||800,tags:[q,'landscape','nature','wallpaper']},'pexels',i)));}}} if(pixabayResult.status==='fulfilled'){const r=pixabayResult.value; if(r.ok){const data=await r.json(); if(Array.isArray(data.hits)&&data.hits.length){pixabayTotal=data.totalHits||0; pixabayMore=pixabayTotal>page*20; images.push(...data.hits.map((hit,i)=>createImageRecord({id:`pixabay-${hit.id}`,img:hit.largeImageURL,orig:hit.largeImageURL,credit:hit.user,creditUrl:`https://pixabay.com/users/${encodeURIComponent(hit.user)}-${hit.user_id}/`,width:hit.webformatWidth||1200,height:hit.webformatHeight||800,tags:[q,'travel','hd','4k']},'pixabay',i)));}}} document.getElementById('loading-area').innerHTML=''; const grid=document.getElementById('img-grid'); if(replace)grid.innerHTML=''; if(images.length===0){ if(replace)grid.innerHTML=`<div class="empty-state" style="grid-column:1/-1"><div class="empty-icon">π</div><div class="empty-title">No results found</div><div class="empty-sub">Try different keywords or check your spelling</div></div>`; showToast('No results found β verify your API keys and search term','error'); return; } currentImages=[...currentImages,...images]; totalResults=pexelsTotal+pixabayTotal; document.getElementById('results-label').textContent=`Results for "${q}"`; document.getElementById('results-count').textContent=`${Math.min(totalResults,10000).toLocaleString()} images`; images.forEach((item,i)=>grid.insertAdjacentHTML('beforeend',buildCard(item,i))); document.getElementById('load-more-wrap').style.display=(pexelsNext||pixabayMore)?'':'none'; } catch(err){ document.getElementById('loading-area').innerHTML=''; showToast('Error fetching images β verify your API keys','error'); showDemoImages(replace); } }
function showDemoImages(replace){ const demos=[{src:'https://images.pexels.com/photos/1366919/pexels-photo-1366919.jpeg?auto=compress&cs=tinysrgb&w=600',credit:'Pexels',creditUrl:'https://pexels.com',orig:'https://images.pexels.com/photos/1366919/pexels-photo-1366919.jpeg',id:'demo-1'},{src:'https://images.pexels.com/photos/3075993/pexels-photo-3075993.jpeg?auto=compress&cs=tinysrgb&w=600',credit:'Pexels',creditUrl:'https://pexels.com',orig:'https://images.pexels.com/photos/3075993/pexels-photo-3075993.jpeg',id:'demo-2'},{src:'https://images.pexels.com/photos/1323550/pexels-photo-1323550.jpeg?auto=compress&cs=tinysrgb&w=600',credit:'Pexels',creditUrl:'https://pexels.com',orig:'https://images.pexels.com/photos/1323550/pexels-photo-1323550.jpeg',id:'demo-3'},{src:'https://images.pexels.com/photos/247431/pexels-photo-247431.jpeg?auto=compress&cs=tinysrgb&w=600',credit:'Pexels',creditUrl:'https://pexels.com',orig:'https://images.pexels.com/photos/247431/pexels-photo-247431.jpeg',id:'demo-4'}]; const items=demos.map((d,i)=>createImageRecord({id:d.id,img:d.src,orig:d.orig,credit:d.credit,creditUrl:d.creditUrl,width:1200,height:800,tags:['demo','nature','wallpaper']},'demo',i)); currentImages=[...items]; renderGallery(items,replace); document.getElementById('results-label').textContent='Demo Gallery'; document.getElementById('results-count').textContent='Demo β add API key for live search'; }
function buildCard(item,i){ return `<div class="img-card" data-id="${item.id}" style="animation-delay:${i*.06}s">
<img src="${item.src}" alt="${item.credit}" loading="lazy" onerror="this.src='https://via.placeholder.com/400x300?text=Image'"/>
<div class="img-overlay">
<div class="img-credit">Photo by <a href="${item.creditUrl}" target="_blank">${item.credit}</a></div>
<div class="img-actions">
<button class="img-action-btn download" data-action="download" data-id="${item.id}">β¬ Save</button>
<button class="img-action-btn bookmark ${favoriteIds.has(item.id)?'active':''}" data-action="bookmark" data-id="${item.id}">β‘ Save</button>
<button class="img-action-btn view" data-action="view" data-url="${item.src}">π View</button>
<button class="img-action-btn detail" data-action="detail" data-id="${item.id}">β¦ Details</button>
<button class="img-action-btn detail" data-action="collection" data-id="${item.id}">π Add</button>
</div>
</div>
</div>`; }
async function loadMore(){ currentPage++; document.getElementById('load-more-wrap').style.display='none'; showSkeletons(); await fetchImages(currentQuery,currentPage,false); }
function downloadImg(url,id){ if(url==='#'){showToast('Add API key for downloads','info');return;} const link=document.createElement('a'); fetch(url).then(r=>r.blob()).then(blob=>{const blobUrl=window.URL.createObjectURL(blob); link.href=blobUrl; link.download=`imagelens-${id}.jpg`; link.style.display='none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(blobUrl); showToast('Download complete!','success');}).catch(()=>{showToast('Opening image in new tab...','info'); window.open(url,'_blank','noopener');}); }
function openLightbox(url){ document.getElementById('lightbox-img').src=url; document.getElementById('lightbox').classList.add('open'); }
function closeLightbox(e){ if(!e||e.target===document.getElementById('lightbox')||e.target.classList.contains('lightbox-close')){ document.getElementById('lightbox').classList.remove('open'); setTimeout(()=>document.getElementById('lightbox-img').src='',300); } }
function openDetailById(id){ const image=currentImages.find(img=>img.id===id) || currentImages[0]; if(!image){ showToast('Image not found','error'); return; } currentDetailsImage=image; renderDetailView(image); showDetailView(); }
function renderDetailView(image){ const el=document.getElementById('detail-view'); const similar=currentImages.filter(item=>item.id!==image.id).slice(0,4); const tags=image.tags||[]; const colors=image.dominantColors||[]; el.innerHTML=`<div class="detail-shell"><div class="preview-card"><div class="image-frame"><img src="${image.src}" alt="${image.credit}"/></div><div class="detail-toolbar"><button class="icon-btn" onclick="zoomImage(1.1)">οΌ <span data-i18n="details.zoomIn">Zoom In</span></button><button class="icon-btn" onclick="zoomImage(0.9)">β <span data-i18n="details.zoomOut">Zoom Out</span></button><button class="icon-btn" onclick="downloadImg('${image.orig||image.src}','${image.id}')">β¬ <span data-i18n="details.download">Download</span></button><button class="icon-btn" onclick="copyLink('${location.href}')">π <span data-i18n="details.copy">Copy Link</span></button><button class="icon-btn" onclick="shareImage('${image.id}')">β <span data-i18n="details.share">Share</span></button><button class="icon-btn" onclick="toggleFavorite('${image.id}')">β‘ <span data-i18n="details.favorite">Favorite</span></button><button class="icon-btn" onclick="window.open('${image.orig||image.src}','_blank')">β¬ <span data-i18n="details.open">Open Original</span></button></div></div><div class="info-card"><h3 data-i18n="details.title">Image Details</h3><div class="info-grid"><div class="info-item"><strong data-i18n="details.resolution">Resolution</strong><span>${image.width} Γ ${image.height}</span></div><div class="info-item"><strong data-i18n="details.width">Width</strong><span>${image.width}</span></div><div class="info-item"><strong data-i18n="details.height">Height</strong><span>${image.height}</span></div><div class="info-item"><strong data-i18n="details.aspect">Aspect Ratio</strong><span>${image.aspectRatio}</span></div><div class="info-item"><strong data-i18n="details.size">File Size</strong><span>${image.size}</span></div><div class="info-item"><strong data-i18n="details.format">Format</strong><span>${image.format}</span></div><div class="info-item"><strong data-i18n="details.colorSpace">Color Space</strong><span>${image.colorSpace}</span></div><div class="info-item"><strong data-i18n="details.orientation">Orientation</strong><span>${image.orientation}</span></div><div class="info-item"><strong data-i18n="details.published">Date Published</strong><span>${image.datePublished}</span></div><div class="info-item"><strong data-i18n="details.license">License</strong><span>${image.license}</span></div><div class="info-item"><strong data-i18n="details.views">Views</strong><span>${image.views}</span></div><div class="info-item"><strong data-i18n="details.downloads">Downloads</strong><span>${image.downloads}</span></div></div><h4 style="margin-top:16px" data-i18n="details.metadata">EXIF Metadata</h4><div class="info-grid">${Object.entries(image.exif||{}).map(([k,v])=>`<div class="info-item"><strong>${k}</strong><span>${v}</span></div>`).join('')}</div><h4 style="margin-top:16px" data-i18n="details.colors">Dominant Colors</h4><div class="color-grid">${(colors||[]).map(c=>`<div><div class="color-swatch" style="background:${c.hex||c}"></div><div>${c.hex||c}</div><div style="font-size:.75rem;color:var(--text-secondary)">${c.rgb||''}</div></div>`).join('')}</div><h4 style="margin-top:16px" data-i18n="details.tags">Related Tags</h4><div class="chip-row">${(tags||[]).map(tag=>`<button class="chip" onclick="setSuggestion('${tag}')">${tag}</button>`).join('')}</div><h4 style="margin-top:16px" data-i18n="details.similar">Similar Images</h4><div class="similar-grid">${similar.map(item=>`<div class="similar-card" onclick="openDetailById('${item.id}')"><img src="${item.src}" alt="${item.credit}"/><div style="padding:10px;font-size:.8rem">${item.credit}</div></div>`).join('')}</div></div></div>`; applyTranslations(); }
function zoomImage(scale){ const img=document.querySelector('#detail-view img'); if(img){ const current=Number(img.style.transform?.replace('scale(','').replace(')',''))||1; img.style.transform=`scale(${Math.min(2.4,Math.max(.8,current*scale))})`; } }
function copyLink(link){ navigator.clipboard.writeText(link).then(()=>showToast('Link copied','success')).catch(()=>showToast('Copy failed','error')); }
function shareImage(id){ if(navigator.share){ navigator.share({title:'Image Explorer AI',text:'Check out this image',url:`${location.href}#/image/${id}`}).catch(()=>{}); } else { copyLink(`${location.href}#/image/${id}`); } }
function toggleFavorite(id){ if(favoriteIds.has(id)){ favoriteIds.delete(id); } else { favoriteIds.add(id); } localStorage.setItem('imageExplorerFavorites',JSON.stringify([...favoriteIds])); showToast('Favorite updated','info'); if(currentDetailsImage) renderDetailView(currentDetailsImage); }
function showGridView(){ document.getElementById('results-grid-view').style.display=''; document.getElementById('detail-view').classList.remove('active'); document.getElementById('collections-view').classList.remove('active'); document.getElementById('three-view').classList.remove('active'); document.getElementById('view-toggle').textContent='3D View'; currentView='grid'; }
function showDetailView(){ document.getElementById('results-grid-view').style.display='none'; document.getElementById('detail-view').classList.add('active'); document.getElementById('collections-view').classList.remove('active'); document.getElementById('three-view').classList.remove('active'); document.getElementById('view-toggle').textContent='Back to Grid'; currentView='detail'; }
function showCollectionsView(){ document.getElementById('results-grid-view').style.display='none'; document.getElementById('detail-view').classList.remove('active'); document.getElementById('collections-view').classList.add('active'); document.getElementById('three-view').classList.remove('active'); renderCollectionsView(); currentView='collections'; }
function toggle3DView(){ if(currentView==='3d'){ showGridView(); return; } document.getElementById('results-grid-view').style.display='none'; document.getElementById('detail-view').classList.remove('active'); document.getElementById('collections-view').classList.remove('active'); document.getElementById('three-view').classList.add('active'); render3DView(); currentView='3d'; document.getElementById('view-toggle').textContent='Grid View'; }
function renderCollectionsView(){ const el=document.getElementById('collections-view'); if(!currentCollections.length){ el.innerHTML=`<div class="collection-card"><h3 data-i18n="collections.title">Collections</h3><p data-i18n="collections.empty">No collections yet</p><div class="collection-form" style="margin-top:12px"><input id="new-collection-name" placeholder="Collection name" /><button class="btn-primary" onclick="createCollection()">Create Collection</button></div></div>`; applyTranslations(); return; } el.innerHTML=`<div class="collection-card"><h3 data-i18n="collections.title">Collections</h3><div class="collection-form"><input id="new-collection-name" placeholder="Collection name" /><input id="new-collection-desc" placeholder="Description" /><button class="btn-primary" onclick="createCollection()">Create Collection</button></div><div class="collection-grid">${currentCollections.map((c,i)=>`<div class="collection-card"><h4>${c.name}</h4><p style="color:var(--text-secondary)">${c.description||''}</p><div class="collection-actions"><button class="icon-btn" onclick="renameCollection('${c.id}')">β <span data-i18n="collections.rename">Rename</span></button><button class="icon-btn" onclick="duplicateCollection('${c.id}')">β§ <span data-i18n="collections.duplicate">Duplicate</span></button><button class="icon-btn" onclick="deleteCollection('${c.id}')">π <span data-i18n="collections.delete">Delete</span></button><button class="icon-btn" onclick="toggleCollectionPrivacy('${c.id}')">π <span>${c.private?'Private':'Public'}</span></button></div><div class="collection-image-list">${(c.images||[]).slice(0,4).map(img=>`<div class="collection-image-item"><img src="${img.src}" alt="${img.credit}"/><div>${img.credit}</div></div>`).join('')}</div></div>`).join('')}</div></div>`; applyTranslations(); }
function createCollection(){ const name=document.getElementById('new-collection-name').value.trim(); const desc=document.getElementById('new-collection-desc')?document.getElementById('new-collection-desc').value.trim():'', id=`collection-${Date.now()}`; if(!name)return showToast('Collection name required','error'); currentCollections.unshift({id,name,description:desc,private:true,images:[],owner:currentUser?.name||'You',createdAt:new Date().toISOString().slice(0,10),updatedAt:new Date().toISOString().slice(0,10)}); localStorage.setItem('imageExplorerCollections',JSON.stringify(currentCollections)); renderCollectionsView(); showToast('Collection created','success'); }
function renameCollection(id){ const current=currentCollections.find(c=>c.id===id); const next=prompt('Rename collection', current?.name||''); if(next){ current.name=next.trim(); localStorage.setItem('imageExplorerCollections',JSON.stringify(currentCollections)); renderCollectionsView(); } }
function duplicateCollection(id){ const current=currentCollections.find(c=>c.id===id); if(!current)return; const clone={...current,id:`collection-${Date.now()}`,name:`${current.name} Copy`,images:[...current.images]}; currentCollections.unshift(clone); localStorage.setItem('imageExplorerCollections',JSON.stringify(currentCollections)); renderCollectionsView(); showToast('Collection duplicated','success'); }
function deleteCollection(id){ currentCollections=currentCollections.filter(c=>c.id!==id); localStorage.setItem('imageExplorerCollections',JSON.stringify(currentCollections)); renderCollectionsView(); showToast('Collection deleted','success'); }
function toggleCollectionPrivacy(id){ const target=currentCollections.find(c=>c.id===id); if(target){ target.private=!target.private; localStorage.setItem('imageExplorerCollections',JSON.stringify(currentCollections)); renderCollectionsView(); } }
function saveCollectionFromModal(){ if(!collectionBuffer.image)return showToast('Select an image first','error'); const name=document.getElementById('collection-name').value.trim(); const desc=document.getElementById('collection-desc').value.trim(); if(!name){ const target=currentCollections[0]; if(!target){ showToast('Create a collection first','error'); return; } } const collection=currentCollections.find(c=>c.name===name)||currentCollections[0]; if(!collection){ currentCollections.unshift({id:`collection-${Date.now()}`,name:name||'New Collection',description:desc,private:true,images:[],owner:currentUser?.name||'You',createdAt:new Date().toISOString().slice(0,10),updatedAt:new Date().toISOString().slice(0,10)}); } const target=currentCollections.find(c=>c.name===name)||currentCollections[0]; if(target){ target.images.push(collectionBuffer.image); target.updatedAt=new Date().toISOString().slice(0,10); localStorage.setItem('imageExplorerCollections',JSON.stringify(currentCollections)); closeModal('collection'); showToast('Saved to collection','success'); } }
function handleCollectionAction(image){ collectionBuffer.image=image; openModal('collection'); }
function render3DView(){ const el=document.getElementById('three-view'); const images=currentImages.slice(0,12); if(!images.length){ el.innerHTML='<div class="empty-state"><div class="empty-icon">π§</div><div class="empty-title">No images to render</div></div>'; return; } el.innerHTML=`<div class="three-stage">${images.map((img,index)=>`<div class="three-card" onclick="openDetailById('${img.id}')" style="transform: rotateY(${(index-(images.length-1)/2)*12}deg) translateX(${(index-(images.length-1)/2)*110}px) translateZ(${index%2===0?80:-40}px);"><img src="${img.src}" alt="${img.credit}"/><div class="three-card-info">${img.credit}</div></div>`).join('')}</div>`; }
function handleFiles(files){ pendingFiles=[...files]; const grid=document.getElementById('upload-preview-grid'); grid.innerHTML=''; [...files].forEach(f=>{ const reader=new FileReader(); reader.onload=e=>{ grid.insertAdjacentHTML('beforeend',`<div class="upload-thumb"><img src="${e.target.result}" alt=""/></div>`); }; reader.readAsDataURL(f); }); }
function handleDrop(e){ e.preventDefault(); document.getElementById('upload-area').classList.remove('dragover'); handleFiles(e.dataTransfer.files); }
function confirmUpload(){ if(!pendingFiles.length)return showToast('No files selected','error'); const grid=document.getElementById('img-grid'); document.getElementById('results-section').style.display=''; document.getElementById('results-label').textContent='Your Uploads'; pendingFiles.forEach((f,i)=>{ const reader=new FileReader(); reader.onload=e=>{ const preview=e.target.result; const item=createImageRecord({id:`upload-${Date.now()}-${i}`,img:preview,orig:preview,credit:'You',creditUrl:'#',width:1200,height:800,tags:['upload','personal','wallpaper']},'upload',i); currentImages.unshift(item); grid.insertAdjacentHTML('afterbegin',buildCard(item,i)); }; reader.readAsDataURL(f); }); document.getElementById('results-count').textContent=`${pendingFiles.length} images`; closeModal('upload'); showToast(`${pendingFiles.length} photo(s) added to gallery`,'success'); pendingFiles=[]; document.getElementById('upload-preview-grid').innerHTML=''; }
function toggleVoiceSearch(){ if(!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)){ showToast('Speech recognition is not supported in this browser','error'); return; } if(!speechRecognition){ const SpeechRecognition=window.SpeechRecognition||window.webkitSpeechRecognition; speechRecognition=new SpeechRecognition(); speechRecognition.continuous=false; speechRecognition.interimResults=false; speechRecognition.lang=currentLanguage==='ar'?'ar-SA':'en-US'; speechRecognition.onstart=()=>{ voiceListening=true; document.getElementById('voice-btn').classList.add('listening'); showToast('Listeningβ¦','info'); }; speechRecognition.onresult=(event)=>{ const transcript=Array.from(event.results).map(r=>r[0].transcript).join(' ').trim(); if(transcript){ document.getElementById('search-input').value=transcript; searchImages(); } }; speechRecognition.onerror=(event)=>{ voiceListening=false; document.getElementById('voice-btn').classList.remove('listening'); const messages={'not-allowed':'Microphone permission denied','no-speech':'No speech detected',network:'Network error',aborted:'Speech timed out'}; showToast(messages[event.error]||'Voice search error','error'); }; speechRecognition.onend=()=>{ voiceListening=false; document.getElementById('voice-btn').classList.remove('listening'); }; } if(voiceListening){ speechRecognition.stop(); } else { speechRecognition.start(); } }
function loadTrendingSearches(){ const container=document.getElementById('trending-tags'); container.innerHTML=trendingSearches.map(tag=>`<div class="tag" onclick="setSuggestion('${tag}')">${tag}</div>`).join(''); }
function loadSavedSearches(){ const container=document.getElementById('saved-searches'); if(!searchHistory.length){ container.innerHTML='<span style="color:var(--text-secondary);font-size:.85rem;">Your searches appear here</span>'; return; } container.innerHTML=searchHistory.slice(0,5).map(search=>`<div class="tag" onclick="setSuggestion('${search}')">${search}</div>`).join(''); }
window.addEventListener('scroll',()=>{ const btn=document.getElementById('scroll-top'); if(window.scrollY>400)btn.classList.add('visible'); else btn.classList.remove('visible'); });
window.addEventListener('DOMContentLoaded',()=>{ const imgGrid=document.getElementById('img-grid'); if(imgGrid){ imgGrid.addEventListener('click',event=>{ const button=event.target.closest('.img-action-btn'); if(!button) return; event.stopPropagation(); const action=button.dataset.action; const id=button.dataset.id; const url=button.dataset.url; if(action==='download'){ const image=currentImages.find(img=>img.id===id); downloadImg(image?.orig||image?.src||'', id); } if(action==='view'){ const image=currentImages.find(img=>img.id===id); openLightbox(image?.src||url); } if(action==='bookmark'){ const image=currentImages.find(img=>img.id===id); if(image){ toggleFavorite(id); } } if(action==='detail'){ openDetailById(id); } if(action==='collection'){ const image=currentImages.find(img=>img.id===id); if(image){ handleCollectionAction(image); } } }); } document.getElementById('lang-selector').addEventListener('change',e=>setLanguage(e.target.value)); setLanguage(currentLanguage); loadTrendingSearches(); loadSavedSearches(); showDemoImages(true); setTimeout(()=>{ document.getElementById('results-section').style.display=''; document.getElementById('results-count').textContent='Demo β add API key for live search'; },700); });