SplatAnything / demo /scene_shell.html
ritianyu's picture
Publish preview-first SplatAnything demo for ZeroGPU
dc09dcc verified
Raw History Blame Contribute Delete
6.1 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>__SCENE_LABEL__</title>
<style>
* { box-sizing:border-box; }
html,body { margin:0; width:100%; height:100%; overflow:hidden; background:#09090b; color:#fff; font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
.poster,.viewer { position:absolute; inset:0; width:100%; height:100%; border:0; }
.poster { object-fit:cover; }
.shade { position:absolute; inset:0; background:linear-gradient(180deg,#09090b42,transparent 30%,transparent 65%,#09090b38); pointer-events:none; }
.identity { position:absolute; top:20px; left:20px; display:flex; align-items:center; gap:8px; padding:9px 12px; border:1px solid #ffffff25; border-radius:30px; background:#17171c80; backdrop-filter:blur(16px); font-size:11px; letter-spacing:.15px; }
.identity::before { content:""; width:5px; height:5px; background:#a7c5ff; border-radius:50%; box-shadow:0 0 10px #8bb7ff80; }
.center { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; }
button { font:inherit; cursor:pointer; -webkit-tap-highlight-color:transparent; }
.explore { position:relative; display:flex; align-items:center; justify-content:center; gap:12px; min-width:174px; padding:17px 25px; border:1px solid #ffffff85; border-radius:40px; color:#fff; background:#17171cb8; backdrop-filter:blur(22px); font-size:14px; font-weight:550; letter-spacing:.1px; box-shadow:0 8px 36px #00000038,inset 0 1px #ffffff22; transition:transform .2s,background .2s,box-shadow .2s; }
.explore:hover { transform:translateY(-2px) scale(1.025); background:#27272ae8; box-shadow:0 10px 42px #00000045,0 0 0 5px #ffffff14; }
.explore:disabled { cursor:wait; transform:none; }
.explore svg { width:19px; height:19px; }
button:focus-visible { outline:2px solid #93baff; outline-offset:5px; }
.loading svg { animation:turn 2s linear infinite; }
@keyframes turn { to { transform:rotate(360deg); } }
.message { position:absolute; top:calc(50% + 48px); left:20px; right:20px; margin:0; text-align:center; font-size:11px; line-height:1.6; text-shadow:0 1px 8px #000; }
.tools { position:absolute; top:17px; right:17px; display:flex; gap:8px; z-index:2; }
.tool { display:flex; align-items:center; justify-content:center; gap:7px; height:35px; padding:0 11px; border:1px solid #ffffff2e; border-radius:10px; background:#18181bc0; color:#fff; backdrop-filter:blur(12px); transition:background .15s,transform .15s; }
.tool:hover { background:#3f3f46; transform:translateY(-1px); }
.tool svg { width:16px; height:16px; }
#back { font-size:11px; }
[hidden] { display:none !important; }
@media(max-width:480px) { .identity{left:13px;top:15px;font-size:10px;padding:8px 10px;} .tools{top:13px;right:13px;} .explore{font-size:13px;padding:15px 21px;} }
@media(prefers-reduced-motion:reduce) { *,*::before,*::after { animation:none !important; transition:none !important; } }
</style>
</head>
<body>
<img class="poster" src="poster?v=__POSTER_VERSION__" alt="Initial Gaussian rendering of __SCENE_LABEL__">
<div class="shade"></div>
<div class="identity">__SCENE_LABEL__</div>
<div class="center" id="preview-controls">
<button class="explore" id="explore" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9L12 3Z"/><path d="m4 7.5 8 4.5 8-4.5M12 12v9"/></svg>
<span>Explore 3D</span>
</button>
<p class="message" id="message" role="status" aria-live="polite"></p>
</div>
<div class="tools">
<button class="tool" id="back" type="button" hidden aria-label="Back to scene preview">← Preview</button>
<button class="tool" id="fullscreen" type="button" aria-label="Toggle fullscreen" title="Fullscreen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M8 3H3v5m13-5h5v5M3 16v5h5m13-5v5h-5"/></svg>
</button>
</div>
<script>
const button=document.getElementById('explore'), controls=document.getElementById('preview-controls');
const message=document.getElementById('message'), back=document.getElementById('back');
let frame=null, controller=null;
button.addEventListener('click',async()=>{
button.disabled=true; button.classList.add('loading');button.querySelector('span').textContent='Opening scene…';message.textContent='';
controller=new AbortController();
try {
const response=await fetch('load',{method:'POST',signal:controller.signal});
if(!response.ok)throw new Error('download');
const result=await response.json();
frame=document.createElement('iframe');frame.className='viewer';frame.title='Interactive Gaussian scene';
frame.allow='fullscreen';frame.allowFullscreen=true;frame.referrerPolicy='no-referrer';
frame.sandbox='allow-scripts allow-pointer-lock allow-downloads';
frame.addEventListener('load',()=>{controls.hidden=true;back.hidden=false;});
frame.src=result.url;document.body.insertBefore(frame,controls);
} catch(error) {
if(error.name==='AbortError')return;
message.textContent='The scene could not be loaded. Please try again.';
button.querySelector('span').textContent='Try again';button.disabled=false;button.classList.remove('loading');
}
});
back.addEventListener('click',()=>{
frame?.remove();frame=null;back.hidden=true;controls.hidden=false;
button.disabled=false;button.classList.remove('loading');button.querySelector('span').textContent='Explore 3D';message.textContent='';
});
document.getElementById('fullscreen').addEventListener('click',async()=>{
try { if(document.fullscreenElement)await document.exitFullscreen();else await document.documentElement.requestFullscreen(); } catch(error) {}
});
window.addEventListener('pagehide',()=>controller?.abort());
</script>
</body>
</html>