amro-alasri's picture
Upload folder using huggingface_hub
73fe9ee verified
#!/usr/bin/env python3
# flake8: noqa: E501
# Copyright (c) 2025 ByteDance Ltd. and/or its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Depth Anything 3 Gallery Server (two-level, single-file)
Now supports paginated depth preview (4 per page).
"""
import argparse
import json
import mimetypes
import os
import posixpath
import sys
from functools import partial
from http import HTTPStatus
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import quote, unquote
# ------------------------------ Embedded HTML ------------------------------ #
HTML_PAGE = r"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Depth Anything 3 Gallery</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="https://i.postimg.cc/rFSzGJ7J/light-icon.jpg" media="(prefers-color-scheme: light)">
<link rel="icon" href="https://i.postimg.cc/P5gZfJsf/dark-icon.jpg" media="(prefers-color-scheme: dark)">
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<style>
:root {
--gap:16px; --card-radius:16px; --shadow:0 8px 24px rgba(0,0,0,.12);
--maxW:1036px; --maxH:518px;
--tech-blue: #00d4ff;
--tech-cyan: #00ffcc;
--tech-purple: #7877c6;
}
*{ box-sizing:border-box }
/* Dark mode tech theme */
@media (prefers-color-scheme: dark) {
body{
margin:0; font:16px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);
color:#e8eaed;
position: relative;
overflow-x: hidden;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.2) 0%, transparent 50%);
animation: techPulse 8s ease-in-out infinite;
z-index: -1;
}
}
/* Light mode tech theme */
@media (prefers-color-scheme: light) {
body{
margin:0; font:16px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 50%, #cbd5e1 100%);
color:#1e293b;
position: relative;
overflow-x: hidden;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 80%, rgba(0, 212, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(0, 102, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(0, 255, 204, 0.08) 0%, transparent 50%);
animation: techPulse 8s ease-in-out infinite;
z-index: -1;
}
}
@keyframes techPulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 0.8; }
}
@keyframes techGradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Dark mode header */
@media (prefers-color-scheme: dark) {
header{
padding:20px 24px; position:sticky; top:0;
background:linear-gradient(180deg,rgba(10,10,10,0.9) 60%,rgba(10,10,10,0));
z-index:2; border-bottom:1px solid rgba(0, 212, 255, 0.2);
backdrop-filter: blur(10px);
}
h1{
margin:0; font-size:22px;
background: linear-gradient(45deg, var(--tech-blue), var(--tech-cyan), var(--tech-purple));
background-size: 400% 400%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: techGradient 3s ease infinite;
text-shadow: 0 0 30px rgba(0, 212, 255, 0.5);
}
.muted{ opacity:.7; font-size:13px; color: #a0a0a0; }
#backBtn{
display:none; padding:6px 10px; border-radius:10px;
border:1px solid rgba(0, 212, 255, 0.3);
background:rgba(0, 0, 0, 0.3);
color:#e8eaed; cursor:pointer;
transition: all 0.3s ease;
}
#backBtn:hover {
border-color: var(--tech-blue);
box-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
}
#search{
flex:1 1 260px; min-width:240px; max-width:520px;
padding:10px 14px; border-radius:12px;
border:1px solid rgba(0, 212, 255, 0.3);
background:rgba(0, 0, 0, 0.3);
color:#e8eaed; outline:none;
transition: all 0.3s ease;
}
#search:focus {
border-color: var(--tech-blue);
box-shadow: 0 0 10px rgba(0, 212, 255, 0.3);
}
}
/* Light mode header */
@media (prefers-color-scheme: light) {
header{
padding:20px 24px; position:sticky; top:0;
background:linear-gradient(180deg,rgba(248,250,252,0.9) 60%,rgba(248,250,252,0));
z-index:2; border-bottom:1px solid rgba(0, 212, 255, 0.3);
backdrop-filter: blur(10px);
}
h1{
margin:0; font-size:22px;
background: linear-gradient(45deg, #0066ff, #00d4ff, #00ffcc);
background-size: 400% 400%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: techGradient 3s ease infinite;
text-shadow: 0 0 20px rgba(0, 102, 255, 0.3);
}
.muted{ opacity:.7; font-size:13px; color: #64748b; }
#backBtn{
display:none; padding:6px 10px; border-radius:10px;
border:1px solid rgba(0, 212, 255, 0.4);
background:rgba(255, 255, 255, 0.8);
color:#1e293b; cursor:pointer;
transition: all 0.3s ease;
}
#backBtn:hover {
border-color: #0066ff;
box-shadow: 0 0 10px rgba(0, 102, 255, 0.3);
}
#search{
flex:1 1 260px; min-width:240px; max-width:520px;
padding:10px 14px; border-radius:12px;
border:1px solid rgba(0, 212, 255, 0.4);
background:rgba(255, 255, 255, 0.8);
color:#1e293b; outline:none;
transition: all 0.3s ease;
}
#search:focus {
border-color: #0066ff;
box-shadow: 0 0 10px rgba(0, 102, 255, 0.3);
}
}
.row{ display:flex; gap:12px; align-items:center; flex-wrap:wrap; justify-content:center; }
main{ padding:16px 24px 24px; display:grid; place-items:center; }
.group-wrap{ width:min(900px,100%); }
.group-list{ list-style:none; margin:0; padding:0; display:grid; gap:10px; }
/* Dark mode cards */
@media (prefers-color-scheme: dark) {
.group-item{
display:flex; align-items:center; gap:12px; padding:12px 14px;
background:rgba(0, 0, 0, 0.3); border:1px solid rgba(0, 212, 255, 0.2); border-radius:14px; cursor:pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.group-item:hover{
transform: translateY(-1px);
border-color:var(--tech-blue);
box-shadow: 0 4px 15px rgba(0, 212, 255, 0.2);
}
.card{
background:rgba(0, 0, 0, 0.3); border:1px solid rgba(0, 212, 255, 0.2); border-radius:var(--card-radius);
overflow:hidden; box-shadow:var(--shadow);
transition:all 0.3s ease; cursor:pointer; display:flex; flex-direction:column; max-width:var(--maxW);
backdrop-filter: blur(10px);
}
.card:hover{
transform:translateY(-2px);
border-color:var(--tech-blue);
box-shadow: 0 8px 25px rgba(0, 212, 255, 0.2);
}
.thumb-box{
position:relative; width:100%; aspect-ratio:2/1;
background:linear-gradient(135deg, #0e121b 0%, #1a1a2e 100%);
display:grid; place-items:center; overflow:hidden;
border-bottom: 1px solid rgba(0, 212, 255, 0.1);
}
.open{
font-size:12px; opacity:.7; padding:6px 8px;
border:1px solid rgba(0, 212, 255, 0.3);
border-radius:10px;
background:rgba(0, 212, 255, 0.1);
transition: all 0.3s ease;
}
.open:hover {
background:rgba(0, 212, 255, 0.2);
border-color: var(--tech-blue);
}
}
/* Light mode cards */
@media (prefers-color-scheme: light) {
.group-item{
display:flex; align-items:center; gap:12px; padding:12px 14px;
background:rgba(255, 255, 255, 0.8); border:1px solid rgba(0, 212, 255, 0.3); border-radius:14px; cursor:pointer;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.group-item:hover{
transform: translateY(-1px);
border-color:#0066ff;
box-shadow: 0 4px 15px rgba(0, 102, 255, 0.2);
}
.card{
background:rgba(255, 255, 255, 0.8); border:1px solid rgba(0, 212, 255, 0.3); border-radius:var(--card-radius);
overflow:hidden; box-shadow:0 4px 6px rgba(0, 0, 0, 0.1);
transition:all 0.3s ease; cursor:pointer; display:flex; flex-direction:column; max-width:var(--maxW);
backdrop-filter: blur(10px);
}
.card:hover{
transform:translateY(-2px);
border-color:#0066ff;
box-shadow: 0 8px 25px rgba(0, 102, 255, 0.2);
}
.thumb-box{
position:relative; width:100%; aspect-ratio:2/1;
background:linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
display:grid; place-items:center; overflow:hidden;
border-bottom: 1px solid rgba(0, 212, 255, 0.2);
}
.open{
font-size:12px; opacity:.7; padding:6px 8px;
border:1px solid rgba(0, 212, 255, 0.4);
border-radius:10px;
background:rgba(0, 212, 255, 0.1);
transition: all 0.3s ease;
}
.open:hover {
background:rgba(0, 212, 255, 0.2);
border-color: #0066ff;
}
}
.gname{ font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; width:100%; }
.grid{
width:min(1200px,100%);
display:grid;
grid-template-columns:repeat(auto-fill,minmax(260px,1fr));
gap:var(--gap);
align-items:start;
justify-items:stretch;
margin: 0 auto;
padding: 0 20px;
}
.thumb{ max-width:100%; max-height:100%; object-fit:contain; display:block; }
.meta{ padding:12px 14px; display:flex; justify-content:space-between; align-items:center; gap:8px; }
.title{ font-weight:600; font-size:14px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.empty{ opacity:.6; padding:40px 0; text-align:center; }
.crumb{ font-size:13px; opacity:.8; }
.overlay{ position:fixed; inset:0; background:rgba(0,0,0,.6); display:none; place-items:center; padding:20px; z-index:10; }
.overlay.show{ display:grid; }
/* Dark mode viewer */
@media (prefers-color-scheme: dark) {
.viewer{
inline-size:min(92vw,var(--maxW));
block-size:min(82vh,var(--maxH));
background:#0e121b; border:1px solid rgba(0, 212, 255, 0.3); border-radius:18px; overflow:hidden; position:relative; box-shadow:0 12px 36px rgba(0,0,0,.35);
display:grid;
}
.chip{ background:rgba(0,0,0,.45); border:1px solid rgba(0, 212, 255, 0.3); color:#e8eaed; padding:6px 10px; border-radius:12px; font-size:12px; max-width:60%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.btn{ margin-left:auto; background:rgba(0, 0, 0, 0.3); color:#e8eaed; border:1px solid rgba(0, 212, 255, 0.3); border-radius:10px; padding:6px 10px; cursor:pointer; transition: all 0.3s ease; }
.btn:hover { border-color: var(--tech-blue); box-shadow: 0 0 10px rgba(0, 212, 255, 0.3); }
.mv-box{ width:100%; aspect-ratio:1036/518; background:#0b0d12; border:1px solid rgba(0, 212, 255, 0.2); border-radius:12px; overflow:hidden; }
.mv-box model-viewer{ width:100%; height:100%; background:#0b0d12; }
.res-cell{ position:relative; width:100%; aspect-ratio:2/1; background:#0e121b; border:1px solid rgba(0, 212, 255, 0.2); border-radius:12px; overflow:hidden; display:grid; place-items:center; }
.res-empty{ position:absolute; inset:0; display:grid; place-items:center; opacity:.55; font-size:12px; color:#9aa0a6; }
.download-icon{ background:rgba(0, 0, 0, 0.6); border:1px solid rgba(0, 212, 255, 0.3); color:#e8eaed; box-shadow:0 4px 12px rgba(0,0,0,0.3); }
.download-icon:hover{ background:rgba(0, 212, 255, 0.2); border-color:var(--tech-blue); box-shadow:0 0 20px rgba(0, 212, 255, 0.4); transform:scale(1.05); }
}
/* Light mode viewer */
@media (prefers-color-scheme: light) {
.viewer{
inline-size:min(92vw,var(--maxW));
block-size:min(82vh,var(--maxH));
background:#f8fafc; border:1px solid rgba(0, 212, 255, 0.4); border-radius:18px; overflow:hidden; position:relative; box-shadow:0 12px 36px rgba(0,0,0,.15);
display:grid;
}
.chip{ background:rgba(255,255,255,0.8); border:1px solid rgba(0, 212, 255, 0.4); color:#1e293b; padding:6px 10px; border-radius:12px; font-size:12px; max-width:60%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.btn{ margin-left:auto; background:rgba(255, 255, 255, 0.8); color:#1e293b; border:1px solid rgba(0, 212, 255, 0.4); border-radius:10px; padding:6px 10px; cursor:pointer; transition: all 0.3s ease; }
.btn:hover { border-color: #0066ff; box-shadow: 0 0 10px rgba(0, 102, 255, 0.3); }
.mv-box{ width:100%; aspect-ratio:1036/518; background:#f8fafc; border:1px solid rgba(0, 212, 255, 0.3); border-radius:12px; overflow:hidden; }
.mv-box model-viewer{ width:100%; height:100%; background:#f8fafc; }
.res-cell{ position:relative; width:100%; aspect-ratio:2/1; background:#f8fafc; border:1px solid rgba(0, 212, 255, 0.3); border-radius:12px; overflow:hidden; display:grid; place-items:center; }
.res-empty{ position:absolute; inset:0; display:grid; place-items:center; opacity:.55; font-size:12px; color:#64748b; }
.download-icon{ background:rgba(255, 255, 255, 0.9); border:1px solid rgba(0, 212, 255, 0.4); color:#1e293b; box-shadow:0 4px 12px rgba(0,0,0,0.15); }
.download-icon:hover{ background:rgba(0, 212, 255, 0.2); border-color:#0066ff; box-shadow:0 0 20px rgba(0, 102, 255, 0.4); transform:scale(1.05); }
}
.viewer-header{ position:absolute; top:8px; left:8px; right:8px; display:flex; gap:8px; align-items:center; z-index:2; }
.viewer-body{ height:100%; display:grid; grid-template-rows:auto auto; gap:12px; padding:36px 8px 8px 8px; overflow:auto; }
.res-grid{ display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.res-img{ max-width:100%; max-height:100%; object-fit:contain; display:block; }
.download-icon{ position:absolute; bottom:16px; right:16px; width:44px; height:44px; border-radius:50%; display:grid; place-items:center; font-size:20px; cursor:pointer; z-index:3; transition:all 0.3s ease; }
/* Pagination controls */
.pager {
grid-column: 1 / -1;
justify-content: center;
align-items: center;
display: flex;
gap: 16px;
margin-top: 8px;
font-size: 13px;
text-align: center;
}
/* Dark mode pagination */
@media (prefers-color-scheme: dark) {
.pager {
color: #ccc;
}
.pager button {
padding: 4px 10px;
border-radius: 8px;
border: 1px solid rgba(0, 212, 255, 0.3);
background: rgba(0, 0, 0, 0.3);
color: #e8eaed;
cursor: pointer;
transition: all 0.3s ease;
}
.pager button:hover:not(:disabled) {
border-color: var(--tech-blue);
box-shadow: 0 0 8px rgba(0, 212, 255, 0.2);
}
.pager button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
/* Light mode pagination */
@media (prefers-color-scheme: light) {
.pager {
color: #64748b;
}
.pager button {
padding: 4px 10px;
border-radius: 8px;
border: 1px solid rgba(0, 212, 255, 0.4);
background: rgba(255, 255, 255, 0.8);
color: #1e293b;
cursor: pointer;
transition: all 0.3s ease;
}
.pager button:hover:not(:disabled) {
border-color: #0066ff;
box-shadow: 0 0 8px rgba(0, 102, 255, 0.2);
}
.pager button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
/* Intro card styles */
@media (prefers-color-scheme: dark) {
.intro-card {
background: linear-gradient(135deg, rgba(0, 212, 255, 0.1) 0%, rgba(0, 102, 255, 0.1) 100%);
border: 1px solid rgba(0, 212, 255, 0.2);
backdrop-filter: blur(10px);
}
.intro-title {
background: linear-gradient(45deg, var(--tech-blue), var(--tech-cyan), var(--tech-purple));
background-size: 400% 400%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: techGradient 3s ease infinite;
text-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
}
.intro-description {
color: #e0e0e0;
}
}
@media (prefers-color-scheme: light) {
.intro-card {
background: linear-gradient(135deg, rgba(0, 212, 255, 0.05) 0%, rgba(0, 102, 255, 0.05) 100%);
border: 1px solid rgba(0, 212, 255, 0.3);
backdrop-filter: blur(10px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.intro-title {
background: linear-gradient(45deg, #0066ff, #00d4ff, #00ffcc);
background-size: 400% 400%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: techGradient 3s ease infinite;
text-shadow: 0 0 15px rgba(0, 102, 255, 0.2);
}
.intro-description {
color: #334155;
}
}
footer{
opacity:.55;
font-size:12px;
padding:12px 24px 24px;
text-align:center;
display:flex;
justify-content:center;
align-items:center;
width:100%;
}
</style>
</head>
<body>
<header>
<div class="row">
<button id="backBtn">← Back</button>
<h1 id="pageTitle">Depth Anything 3 Gallery</h1>
<span id="crumb" class="crumb"></span>
<input id="search" placeholder="Search…" />
</div>
<div class="muted" id="hint" style="text-align: center;">Level 1 shows groups only; click a group to browse scenes and previews.</div>
</header>
<main>
<!-- Tech intro card -->
<div class="intro-card" style="margin-bottom: 30px; padding: 25px; border-radius: 15px; text-align: center; max-width: 800px;">
<h2 class="intro-title" style="margin: 0 0 15px 0; font-size: 1.8em; font-weight: 700;">
🎯 Depth Anything 3 Gallery
</h2>
<p class="intro-description" style="margin: 0; font-size: 1.1em; line-height: 1.6;">
Explore 3D reconstructions and depth visualizations from Depth Anything 3.
Browse through groups of scenes, preview 3D models, and examine depth maps interactively.
</p>
</div>
<div id="level1" class="group-wrap" aria-live="polite">
<ul id="groupList" class="group-list"></ul>
<div id="groupEmpty" class="empty" style="display:none;">No available groups</div>
</div>
<div id="level2" style="display:none; width:100%;" aria-live="polite">
<div id="topPager" class="pager" style="margin-bottom: 16px;"></div>
<div id="grid" class="grid"></div>
<div id="sceneEmpty" class="empty" style="display:none;">No available scenes in this group</div>
</div>
</main>
<div id="overlay" class="overlay" role="dialog" aria-modal="true" aria-label="3D Preview">
<div class="viewer" id="viewer">
<div class="viewer-header">
<div id="viewerTitle" class="chip">Loading…</div>
<button id="toggleView" class="btn" title="Toggle between 3D-only and resource view">Resource View</button>
<button id="closeBtn" class="btn">Close</button>
</div>
<div id="downloadBtn" class="download-icon" title="Download GLB model">⬇</div>
<div class="viewer-body">
<div class="mv-box"><model-viewer id="mv"
src=""
ar
camera-controls
auto-rotate
interaction-prompt="auto"
shadow-intensity="0.7"
exposure="1.0"
alt="GLB Preview"></model-viewer></div>
<div class="res-grid" id="resGrid" hidden></div>
</div>
</div>
</div>
<footer>Depth Anything 3 Gallery. Copyright 2025 Depth Anything 3 authors.</footer>
<script>
const level1=document.getElementById('level1'),level2=document.getElementById('level2'),pageTitle=document.getElementById('pageTitle'),crumb=document.getElementById('crumb'),backBtn=document.getElementById('backBtn'),hint=document.getElementById('hint'),searchInput=document.getElementById('search'),groupList=document.getElementById('groupList'),groupEmpty=document.getElementById('groupEmpty'),topPager=document.getElementById('topPager'),grid=document.getElementById('grid'),sceneEmpty=document.getElementById('sceneEmpty'),overlay=document.getElementById('overlay'),viewer=document.getElementById('viewer'),mv=document.getElementById('mv'),viewerTitle=document.getElementById('viewerTitle'),downloadBtn=document.getElementById('downloadBtn'),toggleViewBtn=document.getElementById('toggleView'),closeBtn=document.getElementById('closeBtn'),resGrid=document.getElementById('resGrid');
let GROUPS=[],SCENES=[],currentGroup=null,currentScene=null,currentPage=1,currentScenePage=1;
const qs=()=>new URLSearchParams(location.search);
async function loadGroups(){const r=await fetch('/manifest.json',{cache:'no-store'});if(!r.ok)throw new Error(r.status+' '+r.statusText);const j=await r.json();GROUPS=j.groups||[];renderGroups(GROUPS);}
async function loadScenes(g){const r=await fetch('/manifest/'+encodeURIComponent(g)+'.json',{cache:'no-store'});if(!r.ok)throw new Error(r.status+' '+r.statusText);const j=await r.json();SCENES=j.items||[];const p=parseInt(qs().get('page'))||1;renderScenes(SCENES,p);}
function renderGroups(list){groupList.innerHTML='';const q=searchInput.value.trim().toLowerCase();const f=list.filter(g=>(g.title||g.id||'').toLowerCase().includes(q));if(!f.length){groupEmpty.style.display='';return;}groupEmpty.style.display='none';for(const g of f){const li=document.createElement('li');li.className='group-item';li.title=g.title||g.id;li.onclick=()=>enterLevel2(g.id,{push:true});const name=document.createElement('div');name.className='gname';name.textContent=g.title||g.id;li.appendChild(name);groupList.appendChild(li);}}
function renderScenes(list,page=1){topPager.innerHTML='';grid.innerHTML='';const q=searchInput.value.trim().toLowerCase();const f=list.filter(x=>(x.title||'').toLowerCase().includes(q)||(x.id||'').toLowerCase().includes(q));if(!f.length){sceneEmpty.style.display='';topPager.style.display='none';return;}sceneEmpty.style.display='none';topPager.style.display='flex';const perPage=16;const total=f.length;const totalPages=Math.max(1,Math.ceil(total/perPage));currentScenePage=page;const u=new URL(location.href);u.searchParams.set('page',page);history.replaceState(null,'',u);const subset=f.slice((page-1)*perPage,page*perPage);for(const i of subset){const c=document.createElement('div');c.className='card';c.title=i.title;const b=document.createElement('div');b.className='thumb-box';const img=document.createElement('img');img.className='thumb';img.loading='lazy';img.alt=i.title;img.src=i.thumbnail;b.appendChild(img);const m=document.createElement('div');m.className='meta';const t=document.createElement('div');t.className='title';t.textContent=i.title;const o=document.createElement('div');o.className='open';o.textContent='Preview';m.appendChild(t);m.appendChild(o);c.appendChild(b);c.appendChild(m);c.onclick=()=>openViewer(i,{push:true});grid.appendChild(c);}function buildPager(){const pg=document.createElement('div');pg.className='pager';const prev=document.createElement('button');prev.textContent='← Prev';prev.disabled=page<=1;prev.onclick=()=>renderScenes(list,page-1);const info=document.createElement('span');info.textContent=`${page} / ${totalPages}`;const next=document.createElement('button');next.textContent='Next →';next.disabled=page>=totalPages;next.onclick=()=>renderScenes(list,page+1);pg.appendChild(prev);pg.appendChild(info);pg.appendChild(next);return pg;}topPager.innerHTML='';topPager.appendChild(buildPager());grid.appendChild(buildPager());}
function enterLevel1({push=false}={}){currentGroup=null;pageTitle.textContent='Depth Anything 3 Gallery';crumb.textContent='';backBtn.style.display='none';hint.style.display='';level1.style.display='';level2.style.display='none';overlay.classList.remove('show');mv.src='';const u=new URL(location.href);u.searchParams.delete('group');u.searchParams.delete('id');u.searchParams.delete('page');push?history.pushState(null,'',u):history.replaceState(null,'',u);searchInput.value='';loadGroups().catch(e=>{groupList.innerHTML='';groupEmpty.style.display='';groupEmpty.textContent='Failed to load groups: '+e;});}
async function enterLevel2(g,{push=false}={}){currentGroup=g;pageTitle.textContent=g;crumb.textContent='(group)';backBtn.style.display='';hint.style.display='none';level1.style.display='none';level2.style.display='';overlay.classList.remove('show');mv.src='';const u=new URL(location.href);u.searchParams.set('group',g);u.searchParams.delete('id');push?history.pushState(null,'',u):history.replaceState(null,'',u);searchInput.value='';try{await loadScenes(g);const id=qs().get('id');if(id){const hit=SCENES.find(x=>x.id