Files
photo-live/docs/designs/viewer-preview-2026-09-22.html
T

30 lines
2.3 MiB
HTML
Raw Normal View History

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><title>PhotoLive · Gallery preview</title><style>
:root{--blue:#1a73e8;--ink:#172033;--muted:#647084;--line:#e5eaf1;--pink:#ff667d}*{box-sizing:border-box}body{margin:0;background:#f3f6fa;color:var(--ink);font:14px/1.5 system-ui,-apple-system,'Segoe UI','PingFang SC',sans-serif}button,select{font:inherit}button{cursor:pointer;border:0;background:none;color:inherit;min-height:44px}button:focus-visible,select:focus-visible{outline:3px solid #76b1ff;outline-offset:3px}button:hover{filter:brightness(.94)}.shell{max-width:1120px;margin:auto;background:white;min-height:100vh}.top{height:52px;padding:0 16px;display:flex;align-items:center;gap:12px}.brand{font-size:21px;font-weight:750;letter-spacing:-.7px;display:flex;gap:8px;align-items:center;margin-right:auto;color:#101723}.mark{width:26px;height:26px;background:var(--blue);color:white;border-radius:50%;display:grid;place-items:center;font-size:23px}.top select{border:0;background:white;min-height:44px;color:var(--ink)}.motion{font-size:18px;width:44px}.art{position:relative;overflow:hidden;background:#dbe6ee}.art img{position:absolute;max-width:none;pointer-events:none}.hero{width:100%;aspect-ratio:2.46;max-height:320px}.info{padding:12px 16px 0}.title-row{display:flex;gap:12px;align-items:center}h1{font-size:22px;line-height:28px;letter-spacing:-.6px;margin:0;flex:1}.share-round{border-radius:50%;background:var(--blue);color:white;width:44px;flex:none;font-size:23px}.description{margin:3px 0 9px;color:var(--muted);font-size:15px}.meta{display:flex;flex-wrap:wrap;gap:8px 20px;color:var(--muted);font-size:12px;padding-bottom:10px;border-bottom:1px solid var(--line)}.meta span{display:flex;gap:6px;align-items:center}.tabs{display:flex;border-bottom:1px solid var(--line);gap:10px}.tabs button{padding:0 16px;color:var(--muted);position:relative}.tabs .active{color:var(--blue)}.tabs .active:after{content:'';position:absolute;height:2px;bottom:0;left:0;right:0;background:var(--blue)}.filters{display:flex;gap:9px;overflow:auto;padding:10px 0}.filters button{border:1px solid var(--line);border-radius:22px;background:#f5f7fa;padding:0 15px;min-height:32px;white-space:nowrap;font-size:12px}.filters .active{background:var(--blue);border-color:var(--blue);color:white}.feed{position:relative;padding:0 5px;min-height:300px}.grid{position:relative}.card{position:absolute;border-radius:5px;overflow:hidden;background:#e5eaf1}.photo-open{display:block;width:100%;height:100%;padding:0}.photo-open .art{width:100%;height:100%}.card:after{content:'';position:absolute;pointer-events:none;bottom:0;left:0;right:0;height:40%;background:linear-gradient(transparent,rgba(0,0,0,.25))}.badge{position:absolute;bottom:0;right:3px;z-index:1;color:white;font-size:12px;display:flex;gap:5px;align-items:center;padding:0 7px;text-shadow:0 1px 4px #222}.badge b{font-size:19px}.new{position:absolute;z-index:3;top:8px;left:50%;transform:translateX(-50%);border-radius:22px;box-shadow:0 3px 15px #17203324;background:white;color:var(--blue);padding:0 17px;font-size:12px;white-space:nowrap;min-height:34px}.end{text-align:center;color:#96a0b0;font-size:12px;padding:24px 16px}.demo{color:#7a879a;font-size:11px;text-align:center;padding:8px 12px;border-top:1px solid var(--line)}dialog{border:0;padding:0;max-width:none;max-height:none;width:100%;height:100%;height:100dvh;background:#0e1218;color:#fff;position:fixed;inset:0;margin:0}dialog::backdrop{background:#0e1218}dialog[open]{display:flex;flex-direction:column}.light-top{height:56px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:env(safe-area-inset-top) 14px 0}.light-top button{width:44px;font-size:26px}.position{font-size:14px;font-variant-numeric:tabular-nums}.stage{flex:1;min-height:80px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;touch-action:pan-y}.stage .art{flex:none;transform-origin:center;transition:transform .2s;cursor:zoom-in}.stage.zoomed .art{transform:scale(1.65);cursor:zoom-out}.arrow{position:absolute;z-index:3;top:calc(50% - 24px);width:44px;font-size:42px;font-weight:200;color:white;text-shadow:0 1px 8px #000;border-radius
</style></head><body><main class="shell" id="shell"><header class="top"><div class="brand"><span class="mark">◈</span>PhotoLive</div><button class="motion" id="motion" aria-label="Reduce motion" title="Reduce motion">◌</button><select id="language" aria-label="Language"><option value="en">EN</option><option value="zh">中文</option></select></header><div id="hero" class="hero art"></div><section class="info"><div class="title-row"><h1 data-t="title"></h1><button class="share-round" id="shareAlbum" aria-label="Share album">↗</button></div><p class="description" data-t="description"></p><div class="meta"><span>◉ <span data-t="views"></span></span><span>▦ <span data-t="date"></span></span><span>♧ <span data-t="place"></span></span></div><nav class="tabs" aria-label="Sort"><button data-sort="latest" class="active" data-t="latest"></button><button data-sort="popular" data-t="popular"></button></nav><div class="filters" id="filters"></div></section><section class="feed" id="feed"><button class="new" id="newPhotos" data-t="new"></button><div class="grid" id="grid"></div></section><div class="end" data-t="end"></div><footer class="demo" data-t="demo"></footer></main><dialog id="viewer" aria-label="Photo viewer"><header class="light-top"><button id="close" aria-label="Close">×</button><span class="position" id="position"></span><button id="lightMotion" aria-label="Reduce motion">◌</button></header><div class="stage" id="stage"><button class="arrow prev" id="prev" aria-label="Previous photo">‹</button><div id="large" class="art"></div><button class="arrow next" id="next" aria-label="Next photo">›</button></div><nav class="thumbs" id="thumbs" aria-label="Thumbnails"></nav><p class="hint" data-t="hint"></p><div class="actions"><button id="like"><span class="action-icon heart-icon">♥</span><span class="count" id="count"></span><span class="label" data-t="like"></span></button><button id="sharePhoto"><span class="action-icon">➦</span><span class="label" data-t="share"></span></button><button id="download"><span class="action-icon">⇩</span><span class="label" data-t="download"></span></button></div></dialog><script>
const SOURCE='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABgAAAAQACAIAAACoEwUVAABVeWNhQlgAAFV5anVtYgAAAB5qdW1kYzJwYQARABCAAACqADibcQNjMnBhAAAAVVNqdW1iAAAAR2p1bWRjMm1hABEAEIAAAKoAOJtxA3VybjpjMnBhOmY3ODY4MTc0LWNkZjgtNDBjYi05NTFjLTRhMWYyN2RmYzRhYwAAAAx8anVtYgAAAClqdW1kYzJhcwARABCAAACqADibcQNjMnBhLmFzc2VydGlvbnMAAAAJ0Wp1bWIAAAA7anVtZEDLDDK7ikidpwsq1vR/Q2kTYzJwYS5pY29uAAAAABhjMnNoRu/9tJ5wISF2JgwbANcFowAAABdiZmRiAGltYWdlL3N2Zyt4bWwAAAAJd2JpZGI8c3ZnIHdpZHRoPSI3MTYiIGhlaWdodD0iNzE2IiB2aWV3Qm94PSIwIDAgNzE2IDcxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUwOC43NDkgMzE3LjM5OUM1MTYuNzc3IDI4Ny4zMTQgNTA4Ljk5MSAyNTMuODg0IDQ4NS4zODkgMjMwLjI4MkM0NjEuNzg4IDIwNi42ODEgNDI4LjM2IDE5OC44OTUgMzk4LjI3MyAyMDYuOTIzQzM3Ni4yMzEgMTg0LjkyOCAzNDMuMzkgMTc0Ljk1NiAzMTEuMTQ4IDE4My41OTZDMjc4LjkwNiAxOTIuMjM0IDI1NS40NSAyMTcuMjkyIDI0Ny4zNiAyNDcuMzYxQzIxNy4yOTEgMjU1LjQ1MSAxOTIuMjMzIDI3OC45MSAxODMuNTk1IDMxMS4xNDlDMTc0Ljk1NyAzNDMuMzkxIDE4NC45MjcgMzc2LjIzMiAyMDYuOTI0IDM5OC4yNzRDMTk4Ljg5NiA0MjguMzU5IDIwNi42ODMgNDYxLjc4OSAyMzAuMjg0IDQ4NS4zOTFDMjUzLjg4NSA1MDguOTkyIDI4Ny4zMTMgNTE2Ljc3OSAzMTcuNDAxIDUwOC43NUMzMzkuNDQyIDUzMC43NDUgMzcyLjI4NiA1NDAuNzE3IDQwNC41MjUgNTMyLjA3OUM0MzYuNzY3IDUyMy40NDEgNDYwLjIyMyA0OTguMzg0IDQ2OC4zMTMgNDY4LjMxNUM0OTguMzgzIDQ2MC4yMjQgNTIzLjQ0IDQzNi43NjYgNTMyLjA3OCA0MDQuNTI2QzU0MC43MTYgMzcyLjI4NSA1MzAuNzQ3IDMzOS40NDMgNTA4Ljc0OSAzMTcuNDAyVjMxNy4zOTlaTTQ3MC44OTkgMjQ0Ljc3NkM0ODYuODkyIDI2MC43NyA0OTMuNDg4IDI4Mi42MDEgNDkwLjY4NyAzMDMuNDEyTDQxNS41NzcgMjYwLjA0NkM0MTIuNDExIDI1OC4yMTggNDA4LjUwOSAyNTguMjE4IDQwNS4zNDUgMjYwLjA0NkwzMTcuNDAxIDMxMC44MlYyNzcuNTI2QzMxNy40MDEgMjc1LjE5MSAzMTguNjUyIDI3My4wMDUgMzIwLjY3NiAyNzEuODM3TDM4Ny42NDQgMjMzLjE3NEM0MTQuMTc4IDIxOC4zNTMgNDQ4LjM0NiAyMjIuMjIzIDQ3MC45MDEgMjQ0Ljc3Nkg0NzAuODk5Wk0zNTcuODM3IDMxMS4xNDRMMzk4LjI3NSAzMzQuNDkxVjM4MS4xODVMMzU3LjgzNyA0MDQuNTMyTDMxNy4zOTggMzgxLjE4NVYzMzQuNDkxTDM1Ny44MzcgMzExLjE0NFpNMjY0Ljc3NiAyNjkuNjkzQzI2NS4yMDcgMjM5LjMwNSAyODUuNjQ0IDIxMS42NDkgMzE2LjQ1MyAyMDMuMzkzQzMzOC4zIDE5Ny41NCAzNjAuNTA1IDIwMi43NDQgMzc3LjEyNyAyMTUuNTczTDMwMi4wMTQgMjU4LjkzN0MyOTguODQ4IDI2MC43NjQgMjk2Ljg5OCAyNjQuMTQ0IDI5Ni44OTggMjY3Ljc5OFYzNjkuMzQ2TDI2OC4wNjUgMzUyLjY5OUMyNjYuMDQzIDM1MS41MzEgMjY0Ljc3NiAzNDkuMzUzIDI2NC43NzYgMzQ3LjAxN1YyNjkuNjkxVjI2OS42OTNaTTIwMy4zOTEgMzE2LjQ1NEMyMDkuMjQ0IDI5NC42MDggMjI0Ljg1NCAyNzcuOTc4IDI0NC4yNzYgMjY5Ljk5OVYzNTYuNzNDMjQ0LjI3NiAzNjAuMzg0IDI0Ni4yMjYgMzYzLjc2MyAyNDkuMzkyIDM2NS41OTFMMzM3LjMzNyA0MTYuMzY1TDMwOC41MDMgNDMzLjAxM0MzMDYuNDgxIDQzNC4xODEgMzAzLjk2MSA0MzQuMTg4IDMwMS45MzkgNDMzLjAyTDIzNC45NzEgMzk0LjM1N0MyMDguODY4IDM3OC43ODkgMTk1LjEzOCAzNDcuMjYxIDIwMy4zOTEgMzE2LjQ1NFpNMjQ0Ljc3NSA0NzAuOUMyMjguNzgxIDQ1NC45MDYgMjIyLjE4NiA0MzMuMDc1IDIyNC45ODYgNDEyLjI2NEwzMDAuMDk2IDQ1NS42M0MzMDMuMjYzIDQ1Ny40NTcgMzA3LjE2NCA0NTcuNDU3IDMxMC4zMjggNDU1LjYzTDM5OC4yNzMgNDA0Ljg1NlY0MzguMTQ5QzM5OC4yNzMgNDQwLjQ4NSAzOTcuMDIyIDQ0Mi42NzEgMzk0Ljk5NyA0NDMuODM5TDMyOC4wMjkgNDgyLjUwMkMzMDEuNDk1IDQ5Ny4zMjIgMjY3LjMyNyA0OTMuNDUyIDI0NC43NzIgNDcwLjlIMjQ0Ljc3NVpNNDUwLjg5NyA0NDUuOTgyQzQ1MC40NjYgNDc2LjM3MSA0MzAuMDI5IDUwNC4wMjcgMzk5LjIyIDUxMi4yODNDMzc3LjM3MyA1MTguMTM2IDM1NS4xNjggNTEyLjkzMiAzMzguNTQ3IDUwMC4xMDJMNDEzLjY1OSA0NTYuNzM4QzQxNi44MjYgNDU0LjkxMSA0MTguNzc1IDQ1MS41MzIgNDE4Ljc3NSA0NDcuODc3VjM0Ni4zMjlMNDQ3LjYwOSAzNjIuOTc3QzQ0OS42MzEgMzY0LjE0NSA0NTAuODk3IDM2Ni4zMjMgNDUwLjg5NyAzNjguNjU5VjQ0NS45ODVWNDQ1Ljk4MlpNNTEyLjI4MiAzOTkuMjIxQzUwNi40MjkgNDIxLjA2OCA0OTAuODE5IDQzNy42OTcgNDcxLjM5NyA0NDUuNjc2VjM1OC45NDZDNDcxLjM5NyAzNTUuMjkyIDQ2OS40NDggMzUxLjkxMiA0NjYuMjgxIDM1MC4wODVMMzc4LjMzNiAyOTkuMzExTDQwNy4xNyAyODIuNjYzQzQwOS4xOTIgMjgxLjQ5NSA0MTEuNzEyIDI4MS40ODcgNDEzLjczNCAyODIuNjU1TDQ4MC43MDIgMzIxLjMxOEM1MDYuODA1IDMzNi44ODcgNTIwLjUzNiAzNjguNDE1IDUxMi4yODIgMzk5LjIyMVoiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPgoAAAG3anVtYgAAAEFqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmFjdGlvbnMudjIAAAAAGGMyc2jlsK0JQZMdazj27vDFsjECAAABbmNib3KiZ2FjdGlvbnODpGZhY3Rpb25sYzJwYS5jcmVhdGVkZHdoZW7AeB4yMDI2LTA5LTIxVDA3OjI0OjQwLjA1MjA4OTA0MVptc29mdHdhcmVBZ2VudKJkbmFtZWdDaGF0R1BUZ3ZlcnNpb25pZ3B0LWltYWdlcWRpZ2l0YWxTb3VyY2VUeXBleEZodHRwOi8vY3YuaXB0Yy5vcmcvbmV3c2NvZGVzL2RpZ2l0YWxzb3VyY2V0eXBlL3RyYWluZWRBbGdvcml0aG1pY01lZGlhomZhY3Rpb25uYzJwYS5jb252ZXJ0ZWRkd2hlbsB4HjIwMjYtMDk
const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];
const words={en:{title:'Moments from the fair',description:'Explore the event, one photo at a time.',views:'12,480 views',date:'Oct 15–19, 2026',place:'Guangzhou, China',latest:'Latest',popular:'Popular',all:'All',exhibition:'Exhibition',people:'People',highlights:'Highlights',new:'New photos ↑',end:'You’ve seen all the moments',demo:'Interactive design preview · Sample images and counts · No live connection',hint:'Tap to send more love',like:'Like',share:'Share',download:'Download',added:'You’re up to date · Demo gallery',copied:'Preview link copied',localShare:'Local preview: host this HTML to share its photo links.',downloadDemo:'Preview only: original photo download requires the live service.',motionOn:'Reduced motion on',motionOff:'Animations on',photo:'Event photo'},zh:{title:'定格展会的精彩瞬间',description:'一张照片,一个故事。一起探索现场精彩。',views:'12,480 次浏览',date:'2026年10月15–19日',place:'中国 · 广州',latest:'最新',popular:'热门',all:'全部',exhibition:'展会现场',people:'人物交流',highlights:'精彩瞬间',new:'有新照片 ↑',end:'已看完全部精彩瞬间',demo:'交互设计预览 · 图片与数据均为示例 · 未连接正式服务',hint:'连续点击,为照片点赞',like:'点赞',share:'分享',download:'下载',added:'已是最新内容 · 演示相册',copied:'预览链接已复制',localShare:'本地效果文件,托管至网站后即可分享照片链接。',downloadDemo:'效果预览:原图下载需在正式项目中接入服务。',motionOn:'已开启减少动画',motionOff:'已开启动画',photo:'展会照片'}};
// Only the photographic regions of the supplied concept board are used as sample artwork.
// All interface elements, geometry and interactions are actual HTML/CSS/JS.
const photos=[{id:'exterior',rect:[262,486,230,128],cat:'exhibition',likes:128},{id:'conversation',rect:[845,248,358,435],cat:'people',likes:1286},{id:'networking',rect:[264,661,224,122],cat:'people',likes:54},{id:'robotics',rect:[506,656,185,142],cat:'highlights',likes:102},{id:'hall',rect:[265,824,224,126],cat:'exhibition',likes:67},{id:'speaker',rect:[504,823,181,129],cat:'highlights',likes:93},{id:'architecture',rect:[260,100,477,193],cat:'exhibition',likes:76},{id:'meeting',rect:[848,300,345,378],cat:'people',likes:118}];
let lang='en',sort='latest',category='all',sequence=[...photos],current=null,restoreFocus=null,reduced=matchMedia('(prefers-reduced-motion: reduce)').matches,toastTimer;
function artwork(el,rect){const [x,y,w,h]=rect;el.style.aspectRatio=`${w}/${h}`;el.innerHTML='';const img=new Image();img.src=SOURCE;img.alt='';img.style.cssText=`width:${1536/w*100}%;height:${1024/h*100}%;left:${-x/w*100}%;top:${-y/h*100}%`;el.append(img)}
artwork($('#hero'),[254,98,487,198]);
function t(key){return words[lang][key]}
function toast(message){let el=$('.toast');if(!el){el=document.createElement('div');el.className='toast';el.setAttribute('role','status');( $('#viewer').open?$('#viewer'):document.body).append(el)}el.textContent=message;clearTimeout(toastTimer);toastTimer=setTimeout(()=>el.remove(),3000)}
function localize(){document.documentElement.lang=lang==='zh'?'zh-CN':'en';$$('[data-t]').forEach(el=>el.textContent=t(el.dataset.t));$('#filters').innerHTML='';['all','exhibition','people','highlights'].forEach(cat=>{const b=document.createElement('button');b.textContent=t(cat);b.className=category===cat?'active':'';b.setAttribute('aria-pressed',category===cat);b.onclick=()=>{category=cat;localize();render()};$('#filters').append(b)});render();if(current)showPhoto(current)}
function render(){sequence=photos.filter(p=>category==='all'||p.cat===category);if(sort==='popular')sequence.sort((a,b)=>b.likes-a.likes);$('#grid').innerHTML='';sequence.forEach(p=>{const card=document.createElement('article');card.className='card';card.dataset.id=p.id;const open=document.createElement('button');open.className='photo-open';open.setAttribute('aria-label',t('photo')+' '+p.id);const art=document.createElement('div');art.className='art';artwork(art,p.rect);open.append(art);open.onclick=()=>openPhoto(p,open);const like=document.createElement('button');like.className='badge';like.innerHTML=`<b>♥</b><span>${p.likes.toLocaleString()}</span>`;like.setAttribute('aria-label',t('like'));like.onclick=()=>{p.likes++;like.lastElementChild.textContent=p.likes.toLocaleString();bubble($('#feed'))};card.append(open,like);$('#grid').append(card)});layout()}
function layout(){const width=$('#grid').clientWidth,cols=innerWidth<600?2:innerWidth<960?3:4,gap=cols===2?4:cols===3?6:8,w=(width-gap*(cols-1))/cols,heights=Array(cols).fill(0);$$('.card').forEach((el,i)=>{const p=sequence[i],col=heights.indexOf(Math.min(...heights)),h=w*p.rect[3]/p.rect[2];el.style.cssText=`left:${col*(w+gap)}px;top:${heights[col]}px;width:${w}px;height:${h}px`;heights[col]+=h+gap});$('#grid').style.height=Math.max(...heights)+'px'}
function fitLarge(){if(!current)return;const stage=$('#stage'),ratio=current.rect[2]/current.rect[3],w=Math.min(stage.clientWidth,stage.clientHeight*ratio);$('#large').style.width=w+'px';$('#large').style.height=w/ratio+'px'}
function openPhoto(p,trigger){restoreFocus=trigger;current=p;$('#viewer').showModal();document.body.style.overflow='hidden';showPhoto(p);$('#close').focus();const u=new URL(location.href);u.hash=p.id;history.replaceState(null,'',u)}
function showPhoto(p){current=p;$('#stage').classList.remove('zoomed');artwork($('#large'),p.rect);fitLarge();$('#count').textContent=p.likes.toLocaleString();$('#position').textContent=`${sequence.indexOf(p)+1} / ${sequence.length}`;$('#thumbs').innerHTML='';sequence.forEach(item=>{const b=document.createElement('button');b.className=item===p?'selected':'';b.setAttribute('aria-label',t('photo')+' '+item.id);b.setAttribute('aria-current',item===p?'true':'false');const art=document.createElement('div');art.className='art';artwork(art,item.rect);b.append(art);b.onclick=()=>{showPhoto(item);syncHash()};$('#thumbs').append(b)});$('#thumbs .selected')?.scrollIntoView({block:'nearest',inline:'center'});$('#prev').disabled=sequence.indexOf(p)===0;$('#next').disabled=sequence.indexOf(p)===sequence.length-1}
function syncHash(){history.replaceState(null,'','#'+current.id)}
function step(delta){const i=sequence.indexOf(current)+delta;if(sequence[i]){showPhoto(sequence[i]);syncHash()}}
function closePhoto(){ $('#viewer').close();document.body.style.overflow='';current=null;history.replaceState(null,'',location.pathname+location.search);restoreFocus?.focus({preventScroll:true})}
function bubble(host){if(reduced||host.querySelectorAll('.particle').length>=12)return;const el=document.createElement('span');el.className='particle';el.textContent='♥';el.setAttribute('aria-hidden','true');el.style.setProperty('--drift',(Math.random()*60-30)+'px');el.style.fontSize=(18+Math.random()*12)+'px';host.append(el);el.onanimationend=()=>el.remove()}
function motion(){reduced=!reduced;document.body.classList.toggle('reduced',reduced);$('#motion').setAttribute('aria-pressed',reduced);$('#lightMotion').setAttribute('aria-pressed',reduced);toast(t(reduced?'motionOn':'motionOff'))}
async function share(single){if(location.protocol==='file:'){toast(t('localShare'));return}const u=new URL(location.href);u.searchParams.set('lang',lang);u.hash=single?current.id:'';try{if(navigator.share)await navigator.share({title:'PhotoLive · '+t('title'),url:u.href});else{await navigator.clipboard.writeText(u.href);toast(t('copied'))}}catch(e){if(e.name!=='AbortError')prompt(t('share'),u.href)}}
$('#language').onchange=e=>{lang=e.target.value;localize()};$$('[data-sort]').forEach(b=>b.onclick=()=>{sort=b.dataset.sort;$$('[data-sort]').forEach(x=>x.classList.toggle('active',x===b));render()});$('#newPhotos').onclick=()=>{toast(t('added'));$('#newPhotos').hidden=true};$('#close').onclick=closePhoto;$('#viewer').addEventListener('cancel',e=>{e.preventDefault();closePhoto()});$('#prev').onclick=()=>step(-1);$('#next').onclick=()=>step(1);$('#like').onclick=()=>{current.likes++;$('#count').textContent=current.likes.toLocaleString();const card=$(`.card[data-id="${current.id}"] .badge span`);if(card)card.textContent=current.likes.toLocaleString();bubble($('#stage'))};$('#motion').onclick=motion;$('#lightMotion').onclick=motion;$('#shareAlbum').onclick=()=>share(false);$('#sharePhoto').onclick=()=>share(true);$('#download').onclick=()=>toast(t('downloadDemo'));$('#large').ondblclick=()=>$('#stage').classList.toggle('zoomed');document.addEventListener('keydown',e=>{if(!$('#viewer').open)return;if(e.key==='ArrowLeft')step(-1);if(e.key==='ArrowRight')step(1)});let start=null;$('#stage').addEventListener('pointerdown',e=>{if(e.target.closest('button'))return;start=[e.clientX,e.clientY]});$('#stage').addEventListener('pointerup',e=>{if(start&&!$('#stage').classList.contains('zoomed')){const dx=e.clientX-start[0],dy=e.clientY-start[1];if(Math.abs(dx)>60&&Math.abs(dx)>Math.abs(dy))step(dx<0?1:-1)}start=null});$('#stage').addEventListener('pointercancel',()=>start=null);window.addEventListener('resize',()=>{layout();fitLarge()});document.addEventListener('visibilitychange',()=>{if(document.hidden)$$('.particle').forEach(el=>el.remove())});lang=new URL(location.href).searchParams.get('lang')==='zh'?'zh':'en';$('#language').value=lang;document.body.classList.toggle('reduced',reduced);localize();const initial=photos.find(p=>p.id===location.hash.slice(1));if(initial)openPhoto(initial,null);
</script></body></html>