Download index.html from FormaLau/microduck-reachy-simulator: direct link, hf CLI and curl.
- Browser
- Download file 16.7 kB
-
https://huggingface.co/spaces/FormaLau/microduck-reachy-simulator/resolve/f402bd3fffde08372a83fe40b24cf895e529d838/index.html
- Command line
-
hf download hf://spaces/FormaLau/microduck-reachy-simulator@f402bd3fffde08372a83fe40b24cf895e529d838/index.html
-
curl -L -o index.html https://huggingface.co/spaces/FormaLau/microduck-reachy-simulator/resolve/f402bd3fffde08372a83fe40b24cf895e529d838/index.html
16.7 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Microduck RL playground</title> | |
| <style> | |
| :root { | |
| --ink: #08080c; | |
| --yellow: #ffd23f; | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| html, body { height: 100%; } | |
| body { | |
| background: var(--ink); | |
| color: rgba(255, 255, 255, 0.85); | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| overflow: hidden; | |
| } | |
| #scene { position: fixed; inset: 0; } | |
| #scene canvas { display: block; } | |
| .halftone { | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| background-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1.5px); | |
| background-size: 21px 21px; | |
| mask-image: linear-gradient(to bottom, transparent 30%, black 100%); | |
| -webkit-mask-image: linear-gradient(to bottom, transparent 30%, black 100%); | |
| } | |
| /* Top-left corner: what this is */ | |
| #brand { | |
| position: fixed; | |
| top: 1.4rem; | |
| left: 1.6rem; | |
| z-index: 10; | |
| font-size: 0.72rem; | |
| font-weight: 600; | |
| letter-spacing: 0.14em; | |
| text-transform: uppercase; | |
| color: rgba(255, 255, 255, 0.55); | |
| line-height: 1.8; | |
| } | |
| #brand b { color: var(--yellow); font-weight: 600; } | |
| #loading { | |
| position: fixed; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 0.8rem; | |
| letter-spacing: 0.14em; | |
| text-transform: uppercase; | |
| color: rgba(255, 255, 255, 0.45); | |
| text-align: center; | |
| padding: 0 2rem; | |
| z-index: 20; | |
| } | |
| /* Bottom edges: controls left, key hints right */ | |
| #hud { | |
| position: fixed; | |
| bottom: 1.4rem; | |
| left: 1.6rem; | |
| right: 1.6rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-end; | |
| gap: 1rem; | |
| z-index: 10; | |
| pointer-events: none; | |
| } | |
| #hud .stack { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 0.45rem; | |
| pointer-events: auto; | |
| } | |
| #hud .row { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| #hud button { | |
| appearance: none; | |
| border: 1px solid rgba(255, 255, 255, 0.14); | |
| background: rgba(255, 255, 255, 0.04); | |
| color: rgba(255, 255, 255, 0.8); | |
| font: inherit; | |
| font-size: 0.72rem; | |
| font-weight: 600; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| padding: 0.5rem 1rem; | |
| border-radius: 999px; | |
| cursor: pointer; | |
| transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease; | |
| } | |
| #hud button:hover { background: rgba(255, 255, 255, 0.1); } | |
| #hud button.on { | |
| background: #ffc61e; | |
| border-color: #ffc61e; | |
| color: #08080c; | |
| } | |
| /* Current-state label: quiet line above the telemetry, slot-machine | |
| roll on change */ | |
| .mode { | |
| position: relative; | |
| overflow: hidden; | |
| height: 1.2em; | |
| min-width: 8ch; | |
| font-size: 0.72rem; | |
| font-weight: 600; | |
| letter-spacing: 0.2em; | |
| text-transform: uppercase; | |
| color: rgba(255, 210, 63, 0.8); | |
| text-align: right; | |
| } | |
| .mode .mode-text { | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| line-height: 1.2; | |
| transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.3s ease; | |
| } | |
| .mode .mode-text.in { transform: translateY(130%); opacity: 0; } | |
| .mode .mode-text.out { transform: translateY(-130%); opacity: 0; } | |
| /* Colour swatches */ | |
| #swatches { gap: 0.6rem; } | |
| #swatches button { | |
| width: 1.5rem; | |
| height: 1.5rem; | |
| padding: 0; | |
| border-radius: 50%; | |
| border: 2px solid rgba(255, 255, 255, 0.18); | |
| } | |
| #swatches button:hover { transform: scale(1.15); } | |
| #swatches button.on { | |
| border-color: #fff; | |
| transform: scale(1.15); | |
| } | |
| #hud .keys { | |
| font-size: 0.7rem; | |
| letter-spacing: 0.08em; | |
| color: rgba(255, 255, 255, 0.4); | |
| text-align: right; | |
| line-height: 1.9; | |
| pointer-events: auto; | |
| } | |
| /* Corner group captions */ | |
| #hud .caption { | |
| display: block; | |
| font-size: 0.55rem; | |
| font-weight: 600; | |
| letter-spacing: 0.2em; | |
| text-transform: uppercase; | |
| color: rgba(255, 255, 255, 0.28); | |
| } | |
| #hud .keys .caption { margin-bottom: 0.45rem; } | |
| /* Keycap chips in the hints */ | |
| #hud .keys .key { | |
| display: inline-block; | |
| min-width: 1.5em; | |
| padding: 0.12em 0.4em; | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| border-bottom-width: 2px; | |
| border-radius: 5px; | |
| background: rgba(255, 255, 255, 0.05); | |
| color: rgba(255, 255, 255, 0.75); | |
| font-size: 0.62rem; | |
| font-weight: 600; | |
| line-height: 1.35; | |
| text-align: center; | |
| transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; | |
| } | |
| #hud .keys .key.lit { | |
| background: var(--yellow); | |
| border-color: var(--yellow); | |
| color: var(--ink); | |
| } | |
| #pad-hint { display: none; } | |
| body.pad-connected #pad-hint { display: inline; } | |
| /* Mini command sticks: dot = the twist the policy actually receives */ | |
| #sticks { | |
| display: flex; | |
| gap: 0.7rem; | |
| justify-content: flex-end; | |
| margin-bottom: 0.5rem; | |
| } | |
| .stickwrap { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 0.15rem; | |
| } | |
| .stickwrap span { | |
| font-size: 0.55rem; | |
| letter-spacing: 0.14em; | |
| text-transform: uppercase; | |
| color: rgba(255, 255, 255, 0.3); | |
| line-height: 1; | |
| } | |
| .stickbox { | |
| position: relative; | |
| width: 46px; | |
| height: 46px; | |
| border-radius: 50%; | |
| border: 1px solid rgba(255, 255, 255, 0.16); | |
| background: rgba(255, 255, 255, 0.03); | |
| } | |
| .stickdot { | |
| position: absolute; | |
| left: 50%; | |
| top: 50%; | |
| width: 12px; | |
| height: 12px; | |
| margin: -6px 0 0 -6px; | |
| border-radius: 50%; | |
| background: rgba(255, 255, 255, 0.35); | |
| transition: background 0.15s ease; | |
| } | |
| .stickbox.live .stickdot { background: var(--yellow); } | |
| #stats-wrap { | |
| position: fixed; | |
| top: 1.4rem; | |
| right: 1.6rem; | |
| z-index: 10; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-end; | |
| gap: 0.15rem; | |
| } | |
| #stats { | |
| font-size: 0.68rem; | |
| letter-spacing: 0.08em; | |
| text-align: right; | |
| color: rgba(255, 255, 255, 0.4); | |
| line-height: 1.7; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| @media (max-width: 700px) { | |
| #hud { flex-direction: column; align-items: flex-start; } | |
| #hud .keys { display: none; } | |
| #brand .sub { display: none; } | |
| } | |
| /* ── Welcome modal ─────────────────────────────────────────────── */ | |
| /* display:flex would override the hidden attribute's UA display:none, | |
| so hidden must win explicitly. */ | |
| #welcome[hidden] { display: none; } | |
| #welcome { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 30; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 1.5rem; | |
| background: rgba(8, 8, 12, 0.45); | |
| backdrop-filter: blur(16px) saturate(1.3); | |
| -webkit-backdrop-filter: blur(16px) saturate(1.3); | |
| opacity: 1; | |
| transition: opacity 0.35s ease; | |
| } | |
| #welcome.closing { opacity: 0; pointer-events: none; } | |
| #welcome .card { | |
| position: relative; | |
| max-width: 430px; | |
| width: 100%; | |
| padding: 2.2rem 2.2rem 2rem; | |
| border: 1px solid rgba(255, 255, 255, 0.12); | |
| border-radius: 20px; | |
| background: | |
| radial-gradient(120% 90% at 50% 0%, rgba(255, 210, 63, 0.07), transparent 55%), | |
| rgba(14, 14, 20, 0.92); | |
| box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55); | |
| text-align: center; | |
| animation: card-in 0.5s cubic-bezier(0.2, 0.9, 0.25, 1.15) both; | |
| } | |
| @keyframes card-in { | |
| from { transform: translateY(18px) scale(0.94); opacity: 0; } | |
| to { transform: none; opacity: 1; } | |
| } | |
| /* Duck-head mark from the site header: closed by default, open on | |
| hover (same swap as the site). */ | |
| #welcome .logo-head { | |
| position: relative; | |
| width: 7.2rem; | |
| margin: 0.3rem auto 0; | |
| filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.45)); | |
| } | |
| #welcome .logo-head img { display: block; width: 100%; } | |
| #welcome .logo-head .open { position: absolute; inset: 0; opacity: 0; } | |
| #welcome .logo-head:hover .open { opacity: 1; } | |
| #welcome .logo-head:hover .closed { opacity: 0; } | |
| #welcome .subtitle { | |
| margin-top: 0.55rem; | |
| font-size: 0.62rem; | |
| font-weight: 700; | |
| letter-spacing: 0.32em; | |
| text-transform: uppercase; | |
| color: rgba(255, 210, 63, 0.75); | |
| } | |
| #welcome .tagline { | |
| margin-top: 0.5rem; | |
| font-size: 0.8rem; | |
| line-height: 1.65; | |
| color: rgba(255, 255, 255, 0.6); | |
| } | |
| #welcome ul { | |
| list-style: none; | |
| margin: 1.4rem 0 1.6rem; | |
| text-align: left; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.55rem; | |
| } | |
| #welcome li { | |
| display: flex; | |
| align-items: baseline; | |
| gap: 0.7rem; | |
| font-size: 0.78rem; | |
| line-height: 1.5; | |
| color: rgba(255, 255, 255, 0.72); | |
| } | |
| #welcome li b { | |
| flex: 0 0 4.6rem; | |
| font-size: 0.62rem; | |
| font-weight: 700; | |
| letter-spacing: 0.16em; | |
| text-transform: uppercase; | |
| color: rgba(255, 210, 63, 0.85); | |
| } | |
| #welcome-go { | |
| appearance: none; | |
| border: none; | |
| background: var(--yellow); | |
| color: var(--ink); | |
| font: inherit; | |
| font-size: 0.75rem; | |
| font-weight: 700; | |
| letter-spacing: 0.14em; | |
| text-transform: uppercase; | |
| padding: 0.75rem 2.2rem; | |
| border-radius: 999px; | |
| cursor: pointer; | |
| transition: transform 0.15s ease, box-shadow 0.15s ease; | |
| } | |
| #welcome-go:hover { | |
| transform: translateY(-1px) scale(1.03); | |
| box-shadow: 0 6px 24px rgba(255, 210, 63, 0.35); | |
| } | |
| #welcome .fineprint { | |
| margin-top: 0.9rem; | |
| font-size: 0.6rem; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: rgba(255, 255, 255, 0.3); | |
| } | |
| #brand { cursor: pointer; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="scene"></div> | |
| <div class="halftone"></div> | |
| <div id="brand"> | |
| <b>Microduck</b> · real trained policies<br /> | |
| <span class="sub">MuJoCo + ONNX, live in your browser</span> | |
| </div> | |
| <div id="stats-wrap"> | |
| <div class="mode" id="mode-label"><span class="mode-text">Run</span></div> | |
| <div id="stats"></div> | |
| </div> | |
| <div id="hud" hidden> | |
| <div class="stack"> | |
| <span class="caption">Colour</span> | |
| <div class="row" id="swatches"></div> | |
| </div> | |
| <div class="keys"> | |
| <span class="caption">Controls</span> | |
| <div id="sticks"> | |
| <div class="stickwrap"> | |
| <div class="stickbox"><div class="stickdot" id="dot-move"></div></div> | |
| <span>move</span> | |
| </div> | |
| <div class="stickwrap"> | |
| <div class="stickbox"><div class="stickdot" id="dot-turn"></div></div> | |
| <span>turn</span> | |
| </div> | |
| </div> | |
| <span class="hint"><span class="key" id="key-fwd">↑</span><span class="key" id="key-back">↓</span> move</span> | |
| <span class="hint"><span class="key" id="key-turnl">←</span><span class="key" id="key-turnr">→</span> turn</span> | |
| <span class="hint"><span class="key" id="key-left">A</span><span class="key" id="key-right">E</span> strafe</span><br /> | |
| <span class="hint"><span class="key" id="key-roll">Space</span> roll</span> | |
| <span class="hint"><span class="key" id="key-reset">R</span> reset</span> | |
| drag to orbit · scroll to zoom<br /> | |
| <span id="pad-hint">gamepad: | |
| <span class="hint"><span class="key" id="key-pad-x">X</span> roll</span> | |
| <span class="hint"><span class="key" id="key-pad-sit">⇓</span> sit</span> | |
| <span class="hint"><span class="key" id="key-pad-run">⇑</span> run</span> | |
| <span class="hint"><span class="key" id="key-pad-rt">RT</span> quack</span> | |
| </span> | |
| </div> | |
| </div> | |
| <div id="loading">Compiling physics…</div> | |
| <div id="welcome" hidden> | |
| <div class="card" role="dialog" aria-modal="true" aria-label="Microduck playground"> | |
| <div class="logo-head"> | |
| <img class="closed" id="logo-head-closed" alt="Microduck" /> | |
| <img class="open" id="logo-head-open" alt="" aria-hidden="true" /> | |
| </div> | |
| <div class="subtitle">RL playground</div> | |
| <p class="tagline"> | |
| A real robot brain in your browser: MuJoCo physics compiled to | |
| WebAssembly, driven by the duck's actual trained neural policies | |
| at 50 Hz. No server, no pre-baked animation. | |
| </p> | |
| <ul> | |
| <li><b>Move</b><span>Arrows or ZQSD to run and turn, A / E to strafe, drag to orbit.</span></li> | |
| <li><b>Roll</b><span>Space triggers a barrel roll, then it gets back up.</span></li> | |
| <li><b>Gamepad</b><span>Plug one in - same mapping as the real robot.</span></li> | |
| <li><b>Ghosts</b><span>Other visitors right now appear as translucent ducks, live.</span></li> | |
| </ul> | |
| <button id="welcome-go">Waddle in</button> | |
| <div class="fineprint">click the title anytime to see this again</div> | |
| </div> | |
| </div> | |
| <script> | |
| // Welcome modal: shown on every load, dismissible by button, Escape | |
| // or clicking the blurred backdrop. The top-left brand reopens it. | |
| (() => { | |
| const modal = document.getElementById("welcome"); | |
| // Logo images need the same ?__sign JWT as every other subresource | |
| // on the private Space (rl.js hasn't installed its signer yet). | |
| const sign = new URLSearchParams(location.search).get("__sign"); | |
| const signedUrl = (u) => (sign ? `${u}?__sign=${encodeURIComponent(sign)}` : u); | |
| document.getElementById("logo-head-closed").src = signedUrl("./assets/duck-head-mark.webp"); | |
| document.getElementById("logo-head-open").src = signedUrl("./assets/duck-head-mark-open.webp"); | |
| const open = () => { | |
| modal.hidden = false; | |
| modal.classList.remove("closing"); | |
| }; | |
| const close = () => { | |
| modal.classList.add("closing"); | |
| setTimeout(() => { modal.hidden = true; }, 380); | |
| }; | |
| open(); | |
| document.getElementById("welcome-go").addEventListener("click", close); | |
| modal.addEventListener("click", (e) => { if (e.target === modal) close(); }); | |
| window.addEventListener("keydown", (e) => { | |
| if (e.code === "Escape" && !modal.hidden) close(); | |
| }); | |
| document.getElementById("brand").addEventListener("click", open); | |
| })(); | |
| </script> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://cdn.jsdelivr.net/npm/three@0.164.1/build/three.module.js", | |
| "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.164.1/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| <!-- rl.js is imported dynamically with the private-space ?__sign JWT | |
| appended: a plain src attribute can 401 when the browser blocks the | |
| *.static.hf.space auth cookie inside the hub iframe. --> | |
| <script type="module"> | |
| const V = "24"; // bump on rl.js changes to defeat heuristic caching | |
| const sign = new URLSearchParams(location.search).get("__sign"); | |
| const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : ""; | |
| import(`./rl.js?v=${V}${suffix}`); | |
| </script> | |
| </body> | |
| </html> | |