1"use strict";const API="",API_CACHE_TTL=3e4,API_CACHE_MAX=50,PAGE_SIZE=60,HERO_INTERVAL=6e3,RESIZE_DEBOUNCE=200,SWIPE_THRESHOLD=40,SLIDESHOW_SPEEDS=[1e3,2e3,3e3,5e3,8e3,12e3,2e4],MONTH_NAMES=["","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],MONTH_NAMES_0=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function defaultFilters(){return{q:"",collection:"",country:"",state:"",city:"",min_rating:0,year:"",month:"",type:"",camera:"",color:"",aspect:"",tag:"",ids:""}}const S={config:{},items:[],total:0,facets:{},collections:[],loading:!1,filters:defaultFilters(),sort:localStorage.getItem("pf_sort")||"date",seed:1,semantic:localStorage.getItem("pf_semantic")==="1",offset:0,limit:60,detailIndex:-1,infoPanelOpen:!1,view:"grid",slideshow:{active:!1,paused:!1,index:0,timer:null,speed:parseInt(localStorage.getItem("ss_speed")||"5000"),front:1},adminToken:sessionStorage.getItem("adminToken")||null,unlistedAlbum:null},_bhChars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~",_bhDecode83=e=>{let t=0;for(const n of e)t=t*83+_bhChars.indexOf(n);return t},_bhSRGB=e=>{const t=e/255;return t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)},_bhLinear=e=>{const t=Math.max(0,Math.min(1,e));return t<=.0031308?Math.round(t*12.92*255+.5):Math.round((1.055*Math.pow(t,1/2.4)-.055)*255+.5)},_bhSignPow=(e,t)=>Math.sign(e)*Math.pow(Math.abs(e),t);function blurhashDCColor(e){if(!e||e.length<6)return null;const t=_bhDecode83(e.substring(2,6));return`rgb(${t>>16&255},${t>>8&255},${t&255})`}function decodeBlurhash(e,t,n){if(!e||e.length<6)return null;const i=_bhDecode83(e[0]),o=Math.floor(i/9)+1,s=i%9+1,r=(_bhDecode83(e[1])+1)/166,l=[],c=_bhDecode83(e.substring(2,6));l.push([_bhSRGB(c>>16&255),_bhSRGB(c>>8&255),_bhSRGB(c&255)]);for(let g=1;g<s*o;g++){const v=_bhDecode83(e.substring(4+g*2,6+g*2));l.push([_bhSignPow((Math.floor(v/361)-9)/9,2)*r,_bhSignPow((Math.floor(v/19)%19-9)/9,2)*r,_bhSignPow((v%19-9)/9,2)*r])}const m=new Uint8ClampedArray(t*n*4);for(let g=0;g<n;g++)for(let v=0;v<t;v++){let b=0,d=0,u=0;for(let y=0;y<o;y++)for(let p=0;p<s;p++){const w=Math.cos(Math.PI*v*p/t)*Math.cos(Math.PI*g*y/n),k=l[p+y*s];b+=k[0]*w,d+=k[1]*w,u+=k[2]*w}const f=4*(v+g*t);m[f]=_bhLinear(b),m[f+1]=_bhLinear(d),m[f+2]=_bhLinear(u),m[f+3]=255}const h=document.createElement("canvas");return h.width=t,h.height=n,h.getContext("2d").putImageData(new ImageData(m,t,n),0,0),h.toDataURL()}function esc(e){return e?String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"):""}function aiStar(){return`<span class="ai-star">\u2726<span class="ai-tip">Image analysis by AI.<br><a href="/about#ai-usage" onclick="event.stopPropagation();event.preventDefault();navigate('/about#ai-usage')">Learn more</a></span></span>`}function renderMd(e){if(!e)return"";let t=esc(e);return t=t.replace(/([^\n])\n([\-\*] )/g,`$1 2 3$2`),t=t.replace(/([^\n])\n(#{1,6} )/g,`$1 4 5$2`),t=t.replace(/([^\n])\n(\d+[\.\)] )/g,`$1 6 7$2`),t=t.replace(/(\*\*[^*]+\*\*:?\s*)\n([^\n])/g,`$1 8 9$2`),t.split(` 10 11`).map(n=>{if(n=n.trim(),!n)return"";const i=n.split(` 12`);return i.every(o=>/^[\-\*]\s/.test(o.trim()))?"<ul>"+i.map(o=>"<li>"+inlineMd(o.trim().replace(/^[\-\*]\s+/,""))+"</li>").join("")+"</ul>":i.every(o=>/^\d+[\.\)]\s/.test(o.trim()))?"<ol>"+i.map(o=>"<li>"+inlineMd(o.trim().replace(/^\d+[\.\)]\s+/,""))+"</li>").join("")+"</ol>":/^#{1,6}\s/.test(n)?"<p><strong>"+inlineMd(n.replace(/^#{1,6}\s+/,""))+"</strong></p>":/^\*\*[^*]+\*\*:?\s*$/.test(i[0])&&i.length===1?"<p>"+inlineMd(n)+"</p>":"<p>"+inlineMd(i.join(" "))+"</p>"}).filter(Boolean).join("")}function inlineMd(e){return e.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>").replace(/__(.+?)__/g,"<strong>$1</strong>").replace(/\*(.+?)\*/g,"<em>$1</em>").replace(/_(.+?)_/g,"<em>$1</em>").replace(/`(.+?)`/g,"<code>$1</code>")}function escJs(e){return e?String(e).replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"').replace(/`/g,"\\`").replace(/\n/g,"\\n").replace(/\r/g,"\\r"):""}let _retryFn=null;function retryBtnHtml(){return'<button onclick="if(_retryFn)_retryFn()" style="margin-left:8px;padding:4px 14px;background:rgba(255,255,255,0.08);color:var(--text);border:1px solid rgba(255,255,255,0.18);border-radius:6px;cursor:pointer">Retry</button>'}const _focusTraps=[],_FOCUSABLE_SEL='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not
12([tabindex="-1"])';function trapFocus(e){if(!e||_focusTraps.some(i=>i.el===e))return;const t={el:e,prev:document.activeElement,handler:null};t.handler=i=>{if(i.key!=="Tab"||_focusTraps[_focusTraps.length-1]!==t)return;const o=Array.from(e.querySelectorAll(_FOCUSABLE_SEL)).filter(l=>l.offsetWidth>0||l.offsetHeight>0||l===document.activeElement);if(!o.length){i.preventDefault();return}const s=o[0],a=o[o.length-1],r=e.contains(document.activeElement);i.shiftKey?(!r||document.activeElement===s)&&(i.preventDefault(),a.focus()):(!r||document.activeElement===a)&&(i.preventDefault(),s.focus())},document.addEventListener("keydown",t.handler,!0),_focusTraps.push(t);const n=e.querySelector(_FOCUSABLE_SEL)||e;requestAnimationFrame(()=>{try{n.focus()}catch{}})}function releaseFocus(e){const t=_focusTraps.findIndex(o=>o.el===e);if(t<0)return;const[n]=_focusTraps.splice(t,1);document.removeEventListener("keydown",n.handler,!0);const i=n.prev;if(i&&document.contains(i)&&typeof i.focus=="function")try{i.focus({preventScroll:!0})}catch{}}function splitCaption(e){let t=e.caption_human||"",n=e.caption_ai||"";if(!t&&!n&&e.caption){const i=e.caption.split(/\n+---\n+/);i.length>=2?(t=i[0].trim(),n=i[1].trim()):t=e.caption}return{human:t,ai:n}}function searchMode(){return!S.filters.q||!S._siteConfig||!S._siteConfig.semantic_enabled?null:S.semantic?"feeling":"precise"}function urlSearchMode(){const e=searchMode();return e&&(e==="feeling"||S._modeExplicit)?e:null}function buildFilterParams(e){const t=new URLSearchParams;S.filters.collection&&t.set("c",S.filters.collection);const n=["q","country","state","city","year","month","type","camera","aspect","tag","color","ids"];for(const i of n)S.filters[i]&&t.set(i,S.filters[i]);if(S.filters.min_rating>0&&t.set("min_rating",S.filters.min_rating),e)for(const[i,o]of Object.entries(e))t.set(i,o);return t}function _buildDetailContextQs(){const e=[],t=S.filters;t.collection&&e.push(`c=${encodeURIComponent(t.collection)}`),t.q&&e.push(`q=${encodeURIComponent(t.q)}`),t.country&&e.push(`country=${encodeURIComponent(t.country)}`),t.state&&e.push(`state=${encodeURIComponent(t.state)}`),t.city&&e.push(`city=${encodeURIComponent(t.city)}`),t.year&&e.push(`year=${encodeURIComponent(t.year)}`),t.month&&e.push(`month=${encodeURIComponent(t.month)}`),t.type&&e.push(`type=${encodeURIComponent(t.type)}`),t.camera&&e.push(`camera=${encodeURIComponent(t.camera)}`),t.aspect&&e.push(`aspect=${encodeURIComponent(t.aspect)}`),t.tag&&e.push(`tag=${encodeURIComponent(t.tag)}`),t.color&&e.push(`color=${encodeURIComponent(t.color)}`),t.min_rating>0&&e.push(`min_rating=${t.min_rating}`),t.ids&&e.push(`ids=${encodeURIComponent(t.ids)}`);const n=urlSearchMode();return n&&e.push(`mode=${n}`),e.length?"?"+e.join("&"):""}function _parseDetailContext(e){const t=new URLSearchParams(e),n=defaultFilters();let i=!1;t.has("c")&&(n.collection=t.get("c"),i=!0);for(const o of["q","country","state","city","year","month","type","camera","aspect","tag","color","ids"])t.has(o)&&(n[o]=t.get(o),i=!0);return t.has("min_rating")&&(n.min_rating=parseInt(t.get("min_rating"))||0,i=!0),t.has("mode")&&(S.semantic=t.get("mode")==="feeling",S._modeExplicit=!0),i?n:null}function _returnUrlFromContext(e){if(!e)return"/";const t=new URLSearchParams;e.collection&&t.set("c",e.collection),e.q&&t.set("q",e.q),e.country&&t.set("country",e.country),e.state&&t.set("state",e.state),e.city&&t.set("city",e.city),e.year&&t.set("year",e.year),e.month&&t.set("month",e.month),e.type&&t.set("type",e.type),e.camera&&t.set("camera",e.camera),e.aspect&&t.set("aspect",e.aspect),e.tag&&t.set("tag",e.tag),e.color&&t.set("color",e.color),e.min_rating>0&&t.set("min_rating",e.min_rating),e.ids&&t.set("ids",e.ids),e.q&&urlSearchMode()&&t.set("mode",urlSearchMode());const n=t.toString();return e.collection&&!e.q&&!e.country&&!e.city&&!e.tag&&!e.camera&&!e.year&&!e.ids?_isHomeCollection(e.collection)?"/":`/c/${e.collection}`:n?`/browse?${n}`:"/"}function animatePageIn(e){e.classList.remove("page-enter"),e.offsetWidth,e.classList.add("page-enter")}function imgUrl(e,t){return`/img/${e}/${t}.webp`}function vidUrl(e,t){return`/vid/${e}/${t}.mp4`}function mediaSrc(e,t){return e.media_type==="video"?`/vid/${e.content_hash}/poster.webp`:imgUrl(e.content_hash,t)}function adaptiveTier(e){const t=navigator.connection;if(!t)return e;const n=t.effectiveType;return n==="slow-2g"||n==="2g"||n==="3g"&&e!=="thumb"?"thumb":e}const _loadedResources=new Set;function loadResource(e,t,n){return _loadedResources.has(e)?Promise.resolve():new Promise(i=>{const o=document.createElement(t==="css"?"link":"script");
12t==="css"?(o.rel="stylesheet",o.href=e):o.src=e,n&&(o.integrity=n,o.crossOrigin="anonymous"),o.onload=()=>{_loadedResources.add(e),i()},o.onerror=i,document.head.appendChild(o)})}async function ensureLeaflet(){typeof L<"u"||(await Promise.all([loadResource("https://unpkg.com/[email protected]/dist/leaflet.css","css","sha384-sHL9NAb7lN7rfvG5lfHpm643Xkcjzp4jFvuavGOndn6pjVqS6ny56CAt3nsEVT4H"),loadResource("https://unpkg.com/[email protected]/dist/MarkerCluster.css","css","sha384-pmjIAcz2bAn0xukfxADbZIb3t8oRT9Sv0rvO+BR5Csr6Dhqq+nZs59P0pPKQJkEV"),loadResource("https://unpkg.com/[email protected]/dist/MarkerCluster.Default.css","css","sha384-wgw+aLYNQ7dlhK47ZPK7FRACiq7ROZwgFNg0m04avm4CaXS+Z9Y7nMu8yNjBKYC+"),loadResource("https://unpkg.com/[email protected]/dist/leaflet.js","js","sha384-cxOPjt7s7Iz04uaHJceBmS+qpjv2JkIHNVcuOrM+YHwZOmJGBXI00mdUXEq65HTH")]),await loadResource("https://unpkg.com/[email protected]/dist/leaflet.markercluster.js","js","sha384-eXVCORTRlv4FUUgS/xmOyr66XBVraen8ATNLMESp92FKXLAMiKkerixTiBvXriZr"))}async function ensureOpenSeadragon(){typeof OpenSeadragon<"u"||await loadResource("https://cdn.jsdelivr.net/npm/[email protected]/build/openseadragon/openseadragon.min.js","js")}const _apiCache=new Map;function _normalizeItem(e){if(e&&e.permalink_id!=null&&(e._media_id=e.id,e.id=e.permalink_id),e&&e.caption&&(e.caption=e.caption.replace(/\s*\[AI[^\]]*\]\s*/gi,"").trim()),e&&typeof e.ext=="string")try{e.ext=JSON.parse(e.ext)}catch{e.ext=null}return e}function _normalizeResponse(e){return e&&e.items&&e.items.forEach(_normalizeItem),e&&e.item&&_normalizeItem(e.item),e&&e.media_type&&_normalizeItem(e),e}async function api(e,t){const n=await fetch(`${e}`,t);if(!n.ok){const i=new Error(`API ${n.status}`);i.status=n.status;try{i.body=await n.json()}catch{}throw i}return n.json().then(_normalizeResponse)}async function cachedApi(e){const t=_apiCache.get(e);if(t&&Date.now()-t.ts<3e4)return t.data;const n=await api(e);if(_apiCache.set(e,{data:n,ts:Date.now()}),_apiCache.size>50){const i=_apiCache.keys().next().value;_apiCache.delete(i)}return n}const _itemsById=new Map;function rebuildItemsMap(e){e||_itemsById.clear();for(const t of S.items)_itemsById.set(t.id,t)}function showToast(e,t){const n=document.getElementById("toastContainer"),i=document.createElement("div");i.className=`toast ${t||"info"}`,i.textContent=e,n.appendChild(i),setTimeout(()=>i.remove(),3e3)}function toggleKbHelp(){const e=document.getElementById("kbHelp");e.classList.toggle("open")?trapFocus(e):releaseFocus(e)}function fmtLocation(e){return[e.city,e.state,e.country].filter(Boolean).join(", ")}function fmtLens(e){const t=e.focal_length,n=e.focal_length_35mm;if(!e.lens&&!t&&!n)return null;const i=e.lens&&/\d+\s*-\s*\d+/.test(e.lens),s=e.lens&&!i&&(!n||n===t);let a="";return s||(n&&t&&n!==t?a=` @ ${n}mm (${t}mm)`:n?a=` @ ${n}mm`:t&&(a=` @ (${t}mm)`)),e.lens?e.lens+a:a.trim().replace(/^@ /,"")}function formatDate(e){if(!e)return"";try{const t=e.replace(/^(\d{4}):(\d{2}):(\d{2})/,"$1-$2-$3"),n=new Date(t);return isNaN(n.getTime())?e:n.toLocaleDateString("en-US",{year:"numeric",month:"long",day:"numeric"})}catch{return e}}function formatDuration(e){if(!e)return"";const t=Math.floor(e/60),n=Math.floor(e%60);return t>0?`${t}:${n.toString().padStart(2,"0")}`:`${n}s`}async function checkOnThisDay(){try{const e=await api("/api/on-this-day");if(e.total===0)return;const t=document.createElement("div");t.className="otd-banner",t.id="otdBanner";const i=`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][e.month-1]} ${e.day}`,o=e.years.map(l=>l.year).join(", ");let s='<div class="otd-banner-text">';s+=`<div class="otd-banner-title">On This Day: ${i}</div>`,s+=`<div class="otd-banner-desc">${e.total} photos from ${o}</div>`,s+="</div>",s+='<div class="otd-banner-thumbs">';const a=e.years.flatMap(l=>l.items).slice(0,4);for(const l of a)s+=`<img src="${mediaSrc(l,"thumb")}" alt="">`;s+="</div>",s+=`<button class="dismiss" onclick="event.stopPropagation(); this.closest('.otd-banner').remove()">×</button>`,t.innerHTML=s,t.onclick=()=>{S.items=e.years.flatMap(l=>l.items),S.total=e.total,S.filters=defaultFilters(),renderGrid(!1),updateStatus(),t.remove()};const r=document.getElementById("grid");r.parentNode.insertBefore(t,r)}catch{}}const _dataRoutes=["/","/search","/browse","/map","/admin-unlock","/surprise"];function _isDataRoute(e){const t=e.split("?")[0];return _dataRoutes.some(n=>t===n)||t.startsWith("/c/")||t.startsWith("/p/")||t.startsWith("/browse")||t.startsWith("/search")}const _pageIds=["aboutPage","termsPage","contactPage","statsPage","searchPage","browsePage","gridSortBar","collectionsPage","storyPage","discoverPage","tagsPage","camerasPage","placesPage","yearReviewPage","seasonsPage","calendarPage"];function hideAllPages(){for(const e of _pageIds){const t=document.getElementById(e);t&&(t.style.display="none")}}function navigate(e,t){_retryFn=null,t!==!1&&history.pushState(null,"",e),document.startViewTransition&&!_isDataRoute(e)?document.startViewTransition(()=>
12_navigateImpl(e)):_navigateImpl(e)}function _navigateImpl(e){const t=e.indexOf("#"),n=t>=0?e.slice(t+1):null,i=e.split("?")[0].split("#")[0];S.detailIndex>=0&&closeDetail(),releaseFocus(document.getElementById("albumGateModal")),document.getElementById("albumGateModal").classList.remove("open"),S.unlistedAlbum=null,S.slideshow.active&&stopSlideshow(),_exhibit.active&&stopExhibition(),closeMobileMenu(),n||window.scrollTo({top:0});const o=document.getElementById("heroContainer"),s=document.getElementById("grid"),a=document.getElementById("emptyState"),r=document.getElementById("main");if(o.innerHTML="",document.getElementById("potdBanner").style.display="none",_heroTimer&&(clearInterval(_heroTimer),_heroTimer=null),_bioRotatorTimer&&(clearInterval(_bioRotatorTimer),_bioRotatorTimer=null),hideAllPages(),closeWalk(),_storyObserver&&(_storyObserver.disconnect(),_storyObserver=null),s.style.display="",a.style.display="none",closeMapView(),i==="/map"){S.view="map",r.style.display="none",showMapView(),updateActiveNav(),updatePageTitle();return}if(r.style.display="",i==="/admin-unlock"){openAdminModal(),S.view="grid",buildGridSortBar(),S.offset=0,loadItems(),updateActiveNav();return}if(i==="/search"){S.view="search",showSearchPage(),buildGridSortBar(),updateActiveNav(),updatePageTitle();return}if(i==="/browse/story"||i==="/search/story"){S.view=i.startsWith("/search")?"search":"browse",location.search&&parseFiltersFromUrl(),showBrowseStory(),updateActiveNav(),updatePageTitle();return}if(i==="/browse"){S.view="browse",location.search?parseFiltersFromUrl():S.filters=defaultFilters(),showBrowsePage(),buildGridSortBar(),updateActiveNav(),updatePageTitle();return}const l={"/about":["about",showAboutPage],"/terms":["terms",showTermsPage],"/contact":["contact",showContactPage],"/stats":["stats",showStatsPage],"/collections":["collections",showCollectionsPage],"/discover":["discover",showDiscoverPage],"/story":["story",showGlobalStory],"/tags":["tags",showTagsPage],"/cameras":["cameras",showCamerasPage],"/places":["places",showPlacesPage],"/seasons":["seasons",showSeasonsPage],"/calendar":["calendar",showCalendarPage]};if(i==="/surprise"){S.view="surprise",s.style.display="none",startSurprise(),updateActiveNav(),updatePageTitle();return}const c=l[i];if(c){S.view=c[0],s.style.display="none",c[1](),updateActiveNav(),updatePageTitle(),n&&requestAnimationFrame(()=>{const b=document.getElementById(n);b&&(b.scrollIntoView({behavior:"smooth"}),b.classList.add("highlight-flash"),setTimeout(()=>b.classList.remove("highlight-flash"),2e3))});return}if(i==="/walk"){openWalk();return}const m=i.match(/^\/c\/([a-z0-9-]+)\/story$/);if(m){const b=m[1];S.view="story",s.style.display="none",showStoryPage(b),updateActiveNav(),updatePageTitle();return}const h=i.match(/^\/year\/(\d{4})$/);if(h){S.view="yearReview",s.style.display="none",showYearReviewPage(parseInt(h[1])),updateActiveNav(),updatePageTitle();return}const g=i.match(/^\/p\/(\d+)$/);if(g){const b=parseInt(g[1]),d=_parseDetailContext(location.search);S.view="grid",s.style.display="none",d&&(S.filters=d),_detailReturnUrl=_returnUrlFromContext(d),api(`/api/media/${b}`).then(u=>{u&&u.id?(S.items=[u],S.total=1,rebuildItemsMap(),openDetail(0),d&&_lazyLoadContext(d,u.id)):(showToast("This image is no longer available"),navigate("/"))}).catch(u=>{if(u&&u.status===401&&d&&d.c){navigate(`/c/${d.c}`);return}showToast("This image is no longer available"),navigate("/")}),updateActiveNav();return}const v=i.match(/^\/c\/([a-z0-9-]+)$/);if(v){const b=v[1];S.view="collection",S.filters.collection=b,S.offset=0,openCollection(b,n),updateActiveNav();return}if(S.view="grid",i==="/"||i===""){S.filters=defaultFilters();const b=S.collections.find(u=>u.slug==="portfolio"),d=S.config.homepage_collection&&S.collections.find(u=>u.slug===S.config.homepage_collection);b?S.filters.collection="portfolio":d?S.filters.collection=S.config.homepage_collection:S.filters.collection="",S.sort="random",S.config.homepage_mode==="slideshow"&&(S.filters.collection||S.config.homepage_collection)&&showHomepageSlideshow(S.filters.collection||S.config.homepage_collection),loadPhotoOfTheDay()}else S.filters.collection="";S.offset=0,buildGridSortBar(),loadItems(),updateActiveNav()}function handlePopState(){const e=location.pathname;(e==="/"||e.startsWith("/c/")||e==="/search"||e==="/browse")&&parseFiltersFromUrl(),navigate(e,!1)}function _isHomeCollection(e){return S.collections.find(n=>n.slug==="portfolio")&&e==="portfolio"?!0:S.config.homepage_collection&&e===S.config.homepage_collection}function syncFiltersToUrl(){const e=new URLSearchParams,t=defaultFilters();for(const[l,c]of Object.entries(S.filters))l==="collection"&&S.view!=="browse"&&S.view!=="search"||c!==t[l]&&c!==""&&c!==0&&e.set(l==="collection"?"c":l,c);const n=location.pathname==="/"||location.pathname==="",i=urlSearchMode();i&&e.set("mode",i),S.sort==="date"||S.sort==="relevance"&&!S.filters.q||S.sort==="random"&&n||searchMode()==="feeling"||e.set("sort",S.sort);const s=e.toString();let a;S.view==="search"?a="/search":S.view==="browse"?a="/browse":S.filters.collection&&!_isHomeCollection(S.filters.collection)?a=`/c/${S.filters.collection}`:a="/";const r=s?`${a}?${s}`:a;r!==location.pathname+location.search&&history.replaceState(null,"",r)}function parseFiltersFromUrl(){const e=new URLSearchParams(location.search),t=defaultFilters();e.has("q")&&(t.q=e.get("q")),e.has("country")&&(t.country=e.get("
12country")),e.has("state")&&(t.state=e.get("state")),e.has("city")&&(t.city=e.get("city")),e.has("min_rating")&&(t.min_rating=parseInt(e.get("min_rating"))||0),e.has("year")&&(t.year=e.get("year")),e.has("month")&&(t.month=e.get("month")),e.has("type")&&(t.type=e.get("type")),e.has("camera")&&(t.camera=e.get("camera")),e.has("aspect")&&(t.aspect=e.get("aspect")),e.has("tag")&&(t.tag=e.get("tag")),e.has("color")&&(t.color=e.get("color")),e.has("ids")&&(t.ids=e.get("ids")),e.has("c")?t.collection=e.get("c"):e.has("collection")&&(t.collection=e.get("collection"));const n=location.pathname.match(/^\/c\/([a-z0-9-]+)/);n&&(t.collection=n[1]),S.filters=t,e.has("sort")&&(S.sort=e.get("sort")),e.has("mode")&&(S.semantic=e.get("mode")==="feeling",S._modeExplicit=!0),S.sort==="relevance"&&!t.q&&(S.sort="date"),S.offset=0}function openAdminModal(){if(S.adminToken){S.adminToken=null,sessionStorage.removeItem("adminToken"),document.cookie="admin_token=; Path=/; Max-Age=0",updateAdminButton();return}const e=document.getElementById("adminModal");e.classList.add("open"),trapFocus(e),document.getElementById("adminPassword").value="",document.getElementById("adminError").style.display="none",setTimeout(()=>document.getElementById("adminPassword").focus(),100)}function closeAdminModal(){releaseFocus(document.getElementById("adminModal")),document.getElementById("adminModal").classList.remove("open")}async function submitAdminPassword(){const e=document.getElementById("adminPassword").value;if(e)try{const t=await fetch("/api/admin/auth",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({password:e})});if(!t.ok){const i=await t.json().catch(()=>({}));document.getElementById("adminError").textContent=i.error||"Invalid password",document.getElementById("adminError").style.display="block";return}const n=await t.json();S.adminToken=n.token,sessionStorage.setItem("adminToken",n.token),closeAdminModal(),updateAdminButton()}catch{document.getElementById("adminError").textContent="Auth failed",document.getElementById("adminError").style.display="block"}}function updateAdminButton(){const e=document.getElementById("adminBtn");S.adminToken?(e.innerHTML="🔓 Admin",e.style.display="",e.title="Admin mode active (click to lock)"):(e.innerHTML="🔒",e.style.display="none",e.title="Admin unlock (Ctrl+U)")}function getUltraSrcset(e){return S.adminToken&&e?`, /img/${e}/ultra.webp 7680w`:""}async function init(){showGridSkeleton();try{const[e,t,n]=await Promise.all([cachedApi("/api/site/config"),cachedApi("/api/facets"),cachedApi("/api/collections")]);S.config=e,S.facets=t,S.collections=n,document.title=e.site_title||"Eric Cheng",document.getElementById("siteTitle").textContent="ERIC CHENG",S._siteConfig=e,buildNav(e),updateAdminButton(),location.search&&parseFiltersFromUrl(),navigate(location.pathname,!1)}catch(e){console.error("Init failed:",e),document.getElementById("emptyState").style.display="flex",document.querySelector("#emptyState h2").textContent="Connection Error",document.querySelector("#emptyState p").textContent="Could not connect to the API."}setupEventListeners()}function buildNav(e){const t=document.getElementById("navLinks");t.innerHTML="";function n(s,a,r){const l=document.createElement("a");return l.className="nav-link",l.textContent=s,l.dataset.route=r||a.slice(1),l.href=a,l.onclick=c=>{c.preventDefault(),closeMobileMenu(),navigate(a)},t.appendChild(l),l}n("Home","/","home"),n("Browse","/browse","browse"),n("Map","/map","map");const i=document.createElement("a");i.className="nav-link",i.textContent="Surprise",i.href="/surprise",i.dataset.route="surprise",i.onclick=s=>{s.preventDefault(),closeMobileMenu(),navigate("/surprise")},t.appendChild(i);const o=document.createElement("a");o.className="nav-link",o.textContent="Projects",o.href="/projects/",o.dataset.route="projects",o.onclick=()=>closeMobileMenu(),t.appendChild(o),n("About","/about","about"),n("Contact","/contact","contact"),window._browseDropdownHandler||(window._browseDropdownHandler=!0,document.addEventListener("click",()=>{const s=document.querySelector(".nav-dropdown.open");s&&s.classList.remove("open")})),updateActiveNav()}function updateActiveNav(){document.querySelectorAll(".nav-link").forEach(n=>{let i=!1;const o=n.dataset.route;o==="home"?i=S.view==="grid"||S.view==="collection":o==="browse"?i=S.view==="browse"||S.view==="search":o==="map"?i=S.view==="map":o==="about"?i=S.view==="about":o==="contact"&&(i=S.view==="contact"),n.classList.toggle("active",i)});const e=document.getElementById("statusBar");if(e){const n=["grid","browse","search","collection"];e.classList.toggle("hidden",!n.includes(S.view))}const t=document.getElementById("termsLinkOverlay");t&&(t.style.display=S.view==="terms"?"none":"")}function closeMobileMenu(){document.getElementById("navLinks").classList.remove("mobile-open")}function showCollectionHero(e){const t=S.collections.find(a=>a.slug===e);if(!t)return;const n=document.getElementById("heroContainer"),i=t.cover_hash;let o='<div class="collection-hero">';if(i){const a=S.items.find(l=>l.content_hash===i),r=a&&a.blurhash;if(r){const l=decodeBlurhash(r,32,32);l&&(o+=`<div class="collection-hero-blurhash" style="position:absolute;inset:0;width:100%;height:100%;background:url(${l}) center/cover;z-index:0;"></div>`)}o+=`<img src="${imgUrl(i,"view")}" alt="${esc(t.name)}" onload="const bh=this.closest('.collection-hero').querySelector('.collection-hero-blurhash');if(bh)bh.style.display='none'">`}o+='<div class="collection-hero-content">',o+=`<div class="collection-hero-title">${esc(t.name)}</div>`,t.description&&(o+=`<div class="collection-hero-desc">${esc(t.description)}</div>`),o+=`<div class="collection-hero-count">${t.item_count||""} photos`,o+=` <a class="story-link" href="/c/${esc(t.slug)}/story" onclick="event.preventDefault();event.stopPropagation();navigate('/c/${esc(t.slug)}/story')">Story View</a>`,o+="</div>",o+="</div>";const s=S.collections.indexOf(t);if(s>0){const a=S.collections[s-1];o+=`<button class="collection-nav prev" onclick="navigate('/c/${esc(a.slug)}')" title="${esc(a.name)}">\u2039</button>`}if(s<S.collections.length-1){const a=S.collections[s+1];o+=`<button class="collection-nav next" onclick="navigate('/c/${esc(a.slug)}')" title="${esc(a.name)}">\u203A</button>`}o+="</div>",n.innerHTML=o}function _shareTokenFromHash(e){if(!e)return null;const t=e.match(/(?:^|&)k=([^&]+)/);return t?decodeURIComponent(t[1]):null}async function _albumUnlock(e,t){return api(`/api/collections/${e}/unlock`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async function openCollection(e,t){if(S.collections.find(o=>o.slug===e)){S.unlistedAlbum=null,showCollectionHero(e),buildGridSortBar(),loadItems();return}const i=_shareTokenFromHash(t);if(i){try{await _albumUnlock(e,{token:i})}catch{}location.hash&&history.replaceState(null,"",`/c/${e}`)}try{const o=await api(`/api/collections/${e}?limit=1&offset=0`);S.unlistedAlbum={slug:e,name:o.name,description:o.description};const s=o.cover_hash||o.items&&o.items[0]&&o.items[0].content_hash;renderAlbumHero(S.unlistedAlbum,s,o.total),buildGridSortBar(),loadItems()}catch(o){o.status===401?showAlbumGate(e,o.body&&o.body.name):(showToast("Collection not found"),navigate("/"))}}function renderAlbumHero(e,t,n){const i=document.getElementById("heroContainer");let o='<div class="collection-hero">';t&&(o+=`<img src="${imgUrl(t,"view")}" alt="${esc(e.name)}">`),o+='<div class="collection-hero-content">',o+=`<div class="collection-hero-title">${esc(e.name)} <span class="album-lock" title="Unlisted album">\u{1F512}</span></div>`,e.description&&(o+=`<div class="collection-hero-desc">${esc(e.description)}</div>`),o+=`<div class="collection-hero-count">${n||""} photos</div>`,o+="</div></div>",i.innerHTML=o}function showAlbumGate(e,t){document.getElementById("heroContainer").innerHTML="",document.getElementById("grid").style.display="none";const n=document.getElementById("albumGateModal");n.dataset.slug=e,document.getElementById("albumGateName").textContent=t||"Private album",document.getElementById("albumGateError").style.display="none";const i=document.getElementById("albumGatePassword");i.value="",n.classList.add("open"),trapFocus(n),setTimeout(()=>i.focus(),50)}async function submitAlbumGate(){const e=document.getElementById("albumGateModal"),t=e.dataset.slug,n=document.getElementById("albumGatePassword").value;if(n)try{await _albumUnlock(t,{password:n}),releaseFocus(e),e.classList.remove("open"),navigate(`/c/${t}`,!1)}catch(i){const o=document.getElementById("albumGateError");o.textContent=i.status===429?"Too many attempts. Try again later.":"Incorrect password",o.style.display="block"}}function closeAlbumGate(){releaseFocus(document.getElementById("albumGateModal")),document.getElementById("albumGateModal").classList.remove("open"),navigate("/")}async function copyAlbumShareLink(e){try{const t=await api(`/api/collections/${e}/share`,{method:"POST",headers:{Authorization:`Bearer ${S.adminToken||""}`}}),n=`${location.origin}/c/${e}#k=${encodeURIComponent(t.token)}`;await navigator.clipboard.writeText(n),showToast("Share link copied (valid 7 days)")}catch{showToast("Could not create share link","error")}}let _heroTimer=null,_storyObserver=null;async function showHomepageSlideshow(e){const t=document.getElementById("heroContainer"),n=S.collections.find(o=>o.slug===e),i=n?n.name:"";try{let m=function(h){l.forEach((g,v)=>
12g.classList.toggle("active",v===h)),c.forEach((g,v)=>g.classList.toggle("active",v===h)),r=h};const s=(await api(`/api/search?collection=${encodeURIComponent(e)}&sort=rating&limit=8`)).items.filter(h=>h.media_type==="image"&&h.content_hash);if(s.length===0)return;let a='<div class="hero-slideshow">';for(let h=0;h<s.length;h++)a+=`<img class="hero-slideshow-img${h===0?" active":""}" src="${imgUrl(s[h].content_hash,"view")}" alt="${esc(s[h].caption||"")}"${h>0?' loading="lazy"':""}>`;a+='<div class="hero-slideshow-content">',a+=`<div class="hero-slideshow-title">${esc(i)}</div>`,a+=`<a class="hero-slideshow-browse" onclick="scrollToGrid()">Browse ${n?n.item_count:""} photos</a>`,a+='<div class="hero-slideshow-dots">';for(let h=0;h<s.length;h++)a+=`<span class="hero-slideshow-dot${h===0?" active":""}" data-idx="${h}"></span>`;a+="</div></div></div>",t.innerHTML=a;let r=0;const l=t.querySelectorAll(".hero-slideshow-img"),c=t.querySelectorAll(".hero-slideshow-dot");_heroTimer&&clearInterval(_heroTimer),_heroTimer=setInterval(()=>m((r+1)%s.length),6e3),c.forEach(h=>h.addEventListener("click",()=>{m(parseInt(h.dataset.idx)),clearInterval(_heroTimer),_heroTimer=setInterval(()=>m((r+1)%s.length),6e3)}))}catch(o){console.error("Homepage slideshow failed:",o),showCollectionHero(e)}}function scrollToGrid(){const e=document.getElementById("grid");e&&e.scrollIntoView({behavior:"smooth"})}function showAboutPage(){const e=document.getElementById("aboutPage");animatePageIn(e);let t='<div class="bio-layout">';t+='<div class="bio-photo-rotator" id="bioRotator"></div>',t+='<div class="bio-text">',t+="<h1>About Eric Cheng</h1>",t+='<div class="bio-social">',t+='<a href="https://instagram.com/echeng" target="_blank" title="Instagram"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/><title>Instagram</title></svg></a>',t+='<a href="https://www.facebook.com/echeng1" target="_blank" title="Facebook"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/><title>Facebook</title></svg></a>',t+='<a href="https://www.linkedin.com/in/ericcheng" target="_blank" title="LinkedIn"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/><title>LinkedIn</title></svg></a>',t+='<a href="https://www.youtube.com/channel/UC_lej5HespbyRbAQbasz92A" target="_blank" title="YouTube"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/><title>YouTube</title></svg></a>',t+='<a href="https://vimeo.com/echeng" target="_blank" title="Vimeo"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M23.977 6.416c-.105 2.338-1.739 5.543-4.894 9.609-3.268 4.247-6.026 6.37-8.29 6.37-1.409 0-2.578-1.294-3.553-3.881L5.322 11.4C4.603 8.816 3.834 7.522 3.01 7.522c-.179 0-.806.378-1.881 1.132L0 7.197a315.065 315.065 0 003.501-3.128C5.08 2.701 6.266 1.984 7.055 1.91c1.867-.18 3.016 1.1 3.447 3.838.465 2.953.789 4.789.971 5.507.539 2.45 1.131 3.674 1.776 3.674.502 0 1.256-.796 2.265-2.385 1.004-1.589 1.54-2.797 1.612-3.628.144-1.371-.395-2.061-1.614-2.061-.574 0-1.167.121-1.777.391 1.186-3.868 3.434-5.757 6.762-5.637 2.473.06 3.628 1.664 3.493 4.797l-.013.01z"/><title>Vimeo</title></svg></a>',t+='<a href="https://twitter.com/echeng" target="_blank" title="X / Twitter"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/><title>X / Twitter</title></svg></a>',t+="</div>",t+='<p><strong>Eric Cheng</strong> is an <a href="http://echengphoto.com" target="_blank">award-winning photographer</a>, creative technologist, producer, <a href="https://www.youtube.com/watch?v=fDzRNitQyg4" target="_blank">cellist</a>, and author. He works at Meta Reality Labs as Director of Immersive Media. A hands-on executive producer, domain leader, and pioneer of cutting-edge production techniques, Eric has supported and produced numerous immersive media projects for VR, including 8 Emmy-nominated films and experiences.</p>',t+=`<p>Eric straddles his passions for storytelling, entrepreneurship, technology and communication. Before returning to the tech industry, Eric was a widely-published professional photographer and CEO of <a href="https://wetpixel.echeng.com/" target="_blank">Wetpixel</a>, the leading underwater-photography community. Eric is featured in 2 episodes of the Netflix series, <a href="https://www.netflix.com/title/80133187" target="_blank"><em>Tales by Light</em></a> (Season 2), which showcase his underwater work with big animals. He has led dozens of photography expeditions and workshops around the world, and has given seminars and lectures internationally at events including <a href="https://www.youtube.com/watch?v=MA7POyk16G8" target="_blank">TEDx</a>, <a href="https://vimeo.com/143376089" target="_blank">EG</a>, TTI/Vanguard, the <a href="https://www.youtube.com/watch?v=i_D-W8ZVPZ8" target="_blank">Churchill Club</a>, <a href="https://www.defcon.org/html/defcon-23/dc-23-speakers.html#Cagle" target="_blank">DEFCON</a>, NASA UTM, <a href="http://www.photoshelter.com/luminance/videos/cheng" target="_blank">Photoshelter Luminance</a>, <a href="https://www.youtube.com/watch?v=13SVH1Tx6Xs" target="_blank">Photographers@Google</a>, CES, NAB, SXSW, AsiaD, Creative Mornings, <a href="https://www.youtube.com/watch?v=WwVbqveNe6c" target="_blank">Tested.com's Live Show</a>, Oculus Connect, ICCCP, DEMA, and others.</p>`,t+='<p>Previously, Eric was Director of Aerial Imaging at <a href="http://dji.com/" target="_blank">DJI</a> and Director of Photography at <a href="http://lytro.com/" target="_blank">Lytro</a>. Eric also served on the Board of Advisers of <a href="http://wirecutter.co/" target="_blank">Wirecutter</a>, and is a Field Associate at the <a href="http://www.calacademy.org/" target="_blank">California Academy of Sciences</a> in San Francisco, California.</p>',t+=`<p>Eric also has a passion for ocean conservation, and served on the Board of Directors of <a href="http://sharksavers.org/" target="_blank">Shark Savers</a> (now part of <a href="http://www.wildaid.org/" target="_blank">WildAid</a>
12) and the Board of Advisors of <a href="http://seashepherd.org/" target="_blank">Sea Shepherd Conservation Society</a>. He was head photographer for Operation Musashi, Sea Shepherd's 2008-2009 anti-whaling campaign in Antarctica, which was featured in season 2 of the TV show, <em>Whale Wars</em>.</p>`,t+=`<p>In October 2015, Peachpit published Eric's first book, <em><a href="https://amzn.to/4rIpkdT" target="_blank">Aerial Photography and Videography Using Drones</a></em>.</p>`,t+="<p>Eric holds BS and MS degrees in computer science from Stanford University, where he also studied classical cello performance. He lives in the San Francisco Bay Area with his wife and two sons.</p>",t+='<h2 style="margin-top:32px">Executive Production Credits</h2>',t+='<p><strong>2023:</strong> <em><a href="https://ocul.us/4862rrI" target="_blank">Ocean of Light: Dolphins VR</a></em> (Talbot Films), <em><a href="https://atlanticproductions.tv/productions/galapagos-with-david-attenborough/" target="_blank">Galapagos with David Attenborough VR</a></em> (Atlantic Productions)</p>',t+=`<p><strong>2022:</strong> <em><a href="https://thesoloist-vr.com/" target="_blank">Alex Honnold: The Soloist VR</a></em> (Jon Griffith Productions, <a href="https://twitter.com/CannesXR/status/1528683364148027392" target="_blank">Cannes XR "Best VR Story"</a>), <em><a href="https://www.youtube.com/watch?v=YqMorPxbtfA" target="_blank">David Attenborough's Conquest of the Skies VR</a></em> (Atlantic Productions), <em><a href="https://www.nowheremedia.net/you-destroy-we-create/more" target="_blank">You Destroy. We Create.</a></em> (NowHere Media, <a href="https://www.emmys.com/sites/default/files/Downloads/75th-nominations-list-v1.pdf?q=2023" target="_blank">Emmy-Nominated</a>, <a href="https://peabodyawards.com/award-profile/you-destroy-we-create/" target="_blank">84th Peabody Award Winner</a>)</p>`,t+=`<p><strong>2021:</strong> <em><a href="http://www.oculus.com/experiences/media/468126267964607/" target="_blank">David Attenborough's First Life</a></em> (Atlantic Productions, <a href="https://theemmys.tv/wp-content/uploads/2022/07/43rd-NewsDoc-Nom-Release-FINAL-.pdf" target="_blank">Emmy-Nominated</a>)</p>`,t+='<p><strong>2020:</strong> <em><a href="https://www.insidecovid19vr.com/" target="_blank">Inside COVID19</a></em> (WisdomVR, <a href="https://www.emmys.com/shows/inside-covid19" target="_blank">Emmy-Nominated</a>), <em><a href="https://www.targostories.com/rebuilding-notre-dame" target="_blank">Rebuilding Notre Dame</a></em> (TARGO, <a href="https://www.emmys.com/shows/rebuilding-notre-dame" target="_blank">Emmy-Nominated</a>), <em><a href="https://www.targostories.com/when-we-stayed-home-april-2020" target="_blank">When We Stayed Home</a></em> (TARGO, <a href="https://www.emmys.com/shows/when-we-stayed-home" target="_blank">Emmy-Nominated</a>), <em><a href="https://alchemyimmersive.com/micromonsters" target="_blank">Micro Monsters with David Attenborough</a></em> (Atlantic Productions, <a href="https://theemmys.tv/news/42nd-nominations/new-approaches/" target="_blank">Emmy-Nominated</a> <a href="https://theemmys.tv/news/42nd-nominations/crafts/" target="_blank">x2</a>)</p>',t+=`<p><strong>2019:</strong> <em><a href="https://creator.oculus.com/community/2475506732722737/" target="_blank">Everest VR</a></em> (Jon Griffith Productions), <em><a href="https://www.oculus.com/experiences/quest/2586839431358655/" target="_blank">Adam Savage's Tested VR</a></em> (Tested), <em>Raw: Emotions Unite Us</em> (PHORIA, <a href="https://theemmys.tv/sports/nominees-2022-sports-programs-events/" target="_blank">Emmy-Nominated</a>, <a href="https://winners.webbyawards.com/2022/virtual-remote/series/media-entertainment-sports/213500/raw-emotions-unite-us" target="_blank">Webby Winner</a>), <em><a href="http://www.oculus.com/experiences/media/4325691130884534/" target="_blank">Surviving 9/11: 27 Hours Under The Rubble</a></em> (TARGO, <a href="https://winners.webbyawards.com/2022/virtual-remote/immersive/best-vr-video/203277/surviving-911" target="_blank">Webby Winner</a>), <em><a href="https://www.oculus.com/blog/the-slow-mo-guys-vr-brings-scientific-showmanship-to-oculus-tv-on-quest/" target="_blank">The Slow Mo Guys VR</a></em>, <em><a href="https://www.oculus.com/experiences/quest/2926036530794417" target="_blank">ecosphere</a></em> (PHORIA), <em><a href="https://www.wisdomvr.org/" target="_blank">WisdomVR Project</a></em></p>`,t+='<p><strong>Other:</strong> <em><a href="https://www.netflix.com/title/80133187" target="_blank">Tales by Light</a></em> (Netflix/Untitled Films), as himself</p>',t+='<div class="about-links"><h2>About This Website</h2>',t+="<p>In March 2026, Eric was inspired to show more of his photography online and spent 10 evenings building a portfolio site from scratch with the goal that it should be faster than any local image viewer and have a native feel on both computers and mobile devices. The site is deployed on the edge via Cloudflare and is optimized for humans, search engines and AI crawlers. Images can be freely downloaded but are both visually and invisibly watermarked.</p>",t+='<p id="ai-usage" style="margin-top:1em"><strong>Use of AI:</strong> Each image is analyzed locally using a vision AI model (Qwen3-VL) that examines the image contents alongside camera metadata and any captions or keywords entered manually. The AI generates searchable descriptions, tags, and location guesses to make the archive browsable. Text preceded by '+aiStar()+" is from AI-based image analysis. Unmarked captions were written by Eric.</p>",t+='<p style="margin-top:1em"><strong>Unlinked / experimental areas:</strong></p>',t+=`<a href="/calendar" onclick="event.preventDefault();navigate('/calendar')">Photo Calendar</a>`,t+='<a href="/vr/" target="_blank">VR Gallery: go to echeng.com/vr in the Meta Quest browser!</a>',t+='<a href="/verify" target="_blank">Image Verification</a>',t+='<a href="https://wetpixel.echeng.com/" target="_blank">Wetpixel Wiki</a>',t+='<a href="https://slseminar.echeng.com/" target="_blank">St. Lawrence Seminar Registry</a>',t+=`<a href="/terms" onclick="event.preventDefault();navigate('/terms')">Terms of Service</a>`,t+="</div>",t+="</div>",t+="</div>",e.innerHTML=t,e.style.display="block",loadBioRotator()}function showTermsPage(){const e=document.getElementById("termsPage");animatePageIn(e);const t=location.origin;let n="";n+="<h1>Terms of Service</h1>",n+='<p class="terms-updated">Last updated: March 29, 2026</p>',n+='<div class="terms-section">',n+="<h2>1. Ownership & Copyright</h2>",n+='<p>All photographs, videos, text, and other content on this website (collectively, "Content") are the original work of Eric Cheng ("Owner") and are protected under United States copyright law (Title 17, U.S. Code) and applicable international copyright treaties, including the Berne Convention. All rights are reserved unless expressly stated otherwise in these Terms.</p>',n+="<p>No transfer of ownership, copyright, or any proprietary right occurs through viewing, accessing, downloading, or any other use of this website or its Content.</p>",n+=`<p>All images and videos on this site contain embedded copyright management information ("CMI") in the form of IPTC, XMP, and EXIF metadata, including copyright notices, author identification, and licensing information. Under 17 U.S.C. \xA7 1202, the intentional removal, alteration, or falsification of this metadata is a separate federal violation, independent of any underlying copyright infringement, carrying statutory damages of $2,500 to $25,000 per violation plus attorney's fees. Removal of embedded metadata will be treated as evidence of willful infringement in any enforcement action.</p>`,n+="</div>",n+='<div class="terms-section">',n+="<h2>2. Permitted Non-Commercial Use</h2>",n+="<p>
12Subject to these Terms, Owner grants you a limited, revocable, non-exclusive, non-transferable, non-sublicensable license to use individual images for personal, non-commercial purposes, provided you comply with all of the following conditions:</p>",n+="<h3>Online Use</h3>",n+='<p>You must include a clearly visible, clickable hyperlink to <a href="'+t+'">'+location.host+"</a> (or to the specific image page URL) that remains active and accessible for the entire duration of use.</p>",n+="<h3>Offline Use</h3>",n+='<p>For print, presentations, or other offline formats, you must include a legible credit line in the format: <strong>"Photo: Eric Cheng / '+location.host+'"</strong></p>',n+="<h3>Definition of Non-Commercial</h3>",n+='<p>"Non-commercial" means use where the primary purpose is not generating revenue, promoting a product or service, or supporting a business operation. The following are expressly <strong>not</strong> non-commercial, even if no direct payment is involved:</p>',n+="<ul>",n+="<li>Use by or on behalf of any for-profit entity</li>",n+="<li>Nonprofit fundraising materials, annual reports, or donor communications</li>",n+="<li>Sponsored content, affiliate marketing, or influencer partnerships</li>",n+="<li>Social media posts made on behalf of a brand, organization, or business</li>",n+="<li>Content monetized through advertising, subscriptions, or paywalls</li>",n+="</ul>",n+="<p>This license is revocable. Owner may withdraw permission for any use at any time, for any reason, with or without notice. Upon revocation, you must promptly remove the Content from all locations where it appears.</p>",n+="</div>",n+='<div class="terms-section">',n+="<h2>3. Social Media Sharing</h2>",n+="<p>Sharing images on social media platforms (including but not limited to Instagram, Facebook, X/Twitter, TikTok, Pinterest, and LinkedIn) is permitted for personal, non-commercial purposes, provided:</p>",n+="<ul>",n+="<li>The post includes visible credit (Eric Cheng and <strong>"+location.host+"</strong>) in the caption or as a text overlay on the image</li>",n+="<li>The image is uploaded natively to the platform (not hotlinked, embedded, or served from this site's servers)</li>",n+="<li>All embedded metadata is preserved in the uploaded file</li>",n+="</ul>",n+="<p>By sharing Content on social media, you acknowledge that the platform's Terms of Service may grant that platform sublicensing rights over uploaded content. This is accepted as a necessary consequence of social sharing but does not extend any license beyond the original platform post.</p>",n+="<p>The following social media uses require a commercial license:</p>",n+="<ul>",n+="<li>Posts by brand accounts, business pages, or organizational accounts</li>",n+="<li>Sponsored posts, promoted content, or paid partnerships</li>",n+="<li>Influencer or creator content made in connection with a brand relationship</li>",n+="<li>Any business-affiliated social media use</li>",n+="</ul>",n+="</div>",n+='<div class="terms-section">',n+="<h2>4. Commercial Use</h2>",n+='<p>Any commercial use of Content requires prior written permission from the Owner. "Commercial use" is defined broadly to include any use that is primarily intended for or directed toward commercial advantage or monetary compensation. This includes, without limitation:</p>',n+="<ul>",n+="<li>Advertising, marketing, or promotional materials (print or digital)</li>",n+="<li>Editorial use in paid publications, whether print or digital</li>",n+="<li>Merchandise, product packaging, or physical goods</li>",n+="<li>Corporate communications, presentations, pitch decks, or proposals</li>",n+="<li>Use to train, develop, fine-tune, or improve machine learning models, artificial intelligence systems, neural networks, or any automated content generation or analysis systems, whether or not the resulting system is offered commercially</li>",n+="<li>Inclusion in datasets, training corpora, or data repositories used for AI/ML development</li>",n+="<li>Stock licensing, syndication, or redistribution through any licensing platform</li>",n+="<li>NFTs, blockchain tokens, digital collectibles, or any tokenized digital assets</li>",n+="<li>Use by any for-profit entity in any context</li>",n+="</ul>",n+=`<p>For licensing inquiries, please use the <a href="/contact" onclick="event.preventDefault();navigate('/contact')">contact form</a>.</p>`,n+="</div>",n+='<div class="terms-section">',n+="<h2>5. Prohibited Uses</h2>",n+="<p>The following uses are expressly prohibited without prior written permission:</p>",n+="<ul>",n+="<li><strong>Redistribution and rehosting:</strong> No person or entity may redistribute, sublicense, or rehost Content, or serve Content as part of their own website, application, API, c
12ontent delivery network, or service</li>",n+="<li><strong>Hotlinking and framing:</strong> No inline linking, hotlinking, framing, or embedding that causes this site's servers to serve Content as part of another property</li>",n+="<li><strong>Modification and derivatives:</strong> No modification, adaptation, transformation, or creation of derivative works from any Content, including AI-generated modifications or style transfers</li>",n+="<li><strong>Metadata removal:</strong> No removal, alteration, or obscuring of watermarks, copyright notices, metadata, or any other copyright management information embedded in Content</li>",n+="<li><strong>False endorsement:</strong> No use that implies endorsement of, affiliation with, or sponsorship by the Owner in connection with any product, service, person, or organization</li>",n+="</ul>",n+="</div>",n+='<div class="terms-section">',n+="<h2>6. Crawling, Indexing & AI Training</h2>",n+="<h3>Search Engine Indexing</h3>",n+=`<p>Web crawlers operated by search engines may index this site for the purpose of making its pages discoverable in search results, consistent with the directives in this site's <a href="/robots.txt" target="_blank">robots.txt</a> file.</p>`,n+="<h3>AI and Machine Learning Training</h3>",n+=`<p>All Content on this site is excluded from use as training data for artificial intelligence, machine learning, large language models, image generation models, or any other automated learning system, with the sole exception of the <a href="/about" onclick="event.preventDefault();navigate('/about')">/about</a> page, which is expressly permitted for AI training use.</p>`,n+="<p>This restriction applies regardless of how the Content is accessed, including but not limited to:</p>",n+="<ul>",n+="<li>Direct crawling of this website</li>",n+="<li>Cached or archived copies (e.g., Internet Archive, Google Cache)</li>",n+="<li>Third-party datasets or data aggregators (e.g., Common Crawl, LAION)</li>",n+="<li>Content obtained through APIs, scraping, or automated access</li>",n+="<li>Screenshots, screen captures, or any reproduction of displayed Content</li>",n+="</ul>",n+="<h3>Machine-Readable Signals</h3>",n+="<p>This site implements multiple machine-readable signals to express these restrictions:</p>",n+="<ul>",n+="<li><strong>robots.txt:</strong> AI training crawlers (GPTBot, ClaudeBot, Google-Extended, CCBot, Bytespider, meta-externalagent, and others) are blocked from all pages except /about and /terms</li>",n+="<li><strong>X-Robots-Tag:</strong> <code>noai, noimageai</code> HTTP headers are served on all responses except /about and /terms</li>",n+="<li><strong>TDMRep:</strong> <code>tdm-reservation=1</code> HTTP headers implement the W3C Text and Data Mining Reservation Protocol, constituting a machine-readable rights reservation under EU Directive 2019/790 (DSM Directive), Article 4(3)</li>",n+="</ul>",n+="<p>In the event of any conflict between machine-readable signals and these Terms, these Terms control. The machine-readable signals are expressions of, not limitations on, the restrictions stated herein.</p>",n+="</div>",n+='<div class="terms-section">',n+="<h2>7. Infrastructure & Technical Restrictions</h2>",n+="<ul>",n+="<li>This site may not be used as a content delivery network, proxy, image host, or content source for any third-party website, application, or service</li>",n+="<li>Automated access (scraping, crawling, or data collection) beyond what is expressly permitted in the robots.txt file is prohibited</li>",n+="<li>Any automated access that degrades site performance, imposes unreasonable server load, or interferes with normal operation is prohibited</li>",n+="<li>Owner reserves the right to block IP addresses, user agents, referrers, or any other identifying attributes associated with access that violates these Terms, without notice</li>",n+="</ul>",n+="</div>",n+='<div class="terms-section">',n+="<h2>8. DMCA Notices</h2>",n+="<p>Owner respects the intellectual property rights of others. If you believe that content on this site infringes your copyright, you may submit a notification under the Digital Millennium Copyright Act (17 U.S.C. \xA7 512(c)(3)) to the designated agent below.</p>",n+="<p>Your DMCA notice must include:</p>",n+="<ol>",n+="<li>Your physical or electronic signature</li>",n+="<li>Identification of the copyrighted work you claim has been infringed</li>",n+="<li>Identification of the material you claim is infringing, with sufficient information to locate it on this site</li>",n+="<li>Your contact information (address, telephone number, and email)</li>",n+="<li>A statement that you have a good faith belief that the use is not authorized by the copyright owner, its agent, or the law</li>",n+="<li>A statement, under penalty of perjury, that the information in your notice is accurate and that you are the copyright owner or authorized to act on the owner's behalf</li>",n+="</ol>",n+=`<p>DMCA notices should be sent via the <a href="/contact" onclick="event.preventDefault();navigate('/contact')">contact form</a> with "DMCA Notice" in the subject line.</p>`,n+="</div>",n+='<div class="terms-section">',n+="<h2>9. Disclaimer & Limitation of Liability</h2>",n+='<p>This website and all Content are provided "as is" and "as available," without warranties of any kind, whether express, implied, or statutory, including but not limited to warranties of merchantability, fitness for a particular purpose, non-infringement, or accuracy.</p>',n+="<p>To the fullest extent permitted by applicable law, Owner shall not be liable for any direct, indirect, incidental, special, consequential, or punitive damages arising out of or related to your access to or use of (or inability to access or use) this website or its Content, regardless of the theory of liability, even if Owner has been advised of the possibility of such damages.</p>",n+="<p>Owner is not responsible for any third-party misuse of Content that you may encounter on other websites or platforms.</p>",n+="</div>",n+='<div class="terms-section">',n+="<h2>10. Termination &
12 Enforcement</h2>",n+="<p>Owner reserves the right to revoke any user's access to this website or terminate any license granted under these Terms at any time, for any reason, without notice.</p>",n+="<p>Violations of these Terms may result in:</p>",n+="<ul>",n+="<li>Immediate termination of all licenses granted herein</li>",n+="<li>Blocking of access to this website</li>",n+="<li>Pursuit of all available legal remedies, including statutory copyright damages of up to $150,000 per work infringed under 17 U.S.C. \xA7 504(c)(2) for willful infringement, plus attorney's fees and costs</li>",n+="<li>Claims under 17 U.S.C. \xA7 1202 for removal or alteration of copyright management information, carrying additional statutory damages of $2,500 to $25,000 per violation</li>",n+="</ul>",n+="<p>Removal of embedded metadata (IPTC, XMP, EXIF copyright fields, or invisible watermarks) from any Content obtained from this site will be treated as prima facie evidence of willful infringement for the purposes of statutory damages calculations.</p>",n+="</div>",n+='<div class="terms-section">',n+="<h2>11. Governing Law & Jurisdiction</h2>",n+="<p>These Terms shall be governed by and construed in accordance with the laws of the State of California, United States, without regard to its conflict of laws principles.</p>",n+="<p>Any dispute, claim, or controversy arising out of or relating to these Terms or the use of this website shall be resolved exclusively in the state or federal courts located in San Mateo County, California (or, for federal claims including copyright, in the United States District Court for the Northern District of California). You consent to the personal jurisdiction of these courts and waive any objection to venue.</p>",n+="<p>Nothing in this section prevents either party from seeking relief in small claims court for claims within that court's jurisdictional limits.</p>",n+="<p>If you access this site from outside the United States, you do so at your own initiative and are responsible for compliance with applicable local laws. These Terms are not intended to subject the Owner to the jurisdiction of any foreign court beyond what applicable law requires.</p>",n+="</div>",n+='<div class="terms-section">',n+="<h2>12. European Union: Text and Data Mining Rights</h2>",n+="<p>Pursuant to Article 4 of Directive (EU) 2019/790 (the Digital Single Market Directive), the Owner expressly reserves all rights with respect to text and data mining of Content on this site, except for the /about page. This reservation is expressed both in these Terms and through machine-readable means (TDMRep protocol headers and robots.txt directives) as required by Article 4(3).
12</p>",n+="<p>The text and data mining exception under Article 4(1) does not apply to any Content on this site (other than the /about page) because the Owner has expressly reserved rights in an appropriate manner, including machine-readable means for content made available online.</p>",n+="</div>",n+='<div class="terms-section">',n+="<h2>13. Modifications</h2>",n+='<p>Owner may update or modify these Terms at any time. Changes take effect immediately upon posting. The "Last updated" date at the top of this page reflects the most recent revision. Your continued use of this website after any modification constitutes acceptance of the updated Terms.</p>',n+="<p>If material changes are made that substantially affect your rights, Owner will make reasonable efforts to provide notice, such as a prominent posting on the site.</p>",n+="</div>",n+='<div class="terms-section">',n+="<h2>Contact</h2>",n+=`<p>For questions about these Terms, licensing inquiries, or DMCA notices, please use the <a href="/contact" onclick="event.preventDefault();navigate('/contact')">contact form</a>.</p>`,n+="</div>",e.innerHTML=n,e.style.display="block"}let _bioRotatorTimer=null;async function loadBioRotator(){const e=document.getElementById("bioRotator");if(!e)return;let t=[];try{const o=await api("/api/search?collection=biography&aspect=landscape&sort=random&limit=20");o.items&&o.items.length>0&&(t=o.items)}catch{}if(t.length<5)try{const o=await api("/api/search?q=Eric+Cheng&aspect=landscape&sort=random&limit=20");o.items&&(t=t.concat(o.items))}catch{}const n=new Set;if(t=t.filter(o=>n.has(o.id)?!1:(n.add(o.id),!0)).slice(0,10),t.length===0||(e.innerHTML="",t.forEach((o,s)=>{const a=document.createElement("img");a.src=mediaSrc(o,"view"),a.alt=o.caption||"",a.loading=s===0?"eager":"lazy",s===0&&a.classList.add("active"),e.appendChild(a)}),t.length<=1))return;let i=0;_bioRotatorTimer&&clearInterval(_bioRotatorTimer),_bioRotatorTimer=setInterval(()=>{const o=e.querySelectorAll("img");if(o.length===0){clearInterval(_bioRotatorTimer);return}o[i].classList.remove("active"),i=(i+1)%o.length,o[i].classList.add("active")},5e3)}function showContactPage(){const e=document.getElementById("contactPage");animatePageIn(e);let t="<h1>Contact</h1>";t+='<img class="contact-photo" src="/img/fc57ec567f246aceed36366f6bd7d8fecd914fb107774c67fae00bdcae43bdd4/view.webp" alt="Eric Cheng">',t+='<form class="contact-form" id="contactForm">',t+='<div><label for="contactName">Your Name</label><input type="text" id="contactName" required></div>',t+='<div><label for="contactEmail">Your Email</label><input type="email" id="contactEmail" required></div>',t+='<div><label for="contactSubject">Subject</label><input type="text" id="contactSubject"></div>',t+='<div><label for="contactMessage">Your Message</label><textarea id="contactMessage" required></textarea></div>',t+='<div id="contactTurnstile"></div>',t+='<button type="submit">Send Message</button>',t+="</form>",e.innerHTML=t,e.style.display="block";const n=_renderTurnstile(document.getElementById("contactTurnstile"));document.getElementById("contactForm").onsubmit=async i=>{i.preventDefault();const o=i.target.querySelector("button");o.disabled=!0,o.textContent="Sending...";try{await api("/api/contact",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:document.getElementById("contactName").value,email:document.getElementById("contactEmail").value,subject:document.getElementById("contactSubject").value,message:document.getElementById("contactMessage").value,turnstile_token:window.turnstile&&window.turnstile.getResponse(n.id())||""})}),i.target.innerHTML='<div class="contact-sent">Thank you! Your message has been sent.</div>'}catch{o.disabled=!1,o.textContent="Send Message",window.turnstile&&n.id()!==null&&window.turnstile.reset(n.id()),showToast("Failed to send message. Please try again.","error")}}}const TURNSTILE_SITEKEY="0x4AAAAAADuv4z6YDCSzwmK6";function _renderTurnstile(e){let t=null;const n=()=>{e.isConnected&&(t=window.turnstile.render(e,{sitekey:TURNSTILE_SITEKEY,action:"turnstile-spin-v1",theme:"dark"}))};if(window.turnstile)n();else if(document.getElementById("turnstileScript"))window._turnstileReady=n;else{window._turnstileReady=n;const i=document.createElement("script");i.id="turnstileScript",i.src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=_turnstileReady&render=explicit",i.async=!0,i.defer=!0,document.head.appendChild(i)}return{id:()=>t}}function showCollectionsPage(){const e=document.getElementById("collectionsPage");e.style.display="block",animatePageIn(e);const t=S.collections.reduce((i,o)=>i+(o.item_count||0),0);let n="<h1>Collections</h1>";n+=`<div style="font-size:14px;color:var(--text-dim);margin-bottom:32px">${S.collections.length} collections, ${t} photos</div>`,n+='<div class="collections-grid">',S.collections.forEach((i,o)=>{n+=_collectionCardHtml(i,o===0)}
12),n+="</div>",e.innerHTML=n,S.adminToken&&_renderUnlistedAlbumsAdmin(e)}function _collectionCardHtml(e,t){const n=e.cover_hash?imgUrl(e.cover_hash,t?"view":"grid"):"",i=n?`<img src="${n}" alt="${esc(e.name)}" loading="lazy">`:'<div style="width:100%;height:100%;background:var(--surface2)"></div>',o=e.description?`<div class="collection-card-desc">${esc(e.description)}</div>`:"",s=e.unlisted?'<div class="lock-badge" title="Unlisted album">\u{1F512}</div>':"",a=e.unlisted?`<button class="album-share-btn" onclick="event.stopPropagation();copyAlbumShareLink('${esc(e.slug)}')">Copy share link</button>`:"";return`<div class="collection-card${t?" featured":""}" onclick="navigate('/c/${esc(e.slug)}')"> 13 ${i}${s} 14 <div class="collection-card-info"> 15 <div class="collection-card-name">${esc(e.name)}</div> 16 <div class="collection-card-count">${e.item_count} ${e.item_count===1?"photo":"photos"}</div> 17 ${o}${a} 18 </div> 19 </div>`}async function _renderUnlistedAlbumsAdmin(e){let t;try{t=await api("/api/admin/collections",{headers:{Authorization:`Bearer ${S.adminToken}`}})}catch{return}const n=(t||[]).filter(o=>o.unlisted);if(!n.length)return;let i='<div class="collections-grid" style="margin-top:8px">';i+='<div class="collections-section-title">\u{1F512} Unlisted albums (admin only)</div>',n.forEach(o=>{i+=_collectionCardHtml(o,!1)}),i+="</div>",e.insertAdjacentHTML("beforeend",i)}const MONTH_NAMES_FULL=["","January","February","March","April","May","June","July","August","September","October","November","December"];function openItemById(e){const t=typeof e=="number"?String(e):e,n=S.items.findIndex(i=>String(i.id)===t);if(n>=0){openDetail(n);return}api(`/api/media/${e}`).then(i=>{i&&i.id?_openStandaloneDetail(i):showToast("This image is no longer available")}).catch(()=>{showToast("This image is no longer available")})}function _openStandaloneDetail(e){S.detailIndex<0&&(_savedScrollY=window.scrollY),S._standaloneItem=e,S.items.unshift(e),rebuildItemsMap(),openDetail(0)}function _discoverCard(e,t){const n=mediaSrc(e,"grid"),i=esc(t||e.caption||e.file_name||""),o=[],s=fmtLocation(e);s?o.push(s):e.date_taken&&o.push(formatDate(e.date_taken));const a=e.blurhash?` style="background-image:url(${decodeBlurhash(e.blurhash,4,4)});background-size:cover"`:"";return`<div class="discover-card" onclick="openItemById(${e.id})"${a}> 20 <img src="${n}" alt="${i}" loading="lazy" decoding="async" onerror="this.style.visibility='hidden'"> 21 <div class="discover-card-info"> 22 <div class="discover-card-title">${i}</div> 23 ${o.length?`<div class="discover-card-meta">${esc(o[0])}</div>`:""} 24 </div> 25 </div>`}async function showDiscoverPage(){const e=document.getElementById("discoverPage");e.style.display="block",animatePageIn(e),e.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-dim)">Loading...</div>';try{const t=await cachedApi("/api/discover");let n='<h1 style="font-size:28px;font-weight:600;margin-bottom:8px;">Discover</h1>';if(t.featured&&t.featured.length){n+='<div class="discover-section">',n+="<h2>Featured</h2>",n+='<div class="discover-featured">';for(const i of t.featured)n+=_discoverCard(i);n+="</div></div>"}if(t.random&&t.random.length){n+='<div class="discover-section">',n+='<h2>Explore <button class="discover-refresh" onclick="_refreshDiscoverRandom()">Refresh</button></h2>',n+='<div class="discover-row" id="discoverRandomRow">';for(const i of t.random)n+=_discoverCard(i);n+="</div></div>"}if(t.locations&&t.locations.length){n+='<div class="discover-section">',n+="<h2>Around the World</h2>",n+='<div class="discover-row">';for(const i of t.locations)n+=_discoverCard(i,i.country);n+="</div></div>"}if(t.recent&&t.recent.length){n+='<div class="discover-section">',n+="<h2>Recently Added</h2>",n+='<div class="discover-row">';for(const i of t.recent)n+=_discoverCard(i);n+="</div></div>"}e.innerHTML=n}catch{_retryFn=()=>showDiscoverPage(),e.innerHTML=`<h1>Discover</h1><p style="color:var(--text-dim)">Failed to load discover data.${retryBtnHtml()}</p>`}}async function _refreshDiscoverRandom(){const e=document.getElementById("discoverRandomRow");if(e)try{const t=await api("/api/random?limit=12");if(t.items&&t.items.length){let n="";for(const i of t.items)n+=_discoverCard(i);e.innerHTML=n}}catch{}}function filterBy(e,t){S.filters[e]=t,S.offset=0,navigate("/search")}function filterByTag(e){filterBy("tag",e)}function filterByCamera(e){filterBy("camera",e)}function filterByLocation(e){filterBy("q",e)}
25async function showFacetPage(e){const{pageId:t,endpoint:n,dataKey:i,title:o,subtitle:s,nameOf:a,filterField:r,filterValue:l}=e,c=document.getElementById(t);c.style.display="block",animatePageIn(c),c.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-dim)">Loading...</div>';try{const h=(await cachedApi(n))[i];if(!h||!h.length){c.innerHTML=`<h1>${esc(o)}</h1><p style="color:var(--text-dim)">No ${o.toLowerCase()} found.</p>`;return}let g=`<h1>${esc(o)}</h1>`;g+=`<div class="tags-subtitle">${h.length} ${s}</div>`,g+='<div class="tags-grid">';for(const v of h){const b=v.cover,d=b?imgUrl(b.content_hash,"grid"):"",u=b&&b.blurhash?` style="background-image:url(${decodeBlurhash(b.blurhash,4,4)});background-size:cover"`:"",f=v.count===1?"photo":"photos",y=a(v),p=l(v);g+=`<div class="tag-card" data-act="filter-by" data-field="${r}" data-val="${esc(p)}"${u}>`,d&&(g+=`<img src="${d}" alt="${esc(y)}" loading="lazy" decoding="async" onerror="this.style.visibility='hidden'">`),g+='<div class="tag-card-overlay">',g+=`<div class="tag-card-name">${esc(y)}</div>`,g+=`<div class="tag-card-count">${v.count} ${f}</div>`,g+="</div></div>"}g+="</div>",c.innerHTML=g}catch{_retryFn=()=>showFacetPage(e),c.innerHTML=`<h1>${esc(o)}</h1><p style="color:var(--text-dim)">Failed to load ${o.toLowerCase()}.${retryBtnHtml()}</p>`}}function showTagsPage(){showFacetPage({pageId:"tagsPage",endpoint:"/api/tags?limit=40",dataKey:"tags",title:"Tags",subtitle:"topics across your portfolio",nameOf:e=>e.tag,filterField:"tag",filterValue:e=>e.tag})}function showCamerasPage(){showFacetPage({pageId:"camerasPage",endpoint:"/api/cameras",dataKey:"cameras",title:"Cameras",subtitle:"cameras in your portfolio",nameOf:e=>e.camera.replace(/^(Apple|FUJIFILM|LEICA CAMERA AG)\s*/i,""),filterField:"camera",filterValue:e=>e.camera})}function showPlacesPage(){showFacetPage({pageId:"placesPage",endpoint:"/api/places",dataKey:"places",title:"Places",subtitle:"locations across your portfolio",nameOf:e=>e.location.split(",")[0].trim(),filterField:"q",filterValue:e=>e.location})}async function showYearReviewPage(e){const t=document.getElementById("yearReviewPage");t.style.display="block",animatePageIn(t),t.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-dim)">Loading...</div>';try{const n=await cachedApi(`/api/year-review?year=${e}`);if(n.error){t.innerHTML=`<h1>${e}</h1><p style="color:var(--text-dim)">${esc(n.error)}</p>`;return}const i=n.stats;let o=`<h1>${e}</h1>`;if(o+='<div class="yr-subtitle">Year in Review</div>',o+='<div class="yr-stats">',o+=_yrStat(i.total,"Total"),o+=_yrStat(i.images,"Photos"),i.videos>0&&(o+=_yrStat(i.videos,"Videos")),i.unique_locations>0&&(o+=_yrStat(i.unique_locations,"Places")),i.unique_countries>1&&(o+=_yrStat(i.unique_countries,"Countries")),i.unique_cameras>0&&(o+=_yrStat(i.unique_cameras,"Cameras")),i.avg_aesthetic&&(o+=_yrStat(i.avg_aesthetic,"Avg Score")),i.total_duration>0){const s=Math.round(i.total_duration/60);o+=_yrStat(s>60?Math.round(s/60)+"h":s+"m","Video Time")}if(o+="</div>",n.months&&n.months.length>1){const s=Math.max(...n.months.map(a=>a.count));o+='<div class="yr-section">',o+="<h2>Month by Month</h2>",o+='<div class="yr-bar-chart">';for(let a=1;a<=12;a++){const r=n.months.find(h=>h.month===a),l=r?r.count:0,c=s>0?Math.max(4,l/s*100):4;o+=`<div class="yr-bar" style="height:${c}%;opacity:${l===0?"0.15":"1"}" `,o+=`onclick="navigate('/?year=${e}&month=${a}')" title="${MONTH_NAMES_FULL[a]}: ${l}">`,o+=`<div class="yr-bar-label">${MONTH_NAMES_FULL[a].slice(0,3)}</div>`,o+="</div>"}o+="</div></div>"}if(n.highlights&&n.highlights.length){o+='<div class="yr-section">',o+="<h2>Top Photos</h2>",o+='<div class="yr-hero-grid">';for(const s of n.highlights){const a=imgUrl(s.content_hash,"grid");o+=`<div class="yr-hero-card" onclick="navigate('/p/${s.id}')">`,o+=`<img src="${a}" alt="${esc(s.caption||"")}" loading="lazy" decoding="async">`,o+="</div>"}o+="</div></div>"}if(n.months&&n.months.length){o+='<div class="yr-section">',o+="<h2>Monthly Highlights</h2>";for(const s of n.months)if(!(!s.highlights||!s.highlights.length)){o+='<div class="yr-month">',o+=`<div class="yr-month-label"><span>${s.count}</span>${MONTH_NAMES_FULL[s.month]}</div>`,o+='<div class="yr-month-photos">';for(const a of s.highlights){const r=imgUrl(a.content_hash,"thumb");o+=`<div class="yr-month-thumb" onclick="navigate('/p/${a.id}')">`,o+=`<img src="${r}" alt="${esc(a.caption||"")}" loading="lazy">`,o+="</div>"}
25o+="</div></div>"}o+="</div>"}if(n.locations&&n.locations.length){o+='<div class="yr-section">',o+="<h2>Places Visited</h2>",o+='<div class="yr-locations">';for(const s of n.locations){const a=s.location_name.split(",")[0].trim();o+=`<div class="yr-location" data-act="filter-by" data-field="q" data-val="${esc(s.location_name)}">`,o+=`<span>${esc(a)}</span>`,o+=`<span class="yr-location-count">${s.count}</span>`,o+="</div>"}o+="</div></div>"}if(n.cameras&&n.cameras.length){const s=n.cameras[0].count;o+='<div class="yr-section">',o+="<h2>Cameras Used</h2>",o+='<div class="yr-cameras">';for(const a of n.cameras){const r=a.camera_model.replace(/^(Apple|FUJIFILM|LEICA CAMERA AG)\s*/i,""),l=Math.max(8,a.count/s*100);o+=`<div class="yr-camera" data-act="filter-by" data-field="camera" data-val="${esc(a.camera_model)}">`,o+=`<div><div>${esc(r)}</div><div class="yr-camera-bar" style="width:${l}%"></div></div>`,o+=`<span class="yr-location-count">${a.count}</span>`,o+="</div>"}o+="</div></div>"}if(n.tags&&n.tags.length){o+='<div class="yr-section">',o+="<h2>Top Tags</h2>",o+='<div class="yr-tag-cloud">';for(const s of n.tags){const a=13+Math.min(s.count/2,8);o+=`<span class="yr-tag" style="font-size:${a}px" data-act="filter-by" data-field="tag" data-val="${esc(s.tag)}">${esc(s.tag)}</span>`}o+="</div></div>"}o+='<div class="yr-nav">',o+=`<a href="/year/${e-1}" onclick="event.preventDefault();navigate('/year/${e-1}')">\u2190 ${e-1}</a>`,o+=`<a href="/story" onclick="event.preventDefault();navigate('/story')">All Years</a>`,o+=`<a href="/year/${e+1}" onclick="event.preventDefault();navigate('/year/${e+1}')">${e+1} \u2192</a>`,o+="</div>",t.innerHTML=o}catch{_retryFn=()=>showYearReviewPage(e),t.innerHTML=`<h1>${e}</h1><p style="color:var(--text-dim)">Failed to load year review.${retryBtnHtml()}</p>`}}function _yrStat(e,t){return`<div class="yr-stat"><div class="yr-stat-value">${e}</div><div class="yr-stat-label">${t}</div></div>`}const SEASON_COLORS={Spring:"#7bc47f",Summer:"#e8b84b",Autumn:"#d4724e",Winter:"#6ea8fe"};async function showSeasonsPage(){const e=document.getElementById("seasonsPage");e.style.display="block",animatePageIn(e),e.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-dim)">Loading...</div>';try{const t=await cachedApi("/api/seasons");if(!t.seasons||!t.seasons.length){e.innerHTML='<h1>Seasons</h1><p style="color:var(--text-dim)">No seasonal data found.</p>';return}let n="<h1>Seasons</h1>";n+='<div class="seasons-subtitle">Your portfolio through the seasons</div>';for(const i of t.seasons){if(i.count===0)continue;const o=SEASON_COLORS[i.name]||"var(--accent)";if(n+='<div class="season-card">',n+=`<div class="season-accent" style="background:${o}"></div>`,n+='<div class="season-header">',n+=`<h2>${esc(i.emoji)} ${esc(i.name)}</h2>`,n+=`<span class="season-count">${i.count} photo${i.count!==1?"s":""}</span>`,n+="</div>",i.highlights&&i.highlights.length){n+='<div class="season-photos">';for(const a of i.highlights){const r=imgUrl(a.content_hash,"grid");n+=`<div class="season-photo" onclick="navigate('/p/${a.id}')">`,n+=`<img src="${r}" alt="${esc(a.caption||"")}" loading="lazy" decoding="async">`,(a.location_name||a.year)&&(n+='<div class="season-photo-overlay">',a.location_name&&(n+=esc(a.location_name.split(",")[0].trim())),a.year&&(n+=` (${a.year})`),n+="</div>"),n+="</div>"}n+="</div>"}const s=i.months.map(a=>MONTH_NAMES_FULL[a].slice(0,3)).join(", ");n+=`<div class="season-explore" onclick="_filterBySeason([${i.months.join(",")}])">`,n+=`Browse all ${esc(i.name)} photos (${esc(s)}) \u2192`,n+="</div>",n+="</div>"}e.innerHTML=n}catch{_retryFn=()=>showSeasonsPage(),e.innerHTML=`<h1>Seasons</h1><p style="color:var(--text-dim)">Failed to load seasons.${retryBtnHtml()}</p>`}}let _calendarYear=null;async function showCalendarPage(e){const t=document.getElementById("calendarPage");t.style.display="block",animatePageIn(t),t.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-dim)">Loading...</div>';const n=e||_calendarYear||"all";_calendarYear=n;const i=n==="all",o=i?2024:n;try{const s=await cachedApi(`/api/calendar?year=${n}`);let a="<h1>Calendar</h1>";i?a+='<div class="cal-subtitle">Photo activity across all years</div>':a+=`<div class="cal-subtitle">Photo activity for <a href="/browse?year=${n}" onclick="event.preventDefault();navigate('/browse?year=${n}')" style="color:var(--accent);text-decoration:none;cursor:pointer">${n}</a></div>`;const r=s.yearCounts||{},l=Math.max(...Object.values(r),1);a+='<div class="cal-year-nav">',a+=`<button class="cal-year-btn${i?" active":""}" onclick="_switchCalendarYear('all')">All Time</button>`;for(const w of s.years||[]){const k=!i&&w===n?" active":"",x=(.05+(r[w]||0)/l*.15).toFixed(2),$=k?"":` style="background:rgba(56,189,248,${x}
25)"`;a+=`<button class="cal-year-btn${k}"${$} onclick="_switchCalendarYear(${w})">${w}</button>`}a+="</div>";const c=new Date(o,0,1),m=new Date(o,11,31),h=c.getDay(),g=[];let v=new Date(c);for(v.setDate(v.getDate()-h);v<=m||v.getDay()!==0;){const w=[];for(let k=0;k<7;k++){const _=v.toISOString().substring(0,10),x=v.getFullYear()===o,$=x?s.days[_]||0:-1;w.push({date:_,count:$,inYear:x}),v.setDate(v.getDate()+1)}if(g.push(w),v>m&&v.getDay()===0)break}const b=s.maxCount||1,d=w=>{if(w<=0)return"transparent";const k=w/b;return k<=.25?"rgba(110, 168, 254, 0.25)":k<=.5?"rgba(110, 168, 254, 0.45)":k<=.75?"rgba(110, 168, 254, 0.7)":"rgba(110, 168, 254, 0.95)"};a+='<div style="display:flex;align-items:flex-start">',a+='<div class="cal-day-labels">',a+="<span></span><span>Mon</span><span></span><span>Wed</span><span></span><span>Fri</span><span></span>",a+="</div>",a+="<div>",a+='<div class="cal-month-labels">';let u=-1;for(let w=0;w<g.length;w++){const k=g[w][3];if(k&&k.inYear){const _=parseInt(k.date.substring(5,7));_!==u?(a+=`<span style="width:16px;text-align:left;cursor:pointer" onclick="navigate('${_calBrowseLink(_)}')">${MONTH_NAMES_FULL[_].slice(0,3)}</span>`,u=_):a+='<span style="width:16px"></span>'}else a+='<span style="width:16px"></span>'}a+="</div>",a+='<div class="cal-heatmap">';for(const w of g){a+='<div class="cal-week" style="margin-right:2px">';for(const k of w)if(!k.inYear)a+='<div style="width:14px;height:14px"></div>';else{const _=k.count>0?d(k.count):"",x=new Date(k.date+"T12:00:00"),C=`${MONTH_NAMES_FULL[x.getMonth()+1]} ${x.getDate()}: ${k.count} photo${k.count!==1?"s":""}`,E=k.count>0?` onclick="_calendarDayClick('${k.date}')"`:"";a+=`<div class="cal-day" data-count="${k.count}" style="background:${_}" title="${C}"${E}></div>`}a+="</div>"}
25a+="</div>",a+="</div></div>",a+='<div class="cal-legend">',a+="<span>Less</span>",a+='<div class="cal-legend-box" style="background:rgba(255,255,255,0.04)"></div>',a+='<div class="cal-legend-box" style="background:rgba(110,168,254,0.25)"></div>',a+='<div class="cal-legend-box" style="background:rgba(110,168,254,0.45)"></div>',a+='<div class="cal-legend-box" style="background:rgba(110,168,254,0.7)"></div>',a+='<div class="cal-legend-box" style="background:rgba(110,168,254,0.95)"></div>',a+="<span>More</span>",a+="</div>",a+='<div class="cal-stats">',a+=`<div class="cal-stat"><div class="cal-stat-value">${s.total}</div><div class="cal-stat-label">Photos</div></div>`,a+=`<div class="cal-stat"><div class="cal-stat-value">${s.activeDays}</div><div class="cal-stat-label">Active Days</div></div>`;const f=s.activeDays>0?(s.total/s.activeDays).toFixed(1):"0";a+=`<div class="cal-stat"><div class="cal-stat-value">${f}</div><div class="cal-stat-label">Avg per Active Day</div></div>`,a+=`<div class="cal-stat"><div class="cal-stat-value">${s.maxCount}</div><div class="cal-stat-label">Best Day</div></div>`,a+="</div>",a+='<div class="cal-month-grid">';const y={};for(const[w,k]of Object.entries(s.days)){const _=parseInt(w.substring(5,7));y[_]=(y[_]||0)+k}const p=Math.max(...Object.values(y),1);for(let w=1;w<=12;w++){const k=y[w]||0,_=(k/p*100).toFixed(0);a+=`<div class="cal-month-card" onclick="navigate('${_calBrowseLink(w)}')">`,a+=`<div class="cal-month-name">${MONTH_NAMES_FULL[w]}</div>`,a+=`<div class="cal-month-count">${k}</div>`,a+=`<div class="cal-month-bar"><div class="cal-month-bar-fill" style="width:${_}%;background:var(--accent,#6ea8fe)"></div></div>`,a+="</div>"}
25a+="</div>",t.innerHTML=a}catch{_retryFn=()=>showCalendarPage(e),t.innerHTML=`<h1>Calendar</h1><p style="color:var(--text-dim)">Failed to load calendar data.${retryBtnHtml()}</p>`}}function _switchCalendarYear(e){_calendarYear=e,showCalendarPage(e)}function _calBrowseLink(e){return _calendarYear==="all"?`/browse?month=${e}`:`/browse?year=${_calendarYear}&month=${e}`}function _calendarDayClick(e){navigate(_calBrowseLink(parseInt(e.split("-")[1])))}function _filterBySeason(e){S.filters.month=e.join(","),S.offset=0,navigate("/search")}let _walkScrollTimer=null,_walkScrollRaf=0,_walkKeyHandler=null;async function openWalk(){const e=document.getElementById("walkPage"),t=document.getElementById("walkStrip");let n=S.items.length>0?S.items:[];if(n.length===0)try{n=(await api("/api/search?limit=60&sort=date")).items||[]}catch{return}const i=n.filter(c=>c.media_type==="image");if(i.length===0){showToast("No photos to browse");return}S.view="walk",updatePageTitle();let o="";for(let c=0;c<i.length;c++){const m=i[c],h=imgUrl(m.content_hash,"view"),g=m.blurhash?` style="background:url(${decodeBlurhash(m.blurhash,4,4)}) center/cover"`:"";o+=`<div class="walk-item" data-id="${m.id}" onclick="closeWalk();openItemById(${m.id})"${g}>`,o+=`<img src="${h}" alt="${esc(m.caption||"")}" loading="lazy" decoding="async" onerror="this.style.visibility='hidden'">`,m.caption&&(o+=`<div class="walk-caption">${esc(m.caption)}</div>`),o+="</div>"}t.innerHTML=o,e.style.display="block",document.body.style.overflow="hidden",document.querySelector(".nav").style.display="none",document.querySelector(".status-bar")?.classList.add("hidden");const s=document.getElementById("walkCounter"),a=document.getElementById("walkHint"),r=i.length,l=()=>{const c=e.scrollLeft,m=t.querySelectorAll(".walk-item");let h=1;for(let g=0;g<m.length;g++)m[g].offsetLeft<=c+e.clientWidth/2&&(h=g+1);s.textContent=`${h} / ${r}`};e.onscroll=()=>{_walkScrollRaf||(_walkScrollRaf=requestAnimationFrame(()=>{_walkScrollRaf=0,l(),a.style.opacity!=="0"&&(a.style.opacity="0")}))},l(),a.style.opacity="1",_walkScrollTimer&&clearTimeout(_walkScrollTimer),_walkScrollTimer=setTimeout(()=>{a.style.opacity="0"},3e3),_walkKeyHandler=c=>{if(e.style.display==="block"){if(c.key==="Escape"){closeWalk(),c.stopPropagation();return}(c.key==="ArrowRight"||c.key==="ArrowDown")&&(e.scrollBy({left:e.clientWidth*.8,behavior:"smooth"}),c.preventDefault()),(c.key==="ArrowLeft"||c.key==="ArrowUp")&&(e.scrollBy({left:-e.clientWidth*.8,behavior:"smooth"}),c.preventDefault())}},document.addEventListener("keydown",_walkKeyHandler,!0),e.focus()}function closeWalk(){const e=document.getElementById("walkPage");e.style.display="none",document.body.style.overflow="",document.querySelector(".nav").style.display="",document.querySelector(".status-bar")?.classList.remove("hidden"),e.onscroll=null,_walkScrollRaf&&(cancelAnimationFrame(_walkScrollRaf),_walkScrollRaf=0),_walkScrollTimer&&(clearTimeout(_walkScrollTimer),_walkScrollTimer=null),_walkKeyHandler&&(document.removeEventListener("keydown",_walkKeyHandler,!0),_walkKeyHandler=null),S.view==="walk"&&(S.view="grid",updatePageTitle(),history.pushState(null,"","/"))}let _storyItems=null;function _openStoryDetail(e){_storyItems&&_storyItems.length&&(S.items=_storyItems,S.total=_storyItems.length,rebuildItemsMap()),openItemById(e)}let _surpriseMode=!1,_surpriseLoading=!1;async function startSurprise(){_surpriseMode=!0;try{const e=await api("/api/search?sort=random&limit=20");if(!e.items||e.items.length===0){showToast("No photos available","error");return}S.items=e.items,S.total=e.items.length,rebuildItemsMap(),_detailReturnUrl="/surprise",openDetail(0)}catch{showToast("Failed to load","error")}}async function _extendSurprise(){if(!_surpriseLoading){_surpriseLoading=!0;try{const e=await api("/api/search?sort=random&limit=20");if(e.items&&e.items.length>0){const t=new Set(S.items.map(i=>i.id)),n=e.items.filter(i=>!t.has(i.id));n.length>0&&(S.items=S.items.concat(n),S.total=S.items.length,rebuildItemsMap())}}catch{}_surpriseLoading=!1}}function _storyItemHtml(e){const t=mediaSrc(e,"grid"),n=`_openStoryDetail(${e.id})`;let i='<div class="story-item">';i+=`<img src="${t}" alt="${esc(e.caption||"")}" loading="lazy" onclick="${n}" onerror="this.style.visibility='hidden'">`;const{human:o,ai:s}=splitCaption(e);o?i+=`<div class="story-item-caption">${esc(o)}</div>`:s&&(i+=`<div class="story-item-caption" style="color:var(--text-dim);font-size:0.9em">${esc(s)}</div>`);const a=[];e.date_taken&&a.push(`<span>${esc(formatDate(e.date_taken))}</span>`);const r=fmtLocation(e);r&&a.push(`<span>${esc(r)}</span>`),e.camera_model&&a.push(`<span>${esc(e.camera_model)}</span>`),a.length&&(i+=`<div class="story-item-meta">${a.join("")}</div>`);const l=getLikedIds().has(e.id);return i+=`<button class="like-btn story-like${l?" liked":""}" onclick="event.stopPropagation();likeItem(${e.id}, this)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="1" y="12" width="4" height="10" rx="1"/>
25<path d="M7 22h12a2 2 0 002-1.7l1.4-9A2 2 0 0020.4 9H14V5a3 3 0 00-3-3L7 11v11z" stroke-linejoin="round"/><path d="M14.5 15.5h3M14.5 18h2.5" stroke-linecap="round" opacity="0.4"/></svg> <span class="like-count">${e.likes||0}</span></button>`,i+="</div>",i}function _attachStoryObserver(e){_storyObserver&&_storyObserver.disconnect(),_storyObserver=new IntersectionObserver(t=>{for(const n of t)n.isIntersecting&&(n.target.classList.add("visible"),_storyObserver.unobserve(n.target))},{threshold:.1}),e.querySelectorAll(".story-item").forEach(t=>_storyObserver.observe(t))}async function showGlobalStory(){const e=document.getElementById("storyPage");e.style.display="block",animatePageIn(e),e.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-dim)">Loading story...</div>';try{const t=await cachedApi("/api/story");if(!t||!t.length){e.innerHTML='<div style="text-align:center;padding:80px 24px;color:var(--text-dim)">No items to show.</div>';return}let n=`<a class="story-back" onclick="navigate('/')">\u2190 Back to gallery</a>`;n+="<h1>Journal</h1>",n+='<div class="story-subtitle">A year-by-year visual narrative</div>';const i=[];for(const o of t){n+=`<div class="story-year-divider"><span>${o.year}</span></div>`,n+=`<div class="story-year-count">${o.items.length} photo${o.items.length!==1?"s":""} <a href="/year/${o.year}" onclick="event.preventDefault();navigate('/year/${o.year}')" style="color:var(--accent,#6ea8fe);margin-left:8px;font-size:12px">Year in Review \u2192</a></div>`;for(const s of o.items)n+=_storyItemHtml(s),i.push(s)}_storyItems=i,S.items=i,S.total=i.length,rebuildItemsMap(),e.innerHTML=n,_attachStoryObserver(e)}catch{_retryFn=()=>showGlobalStory(),e.innerHTML=`<div style="text-align:center;padding:40px;color:var(--text-dim)">Failed to load story.${retryBtnHtml()}</div>`}}async function showStoryPage(e){const t=document.getElementById("storyPage");t.style.display="block",animatePageIn(t),t.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-dim)">Loading story...</div>';try{const n=S.collections.find(m=>m.slug===e),i=n?n.name:e,o=await cachedApi(`/api/search?collection=${encodeURIComponent(e)}&limit=200&sort=date`),s=o.items||[];let r=`<a class="story-back" onclick="navigate('${_isHomeCollection(e)?"/":`/c/${esc(e)}`}')">\u2190 Back to ${esc(i)}</a>`;r+=`<h1>${esc(i)}</h1>`;const l=o.total||s.length,c=l>s.length?`${s.length} of ${l.toLocaleString()} photos`:`${s.length} photo${s.length!==1?"s":""}`;n&&n.description?r+=`<div class="story-subtitle">${esc(n.description)} \xB7 ${c}</div>`:r+=`<div class="story-subtitle">${c}</div>`;for(const m of s)r+=_storyItemHtml(m);_storyItems=s,S.items=s,S.total=s.length,rebuildItemsMap(),t.innerHTML=r,_attachStoryObserver(t)}catch{_retryFn=()=>showStoryPage(e),t.innerHTML=`<div style="text-align:center;padding:40px;color:var(--text-dim)">Failed to load story.${retryBtnHtml()}</div>`}}async function showStatsPage(){const e=document.getElementById("statsPage");e.style.display="block",animatePageIn(e),e.innerHTML='<h1>Portfolio Statistics</h1><div class="stats-grid"><div class="stat-card"><div class="stat-value">...</div><div class="stat-label">Loading</div></div></div>';try{const t=await api("/api/stats");let n="<h1>Portfolio Statistics</h1>";if(n+='<div class="stats-grid">',n+=statCard(t.total_items.toLocaleString(),"Total Photos"),t.total_images&&(n+=statCard(t.total_images.toLocaleString(),"Images")),t.total_videos&&(n+=statCard(t.total_videos.toLocaleString(),"Videos")),t.total_collections&&(n+=statCard(t.total_collections.toLocaleString(),"Collections")),t.year_range&&t.year_range.min&&t.year_range.max){const i=t.year_range.max-t.year_range.min+1;n+=statCard(`${t.year_range.min}\u2013${t.year_range.max}`,`${i} Year${i!==1?"s":""}`)}if(t.total_countries&&(n+=statCard(t.total_countries.toLocaleString(),"Countries")),t.total_cameras&&(n+=statCard(t.total_cameras.toLocaleString(),"Cameras")),n+="</div>",n+='<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(380px,1fr));gap:32px">',t.top_countries&&t.top_countries.length>0&&(n+='<div class="stats-section"><h2>Top Locations</h2>',n+=statsBarChart(t.top_countries,"country",i=>`data-act="stats-filter" data-field="country" data-val="${esc(i.country)}"`),n+="</div>"),t.top_cameras&&t.top_cameras.length>0&&(n+='<div class="stats-section"><h2>Cameras</h2>',n+=statsBarChart(t.top_cameras,"camera",i=>`data-act="stats-filter" data-field="camera" data-val="${esc(i.camera)}"`),n+="</div>"),t.top_tags&&t.top_tags.length>0&&(n+='<div class="stats-section"><h2>Top Tags</h2>',n+=statsBarChart(t.top_tags,"tag",i=>`data-act="home-tag" data-val="${esc(i.tag)}"`),n+="</div>"),n+="</div>",t.top_tags&&t.top_tags.length>0&&(n+='<div class="stats-section"><h2>Tag Cloud</h2>
25',n+=renderTagCloud(t.top_tags),n+="</div>"),t.year_distribution&&t.year_distribution.length>1){n+='<div class="stats-section"><h2>Photos by Year</h2>';const i=Math.max(...t.year_distribution.map(o=>o.count));n+='<div style="display:flex;align-items:flex-end;gap:4px;height:120px;padding:8px 0">';for(const o of t.year_distribution){const s=Math.max(4,Math.round(o.count/i*100));n+=`<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:4px" title="${o.year}: ${o.count} photos">`,n+=`<span style="font-size:10px;color:var(--text-dimmer)">${o.count}</span>`,n+=`<div style="width:100%;height:${s}px;background:var(--accent);border-radius:2px;cursor:pointer" onclick="navigate('/year/${o.year}')" title="View ${o.year} in Review"></div>`,n+=`<span style="font-size:10px;color:var(--text-dim)">${o.year}</span>`,n+="</div>"}n+="</div></div>"}if(t.rating_distribution&&t.rating_distribution.length>0&&(n+='<div class="stats-section"><h2>Ratings</h2>',n+=statsBarChart(t.rating_distribution,"rating",null,i=>i.rating===0?"Unrated":"\u2605".repeat(i.rating),"80px"),n+="</div>"),t.daily_counts&&t.daily_counts.length>0&&(n+='<div class="stats-section"><h2>Photo Calendar</h2>',n+=renderCalendarHeatmap(t.daily_counts),n+="</div>"),t.top_colors&&t.top_colors.length>0){n+='<div class="stats-section"><h2>Color Palette</h2>',n+='<div class="color-palette">';for(const i of t.top_colors)n+=`<div class="color-swatch" style="background:${esc(i.color)}" title="${esc(i.color)} (${i.count} photos)" data-act="home-color" data-val="${esc(i.color)}"></div>`;n+="</div></div>"}e.innerHTML=n}catch{_retryFn=()=>showStatsPage(),e.innerHTML=`<h1>Portfolio Statistics</h1><p style="color:var(--text-dim)">Could not load statistics.${retryBtnHtml()}</p>`}}function renderTagCloud(e){if(!e||e.length===0)return"";const t=e.map(a=>a.count),n=Math.min(...t),o=Math.max(...t)-n||1;let s='<div class="tag-cloud">';for(const a of e){const r=(a.count-n)/o,l=13+Math.round(r*22),c=.5+r*.5;s+=`<span class="tag-cloud-item" style="font-size:${l}px;opacity:${c}" title="${esc(a.tag)}: ${a.count} photos" data-act="home-tag" data-val="${esc(a.tag)}">${esc(a.tag)}</span>`}return s+="</div>",s}function renderCalendarHeatmap(e){const t={};let n=1;for(const u of e)t[u.day]=u.count,u.count>n&&(n=u.count);const i=new Date,o=new Date(i);o.setFullYear(o.getFullYear()-1),o.setDate(o.getDate()-o.getDay());const s=[];let a=[];const r=[];let l=-1;const c=new Date(o);for(;c<=i;){const u=c.toISOString().slice(0,10),f=t[u]||0,y=c.getDay(),p=c.getMonth();y===0&&a.length>0&&(s.push(a),a=[]),p!==l&&(r.push({weekIdx:s.length,label:MONTH_NAMES_0[p]}),l=p),a.push({dayStr:u,count:f,dow:y}),c.setDate(c.getDate()+1)}a.length>0&&s.push(a);const m=11,h=2,g=s.length*(m+h)+30,v=7*(m+h)+20;let b=`<svg width="${g}" height="${v}" style="display:block;overflow-x:auto">`;for(const u of r){const f=u.weekIdx*(m+h)+30;b+=`<text x="${f}" y="10" fill="var(--text-dimmer)" font-size="10">${u.label}</text>`}const d=["","M","","W","","F",""];for(let u=0;u<7;u++)d[u]&&(b+=`<text x="12" y="${u*(m+h)+25}" fill="var(--text-dimmer)" font-size="9" text-anchor="middle">${d[u]}</text>`);for(let u=0;u<s.length;u++)for(const f of s[u]){const y=u*(m+h)+30,p=f.dow*(m+h)+15;let w;f.count===0?w="rgba(255,255,255,0.04)":w=`rgba(99,179,237,${(.2+Math.min(1,f.count/Math.max(n*.5,1))*.8).toFixed(2)})`,b+=`<rect x="${y}" y="${p}" width="${m}" height="${m}" rx="2" fill="${w}" cursor="pointer" onclick="S.filters.year='${f.dayStr.slice(0,4)}'; S.filters.month='${parseInt(f.dayStr.slice(5,7))}'; S.offset=0; navigate('/search');">`,b+=`<title>${f.dayStr}: ${f.count} photo${f.count!==1?"s":""}</title></rect>`}return b+="</svg>",`<div style="overflow-x:auto;padding:8px 0">${b}</div>`}function statCard(e,t){return`<div class="stat-card"><div class="stat-value">${esc(String(e))}</div><div class="stat-label">${esc(t)}</div></div>`}function statsBarChart(e,t,n,i,o){const s=Math.max(...e.map(r=>r.count));let a="";for(const r of e){const l=Math.round(r.count/s*100),c=i?i(r):esc(r[t]),m=o?` style="width:${o}"`:"",h=n?` ${n(r)}`:"";a+='<div class="stats-bar-row">',a+=`<span class="stats-bar-label"${m}${h}>${c}</span>`,a+=`<div class="stats-bar-track"><div class="stats-bar-fill" style="width:${l}%"></div></div>`,a+=`<span class="stats-bar-count">${r.count}</span>`,a+="</div>"}return a}function buildGridSortBar(){const e=document.getElementById("gridSortBar");e.innerHTML="",e.style.display="";const t=document.createElement("span");if(t.className="sort-bar-count",t.id="sortBarCount",e.appendChild(t),updateSortBarCount(),S.view==="browse"||S.view==="search"){const i=document.createElement("a");i.href="#",i.className="sort-bar-story",i.textContent="Story",i.onclick=o=>{o.preventDefault();const s=S.view==="search"?"/search/story":"/browse/story",a=buildFilterParams().toString();navigate(a?`${s}?${a}`:s)},e.appendChild(i)}else if(S.filters.collection){const i=document.createElement("a");i.href=`/c/${S.filters.collection}/story`,i.className="sort-bar-story",i.textContent="Story",i.onclick=o=>{o.preventDefault(),navigate(`/c/${S.filters.collection}/story`)},e.appendChild(i)}if(S.filters.q&&S.sort==="date"&&(S.sort="relevance"),!S.filters.q&&S.sort==="relevance"&&(S.sort="date"),S.view==="search"&&S._siteConfig&&S._siteConfig.semantic_enabled){const i=document.createElement("div");i.className="sort-bar-mode",i.setAttribute("role","group"),i.setAttribute("aria-label","Search mode");const o=(s,a,r)=>{const l=document.createElement("button");l.type="button",l.textContent=s,l.title=r,l.classList.toggle("active",S.semantic===a),l.setAttribute("aria-pressed",String(S.semantic===a)),l.onclick=()=>
25{if(S.semantic!==a){S.semantic=a,S._modeExplicit=!0;try{localStorage.setItem("pf_semantic",a?"1":"")}catch{}buildGridSortBar(),S.filters.q&&reloadGrid()}},i.appendChild(l)};o("Precise",!1,"Match keywords exactly"),o("By feeling",!0,"Match by meaning: keyword hits first, then similar images"),e.appendChild(i)}if(searchMode()==="feeling")return;const n=document.createElement("select");n.className="filter-select",n.innerHTML=(S.filters.q?'<option value="relevance">Relevant</option>':"")+'<option value="date">Newest</option><option value="oldest">Oldest</option><option value="likes">Most Liked</option><option value="name">Name</option><option value="random">Random</option>',n.value=S.sort,n.onchange=()=>{S.sort=n.value,S.offset=0;try{localStorage.setItem("pf_sort",S.sort)}catch{}reloadGrid()},e.appendChild(n)}function updateSortBarCount(){const e=document.getElementById("sortBarCount");if(!e)return;const t=S.items.length,n=S.total;if(n===0){e.textContent="";return}const i=n===1?"photo":"photos";e.textContent=t<n?`${t.toLocaleString()} of ${n.toLocaleString()} ${i}`:`${n.toLocaleString()} ${i}`}function getSearchHistory(){try{return JSON.parse(localStorage.getItem("search_history")||"[]")}catch{return[]}}function addSearchHistory(e){if(!e||e.length<2)return;let t=getSearchHistory().filter(n=>n!==e);t.unshift(e),t.length>10&&(t=t.slice(0,10)),localStorage.setItem("search_history",JSON.stringify(t))}function showSearchPage(){const e=document.getElementById("searchPage");e.style.display="",e.innerHTML="";const t=document.createElement("input");t.type="text",t.className="search-page-input",t.placeholder="Search photos, places, tags, people, cameras...",t.value=S.filters.q||"",t.id="searchPageInput",t.maxLength=200,e.appendChild(t);const n=document.createElement("div");n.className="search-filters",n.id="searchFilters",e.appendChild(n);const i=document.createElement("div");i.className="search-active-chips",i.id="searchChips",e.appendChild(i),buildFilterBar();let o;if(t.addEventListener("input",()=>{clearTimeout(o),o=setTimeout(()=>{S.filters.q=t.value.trim(),S.offset=0,t.value.trim()&&addSearchHistory(t.value.trim()),reloadGrid(),buildFilterBar()},300)}),t.addEventListener("keydown",s=>{s.key==="Enter"&&(clearTimeout(o),S.filters.q=t.value.trim(),S.offset=0,S.loading=!1,t.value.trim()&&addSearchHistory(t.value.trim()),reloadGrid(),buildFilterBar())}),t.focus(),hasActiveSearchFilters())reloadGrid();else{const s=document.getElementById("grid");s.innerHTML="",s.style.height="";const a=document.getElementById("emptyState");a.querySelector("h2").textContent="Search your photos",a.querySelector("p").textContent="Use the search bar or filters above to find photos.";let r=a.querySelector(".clear-btn");r&&(r.style.display="none"),a.style.display="flex"}}function hasActiveSearchFilters(){return Object.entries(S.filters).some(([e,t])=>e!=="collection"&&t&&t!==""&&t!==0)}function showBrowsePage(){const e=document.getElementById("browsePage");e.style.display="",e.innerHTML="";const t=document.createElement("h1");t.style.cssText="font-size:28px;font-weight:600;margin-bottom:24px",t.textContent="Browse",e.appendChild(t);const n=document.createElement("div");n.className="search-filters",e.appendChild(n);const i=document.createElement("div");i.className="search-active-chips",i.id="browseChips",e.appendChild(i),buildBrowseFilterBar(n,i),reloadGrid()}function reloadGrid(){S.offset=0,loadItems()}async function showBrowseStory(){document.getElementById("grid").style.display="none",document.getElementById("gridSortBar").style.display="none";const e=document.getElementById("storyPage");e.style.display="block",animatePageIn(e),e.innerHTML='<div style="text-align:center;padding:40px;color:var(--text-dim)">Loading story...</div>';try{const t=buildFilterParams().toString(),n=S.view==="search"?"/search/story":"/browse/story";history.replaceState(null,"",t?`${n}?${t}`:n);const i=await fetchFilteredItems({sort:"date",limit:200,offset:0}),o=i.items,s=[],a=S.filters;if(a.collection){const b=S.collections.find(d=>d.slug===a.collection);s.push(b?b.name:a.collection)}a.city?s.push(a.city):a.state?s.push(a.state):a.country&&s.push(a.country),a.tag&&s.push(a.tag),a.camera&&s.push(a.camera),a.year&&s.push(a.year),a.type&&s.push(a.type.charAt(0).toUpperCase()+a.type.slice(1)+"s"),a.aspect&&s.push(a.aspect.charAt(0).toUpperCase()+a.aspect.slice(1)),a.color&&s.push("Color: "+a.color),a.min_rating>0&&s.push(a.min_rating+"+ Stars"),a.q&&s.push('"'+a.q+'"');const r=s.length?s.join(" \xB7 "):"All Photos",l=S.view==="search"?"/search":"/browse",c=S.view==="search"?"Search":"Browse",m=t?`?${t}`:"";let h=`<a class="story-back" onclick="navigate('${l}${m}')">\u2190 Back to ${c}</a>`;h+=`<h1>${esc(r)}</h1>`;const g={};for(const b of o){const d=b.year||(b.date_taken?parseInt(b.date_taken):null)||"Unknown";g[d]||(g[d]=[]),g[d].push(b)}const v=Object.keys(g).sort((b,d)=>d-b);if(v.length===0)h+='<div style="text-align:center;padding:40px;color:var(--text-dim)">No items to show.</div>';else{const b=i.total>o.length?`${o.length}
25 of ${i.total.toLocaleString()} photos`:`${o.length} photo${o.length!==1?"s":""}`;h+=`<div class="story-subtitle">${b}</div>`;for(const d of v){h+=`<div class="story-year-divider"><span>${d}</span></div>`,h+=`<div class="story-year-count">${g[d].length} photo${g[d].length!==1?"s":""}</div>`;for(const u of g[d])h+=_storyItemHtml(u)}}_storyItems=o,S.items=o,S.total=o.length,rebuildItemsMap(),e.innerHTML=h,_attachStoryObserver(e)}catch{_retryFn=()=>showBrowseStory(),e.innerHTML=`<div style="text-align:center;padding:40px;color:var(--text-dim)">Failed to load story.${retryBtnHtml()}</div>`}}let _colorDebounce;const _COLOR_PALETTE=["#e53935","#f4511e","#fb8c00","#fdd835","#7cb342","#43a047","#00897b","#00acc1","#039be5","#1e88e5","#3949ab","#5e35b1","#8e24aa","#d81b60","#6d4c41","#546e7a","#ef9a9a","#ffab91","#ffcc80","#fff59d","#c5e1a5","#a5d6a7","#80cbc4","#80deea","#90caf9","#9fa8da","#b39ddb","#ce93d8","#f48fb1","#bcaaa4","#b0bec5","#eeeeee","#b71c1c","#bf360c","#e65100","#f57f17","#33691e","#1b5e20","#004d40","#006064","#01579b","#0d47a1","#1a237e","#311b92","#4a148c","#880e4f","#3e2723","#212121"];function buildColorPicker(e){const t=document.createElement("div");t.style.cssText="position:relative;display:inline-block";const n=document.createElement("button");n.className="filter-select",n.style.cssText="cursor:pointer;display:flex;align-items:center;gap:4px;padding:4px 8px;border:1px solid var(--border);background:var(--surface);border-radius:4px";const i=document.createElement("span");i.style.cssText=`width:14px;height:14px;border-radius:3px;border:1px solid var(--border);background:${S.filters.color||"#666"}`;const o=document.createElement("span");if(o.textContent=(S.filters.color,"Color"),o.style.fontSize="12px",n.appendChild(i),n.appendChild(o),S.filters.color){const c=document.createElement("span");c.textContent="\xD7",c.style.cssText="margin-left:4px;opacity:0.5;cursor:pointer",c.onclick=m=>{m.preventDefault(),m.stopPropagation(),S.filters.color="",S.offset=0,reloadGrid(),e()},n.appendChild(c)}const s=document.createElement("div");s.style.cssText="display:none;position:absolute;top:100%;left:0;z-index:200;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:10px;box-shadow:0 8px 24px rgba(0,0,0,0.4);width:240px;margin-top:4px";const a=document.createElement("div");a.style.cssText="display:grid;grid-template-columns:repeat(16,1fr);gap:2px";for(const c of _COLOR_PALETTE){const m=document.createElement("div");m.style.cssText=`width:100%;aspect-ratio:1;border-radius:2px;cursor:pointer;background:${c};border:${S.filters.color===c?"2px solid #fff":"1px solid rgba(255,255,255,0.1)"}`,m.onclick=()=>{S.filters.color=c,S.offset=0,i.style.background=c,s.style.display="none",reloadGrid(),e()},a.appendChild(m)}s.appendChild(a);const r=document.createElement("div");r.style.cssText="margin-top:8px;display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-dim)",r.textContent="Custom: ";const l=document.createElement("input");return l.type="color",l.value=S.filters.color||"#666666",l.style.cssText="width:28px;height:22px;border:none;padding:0;cursor:pointer;background:none",l.oninput=()=>{i.style.background=l.value,clearTimeout(_colorDebounce),_colorDebounce=setTimeout(()=>{S.filters.color=l.value,S.offset=0,s.style.display="none",reloadGrid(),e()},300)},r.appendChild(l),s.appendChild(r),n.onclick=c=>{c.preventDefault(),s.style.display=s.style.display==="none"?"":"none"},document.addEventListener("click",c=>{t.contains(c.target)||(s.style.display="none")}),t.appendChild(n),t.appendChild(s),t}function buildBrowseFilterBar(e,t){if(e.innerHTML="",S.collections&&S.collections.length>0){const o=document.createElement("select");o.className="filter-select";const s=S.collections.reduce((r,l)=>r+(l.item_count||0),0),a=[...S.collections].filter(r=>r.slug!=="all").sort((r,l)=>r.name.localeCompare(l.name));o.innerHTML=`<option value="">All Media (${s})</option>`;for(const r of a)o.innerHTML+=`<option value="${esc(r.slug)}">${esc(r.name)} (${r.item_count})</option>`;o.value=S.filters.collection,o.onchange=()=>{S.filters.collection=o.value,S.offset=0,reloadGrid(),buildBrowseFilterBar(e,t)},e.appendChild(o)}if(S.facets.countries&&S.facets.countries.length>0){const o=document.createElement("select");o.className="filter-select",o.id="browseCountry",o.innerHTML='<option value="">Places</option>';const s=[...S.facets.countries].sort((a,r)=>a.country.localeCompare(r.country));for(const a of s)o.innerHTML+=`<option value="${esc(a.country)}">${esc(a.country)} (${a.count})</option>`;o.value=S.filters.country,o.onchange=()=>{S.filters.country=o.value,S.filters.state="",S.filters.city="",S.offset=0,reloadGrid(),buildBrowseFilterBar(e,t)},e.appendChild(o)}const n=document.createElement("select");if(n.className="filter-select",n.innerHTML='<option value="">Aspect</option><option value="landscape">Landscape</option><option value="portrait">Portrait</option><option value="square">Square</option><option value="panorama">Panorama</option>',n.value=S.filters.aspect,n.onchange=()=>{S.filters.aspect=n.value,S.offset=0,reloadGrid(),buildBrowseFilterBar(e,t)},e.appendChild(n),S.facets.types&&S.facets.types.some(o=>o.media_type!=="image")){const o=document.createElement("select");o.className="filter-select",o.innerHTML='<option value="">Type</option>';const s=[...S.facets.types].sort((a,r)=>a.media_type.localeCompare(r.media_type));for(const a of s)o.innerHTML+=`<option value="${esc(a.media_type)}">${esc(a.media_type)} (${a.count})</option>`;o.value=S.filters.type,o.onchange=()=>{S.filters.type=o.value,S.offset=0,reloadGrid(),buildBrowseFilterBar(e,t)},e.appendChild(o)}if(e.appendChild(buildColorPicker(()=>buildBrowseFilterBar(e,t))),t){t.innerHTML="";const o=[];if(S.filters.collection&&o.push({label:`Collection: ${S.filters.collection}`,clear:()=>{S.filters.collection=""}}),S.filters.country&&o.push({label:S.filters.country,clear:()=>{S.filters.country="",S.filters.state="",S.filters.city=""}}),S.filters.aspect&&o.push({label:S.filters.aspect.charAt(0).toUpperCase()+S.filters.aspect.slice(1),clear:()=>{S.filters.aspect=""}}),S.filters.type&&o.push({label:S.filters.type.charAt(0).toUpperCase()+S.filters.type.slice(1)+"s",clear:()=>{S.filters.type=""}}),S.filters.color&&o.push({label:"Color filter",clear:()=>{S.filters.color=""}}),S.filters.year){const s=S.filters.month?`${MONTH_NAMES_FULL[parseInt(S.filters.month)]} ${S.filters.year}`:`Year: ${S.filters.year}`;o.push({label:s,clear:()=>{S.filters.year="",S.filters.month=""}})}else S.filters.month&&o.push({label:MONTH_NAMES_FULL[parseInt(S.filters.month)],clear:()=>{S.filters.month=""}});if(S.filters.camera&&o.push({label:S.filters.camera,clear:()=>{S.filters.camera=""}}
25),S.filters.tag){const s=S.filters.tag.split(",").filter(Boolean);o.push({label:s.length>1?`${s.length} tags`:`Tag: ${S.filters.tag}`,clear:()=>{S.filters.tag=""}})}S.filters.min_rating>0&&o.push({label:`${S.filters.min_rating}+ Stars`,clear:()=>{S.filters.min_rating=0}}),S.filters.ids&&o.push({label:"Related",clear:()=>{S.filters.ids=""}});for(const s of o){const a=document.createElement("span");a.className="filter-chip active",a.innerHTML=`${esc(s.label)} <span class="remove">\xD7</span>`,a.onclick=()=>{s.clear(),S.offset=0,reloadGrid(),buildBrowseFilterBar(e,t)},t.appendChild(a)}if(o.length>1){const s=document.createElement("span");s.className="filter-chip",s.textContent="Clear all",s.onclick=()=>{S.filters=defaultFilters(),S.offset=0,reloadGrid(),buildBrowseFilterBar(e,t)},t.appendChild(s)}}}function buildFilterBar(){const e=document.getElementById("searchFilters");if(!e)return;if(e.innerHTML="",S.collections&&S.collections.length>1){const o=document.createElement("select");o.className="filter-select";const s=[...S.collections].sort((a,r)=>a.name.localeCompare(r.name));o.innerHTML='<option value="">Collection</option>';for(const a of s)o.innerHTML+=`<option value="${esc(a.slug)}">${esc(a.name)} (${a.item_count})</option>`;o.value=S.filters.collection,o.onchange=()=>{S.filters.collection=o.value,S.offset=0,reloadGrid(),buildFilterBar()},e.appendChild(o)}if(S.facets.countries&&S.facets.countries.length>0){const o=document.createElement("select");o.className="filter-select",o.innerHTML='<option value="">Places</option>';const s=[...S.facets.countries].sort((a,r)=>a.country.localeCompare(r.country));for(const a of s)o.innerHTML+=`<option value="${esc(a.country)}">${esc(a.country)} (${a.count})</option>`;o.value=S.filters.country,o.onchange=()=>{S.filters.country=o.value,S.filters.state="",S.filters.city="",S.offset=0,reloadGrid(),buildFilterBar()},e.appendChild(o)}const t=document.createElement("select");if(t.className="filter-select",t.innerHTML='<option value="">Aspect</option><option value="landscape">Landscape</option><option value="portrait">Portrait</option><option value="square">Square</option><option value="panorama">Panorama</option>',t.value=S.filters.aspect,t.onchange=()=>{S.filters.aspect=t.value,S.offset=0,reloadGrid(),buildFilterBar()},e.appendChild(t),S.facets.types&&S.facets.types.some(o=>o.media_type!=="image")){const o=document.createElement("select");o.className="filter-select",o.innerHTML='<option value="">Type</option>';const s=[...S.facets.types].sort((a,r)=>a.media_type.localeCompare(r.media_type));for(const a of s)o.innerHTML+=`<option value="${esc(a.media_type)}">${esc(a.media_type)} (${a.count})</option>`;o.value=S.filters.type,o.onchange=()=>{S.filters.type=o.value,S.offset=0,reloadGrid(),buildFilterBar()},e.appendChild(o)}e.appendChild(buildColorPicker(()=>buildFilterBar()));const i=document.getElementById("searchChips");if(i){i.innerHTML="";const o=[];if(S.filters.q&&o.push({label:`Search: ${S.filters.q}`,clear:()=>{S.filters.q="";const s=document.getElementById("searchPageInput");s&&(s.value="")}}),S.filters.collection&&o.push({label:`Collection: ${S.filters.collection}`,clear:()=>{S.filters.collection=""}}),S.filters.country&&o.push({label:S.filters.country,clear:()=>{S.filters.country="",S.filters.state="",S.filters.city=""}}),S.filters.state&&o.push({label:S.filters.state,clear:()=>{S.filters.state="",S.filters.city=""}}),S.filters.city&&o.push({label:S.filters.city,clear:()=>{S.filters.city=""}}),S.filters.min_rating>0&&o.push({label:`${S.filters.min_rating}+ Stars`,clear:()=>{S.filters.min_rating=0}}),S.filters.year){const s=S.filters.month?`${MONTH_NAMES_FULL[parseInt(S.filters.month)]} ${S.filters.year}`:`Year: ${S.filters.year}`;o.push({label:s,clear:()=>{S.filters.year="",S.filters.month=""}})}else if(S.filters.month&&S.filters.month.includes(",")){const a={"3,4,5":"Spring","6,7,8":"Summer","9,10,11":"Autumn","12,1,2":"Winter"}[S.filters.month]||`Months: ${S.filters.month}`;o.push({label:a,clear:()=>{S.filters.month=""}})}else S.filters.month&&o.push({label:MONTH_NAMES_FULL[parseInt(S.filters.month)],clear:()=>{S.filters.month=""}});if(S.filters.type&&o.push({label:S.filters.type,clear:()=>{S.filters.type=""}}),S.filters.camera&&o.push({label:S.filters.camera,clear:()=>{S.filters.camera=""}}),S.filters.aspect&&o.push({label:S.filters.aspect.charAt(0).toUpperCase()+S.filters.aspect.slice(1),clear:()=>{S.filters.aspect=""}}
25),S.filters.tag){const s=S.filters.tag.split(",").filter(Boolean).length;o.push({label:s>1?`${s} tags`:`Tag: ${S.filters.tag}`,clear:()=>{S.filters.tag=""}})}S.filters.color&&o.push({label:"Color filter",clear:()=>{S.filters.color=""}});for(const s of o){const a=document.createElement("span");a.className="filter-chip active",a.innerHTML=`${esc(s.label)} <span class="remove">\xD7</span>`,a.onclick=()=>{s.clear(),S.offset=0,reloadGrid(),buildFilterBar()},i.appendChild(a)}if(o.length>1){const s=document.createElement("span");s.className="filter-chip",s.textContent="Clear all",s.onclick=()=>clearAllFilters(),i.appendChild(s)}}}function clearAllFilters(){S.filters=defaultFilters(),S.offset=0;const e=document.querySelector(".search-bar input");e&&(e.value="");const t=document.getElementById("searchPageInput");t&&(t.value=""),reloadGrid(),buildFilterBar()}async function updateLocationDrilldown(){const e=document.getElementById("filterState"),t=document.getElementById("filterCity");if(!(!e||!t)){if(!S.filters.country){e.style.display="none",t.style.display="none";return}e.style.display="",e.innerHTML='<option value="">State/Region</option>';try{const n=await api(`/api/locations?country=${encodeURIComponent(S.filters.country)}`);for(const i of n.items)e.innerHTML+=`<option value="${esc(i.state)}">${esc(i.state)} (${i.count})</option>`;e.value=S.filters.state,n.items.length===0&&(e.style.display="none")}catch{e.style.display="none"}if(!S.filters.state){t.style.display="none";return}t.style.display="",t.innerHTML='<option value="">City</option>';try{const n=await api(`/api/locations?country=${encodeURIComponent(S.filters.country)}&state=${encodeURIComponent(S.filters.state)}`);for(const i of n.items)t.innerHTML+=`<option value="${esc(i.city)}">${esc(i.city)} (${i.count})</option>`;t.value=S.filters.city,n.items.length===0&&(t.style.display="none")}catch{t.style.display="none"}}}function _applyClientFilters(e){const t=S.filters;if(t.q){const n=t.q.toLowerCase();e=e.filter(i=>[i.caption,i.caption_human,i.caption_ai,i.description,i.tags,i.file_name,i.location_name,i.people].filter(Boolean).join(" ").toLowerCase().includes(n))}if(t.collection&&S._colorCollectionIds&&(e=e.filter(n=>S._colorCollectionIds.has(n.id))),t.country&&(e=e.filter(n=>n.country===t.country)),t.state&&(e=e.filter(n=>n.state===t.state)),t.city&&(e=e.filter(n=>n.city===t.city)),t.min_rating>0&&(e=e.filter(n=>(n.rating||0)>=t.min_rating)),t.year&&(e=e.filter(n=>n.year===parseInt(t.year))),t.month){const n=new Set(t.month.split(",").map(i=>parseInt(i.trim())));e=e.filter(i=>n.has(i.month))}if(t.type==="gigapixel"?e=e.filter(n=>n.deep_zoom&&n.media_type!=="image-360"):t.type&&(e=e.filter(n=>n.media_type===t.type)),t.camera&&(e=e.filter(n=>n.camera_model===t.camera)),t.aspect){const n=i=>i.aspect_ratio||1;t.aspect==="landscape"?e=e.filter(i=>n(i)>1.2):t.aspect==="portrait"?e=e.filter(i=>n(i)<.8):t.aspect==="square"?e=e.filter(i=>n(i)>=.8&&n(i)<=1.2):t.aspect==="panorama"&&(e=e.filter(i=>n(i)>2))}if(t.tag){const n=t.tag.split(",").map(i=>i.trim()).filter(Boolean);e=e.filter(i=>{try{const o=JSON.parse(i.tags||"[]");return n.every(s=>o.includes(s))}catch{return!1}})}return e}function _sortItems(e,t){return t=t||S.sort||"date",t==="date"?e.sort((n,i)=>(i.date_taken||"").localeCompare(n.date_taken||"")):t==="oldest"?e.sort((n,i)=>(n.date_taken||"").localeCompare(i.date_taken||"")):t==="likes"?e.sort((n,i)=>(i.likes||0)-(n.likes||0)):t==="rating"?e.sort((n,i)=>(i.rating||0)-(n.rating||0)):t==="aesthetic"?e.sort((n,i)=>(i.aesthetic_score||0)-(n.aesthetic_score||0)):t==="name"?e.sort((n,i)=>(n.file_name||"").localeCompare(i.file_name||"")):t==="random"&&e.sort(()=>Math.random()-.5),e}async function fetchFilteredItems(e={}){const t=e.sort||S.sort||"date",n=e.limit||S.limit||60,i=e.offset??S.offset??0,o=e.signal||null;if(S.unlistedAlbum&&S.filters.collection===S.unlistedAlbum.slug){const l=await api(`/api/collections/${S.unlistedAlbum.slug}?limit=${n}&offset=${i}&sort=${encodeURIComponent(t)}`,o?{signal:o}:void 0);return{items:l.items||[],total:l.total||0}}if(S.filters.color){if(S.filters.collection)try{const m=await api(`/api/collections/${S.filters.collection}`,o?{signal:o}:void 0);S._colorCollectionIds=new Set((m.items||[]).map(h=>h.id))}catch{S._colorCollectionIds=null}else S._colorCollectionIds=null;const l=await api(`/api/color-search?color=${encodeURIComponent(S.filters.color)}&threshold=120&limit=2000`,o?{signal:o}:void 0);let c=_applyClientFilters(l.items||[]);return c=_sortItems(c,t),{items:c,total:c.length}}const s=buildFilterParams({sort:t,limit:String(n),offset:String(i)});t==="random"&&s.set("seed",String(S.seed)),s.has("c")&&(s.set("collection",s.get("c")),s.delete("c"));const a=searchMode()==="feeling"?"/api/search/semantic":"/api/search",r=await api(`${a}?${s}`,o?{signal:o}:void 0);return{items:r.items||[],total:r.total||0}}let _loadAbort=null;async function loadItems(e){if(!e&&_loadAbort&&(_loadAbort.abort(),_loadAbort=null),S.loading&&e)return;S.loading=!0;const t=new AbortController;_loadAbort=t;const n=document.getElementById("grid"),i=!e&&n.children.length===0;document.getElementById("spinner").style.display=i?"block":"none",e||(_gridFocus=-1,S.seed=1+Math.floor(Math.random()*2147483646));try{const s=await fetchFilteredItems({offset:e?S.offset:0,signal:t.signal});e?S.items=S.items.concat(s.items):S.items=s.items,S.total=s.total,rebuildItemsMap(e),renderGrid(e),updateStatus()}catch(s){if(s.name==="AbortError")return;console.error("Load failed:",s),_retryFn=()=>loadItems(e),showToast("Failed to load items","error")}if(t.signal.aborted)return;if(document.getElementById("spinner").style.display="none",S.loading=!1,e||syncFiltersToUrl(),e&&S.items.length<S.total&&_vgrid.rows.length>0){const s=_vgrid.rows[_vgrid.rows.length-1],a=_vgrid.gridTop+s.top+s.height;window.scrollY+window.innerHeight>=a-window.innerHeight&&setTimeout(()=>{!S.loading&&S.items.length<S.total&&(S.offset=S.items.length,loadItems(!0))},100)}const o=document.getElementById("potdBanner");if(o&&o.dataset.loaded){const s=S.view!=="grid"&&S.view!=="collection",a=S.filters,r=a.q||a.year||a.month||a.country||a.state||a.city||a.camera||a.tag||a.type||a.aspect||a.color||a.min_rating>0;o.style.display=s||r?"none":""}}function showGridSkeleton(){const e=document.getElementById("grid"),t=e.clientWidth-16,n=window.innerWidth<768?180:240,i=4;let o="";for(let s=0;s<4;s++){o+='<div class="skeleton-row">';const a=3+Math.floor(Math.random()*3),r=Math.floor((t-(a-1)*i)/a);for(let l=0;l<a;l++)o+=`<div class="skeleton-item" style="width:${r}px;height:${n}px"></div>`;o+="</div>"}e.innerHTML=o}const _vgrid={rows:[],totalHeight:0,renderedRange:[-1,-1],gap:4,gridTop:0,gridTier:"grid"},GRID_SIZES=[160,200,240,320,400],GRID_SIZE_LABELS=["XS","S","M","L","XL"];let _gridSizeIdx=parseInt(localStorage.getItem("grid_size_idx")||"2");function cycleGridSize(e){_gridSizeIdx=Math.max(0,Math.min(GRID_SIZES.length-1,_gridSizeIdx+e)),localStorage.setItem("grid_size_idx",_gridSizeIdx),computeGridRows();const t=document.getElementById("grid");t.style.height=_vgrid.totalHeight+"px",_vgrid.renderedRange=[-1,-1],renderVisibleRows(),showToast(`Grid: ${GRID_SIZE_LABELS[_gridSizeIdx]}`)}function computeGridRows(){_vgrid.rows=[];const e=document.getElementById("grid");if(!e||S.items.length===0){_vgrid.totalHeight=0;return}const t=e.clientWidth-16,n=GRID_SIZES[_gridSizeIdx],i=window.innerWidth<768?Math.min(n,180):n,o=_vgrid.gap;let s=[],a=0,r=0;for(let l=0;l<S.items.length;l++){const c=S.items[l],m=c.aspect_ratio||(c.width&&c.height?c.width/c.height:1.5);if(s.push({item:c,ar:m,index:l}),a+=m,a*i+(s.length-1)*o>=t||l===S.items.length-1){const g=t-(s.length-1)*o,v=Math.min(i,Math.floor(g/a));_vgrid.rows.push({items:s,height:v,top:r}),r+=v+o,s=[],a=0}}if(_vgrid.totalHeight=r,S.items.length>0&&S.items.length<S.total&&_vgrid.rows.length>0){const l=r/_vgrid.rows.length,c=S.items.length/_vgrid.rows.length,h=(S.total-S.items.length)/c;_vgrid.totalHeight+=h*l}}function renderGrid(e){const t=document.getElementById("grid"),n=document.getElementById("emptyState");if(S.items.length===0){t.innerHTML="",t.style.height="";const o=Object.entries(S.filters).some(([a,r])=>a!=="collection"&&r&&r!==""&&r!==0);S.view==="search"&&!o?(n.querySelector("h2").textContent="Search your photos",n.querySelector("p").textContent="Use the search bar or filters above to find photos."):(n.querySelector("h2").textContent=o?"No matching photos":"No photos foun
25d",n.querySelector("p").textContent=o?"Try adjusting your filters or search terms.":"This collection is empty.");let s=n.querySelector(".clear-btn");s||(s=document.createElement("button"),s.className="clear-btn",s.textContent="Clear all filters",s.onclick=()=>clearAllFilters(),n.appendChild(s)),s.style.display=o?"":"none",n.style.display="flex",_vgrid.rows=[],_vgrid.totalHeight=0;return}n.style.display="none",computeGridRows(),t.style.height=_vgrid.totalHeight+"px",_vgrid.gridTop=t.getBoundingClientRect().top+window.scrollY;const i=window.innerWidth<768?"thumb":"grid";_vgrid.gridTier=adaptiveTier(i),_vgrid.renderedRange=[-1,-1],renderVisibleRows()}function renderVisibleRows(){const e=document.getElementById("grid");if(!e||_vgrid.rows.length===0)return;const t=window.scrollY,n=_vgrid.gridTop,i=window.innerHeight,o="ontouchstart"in window?i*3:i,s=t-n-o,a=t-n+i+o;let r=0,l=_vgrid.rows.length-1;for(let u=0,f=_vgrid.rows.length-1;u<=f;){const y=u+f>>1;_vgrid.rows[y].top+_vgrid.rows[y].height<s?(r=y+1,u=y+1):f=y-1}for(let u=r,f=_vgrid.rows.length-1;u<=f;){const y=u+f>>1;_vgrid.rows[y].top<=a?(l=y,u=y+1):(l=y-1,f=y-1)}if(r=Math.max(0,r),l=Math.min(_vgrid.rows.length-1,l),r>l||l<0){if(e.children.length!==1||e.firstChild?.style?.height!==_vgrid.totalHeight+"px"){const u=document.createElement("div");u.style.height=_vgrid.totalHeight+"px",e.replaceChildren(u),_vgrid.renderedRange=[-1,-1]}return}if(_vgrid.renderedRange[0]===r&&_vgrid.renderedRange[1]===l)return;const[c,m]=_vgrid.renderedRange;if(_vgrid.renderedRange=[r,l],c<0||r>m||l<c){const u=document.createDocumentFragment();if(r>0){const p=document.createElement("div");p.className="vgrid-spacer-top",p.style.height=_vgrid.rows[r].top+"px",u.appendChild(p)}for(let p=r;p<=l;p++)u.appendChild(_buildRowDiv(p));const f=_vgrid.rows[l],y=f.top+f.height+_vgrid.gap;if(y<_vgrid.totalHeight){const p=document.createElement("div");p.className="vgrid-spacer-bottom",p.style.height=_vgrid.totalHeight-y+"px",u.appendChild(p)}e.replaceChildren(u);return}for(let u=c;u<r;u++){const f=e.querySelector(`[data-row="${u}"]`);f&&f.remove()}for(let u=m;u>l;u--){const f=e.querySelector(`[data-row="${u}"]`);f&&f.remove()}let h=e.querySelector(".vgrid-spacer-top");if(r>0){const u=_vgrid.rows[r].top+"px";h?h.style.height=u:(h=document.createElement("div"),h.className="vgrid-spacer-top",h.style.height=u,e.prepend(h))}else h&&h.remove();const g=e.querySelector(".grid-row");for(let u=Math.min(r,c);u<c&&u<=l;u++){const f=_buildRowDiv(u);g?e.insertBefore(f,g):e.appendChild(f)}const v=e.querySelector(".vgrid-spacer-bottom");for(let u=Math.max(m+1,r);u<=l;u++){const f=_buildRowDiv(u);v?e.insertBefore(f,v):e.appendChild(f)}const b=_vgrid.rows[l],d=b.top+b.height+_vgrid.gap;if(d<_vgrid.totalHeight){const u=_vgrid.totalHeight-d+"px";if(v)v.style.height=u;else{const f=document.createElement("div");f.className="vgrid-spacer-bottom",f.style.height=u,e.appendChild(f)}}else v&&v.remove()}function _buildRowDiv(e){const t=_vgrid.rows[e],n=document.createElement("div");n.className="grid-row",n.dataset.row=e;for(const{item:i,ar:o,index:s}of t.items){const a=Math.floor(o*t.height);n.appendChild(createGridItem(i,a,t.height,s))}return n}function createGridItem(e,t,n,i){const o=document.createElement("div");if(o.className="grid-item",o.dataset.idx=i,o.style.width=t+"px",o.style.height=n+"px",o.tabIndex=0,o.setAttribute("role","button"),o.setAttribute("aria-label",e.caption||e.file_name||"Photo"),o.addEventListener("keydown",l=>{(l.key==="Enter"||l.key===" ")&&(l.preventDefault(),openDetail(i))}),e.blurhash){const l=blurhashDCColor(e.blurhash);l&&(o.style.backgroundColor=l)}const s=document.createElement("img");s.className="loading",s.alt=e.caption||e.file_name,s.loading="lazy",s.decoding="async";const a=()=>{if(e.blurhash){const l=blurhashDCColor(e.blurhash);l&&(o.style.backgroundColor=l)}else o.style.background="var(--surface2)";if(s.style.display="none",e.media_type==="video"){const l=document.createElement("div");l.className="grid-video-play",l.textContent="\u25B6",o.appendChild(l)}};if(e.media_type==="video")s.src=`/vid/${e.content_hash}/poster.webp`,s.onerror=a;else{const l=e.content_hash;s.src=imgUrl(l,_vgrid.gridTier),s.srcset=`${imgUrl(l,"thumb")} 400w, ${imgUrl(l,"grid")} 800w`,s.sizes=`${t}px`,s.onerror=a}if(s.onload=()=>{s.classList.remove("loading"),s.classList.add("loaded"),o.style.backgroundColor=""},o.appendChild(s),e.rating>0){const l=document.createElement("div");l.className="rating-badge",l.textContent="\u2605".repeat(e.rating),o.appendChild(l)}const r=document.createElement("button");if(r.className="like-btn grid-like"+(getLikedIds().has(e.id)?" liked":""),r.dataset.likeId=e.id,r.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="1" y="12" width="4" height="10" rx="1"/>
25<path d="M7 22h12a2 2 0 002-1.7l1.4-9A2 2 0 0020.4 9H14V5a3 3 0 00-3-3L7 11v11z" stroke-linejoin="round"/><path d="M14.5 15.5h3M14.5 18h2.5" stroke-linecap="round" opacity="0.4"/></svg>',r.onclick=l=>{l.stopPropagation(),likeItem(e.id,r)},o.appendChild(r),e.media_type==="3d"){const l=document.createElement("div");l.className="video-badge",l.textContent=(e.ext&&e.ext.protected?"\u{1F512} ":"")+"3D",o.appendChild(l)}else if(e.media_type==="image-360"){const l=document.createElement("div");l.className="video-badge",l.textContent="360\xB0",o.appendChild(l)}else if(e.deep_zoom){const l=document.createElement("div");l.className="video-badge",l.textContent="giga",o.appendChild(l)}else if(e.media_type==="video"){const l=document.createElement("div");l.className="video-badge",l.textContent=formatDuration(e.duration_sec)||"Video",o.appendChild(l);const c=document.createElement("div");c.className="scrub-progress",c.innerHTML='<div class="scrub-progress-bar" style="width:0"></div>',c.style.display="none",o.appendChild(c);const m=`/vid/${e.content_hash}/scrub.jpg`;let h=!1,g=null,v=0,b=0;o.addEventListener("mouseenter",()=>{h||(g=new Image,g.onload=()=>{b=g.naturalWidth>0?Math.round(g.naturalHeight/Math.max(1,Math.round(g.naturalHeight/(g.naturalWidth*(n/t))))):0,v=b>0?Math.round(g.naturalHeight/b):0,h=!0},g.src=m),c.style.display=""}),o.addEventListener("mouseleave",()=>{s.style.objectPosition="",s.src=`/vid/${e.content_hash}/poster.webp`,c.style.display="none",c.querySelector(".scrub-progress-bar").style.width="0"}),o.addEventListener("mousemove",d=>{if(!h||v<2)return;const u=o.getBoundingClientRect(),f=(d.clientX-u.left)/u.width,y=Math.min(v-1,Math.floor(f*v));s.style.objectFit="cover",s.src=m,s.style.objectPosition=`0 ${-(y*n)}px`,c.querySelector(".scrub-progress-bar").style.width=`${f*100}%`})}if(e.caption||e.location_name||e.date_taken){const l=document.createElement("div");if(l.className="caption-overlay",e.caption){const m=document.createElement("div");m.textContent=e.caption,l.appendChild(m)}const c=[];if(e.date_taken){const m=e.date_taken.replace(/^(\d{4}):(\d{2}):(\d{2}).*/,"$1-$2-$3");c.push(m)}if(e.location_name&&c.push(e.location_name),c.length){const m=document.createElement("div");m.style.cssText="font-size:10px;opacity:0.7;margin-top:2px",m.textContent=c.join(" \xB7 "),l.appendChild(m)}o.appendChild(l)}return o.onmousedown=()=>{_savedScrollY=window.scrollY},o.onclick=()=>openDetail(i),o}let _gridFocus=-1;function setGridFocus(e){const t=document.querySelector(".grid-item.focused");if(t&&t.classList.remove("focused"),_gridFocus=e,e<0)return;const n=document.querySelector(`.grid-item[data-idx="${e}"]`);if(n){n.classList.add("focused"),n.scrollIntoView({block:"nearest",behavior:"smooth"});try{n.focus({preventScroll:!0})}catch{}}}function gridFocusMove(e){if(S.items.length===0)return;if(_gridFocus<0){setGridFocus(0);return}const t=[],n=[];for(let o=0;o<_vgrid.rows.length;o++){const s=_vgrid.rows[o];if(!s.header)for(const a of s.items)t.push(a.index),n.push(o)}const i=t.indexOf(_gridFocus);if(i<0){setGridFocus(t[0]||0);return}if(e==="left")i>0&&setGridFocus(t[i-1]);else if(e==="right")i<t.length-1&&setGridFocus(t[i+1]);else if(e==="up"||e==="down"){const o=n[i];let s=0;for(let m=i-1;m>=0&&n[m]===o;m--)s++;const a=e==="up"?-1:1;let r=o;do r+=a;while(r>=0&&r<_vgrid.rows.length&&_vgrid.rows[r]?.header);if(r<0||r>=_vgrid.rows.length)return;const l=_vgrid.rows[r];if(!l||l.header)return;const c=Math.min(s,l.items.length-1);setGridFocus(l.items[c].index)}}async function loadPhotoOfTheDay(){const e=document.getElementById("potdBanner");if(e){e.style.display="none";try{const t=await cachedApi("/api/photo-of-the-day");if(!t||!t.item)return;const n=t.item,i=mediaSrc(n,"view"),o=[];n.date_taken&&o.push(`<span>${esc(formatDate(n.date_taken))}</span>`);const s=fmtLocation(n);s&&o.push(`<span>${esc(s)}</span>`),n.camera_model&&o.push(`<span>${esc(n.camera_model)}</span>`),e.innerHTML=`<div class="potd-inner" onclick="openItemById(${n.id})"> 26 <img class="potd-img" src="${i}" alt="${esc(n.caption||"")}" onerror="this.closest('.potd-banner').style.display='none'"> 27 <div class="potd-info"> 28 <div class="potd-label">Photo of the Day</div> 29 ${(()=>{const{human:a,ai:r}=splitCaption(n);let l="";return a&&(l+=`<div class="potd-caption">${esc(a)}</div>`),r&&(l+=`<div class="potd-caption" style="font-size:13px;color:var(--text-dim)">${aiStar()} ${esc(r)}</div>`),l})()} 30 ${o.length?`<div class="potd-meta">${o.join("")}</div>`:""} 31 </div> 32 </div>`,e.style.display="block",e.dataset.loaded="1"}catch{}}}async function openRandomPhoto(){try{const e=await api("/api/random?limit=1");if(e.items&&e.items.length>0){const t=e.items[0];S.items.unshift(t),rebuildItemsMap(),renderGrid(),openDetail(0),showToast("Random photo")}}catch{}}let _savedScrollY=0,_detailIdleTimer=null;function _startDetailIdleTimer(){const e=document.getElementById("detail");e&&(clearTimeout(_detailIdleTimer),_detailIdleTimer=setTimeout(()=>{S.detailIndex>=0&&!e.classList.contains("info-open")&&e.classList.add("idle")},2e3))}function _resetDetailIdle(){const e=document.getElementById("detail");e&&(e.classList.remove("idle"),_startDetailIdleTimer())}function _stopDetailIdle(){clearTimeout(_detailIdleTimer),_detailIdleTimer=null;const e=document.getElementById("detail");e&&e.classList.remove("idle")}let _detailReturnUrl=null;async function _lazyLoadContext(e,t){try{const n=new URLSearchParams;e.collection&&n.set("collection",e.collection),e.q&&n.set("q",e.q),e.country&&n.set("country",e.country),e.state&&n.set("state",e.state),e.city&&n.set("city",e.city),e.year&&n.set("year",e.year),e.month&&n.set("month",e.month),e.type&&n.set("type",e.type),e.camera&&n.set("camera",e.camera),e.as
32pect&&n.set("aspect",e.aspect),e.tag&&n.set("tag",e.tag),e.ids&&n.set("ids",e.ids),e.min_rating>0&&n.set("min_rating",e.min_rating),n.set("sort","date"),n.set("limit","500"),n.set("offset","0");const i=searchMode()==="feeling"?"/api/search/semantic":"/api/search",o=await api(`${i}?${n}`);if(!o.items||o.items.length===0)return;o.items.findIndex(r=>r.id===t)<0&&o.items.unshift(S.items[0]),S.items=o.items,S.total=o.items.length,rebuildItemsMap(),S.detailIndex=Math.max(0,o.items.findIndex(r=>r.id===t));const a=document.getElementById("detailCounter");a&&(a.textContent=`${S.detailIndex+1} / ${S.total}`)}catch{}}function openDetail(e){if(e<0||e>=S.items.length)return;S.detailIndex<0&&(_savedScrollY=window.scrollY),S.detailIndex=e;const t=S.items[e],n=t.permalink_id||t.id,i=location.pathname.startsWith("/p/"),o=i?location.search:_buildDetailContextQs(),s=`/p/${n}${o}`;i?history.replaceState({photo:n},"",s):(_detailReturnUrl=location.pathname+location.search,history.pushState({photo:n},"",s));const a=document.getElementById("detail"),r=document.getElementById("detailContent"),l=document.getElementById("termsLinkOverlay");if(l&&(l.style.display="none"),window._osdViewer){window._dzTileXHRs&&(window._dzTileXHRs.forEach(p=>{try{p.abort()}catch{}}),window._dzTileXHRs.clear()),window._dzOrigXHROpen&&(XMLHttpRequest.prototype.open=window._dzOrigXHROpen,window._dzOrigXHROpen=null);try{window._osdViewer.close()}catch{}try{window._osdViewer.destroy()}catch{}window._osdViewer=null}if(r.innerHTML="",t.media_type==="video"){let w=function(){return window.screen.width*(window.devicePixelRatio||1)>1920?"high":"standard"};const p=document.createElement("video");p.className="detail-video",p.controls=!0,p.autoplay=!0,p.playsInline=!0,p.tabIndex=-1,p.poster=`/vid/${t.content_hash}/poster.webp`;let k=w();const _=document.createElement("source");_.src=vidUrl(t.content_hash,"high"),_.type="video/mp4";const x=document.createElement("source");x.src=vidUrl(t.content_hash,"standard"),x.type="video/mp4";const $=document.createElement("source");$.src=vidUrl(t.content_hash,"preview"),$.type="video/mp4",k==="high"?(p.appendChild(_),p.appendChild(x),p.appendChild($)):(p.appendChild(x),p.appendChild($));const C=E=>{if(E===k)return;const B=p.currentTime,T=!p.paused;for(;p.firstChild&&p.firstChild.nodeName==="SOURCE";)p.removeChild(p.firstChild);E==="high"?(p.prepend($),p.prepend(x),p.prepend(_)):(p.prepend($),p.prepend(x)),k=E,p.load(),p.currentTime=B,T&&p.play()};p.addEventListener("fullscreenchange",()=>{C(document.fullscreenElement?"high":w())}),p.addEventListener("webkitfullscreenchange",()=>{C(p.webkitDisplayingFullscreen?"high":w())}),p.addEventListener("keydown",E=>{E.key==="Escape"&&(E.preventDefault(),closeDetail())}),r.appendChild(p)}else if(t.media_type==="image-360"&&t.deep_zoom){const p=document.createElement("div");p.id="detail-pano",p.style.cssText="width:100%;aspect-ratio:16/9;border-radius:8px;overflow:hidden",r.appendChild(p);const w=()=>{const k=p.getBoundingClientRect(),_=k.width>=k.height?90:Math.round(90*k.width/k.height);fetch(`/pano/${t.content_hash}/config.json`).then(x=>x.json()).then(x=>{if(S.detailIndex===e){if(x.autoLoad=!0,x.showControls=!1,x.mouseZoom=!0,x.hfov=_,x.minHfov=30,x.maxHfov=120,x.friction=.15,x.compass=!1,x.multiRes.basePath=`/pano/${t.content_hash}`,window._panoViewer)try{window._panoViewer.destroy()}catch{}window._panoViewer=pannellum.viewer("detail-pano",x)}}).catch(()=>{p.remove();const x=document.createElement("img");x.className="detail-img",x.src=imgUrl(t.content_hash,"view"),r.insertBefore(x,r.firstChild)})};typeof pannellum>"u"?Promise.all([loadResource("https://cdn.jsdelivr.net/npm/[email protected]/build/pannellum.css","css"),loadResource("https://cdn.jsdelivr.net/npm/[email protected]/build/pannellum.js","js")]).then(w):w()}else if(t.deep_zoom){const p=document.createElement("div");p.id="detail-deepzoom",p.style.cssText="width:100vw;height:100vh;position:absolute;inset:0;overflow:hidden;background:#000;z-index:1",r.appendChild(p);const w="ontouchstart"in window||navigator.maxTouchPoints>0;document.getElementById("detail").classList.add("dz-active");const k=()=>
32{if(window._osdViewer)try{window._osdViewer.destroy()}catch{}const _=new Set,x=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(...I){return typeof I[1]=="string"&&I[1].includes("/dz/")&&(_.add(this),this.addEventListener("loadend",()=>_.delete(this))),x.apply(this,I)},window._dzTileXHRs=_,window._dzOrigXHROpen=x,window._osdViewer=OpenSeadragon({element:p,tileSources:`/dz/${t.content_hash}/image.dzi`,gestureSettingsTouch:{pinchToZoom:!0,dragToPan:!0,flickEnabled:!0,flickMomentum:.4},gestureSettingsMouse:{scrollToZoom:!0,clickToZoom:!0,dblClickToZoom:!0,flickEnabled:!0,flickMomentum:.4},springStiffness:15,animationTime:1,showNavigator:!w,navigatorPosition:"BOTTOM_RIGHT",navigatorSizeRatio:.12,navigatorAutoFade:!0,homeFillsViewer:!1,defaultZoomLevel:0,minZoomLevel:0,visibilityRatio:1,constrainDuringPan:!0,crossOriginPolicy:"Anonymous",immediateRender:!1,blendTime:.1,loadTilesWithAjax:!0,imageLoaderLimit:4,showZoomControl:!1,showHomeControl:!1,showFullPageControl:!1,showRotationControl:!1,tabIndex:-1}),window._osdViewer.innerTracker.keyDownHandler=null,window._osdViewer.innerTracker.keyUpHandler=null,window._osdViewer.innerTracker.keyHandler=null;const $=document.createElement("div");$.className="dz-watermark-overlay",$.textContent="ECHENG.COM",document.getElementById("detail").appendChild($);const C=document.querySelector(".detail-toolbar-center"),E=document.querySelectorAll(".detail-nav");function B(){C&&(C.style.opacity="",C.style.pointerEvents=""),E.forEach(I=>{I.style.opacity="",I.style.pointerEvents=""}),clearTimeout(window._dzHideTimer),window._dzHideTimer=setTimeout(T,2500)}function T(){C&&(C.style.opacity="0",C.style.pointerEvents="none"),E.forEach(I=>{I.style.opacity="0",I.style.pointerEvents="none"})}let P=!1;const D=T,R=B;T=()=>{P=!0,D()},B=()=>{P=!1,R()};let M=null;p.addEventListener("touchstart",I=>{I.touches.length===1?M={x:I.touches[0].clientX,y:I.touches[0].clientY,t:Date.now()}:M=null},{passive:!0}),p.addEventListener("touchend",()=>{M&&Date.now()-M.t<300&&(P?B():T()),M=null},{passive:!0}),p.addEventListener("touchmove",I=>{if(M&&I.touches.length===1){const A=I.touches[0].clientX-M.x,H=I.touches[0].clientY-M.y;A*A+H*H>225&&(M=null)}T()},{passive:!0}),p.addEventListener("wheel",T,{passive:!0}),p.addEventListener("click",()=>{P?B():T()}),window._dzHideTimer=setTimeout(T,2500)};typeof OpenSeadragon>"u"?ensureOpenSeadragon().then(k):k()}else if(t.media_type==="3d"&&t.ext&&t.ext.preview&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches){const p=document.createElement("video");p.className="detail-img",p.autoplay=!0,p.muted=!0,p.loop=!0,p.playsInline=!0,p.preload="metadata",p.tabIndex=-1,p.poster=imgUrl(t.content_hash,"view"),p.src=`/img/${t.content_hash}/preview.mp4`,r.appendChild(p)}else{const p=document.createElement("img");p.className="detail-img",p.alt=t.caption||t.file_name;const w=imgUrl(t.content_hash,"view"),k=imgUrl(t.content_hash,"grid"),_=new Image;_.src=w;const x=()=>{const $=r.querySelector(".detail-meta-bar");$&&($.style.opacity="1")};if(_.complete&&_.naturalWidth>0)p.src=w,requestAnimationFrame(x);else{p.src=k,p.onload=x;const $=new Image;$.onload=()=>{p.src=w},$.src=w}r.appendChild(p)}const c=document.createElement("div");c.className="detail-meta-bar",c.style.opacity="0";let m="";const{human:h,ai:g}=splitCaption(t);h?m=esc(h):g&&(m=`<span class="dm-ai">${aiStar()} ${esc(g)}</span>`);const v=[];t.date_taken&&v.push(`<span>${esc(formatDate(t.date_taken))}</span>`);const b=fmtLocation(t);b&&v.push(`<span>${esc(b)}</span>`),t.camera_model&&v.push(`<span>${esc(t.camera_model)}</span>`),m&&(c.innerHTML+=`<div class="dm-caption">${m}</div>`);const d=t.media_type==="image-360"&&t.deep_zoom&&("ontouchstart"in window||navigator.maxTouchPoints>0);if(v.length&&(c.innerHTML+=`<div class="dm-info">${v.join("")}</div>`),t.media_type==="3d"&&t.ext&&t.ext.url){const p=t.ext.bytes?`<span class="dm-3d-size">${Math.round(t.ext.bytes/1048576)} MB</span>`:"",w=t.ext.protected?'<span class="dm-3d-lock" title="Password required">\u{1F512}</span>':"",k=esc(t.ext.url);c.innerHTML+=`<div class="dm-3d"><a class="dm-3d-btn" href="${k}">${w}Open interactive 3D scene${p}</a><span class="dm-3d-note">Gaussian splat. Works on desktop, mobile, and VR headsets.<br><a class="dm-3d-vr" href="${k}vr/">VR version here</a></span></div>`}if(r.appendChild(c),(t.media_type==="video"||t.media_type==="image-360"||t.media_type==="3d")&&requestAnimationFrame(()=>{c.style.opacity="1"}),d){const p=document.createElement("button");p.id="dm-gyro-btn",p.innerHTML="\u{1F9ED} Gyro",p.addEventListener("touchend",w=>{w.preventDefault(),w.stopPropagation(),_togglePanoGyro(p)}),p.addEventListener("click",w=>{w.stopPropagation()}),Object.assign(p.style,{position:"fixed",top:"16px",left:"16px",zIndex:"220",background:"rgba(0,0,0,0.5)",border:"1px solid rgba(255,255,255,0.3)",color:"#fff",fontSize:"14px",padding:"8px 14px",borderRadius:"20px",cursor:"pointer"}),a.appendChild(p),_gyroActive=!1,setTimeout(_initPanoGyro,800)}try{const p=JSON.parse(t.colors||"[]");a.style.setProperty("--ambient",p[0]||"transparent")}catch{a.style.setProperty("--ambient","transparent")}const u=a.classList.contains("open");a.classList.add("open"),u||trapFocus(a),document.body.style.overflow="hidden",u?_startDetailIdleTimer():_resetDetailIdle(),document.getElementById("detailCounter").textContent=`${e+1} of ${S.total}`;const f=document.getElementById("detailLikeBtn"),y=document.getElementById("detailLikeCount");if(f&&y&&(f.classList.toggle("liked",getLikedIds().has(t.id)),y.textContent=t.likes||0,f.onclick=()=>likeItem(t.id,f)),updateInfoPanel(t),resetZoom(),prefetchAdjacent(e),("ontouchstart"in window||navigator.maxTouchPoints>0)&&!_detailTouchHintShown){_detailTouchHintShown=!0;const p=document.createElement("div");p.className="detail-touch-hint",p.textContent="Swipe up/down to browse, left for details",a.appendChild(p),setTimeout(()=>p.classList.add("fade-out"),2e3),setTimeout(()=>p.remove(),2600)}updatePageTitle()}let _videoDebugInterval=null;function toggleVideoDebug(){const e=document.querySelector(".video-debug");if(e){e.remove(),_videoDebugInterval&&(clearInterval(_videoDebugInterval),_videoDebugInterval=null);return}const t=document.querySelector(".detail-video");if(!t){showToast("No video in current view","error");return}const n=document.createElement("div");n.className="video-debug",t.parentElement.style.position="relative",t.parentElement.appendChild(n);
32const i={preview:"H.264 / AAC",standard:"AV1 10-bit / Opus",high:"AV1 10-bit / Opus"};let o=null;function s(){if(!document.contains(n)){clearInterval(_videoDebugInterval),_videoDebugInterval=null;return}const a=t.currentSrc||"",r=a.match(/\/(preview|standard|high)\.mp4/)?.[1]||"unknown",l=i[r]||"unknown",c=t.videoWidth,m=t.videoHeight,h=t.duration||0,g=t.currentTime||0,v=t.buffered.length>0?t.buffered.end(t.buffered.length-1):0,b=["HAVE_NOTHING","HAVE_METADATA","HAVE_CURRENT_DATA","HAVE_FUTURE_DATA","HAVE_ENOUGH_DATA"];o===null&&a&&(o=0,fetch(a,{method:"HEAD"}).then(f=>{const y=f.headers.get("content-length");y&&(o=parseInt(y,10))}).catch(()=>{}));const d=[`Tier: ${r}`,`Codec: ${l}`,`Resolution: ${c}x${m}`];if(o>0&&h>0){const f=o*8/h;d.push(`Avg bitrate: ${(f/1e6).toFixed(2)} Mbps`),d.push(`File size: ${(o/(1024*1024)).toFixed(1)} MB`)}d.push(`Screen: ${screen.width}x${screen.height} @${devicePixelRatio}x`,`Time: ${g.toFixed(1)}s / ${h.toFixed(1)}s`,`Buffered: ${v.toFixed(1)}s`,`State: ${b[t.readyState]||t.readyState}`,`Paused: ${t.paused}`);const u=t.getVideoPlaybackQuality?.();u&&d.push(`Frames: ${u.totalVideoFrames} (dropped: ${u.droppedVideoFrames})`),n.textContent=d.join(` 33`)}s(),_videoDebugInterval=setInterval(s,500)}let _gyroPermissionGranted=!1,_gyroActive=!1;function _enablePanoGyro(e){const t=window._panoViewer;if(!t)return;try{t.startOrientation()}catch{return}_gyroActive=!0,e&&e.classList.add("active");const n=document.getElementById("detail-pano");n&&!n._gyroTouchHandler&&(n._gyroTouchHandler=()=>{_gyroActive&&window._panoViewer&&setTimeout(()=>{try{window._panoViewer.startOrientation()}catch{}},100)},n.addEventListener("touchend",n._gyroTouchHandler),n.addEventListener("pointerup",n._gyroTouchHandler))}function _disablePanoGyro(e){const t=window._panoViewer;if(t)try{t.stopOrientation()}catch{}_gyroActive=!1,e&&e.classList.remove("active");const n=document.getElementById("detail-pano");n&&n._gyroTouchHandler&&(n.removeEventListener("touchend",n._gyroTouchHandler),n.removeEventListener("pointerup",n._gyroTouchHandler),n._gyroTouchHandler=null)}function _togglePanoGyro(e){if(_gyroActive){_disablePanoGyro(e);return}if(_gyroPermissionGranted){_enablePanoGyro(e);return}typeof DeviceOrientationEvent<"u"&&typeof DeviceOrientationEvent.requestPermission=="function"?DeviceOrientationEvent.requestPermission().then(t=>{t==="granted"&&(_gyroPermissionGranted=!0,_enablePanoGyro(e))}).catch(()=>{}):(_gyroPermissionGranted=!0,_enablePanoGyro(e))}function _initPanoGyro(){if(typeof DeviceOrientationEvent<"u"&&typeof DeviceOrientationEvent.requestPermission!="function"){_gyroPermissionGranted=!0;const e=document.getElementById("dm-gyro-btn");setTimeout(()=>_enablePanoGyro(e),500)}}function closeDetail(){if(releaseFocus(document.getElementById("detail")),S.detailIndex<0){document.getElementById("detail").classList.remove("open","cinema","info-open","idle");return}if(resetZoom(),deactivateLoupe(),window._panoViewer){try{window._panoViewer.destroy()}catch{}window._panoViewer=null,_gyroActive=!1}if(window._osdViewer){window._dzTileXHRs&&(window._dzTileXHRs.forEach(r=>{try{r.abort()}catch{}}),window._dzTileXHRs.clear()),window._dzOrigXHROpen&&(XMLHttpRequest.prototype.open=window._dzOrigXHROpen,window._dzOrigXHROpen=null);try{window._osdViewer.close()}catch{}try{window._osdViewer.destroy()}catch{}window._osdViewer=null}document.getElementById("detail").classList.remove("dz-active");const e=document.getElementById("detail-deepzoom");e&&(e.innerHTML="",e.remove());const t=document.querySelector("#detail .dz-watermark-overlay");t&&t.remove(),document.getElementById("detailContent").innerHTML="";const n=document.getElementById("dm-gyro-btn");n&&n.remove(),_stopDetailIdle(),_surpriseMode=!1,document.getElementById("detail").classList.remove("open","cinema","info-open","idle");const i=document.getElementById("termsLinkOverlay");i&&S.view!=="terms"&&(i.style.display=""),document.body.style.overflow="",window.scrollTo(0,_savedScrollY);const o=document.querySelector(".detail-video");o&&o.pause(),document.getElementById("infoPanel").classList.remove("open"),S.infoPanelOpen=!1;const s=S.detailIndex,a=!!S._standaloneItem;if(S._standaloneItem){const r=S._standaloneItem.id;S.items=S.items.filter(l=>l.id!==r),rebuildItemsMap(),S._standaloneItem=null}if(S.detailIndex=-1,s>=0&&!a&&requestAnimationFrame(()=>setGridFocus(s)),location.pathname.startsWith("/p/")&&_detailReturnUrl){const r=_detailReturnUrl;if(_detailReturnUrl=null,history.pushState(null,"",r),r==="/surprise"){navigate("/",!0);return}if(!(document.querySelector(".grid-item")!==null)||r.includes("/story")){navigate(r,!1);return}document.getElementById("grid").style.display=""}else if(location.pathname.startsWith("/p/")){_detailReturnUrl=null;const r=_parseDetailContext(location.search),l=_returnUrlFromContext(r);history.pushState(null,"",l),navigate(l,!1);return}updatePageTitle()}function navigateDetail(e){deactivateLoupe();const t=S.detailIndex+e;if(_surpriseMode&&t>=S.items.length-3&&_extendSurprise(),t<0||t>=S.items.length)return;const n=document.getElementById("detailContent"),i=_detailSwipeEnterFrom;_detailSwipeEnterFrom=null,i?(n.style.transition="none",n.style.transform=i==="below"?"translateY(30vh)":"translateY(-30vh)",n.style.opacity="0",openDetail(t),requestAnimationFrame(()=>requestAnimationFrame(()=>{n.style.transition="transform 0.15s ease-out, opacity 0.12s ease-out",n.style.transform="",n.style.opacity="",setTimeout(()=>{n.style.transition=""},180)}))):openDetail(t)}function updateInfoPanel(e){const t=document.getElementById("infoPanelContent");let n="";const{human:i,ai:o}=splitCaption(e);if(i||o){let r="";
33i?(r+=esc(i),o&&(r+=` 34 35<span style="color:var(--text-dim);font-size:0.9em">--- 36${aiStar()} ${esc(o)}</span>`)):r+=`<span style="color:var(--text-dim)">${aiStar()} ${esc(o)}</span>`,n+=`<div class="info-section"><div class="label">Caption</div><div class="value">${r}</div></div>`}e.date_taken&&(n+=`<div class="info-section"><div class="label">Date</div><div class="value">${esc(formatDate(e.date_taken))}</div></div>`);const s=fmtLocation(e);if(s&&(n+=`<div class="info-section"><div class="label">Location</div><div class="value">${esc(s)}</div></div>`),e.camera_model||e.lens||e.aperture||e.shutter_speed||e.iso){const r=[];e.camera_model&&r.push(esc(e.camera_model));const l=fmtLens(e);if(l&&r.push(esc(l)),e.shutter_speed||e.aperture||e.iso){let c="";if(e.shutter_speed&&e.aperture)c=`${esc(e.shutter_speed)} sec @ ${esc(e.aperture)}`,e.iso&&(c+=`, ISO ${e.iso}`);else{const m=[];e.shutter_speed&&m.push(`${esc(e.shutter_speed)} sec`),e.aperture&&m.push(esc(e.aperture)),e.iso&&m.push(`ISO ${e.iso}`),c=m.join(", ")}e.flash_fired===1&&(c+=" (flash fired)"),r.push(c)}n+=`<div class="info-section"><div class="label">Camera</div><div class="value" style="line-height:1.5">${r.join("<br>")}</div></div>`}e.file_name&&(n+=`<div class="info-section"><div class="label">File</div><div class="value" style="font-family:monospace;font-size:12px;opacity:0.7">${esc(e.file_name)}</div></div>`);try{const r=JSON.parse(e.tags||"[]");if(r.length>0){n+='<div class="info-section"><div class="label">Tags</div><div class="info-tags">';for(const l of r)n+=`<span class="info-tag" data-act="search-tag" data-val="${esc(l)}">${esc(l)}</span>`;n+="</div></div>"}}catch{}e.rating>0&&(n+=`<div class="info-section"><div class="label">Rating</div><div class="value">${"\u2605".repeat(e.rating)}${"\u2606".repeat(5-e.rating)}</div></div>`);try{const r=JSON.parse(e.people||"[]");if(r.length>0){n+='<div class="info-section"><div class="label">People</div><div class="info-tags">';for(const l of r)n+=`<span class="info-tag" data-act="search-tag" data-val="${esc(l)}">${esc(l)}</span>`;n+="</div></div>"}}catch{}try{const r=JSON.parse(e.colors||"[]");if(r.length>0){n+='<div class="info-section"><div class="label">Colors</div><div class="info-colors">';for(const l of r)n+=`<span class="info-color-swatch" style="background:${esc(l)}" title="${esc(l)}" data-act="search-color" data-val="${esc(l)}"></span>`;n+="</div></div>"}}catch{}const a=[];if(e.date_taken){const r=e.date_taken.slice(0,10);a.push(`<button class="info-nav-btn" data-act="detail-nav" data-nav-type="date" data-val="${esc(r)}">Same Day</button>`)}e.country&&a.push(`<button class="info-nav-btn" data-act="detail-nav" data-nav-type="country" data-val="${esc(e.country)}">More from ${esc(e.country)}</button>`),e.camera_model&&a.push(`<button class="info-nav-btn" data-act="detail-nav" data-nav-type="camera" data-val="${esc(e.camera_model)}">Shot with ${esc(e.camera_model)}</button>`),e.year&&a.push(`<button class="info-nav-btn" onclick="navigateFromDetail('year','${e.year}')">All of ${e.year}</button>`),e.latitude&&e.longitude&&a.push(`<button class="info-nav-btn" onclick="showOnMap(${e.latitude},${e.longitude})">Show on Map</button>`),a.length&&(n+=`<div class="info-section"><div class="label">Explore</div><div class="info-nav-btns">${a.join("")}</div></div>`),n+='<div class="info-section" id="relatedSection" style="display:none"><div class="label">Related</div><div class="info-related" id="relatedGrid"></div></div>',e.description&&(n+=`<div class="info-section"><div class="label desc-toggle" onclick="this.classList.toggle('expanded');this.nextElementSibling.style.display=this.classList.contains('expanded')?'block':'none'" style="cursor:pointer;user-select:none">${aiStar()} Description <span class="desc-arrow">▶</span></div><div class="value ai-description" style="color:var(--text-dim);display:none;padding-top:8px">${renderMd(e.description)}</div></div>`),t.innerHTML=n,loadRelated(e.id)}async function loadRelated(e){try{const t=await cachedApi(`/api/media/${e}/related`);if(!t.items||t.items.length===0)return;const n=document.getElementById("relatedSection"),i=document.getElementById("relatedGrid");if(!n||!i)return;let o="";for(const s of t.items){const a=mediaSrc(s,"thumb");o+=`<img class="related-thumb" src="${a}" alt="${esc(s.caption||"")}" title="${esc(s.caption||s.file_name)}" onclick="openRelatedItem(${s.id})" loading="lazy">`}i.innerHTML=o,n.style.display=""}catch{}}function openRelatedItem(e){const t=S.items.findIndex(n=>n.id===e);if(t>=0){openDetail(t);return}cachedApi(`/api/media/${e}`).then(n=>{if(!n)return;const i=S.items.findIndex(o=>o.id===n.id);if(i>=0){openDetail(i);return}S.items.push(n),rebuildItemsMap(),openDetail(S.items.length-1)}).catch(()=>navigate("/p/"+e))}function toggleInfoPanel(){S.infoPanelOpen=!S.infoPanelOpen,document.getElementById("infoPanel").classList.toggle("open",S.infoPanelOpen),document.getElementById("detail").classList.toggle("info-open",S.infoPanelOpen)}function showRelated(){S.infoPanelOpen||(S.infoPanelOpen=!0,document.getElementById("infoPanel").classList.add("open"),document.getElementById("detail").classList.add("info-open"));const e=document.getElementById("relatedSection");e&&e.style.display!=="none"&&e.scrollIntoView({behavior:"smooth",block:"start"})}function searchByTag(e){closeDetail(),navigate(`/browse?tag=${encodeURIComponent(e)}`)}function getLikedIds(){try{const e=document.cookie.split(";").find(t=>t.trim().startsWith("liked="));return e?new Set(JSON.parse(decodeURIComponent(e.trim().slice(6))
36)):new Set}catch{return new Set}}function saveLikedIds(e){const t=Array.from(e).slice(-500);document.cookie=`liked=${encodeURIComponent(JSON.stringify(t))};path=/;max-age=${365*86400};SameSite=Lax`}async function likeItem(e,t){const n=getLikedIds(),i=n.has(e);if(i)n.delete(e),t.classList.remove("liked");else{n.add(e),t.classList.add("liked","popping"),t.addEventListener("animationend",()=>t.classList.remove("popping"),{once:!0});const r=["#4ade80","#34d399","#6ee7b7","#a7f3d0"];for(let l=0;l<8;l++){const c=document.createElement("div");c.className="like-particle";const m=Math.PI*2*l/8+(Math.random()-.5)*.5,h=15+Math.random()*15;c.style.setProperty("--px",Math.cos(m)*h+"px"),c.style.setProperty("--py",Math.sin(m)*h+"px"),c.style.background=r[l%4],c.style.left="50%",c.style.top="50%",t.appendChild(c),setTimeout(()=>c.remove(),700)}}saveLikedIds(n);const o=t.querySelector(".like-count");if(o){const r=parseInt(o.textContent)||0;o.textContent=Math.max(0,r+(i?-1:1))}const s=S.items.find(r=>r.id===e||r.id===String(e));s&&(s.likes=Math.max(0,(s.likes||0)+(i?-1:1)));const a=document.querySelector(`[data-like-id="${e}"]`);a&&a.classList.toggle("liked",!i);try{await api("/api/like/"+e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({unlike:i})})}catch{}}function shareCurrentItem(){if(S.detailIndex<0)return;const e=S.items[S.detailIndex],t=`${location.origin}${location.pathname}${location.search}`,n=location.origin,i=e.media_type==="video"?`${n}/vid/${e.content_hash}/poster.webp`:`${n}/img/${e.content_hash}/view.webp`,o=esc((e.caption||e.file_name||"Photo")+" | ECHENG.COM"),s=document.createElement("div");s.className="share-modal";const a=()=>{releaseFocus(s),s.remove()};s.onclick=m=>{m.target===s&&a()},s.addEventListener("keydown",m=>{m.key==="Escape"&&(m.stopPropagation(),a())}),s.tabIndex=-1;const r=`<img src="${i}" alt="${o}" style="max-width:100%;height:auto">`,l=`<a href="${t}" title="${o}">${r}</a>`,c=document.createElement("div");if(c.className="share-modal-box",c.innerHTML=` 37 <div class="share-modal-header"> 38 <h3>Share</h3> 39 <button class="share-modal-close">×</button> 40 </div> 41 <div class="share-field"> 42 <label class="share-label">Direct Link</label> 43 <div style="display:flex;gap:8px"> 44 <input type="text" value="${t}" readonly class="share-input" onclick="this.select()"> 45 <button class="share-copy-btn" onclick="navigator.clipboard.writeText(this.closest('.share-field').querySelector('input').value).then(()=>{this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500)})">Copy</button> 46 </div> 47 </div> 48 <div class="share-field" style="margin-bottom:8px"> 49 <label class="share-label">Embed (Image)</label> 50 <textarea readonly rows="3" class="share-textarea" onclick="this.select()">${esc(l)}</textarea> 51 </div> 52 `,navigator.share){const m=document.createElement("button");m.className="share-system-btn",m.textContent="Share via System...",m.onclick=()=>{const h=e.caption||e.file_name||"Photo",v=[fmtLocation(e),"ECHENG.COM"].filter(Boolean).join(" | ");navigator.share({title:h,text:v,url:t}).catch(()=>{}),a()},c.appendChild(m)}c.querySelector(".share-modal-close").onclick=a,s.appendChild(c),document.body.appendChild(s),trapFocus(s)}function downloadTier(e){if(S.detailIndex<0)return;const t=S.items[S.detailIndex],n=t.media_type==="video",i=n?vidUrl(t.content_hash,e):imgUrl(t.content_hash,e),o=n?"mp4":"webp",s=(t.file_name||"download").replace(/\.[^./]+$/,""),a=document.createElement("a");a.href=i,a.download=`${s}.${o}`,a.target="_blank",document.body.appendChild(a),a.click(),document.body.removeChild(a)}function _downloadOptions(e){if(e.media_type==="video")return[["Standard (1080p)","standard"],["Preview (480p)","preview"]];const t=[["Web (2400px)","view"],["Full (3840px)","full"]];return e.has_ultra&&(S.adminToken||e.unlisted||S.unlistedAlbum)&&t.push(["Original (up to 8K)","ultra"]),t}function toggleDownloadMenu(){const e=document.getElementById("downloadMenu");if(e){e.remove();return}if(S.detailIndex<0)return;const t=S.items[S.detailIndex],n=document.createElement("div");n.id="downloadMenu",n.className="download-menu",_downloadOptions(t).forEach(([c,m])=>{const h=document.createElement("button");h.textContent=c,h.onclick=g=>{g.stopPropagation(),downloadTier(m),n.remove()},n.appendChild(h)});const i=document.getElementById("detailDownloadBtn").getBoundingClientRect();document.getElementById("detail").appendChild(n);const o=8,s=n.offsetWidth,a=n.offsetHeight;let r=i.bottom+o;r+a+o>
52window.innerHeight&&(r=i.top-a-o),r=Math.max(o,Math.min(r,window.innerHeight-a-o)),n.style.top=`${r}px`,n.style.left=`${Math.max(o,Math.min(i.left,window.innerWidth-s-o))}px`;const l=c=>{!n.contains(c.target)&&!c.target.closest("#detailDownloadBtn")&&(n.remove(),document.removeEventListener("click",l))};setTimeout(()=>document.addEventListener("click",l),0)}function showOnMap(e,t){closeDetail(),navigate("/map");let n=0;const i=()=>{if(mapInstance){mapInstance.setView([e,t],14);return}++n<50&&setTimeout(i,100)};setTimeout(i,200)}function toggleDetailFullscreen(){const e=document.getElementById("detail");document.fullscreenElement?document.exitFullscreen():e.requestFullscreen().catch(()=>{})}async function browseRelated(){if(S.detailIndex<0)return;const e=S.items[S.detailIndex];try{const t=await cachedApi(`/api/media/${e.id}/related`);if(t.items&&t.items.length>0){const n=t.items.map(i=>i.id).join(",");closeDetail(),navigate(`/browse?ids=${n}`);return}}catch{}showToast("No related items found")}function toggleSlideshow(){S.slideshow&&S.slideshow.active?stopSlideshow():startSlideshow()}function navigateFromDetail(e,t){closeDetail();const n=new URLSearchParams;if(e==="date"){const i=t.replace(/:/g,"-").split("-");n.set("year",i[0]),i[1]&&n.set("month",parseInt(i[1],10).toString())}else n.set(e,t);navigate(`/browse?${n}`)}function searchByColor(e){closeDetail(),navigate(`/browse?color=${encodeURIComponent(e)}`)}let _ssControlsTimer=0,_ssMetadataOn=localStorage.getItem("ss_metadata")==="1",_ssAnimatedOn=localStorage.getItem("ss_animated")!=="0";function startSlideshow(){const e=S.slideshow;e.active=!0,e.paused=!1,e.index=S.detailIndex>=0?S.detailIndex:0,e.front=1,S.detailIndex>=0&&closeDetail();const t=document.getElementById("slideshow");t.classList.add("open"),t.classList.remove("controls-visible"),t.classList.toggle("metadata-on",_ssMetadataOn),document.body.style.overflow="hidden",document.getElementById("ssMetadataToggle").checked=_ssMetadataOn,document.getElementById("ssAnimatedToggle").checked=_ssAnimatedOn,document.getElementById("slideshowPlayBtn").innerHTML="⏸",updateSlideshowSpeedLabel(),showSlideshowImage(e.index),scheduleSlideshowAdvance()}function stopSlideshow(){const e=S.slideshow;e.active=!1,clearTimeout(e.timer),clearTimeout(_ssControlsTimer),document.getElementById("slideshow").classList.remove("open","controls-visible","metadata-on"),document.body.style.overflow="",document.fullscreenElement&&document.exitFullscreen().catch(()=>{})}function showSlideshowImage(e){if(e<0||e>=S.items.length)return;const t=S.slideshow;t.index=e;const n=S.items[e],i=t.front===1?"slideshowImg1":"slideshowImg2",o=t.front===1?"slideshowImg2":"slideshowImg1",s=document.getElementById(i),a=document.getElementById(o);if(n.media_type==="video"){const r=e+1<S.items.length?e+1:0;if(r!==e){showSlideshowImage(r);return}}if(a.src=imgUrl(n.content_hash,"full"),a.classList.remove("fade-out","ken-burns","ken-burns-freeze"),_ssAnimatedOn){const r=(Math.random()*2-1).toFixed(1),l=(Math.random()*1.5-.75).toFixed(1);a.style.setProperty("--kb-x",r+"%"),a.style.setProperty("--kb-y",l+"%"),a.style.setProperty("--kb-duration",t.speed/1e3+1.2+"s"),requestAnimationFrame(()=>
52a.classList.add("ken-burns")),s.classList.remove("ken-burns"),s.classList.add("ken-burns-freeze")}else s.classList.remove("ken-burns","ken-burns-freeze"),a.style.removeProperty("transform");s.classList.add("fade-out"),t.front=t.front===1?2:1,document.getElementById("slideshowCounter").textContent=`${e+1} / ${S.items.length}`,updateSlideshowMetadata(n)}function updateSlideshowMetadata(e){const t=document.getElementById("ssMetaCaption"),n=document.getElementById("ssMetaMeta"),i=document.getElementById("ssMetaExif");let o="";const{human:s,ai:a}=splitCaption(e);s?o=esc(s):a&&(o=`<span style="font-size:12px;color:rgba(255,255,255,0.4)">${esc(a)}</span>`),t.innerHTML=o,t.style.display=o?"":"none";const r=[];e.date_taken&&r.push(formatDate(e.date_taken));const l=fmtLocation(e);l&&r.push(l),n.textContent=r.join(" \u2022 "),n.style.display=r.length?"":"none";const c=[];e.camera_model&&c.push(e.camera_model);const m=fmtLens(e);if(m&&c.push(m),e.shutter_speed||e.aperture||e.iso){let h="";if(e.shutter_speed&&e.aperture)h=`${e.shutter_speed} sec @ ${e.aperture}`,e.iso&&(h+=`, ISO ${e.iso}`);else{const g=[];e.shutter_speed&&g.push(e.shutter_speed+" sec"),e.aperture&&g.push(e.aperture),e.iso&&g.push("ISO "+e.iso),h=g.join(", ")}e.flash_fired===1&&(h+=" (flash fired)"),c.push(h)}i.textContent=c.join(" \u2022 "),i.style.display=c.length?"":"none"}function scheduleSlideshowAdvance(){const e=S.slideshow;clearTimeout(e.timer),e.timer=null,!(e.paused||!e.active)&&(e.timer=setTimeout(()=>{const t=(e.index+1)%S.items.length;showSlideshowImage(t),scheduleSlideshowAdvance()},e.speed))}function showSlideshowControls(){const e=document.getElementById("slideshow");e.classList.add("controls-visible"),clearTimeout(_ssControlsTimer),_ssControlsTimer=setTimeout(()=>{S.slideshow.paused||e.classList.remove("controls-visible")},4e3)}function toggleSlideshowPause(){const e=S.slideshow;e.active&&(e.paused=!e.paused,document.getElementById("slideshowPlayBtn").innerHTML=e.paused?"▶":"⏸",e.paused?(clearTimeout(e.timer),e.timer=null,showSlideshowControls()):(scheduleSlideshowAdvance(),clearTimeout(_ssControlsTimer),_ssControlsTimer=setTimeout(()=>{document.getElementById("slideshow").classList.remove("controls-visible")},2e3)))}function slideshowNext(){const e=(S.slideshow.index+1)%S.items.length;showSlideshowImage(e),S.slideshow.paused||scheduleSlideshowAdvance()}function slideshowPrev(){const e=S.slideshow.index>0?S.slideshow.index-1:S.items.length-1;showSlideshowImage(e),S.slideshow.paused||scheduleSlideshowAdvance()}function slideshowFaster(){const e=SLIDESHOW_SPEEDS.indexOf(S.slideshow.speed);e>0&&(S.slideshow.speed=SLIDESHOW_SPEEDS[e-1],localStorage.setItem("ss_speed",S.slideshow.speed),updateSlideshowSpeedLabel())}function slideshowSlower(){const e=SLIDESHOW_SPEEDS.indexOf(S.slideshow.speed);e<SLIDESHOW_SPEEDS.length-1&&(S.slideshow.speed=SLIDESHOW_SPEEDS[e+1],localStorage.setItem("ss_speed",S.slideshow.speed),updateSlideshowSpeedLabel())}function updateSlideshowSpeedLabel(){document.getElementById("slideshowSpeedLabel").textContent=`${S.slideshow.speed/1e3}s`}function toggleSlideshowMetadata(){_ssMetadataOn=document.getElementById("ssMetadataToggle").checked,localStorage.setItem("ss_metadata",_ssMetadataOn?"1":"0"),document.getElementById("slideshow").classList.toggle("metadata-on",_ssMetadataOn)}function toggleSlideshowAnimated(){_ssAnimatedOn=document.getElementById("ssAnimatedToggle").checked,localStorage.setItem("ss_animated",_ssAnimatedOn?"1":"0");const e=S.slideshow;if(!e.active)return;const t=e.front===1?"slideshowImg1":"slideshowImg2",n=e.front===1?"slideshowImg2":"slideshowImg1";_ssAnimatedOn||(document.getElementById(t).classList.remove("ken-burns","ken-burns-freeze"),document.getElementById(n).classList.remove("ken-burns","ken-burns-freeze"))}function toggleSlideshowFullscreen(){document.fullscreenElement?document.exitFullscreen().catch(()=>{}):document.documentElement.requestFullscreen().catch(()=>{})}function startExhibition(){startSlideshow(),toggleSlideshowFullscreen()}function stopExhibition(){stopSlideshow()}
52function toggleExhibitionPause(){toggleSlideshowPause()}function exhibitionNext(){slideshowNext()}function exhibitionPrev(){slideshowPrev()}const _exhibit={get active(){return S.slideshow.active&&!!document.fullscreenElement}};let mapInstance=null,mapMarkers=null;async function showMapView(){if(document.getElementById("mapView").classList.add("open"),mapInstance){mapInstance.invalidateSize();return}await ensureLeaflet();const n=(await cachedApi("/api/map")).items;if(n.length===0){document.getElementById("mapContainer").innerHTML='<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-dim)">No geo-tagged photos found</div>';return}mapInstance=L.map("mapContainer",{zoomControl:!0,attributionControl:!1}).setView([0,0],2),L.control.attribution({prefix:!1}).addTo(mapInstance),L.tileLayer("https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png?key=cb1_3jnv_1_5d077cc10e4aed0344805ca3",{maxZoom:19,subdomains:"abcd",attribution:'© <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a> © <a href="https://carto.com/attributions" target="_blank" rel="noopener">CARTO</a>'}).addTo(mapInstance);function i(r){const c=r.layer.getAllChildMarkers().map(m=>m._geoItem).filter(Boolean);c.sort((m,h)=>{const g=m.year||0,v=h.year||0;return g!==v?g-v:(m.file_name||"").localeCompare(h.file_name||"")}),c.length>0&&openMapPreview(c,r.layer.getLatLng())}mapMarkers=L.markerClusterGroup({maxClusterRadius:50,spiderfyOnMaxZoom:!1,zoomToBoundsOnClick:!1,showCoverageOnHover:!1,iconCreateFunction:function(r){const l=r.getChildCount();let c="small";return l>50?c="large":l>10&&(c="medium"),L.divIcon({html:`<div style="background:var(--accent);color:#000;border-radius:50%;width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600">${l}</div>`,className:"marker-cluster marker-cluster-"+c,iconSize:L.point(36,36)})}}),mapMarkers.on("clusterclick",i);const o=L.markerClusterGroup({maxClusterRadius:15,spiderfyOnMaxZoom:!1,zoomToBoundsOnClick:!1,showCoverageOnHover:!1,iconCreateFunction:function(r){const l=r.getChildCount();return L.divIcon({html:`<div class="map-cluster-approx">${l}</div>`,className:"marker-cluster marker-cluster-"+(l>50?"large":l>10?"medium":"small"),iconSize:L.point(36,36)})}});o.on("clusterclick",i);for(const r of n){const l=r.geo_precision==="city"||r.geo_precision==="state"||r.geo_precision==="country",c=L.marker([r.latitude,r.longitude],{icon:L.divIcon({className:"map-marker-dot"+(l?" map-marker-approx":""),html:"",iconSize:[44,44],iconAnchor:[22,22]})});c._geoItem=r,c.on("click",function(){openMapPreview([r],c.getLatLng())}),l?o.addLayer(c):mapMarkers.addLayer(c)}mapInstance.addLayer(mapMarkers),mapInstance.addLayer(o);let s=mapMarkers.getBounds();const a=o.getBounds();s.isValid()?a.isValid()&&s.extend(a):s=a,s.isValid()&&mapInstance.fitBounds(s,{padding:[40,40]}),S._geoItems=n,mapInstance.on("click",function(){_mapPreviewOpen&&closeMapPreview()}),mapInstance.on("movestart zoomstart",function(){_mapPreviewOpen&&closeMapPreview()}),_mapEscListenerAdded||(_mapEscListenerAdded=!0,document.addEventListener("keydown",function(r){_mapPreviewOpen&&r.key==="Escape"&&(closeMapPreview(),r.preventDefault(),r.stopPropagation())}))}let _mapPreviewOpen=!1,_mapPreviewGroup=[],_mapEscListenerAdded=!1;const MAP_PREVIEW_MAX=9;function _gridCols(e){return e<=1?1:e<=4?2:3}function openMapPreview(e,t){_mapPreviewOpen=!0,_mapPreviewGroup=e;const n=e.slice(0,MAP_PREVIEW_MAX),i=_gridCols(n.length);document.getElementById("mapPreviewCounter").textContent=e.length+" photo"+(e.length!==1?"s":"");const o=document.getElementById("mapPreviewGrid");o.style.gridTemplateColumns=`repeat(${i}, 110px)`,o.innerHTML=n.map((r,l)=>`<img src="${mediaSrc(r,"thumb")}" onclick="mapPreviewOpenDetail(${l})" 53 onerror="this.style.visibility='hidden'" 54 alt="${esc(r.caption||r.file_name||"")}">`).join("");const s=document.getElementById("mapPreviewMore");e.length>MAP_PREVIEW_MAX?(s.textContent=`+${e.length-MAP_PREVIEW_MAX} more`,s.style.display=""):s.style.display="none";const a=document.getElementById("mapPreview");a.classList.remove("hidden"),t&&mapInstance&&requestAnimationFrame(()=>_positionMapPreview(a,t))}function _positionMapPreview(e,t){const n=mapInstance.getContainer(),i=mapInstance.latLngToContainerPoint(t),o=n.clientWidth,s=n.clientHeight,a=e.offsetWidth,r=e.offsetHeight,l=16;let c=i.x+l;c+a>o-l&&(c=i.x-a-l),c=Math.max(l,Math.min(c,o-a-l));let m=i.y-r/2;m=Math.max(l,Math.min(m,s-r-l)),e.style.left=c+"px",e.style.top=m+"px"}function closeMapPreview(){_mapPreviewOpen=!1,_mapPreviewGroup=[],document.getElementById("mapPreview").classList.add("hidden")}function mapPreviewOpenDetail(e){_mapPreviewGroup.length!==0&&(S.items=_mapPreviewGroup,S.total=_mapPreviewGroup.length,openDetail(e||0
54))}function closeMapView(){closeMapPreview(),document.getElementById("mapView").classList.remove("open")}const touch={startX:0,startY:0,dx:0,dy:0,active:!1,pinching:!1,startDist:0,scale:1,panX:0,panY:0,lastPanX:0,lastPanY:0,axis:null,startTime:0};let _detailSwipeEnterFrom=null,_detailTouchHintShown=!1;function initTouchGestures(){const e=document.getElementById("detail");e.addEventListener("touchstart",o=>{if(S.detailIndex<0||window._panoViewer&&o.target.closest("#detail-pano")||window._osdViewer&&o.target.closest("#detail-deepzoom"))return;if(o.touches.length===2){touch.pinching=!0,touch.startDist=pinchDist(o.touches),touch.lastPanX=touch.panX,touch.lastPanY=touch.panY,o.preventDefault();return}if(o.touches.length===1&&touch.scale>1){touch.startX=o.touches[0].clientX,touch.startY=o.touches[0].clientY,touch.lastPanX=touch.panX,touch.lastPanY=touch.panY,touch.active=!0;const a=e.querySelector(".detail-img");a&&a.classList.add("panning"),o.preventDefault();return}const s=o.target.closest&&o.target.closest(".detail-meta-bar");touch.inMetaBar=!!(s&&s.scrollHeight>s.clientHeight),touch.startX=o.touches[0].clientX,touch.startY=o.touches[0].clientY,touch.dx=0,touch.dy=0,touch.axis=null,touch.startTime=Date.now(),touch.active=!touch.inMetaBar},{passive:!1}),e.addEventListener("touchmove",o=>{if(S.detailIndex<0||window._osdViewer&&o.target.closest("#detail-deepzoom"))return;if(touch.pinching&&o.touches.length===2){const l=pinchDist(o.touches),c=Math.max(1,Math.min(5,touch.scale*l/touch.startDist));applyZoom(c,touch.lastPanX,touch.lastPanY),o.preventDefault();return}if(!touch.active)return;const s=o.touches[0].clientX-touch.startX,a=o.touches[0].clientY-touch.startY;if(touch.scale>1){touch.panX=touch.lastPanX+s,touch.panY=touch.lastPanY+a,applyZoom(touch.scale,touch.panX,touch.panY),o.preventDefault();return}if(touch.dx=s,touch.dy=a,!touch.axis&&(Math.abs(s)>10||Math.abs(a)>10)&&(touch.axis=Math.abs(s)>Math.abs(a)?"x":"y"),!touch.axis){o.preventDefault();return}const r=document.getElementById("detailContent");if(touch.axis==="y"){r.classList.add("swiping");const l=a<0?1:-1,c=l===1&&S.detailIndex>=S.items.length-1||l===-1&&S.detailIndex<=0,m=c?a*.25:a;r.style.transform=`translateY(${m}px)`,r.style.opacity=c?1:Math.max(.3,1-Math.abs(a)/300)}o.preventDefault()},{passive:!1}),e.addEventListener("touchend",o=>{if(window._osdViewer&&o.target.closest("#detail-deepzoom"))return;if(touch.pinching){touch.pinching=!1,touch.scale<1.1&&resetZoom();return}if(!touch.active)return;touch.active=!1;const s=document.getElementById("detailContent"),a=e.querySelector(".detail-img");if(a&&a.classList.remove("panning"),s.classList.remove("swiping"),touch.scale>1){s.style.transform="",s.style.opacity="";return}const r=Date.now()-touch.startTime,l=c=>Math.abs(c==="x"?touch.dx:touch.dy)/Math.max(1,r)*1e3;if(touch.axis==="x"){s.style.transform="",s.style.opacity="";const c=l("x");touch.dx<-80||touch.dx<-30&&c>400?S.infoPanelOpen||toggleInfoPanel():(touch.dx>80||touch.dx>30&&c>400)&&(S.infoPanelOpen?toggleInfoPanel():closeDetail())}else if(touch.axis==="y"){const c=l("y"),m=Math.abs(touch.dy)>80||Math.abs(touch.dy)>30&&c>400,h=touch.dy<0?1:-1,g=h===1&&S.detailIndex>=S.items.length-1||h===-1&&S.detailIndex<=0;if(m&&!g){const v=touch.dy<0?"swipe-exit-up":"swipe-exit-down";s.style.transform="",s.style.opacity="",s.classList.add(v),_detailSwipeEnterFrom=touch.dy<0?"below":"above",setTimeout(()=>{s.classList.remove(v),navigateDetail(h)},150)}else s.style.transition="transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.2s ease",s.style.transform="",s.style.opacity="",setTimeout(()=>{s.style.transition=""},320)}else s.style.transform="",s.style.opacity=""});const t=document.getElementById("infoPanel");let n={startX:0,active:!1};t.addEventListener("touchstart",o=>{o.touches.length===1&&(n={startX:o.touches[0].clientX,active:!0})},{passive:!0}),t.addEventListener("touchend",o=>{if(!n.active)return;n.active=!1,o.changedTouches[0].clientX-n.startX>80&&toggleInfoPanel()});let i=0;e.addEventListener("touchend",o=>{if(o.touches.length>0||window._osdViewer&&o.target.closest("#detail-deepzoom"))return;const s=Date.now();s-i<300&&(touch.scale>1?resetZoom():applyZoom(2.5,0,0)),i=s})}function pinchDist(e){const t=e[0].clientX-e[1].clientX,n=e[0].clientY-e[1].clientY;return Math.sqrt(t*t+n*n)}function applyZoom(e,t,n){touch.scale=e,touch.panX=t,touch.panY=n;const i=document.querySelector(".detail-img");if(i){if(e<=1){resetZoom();return}i.classList.add("zoomed"),i.style.transformOrigin="center center",i.style.transform=`translate(${t}px, ${n}px) scale(${e})`,document.getElementById("detail").classList.add("pinch-zoomed")}}function resetZoom(){touch.scale=1,touch.panX=0,touch.panY=0;const e=document.querySelector(".detail-img");e&&(e.classList.remove("zoomed","panning"),e.style.transform="",e.style.transformOrigin="",document.getElementById("detail").classList.remove("pinch-zoomed"))}let _loupeActive=!1,_loupeHandler=null;function toggleLoupe(){if(_loupeActive){deactivateLoupe();return}const e=document.getElementById("loupe"),t=document.querySelector(".detail-img");if(!t)return;_loupeActive=!0;const n=S.items[S.detailIndex],i=imgUrl(n.content_hash,"full");e.style.backgroundImage=`url(${i})`,e.classList.add("active"),_loupeHandler=o=>{if(!_loupeActive)return;const s=t.getBoundingClientRect(),a=(o.clientX-s.left)/s.width,r=(o.clientY-s.top)/s.height;if(a<0||a>1||r<0||r>1){e.style.display="none";return}e.style.display="block",e.style.left=o.clientX-100+"px",e.style.top=o.clientY-100+"px";const l=3,c=s.width*l,m=s.height*l;e.style.backgroundSize=`${c}px ${m}px`,e.style.backgroundPosition=`${-(a*c-100)}px ${-(r*m-100)}px`},document.addEventListener("mousemove",_loupeHandler),showToast("Loupe: 3x")}function deactivateLoupe(){_loupeActive&&(_loupeActive=!1,document.getElementById("loupe").classList.remove("active"),document.getElementById("loupe").style.display="none",_loupeHandler&&(document.removeEventListener("mousemove",_loupeHandler),_loupeHandler=null))}function initWheelZoom(){const e=document.getElementById("detail");let t=null;e.addEventListener("wheel",n=>{if(S.detailIndex<0)return;const i=document.querySelector(".detail-img");if(!i)return;n.preventDefault();const o=n.deltaY>0?-.2:.2;if(touch.scale=Math.max(1,Math.min(6,touch.scale+o)),touch.scale<=1){resetZoom();return}const s=i.getBoundingClientRect(),a=(n.clientX-s.left)/s.width*100,r=(n.clientY-s.top)/s.height*100;i.classList.add("zoomed"),i.style.transformOrigin=`${a}% ${r}%`,i.style.transform=`scale(${touch.scale}) translate(${touch.panX}px, ${touch.panY}px)`},{passive:!1}),e.addEventListener("mousedown",n=>{if(touch.scale<=1||S.detailIndex<0)return;const i=document.querySelector(".detail-img");!i||!n.target.closest(".detail-img")||(t={x:n.clientX-touch.panX,y:n.clientY-touch.panY},i.classList.add("panning"),n.preventDefault())}),document.addEventListener("mousemove",n=>{if(!t||touch.scale<=1)return;const i=document.querySelector(".detail-img");i&&(touch.panX=n.clientX-t.x,touch.panY=n.clientY-t.y,i.style.transform=`scale(${touch.scale}) translate(${touch.panX}px, ${touch.panY}px)`)}
54),document.addEventListener("mouseup",()=>{if(t){t=null;const n=document.querySelector(".detail-img");n&&n.classList.remove("panning")}}),e.addEventListener("dblclick",n=>{if(S.detailIndex<0)return;const i=document.querySelector(".detail-img");if(!(!i||!n.target.closest(".detail-img")))if(touch.scale>1)resetZoom();else{touch.scale=3;const o=i.getBoundingClientRect(),s=(n.clientX-o.left)/o.width*100,a=(n.clientY-o.top)/o.height*100;i.classList.add("zoomed"),i.style.transformOrigin=`${s}% ${a}%`,i.style.transform=`scale(${touch.scale})`}})}const prefetchCache=new Set;function prefetchAdjacent(e){const t=document.getElementById("prefetchContainer");for(let n=-2;n<=2;n++){if(n===0)continue;const i=e+n;if(i<0||i>=S.items.length)continue;const o=S.items[i];if(o.media_type==="video")continue;const s=imgUrl(o.content_hash,"view");if(prefetchCache.has(s))continue;prefetchCache.add(s);const a=document.createElement("link");a.rel="prefetch",a.as="image",a.href=s,t.appendChild(a)}}function updateStatus(){updateSortBarCount();const e=document.getElementById("statusBar"),t=[];if(S.items.length<S.total?t.push(`${S.items.length} of ${S.total} loaded`):t.push(`${S.total} ${S.total===1?"item":"items"}`),S.filters.q&&t.push(`"${S.filters.q}"`),S.filters.collection){const o=S.collections.find(s=>s.slug===S.filters.collection);o&&t.push(o.name)}const n=[S.filters.country,S.filters.camera,S.filters.year,S.filters.min_rating>0&&`${S.filters.min_rating}+ stars`,S.filters.type].filter(Boolean);n.length&&t.push(n.join(", "));const i=t.join(" \xB7 ");e._lastHtml!==i&&(e.textContent=i,e._lastHtml=i),updatePageTitle()}function updatePageTitle(){const e=(S.config?.site_title||"Eric Cheng").toUpperCase();let t="";if(S.detailIndex>=0&&S.items[S.detailIndex]){const n=S.items[S.detailIndex];t=n.caption||n.file_name||`Photo ${S.detailIndex+1}`}else if(S.filters.collection&&["grid","browse","search","collection","story"].includes(S.view)&&!_isHomeCollection(S.filters.collection)){const n=S.collections.find(i=>i.slug===S.filters.collection);t=n?n.name:S.filters.collection,S.view==="story"&&(t+=" Story")}else S.filters.q?(t=S.filters.q,S.view==="story"&&(t+=" Story")):S.view==="search"?t="Search":S.view==="browse"?t="Browse":S.view==="story"?t="Story":S.view==="map"?t="Map":S.view==="stats"?t="Stats":S.view==="about"?t="About":S.view==="terms"?t="Terms of Service":S.view==="contact"?t="Contact":S.view==="collections"?t="Collections":S.view==="timeline"?t="Timeline":S.view==="discover"?t="Discover":S.view==="tags"?t="Tags":S.view==="cameras"?t="Cameras":S.view==="places"?t="Places":S.view==="yearReview"?t="Year in Review":S.view==="seasons"?t="Seasons":S.view==="calendar"?t="Calendar":S.view==="surprise"?t="Surprise":S.view==="walk"&&(t="Gallery Walk");document.title=t?`${t} | ${e}`:e}function setupEventListeners(){document.addEventListener("click",d=>{const u=d.target.closest("[data-act]");if(!u)return;const f=u.dataset.val;switch(u.dataset.act){case"stats-filter":S.filters[u.dataset.field]=f,S.offset=0,navigate("/search");break;case"home-tag":navigate("/"),searchByTag(f);break;case"home-color":navigate("/"),searchByColor(f);break;case"filter-by":filterBy(u.dataset.field,f);break;case"search-tag":searchByTag(f);break;case"search-color":searchByColor(f);break;case"detail-nav":navigateFromDetail(u.dataset.navType,f);break}}),initTouchGestures(),initWheelZoom(),window.addEventListener("popstate",handlePopState);let e=!1,t=0;const n=document.getElementById("nav"),i=document.getElementById("statusBar"),o=document.getElementById("backToTop"),s=document.getElementById("scrollProgress");window.addEventListener("scroll",()=>{e||(e=!0,requestAnimationFrame(()=>{const d=window.scrollY;n.classList.toggle("scrolled",d>20),d>80&&d>t+5?(n.classList.add("hidden"),i.classList.add("hidden")):(d<t-5||d<=20)&&(n.classList.remove("hidden"),i.classList.remove("hidden")),t=d,o.classList.toggle("visible",d>600);const u=document.documentElement.scrollHeight-window.innerHeight;if(u>0&&(s.style.width=d/u*100+"%",s.classList.toggle("visible",d>20)),S.view==="grid"||S.view==="collection"||S.view==="browse"||S.view==="search"){renderVisibleRows();const f=_vgrid.rows.length>0?_vgrid.gridTop+_vgrid.rows[_vgrid.rows.length-1].top+_vgrid.rows[_vgrid.rows.length-1].height:0;f>0&&window.scrollY+window.innerHeight>=f-window.innerHeight&&S.items.length<S.total&&!S.loading&&(S.offset=S.items.length,loadItems(!0))}e=!1}))}),document.getElementById("hamburger").onclick=()=>{document.getElementById("navLinks").classList.toggle("mobile-open")},document.getElementById("siteTitle").onclick=d=>{d.preventDefault(),navigate("/")},document.getElementById("adminBtn").onclick=openAdminModal,document.getElementById("adminPassword").addEventListener("keydown",d=>{d.key==="Enter"&&submitAdminPassword(),d.key==="Escape"&&closeAdminModal()}),document.getElementById("adminModal").addEventListener("click",d=>{d.target.id==="adminModal"&&closeAdminModal()});const a=document.getElementById("searchBar"),r=document.getElementById("searchInput");let l;const c=document.getElementById("searchSuggestions");let m=-1;function h(d){if(!d){const y=getSearchHistory();if(y.length===0){c.classList.remove("open"),m=-1;return}c.innerHTML=y.map((p,w)=>`<div class="search-suggestion" data-idx="${w}" data-type="history" data-value="${esc(p)}"><span class="sug-label">${esc(p)}</span><span class="sug-type">recent</span></div>`).join(""),c.classList.add("open"),m=-1,c.querySelectorAll(".search-suggestion").forEach(p=>{p.onclick=()=>{r.value=p.dataset.value,g("history",p.dataset.value)}});return}if(d.length<2){c.classList.remove("open"),m=-1;return}const u=d.toLowerCase(),f=[];if(S.facets.tags){for(const y of S.facets.tags)if(y.tag.toLowerCase().includes(u)&&f.push({label:y.tag,type:"tag",count:y.count}),f.length>=8)break}if(S.facets.countries){for(const y of S.facets.countries)if((y.country||"").toLowerCase().includes(u)&&f.push({label:y.country,type:"location",count:y.count}),f.length>=10)break}if(S.facets.cameras){for(const y of S.facets.cameras)if((y.camera_model||"").toLowerCase().includes(u)&&f.push({label:y.camera_model,type:"camera",count:y.count}),f.length>=12)break}if(f.length===0){c.classList.remove("open"),m=-1;return}c.innerHTML=f.map((y,p)=>`<div class="search-suggestion" data-idx="${p}" data-type="${y.type}" data-value="${esc(y.label)}"><span class="sug-label">${esc(y.label)}</span><span class="sug-type">${y.type}</span></div>`).join(""),c.classList.add("open"),m=-1,c.querySelectorAll(".search-suggestion").forEach(y=>{y.onclick=()=>g(y.dataset.type,y.dataset.value)})}function g(d,u){if(c.classList.remove("open"),m=-1,d==="history"){S.filters.q=u,r.value=u,S.offset=0,addSearchHistory(u),navigate("/search");return}
54d==="tag"?(r.value="",S.filters.q="",S.filters.tag=u,S.offset=0,navigate("/search")):d==="location"?(r.value="",S.filters.q="",S.filters.country=u,S.offset=0,navigate("/search")):d==="camera"&&(r.value="",S.filters.q="",S.filters.camera=u,S.offset=0,navigate("/search"))}r.addEventListener("input",()=>{clearTimeout(l),h(r.value.trim())}),r.addEventListener("keydown",d=>{const u=c.querySelectorAll(".search-suggestion");if(c.classList.contains("open")&&u.length>0){if(d.key==="ArrowDown"){d.preventDefault(),m=Math.min(m+1,u.length-1),u.forEach((f,y)=>f.classList.toggle("active",y===m));return}if(d.key==="ArrowUp"){d.preventDefault(),m=Math.max(m-1,-1),u.forEach((f,y)=>f.classList.toggle("active",y===m));return}if(d.key==="Enter"&&m>=0){d.preventDefault(),g(u[m].dataset.type,u[m].dataset.value);return}}if(d.key==="Escape"&&(c.classList.remove("open"),r.blur()),d.key==="Enter"){d.preventDefault(),c.classList.remove("open"),r.blur(),clearTimeout(l);const f=r.value.trim();if(S.filters.q=f,S.filters.collection="",S.offset=0,f&&(addSearchHistory(f),S.sort="relevance"),S.view==="search"){const y=document.getElementById("searchPageInput");y&&(y.value=f),reloadGrid(),buildFilterBar()}else navigate("/search")}}),r.addEventListener("focus",()=>{h(r.value.trim())}),r.addEventListener("blur",()=>{setTimeout(()=>c.classList.remove("open"),200)}),document.getElementById("detailClose").onclick=closeDetail,document.getElementById("detailBack").onclick=closeDetail,document.getElementById("detailPrev").onclick=()=>navigateDetail(-1),document.getElementById("detailNext").onclick=()=>navigateDetail(1),document.getElementById("detailContent").addEventListener("click",d=>{d.target.classList.contains("detail-img")&&document.getElementById("detail").classList.toggle("toolbar-visible")}),document.addEventListener("keydown",d=>{if(d.key==="/"&&d.target.id!=="searchInput"){d.preventDefault(),S.slideshow.active&&stopSlideshow(),_exhibit.active&&stopExhibition(),S.detailIndex>=0&&closeDetail(),closeWalk(),window.scrollTo({top:0,behavior:"smooth"});const u=document.getElementById("searchInput");u.focus(),u.select();return}if(d.target.tagName!=="INPUT"&&!((d.metaKey||d.ctrlKey)&&d.key.length===1&&d.key!=="u")){if("hbmsa".includes(d.key)&&!d.metaKey&&!d.ctrlKey&&!d.altKey){const u={h:"/",b:"/browse",m:"/map",s:"/surprise",a:"/about"};if(u[d.key]){navigate(u[d.key]);return}}if(S.slideshow.active){if(d.key==="Escape"){stopSlideshow();return}if(d.key===" "){d.preventDefault(),toggleSlideshowPause();return}if(d.key==="ArrowLeft"){slideshowPrev();return}if(d.key==="ArrowRight"){slideshowNext();return}if(d.key==="+"||d.key==="="){slideshowFaster();return}if(d.key==="-"){slideshowSlower();return}if(d.key==="c"){const u=document.getElementById("ssMetadataToggle");u.checked=!u.checked,toggleSlideshowMetadata();return}if(d.key==="f"){toggleSlideshowFullscreen();return}return}if(d.key==="u"&&d.ctrlKey){d.preventDefault(),openAdminModal();return}if(d.key==="?"){toggleKbHelp();return}if(d.key==="Escape"){if(document.getElementById("kbHelp").classList.contains("open")){toggleKbHelp();return}if(S.detailIndex>=0&&S.infoPanelOpen){toggleInfoPanel();return}S.detailIndex>=0&&closeDetail()}if(!(window._osdViewer&&S.detailIndex>=0&&["+","-","=","0"].includes(d.key))){if(d.key==="ArrowLeft"&&S.detailIndex>=0?navigateDetail(-1):d.key==="ArrowLeft"&&S.detailIndex<0&&(d.preventDefault(),gridFocusMove("left")),d.key==="ArrowRight"&&S.detailIndex>=0?navigateDetail(1):d.key==="ArrowRight"&&S.detailIndex<0&&(d.preventDefault(),gridFocusMove("right")),d.key==="ArrowUp"&&S.detailIndex<0&&(d.preventDefault(),gridFocusMove("up")),d.key==="ArrowDown"&&S.detailIndex<0&&(d.preventDefault(),gridFocusMove("down")),d.key==="Enter"&&S.detailIndex<0&&_gridFocus>=0&&openDetail(_gridFocus),d.key===" "&&S.detailIndex>=0){const u=document.querySelector(".detail-video");if(u){d.preventDefault(),u.paused?u.play():u.pause();return}}if(d.key==="i"&&S.detailIndex>=0&&toggleInfoPanel(),d.key==="I"&&d.shiftKey&&S.detailIndex>=0){toggleVideoDebug();return}if(d.key==="p"&&S.detailIndex>=0&&startSlideshow(),d.key==="e"){startExhibition();return}if(d.key==="["&&S.detailIndex<0){cycleGridSize(-1);return}if(d.key==="]"&&S.detailIndex<0){cycleGridSize(1);return}if(d.key==="Home"&&S.detailIndex<0){d.preventDefault(),window.scrollTo({top:0,behavior:"smooth"}),setGridFocus(0);return}if(d.key==="End"&&S.detailIndex<0){d.preventDefault(),window.scrollTo({top:document.body.scrollHeight,behavior:"smooth"}),setGridFocus(S.items.length-1);return}if(d.key==="j"&&S.detailIndex<0){const u=S.view==="search"?"/search/story":"/browse/story",f=buildFilterParams().toString();navigate(f?`${u}?${f}`:u);return}if(d.key==="z"&&S.detailIndex>=0){toggleLoupe();return}if(d.key==="l"&&S.detailIndex>=0){_resetDetailIdle();const u=document.getElementById("detailLikeBtn"),f=S.items[S.detailIndex];u&&f&&likeItem(f.id,u);return}if(d.key==="f"&&S.detailIndex>=0){const u=document.getElementById("detail");document.fullscreenElement?document.exitFullscreen():u.requestFullscreen().catch(()=>{})}}}}),document.getElementById("detail").addEventListener("click",d=>{d.target.id==="detail"&&closeDetail()}),document.getElementById("detail").addEventListener("mousemove",_resetDetailIdle),document.getElementById("slideshow").addEventListener("click",d=>{(d.target.id==="slideshow"||d.target.classList.contains("slideshow-img"))&&showSlideshowControls()});function v(d,u,f,y,p){let w=0,k=0;d.addEventListener("touchstart",_=>{!u()||_.touches.length!==1||(w=_.touches[0].clientX,k=_.touches[0].clientY)},{passive:!0}),d.addEventListener("touchend",_=>{if(!u())return;const x=(_.changedTouches[0]?.clientX||0)-w,$=(_.changedTouches[0]?.clientY||0)-k;Math.abs(x)<40&&Math.abs($)<40||(Math.abs(x)>Math.abs($)?x<-40?f():x>40&&y():$>60&&p())},{passive:!0})}v(document.getElementById("slideshow"),()=>S.slideshow.active,slideshowNext,slideshowPrev,stopSlideshow),document.getElementById("kbHelp").addEventListener("click",d=>{d.target.id==="kbHelp"&&toggleKbHelp()}),window.addEventListener("offline",()=>{document.getElementById("offlineBanner").classList.add("visible")}),window.addEventListener("online",()=>{if(document.getElementById("offlineBanner").classList.remove("visible"),_retryFn){const d=_retryFn;_retryFn=null,d()}});let b;window.addEventListener("resize",()=>{clearTimeout(b),b=setTimeout(()=>{S.view!=="about"&&renderGrid(!1)},200)})}
54init(),"serviceWorker"in navigator&&window.addEventListener("load",()=>{navigator.serviceWorker.register("/sw.js").catch(()=>{})});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.