tfrere HF Staff Cursor commited on
Commit
073d628
·
1 Parent(s): c9f7a64

feat: infinite shader grid, edge-anchored UI, drop hero title

Browse files
Files changed (2) hide show
  1. index.html +42 -34
  2. rl.js +54 -34
index.html CHANGED
@@ -28,30 +28,20 @@
28
  mask-image: linear-gradient(to bottom, transparent 30%, black 100%);
29
  -webkit-mask-image: linear-gradient(to bottom, transparent 30%, black 100%);
30
  }
31
- header {
 
32
  position: fixed;
33
- top: 2.6rem;
34
- left: 0;
35
- right: 0;
36
- text-align: center;
37
- pointer-events: none;
38
- }
39
- header .facts {
40
  font-size: 0.72rem;
41
  font-weight: 600;
42
  letter-spacing: 0.14em;
43
  text-transform: uppercase;
44
  color: rgba(255, 255, 255, 0.55);
 
45
  }
46
- header .facts b { color: var(--yellow); font-weight: 600; }
47
- header h1 {
48
- margin-top: 0.5rem;
49
- font-size: clamp(1.6rem, 4vw, 2.6rem);
50
- font-weight: 700;
51
- letter-spacing: -0.03em;
52
- color: #fff;
53
- }
54
- header h1 #verb { color: var(--yellow); }
55
  #loading {
56
  position: fixed;
57
  inset: 0;
@@ -66,17 +56,25 @@
66
  padding: 0 2rem;
67
  z-index: 20;
68
  }
69
- /* Bottom control bar */
70
  #hud {
71
  position: fixed;
72
  bottom: 1.4rem;
73
- left: 0;
74
- right: 0;
 
 
 
 
 
 
 
 
75
  display: flex;
76
  flex-direction: column;
77
- align-items: center;
78
  gap: 0.7rem;
79
- z-index: 10;
80
  }
81
  #hud .row {
82
  display: flex;
@@ -122,6 +120,9 @@
122
  font-size: 0.7rem;
123
  letter-spacing: 0.08em;
124
  color: rgba(255, 255, 255, 0.4);
 
 
 
125
  }
126
  #hud .keys b { color: rgba(255, 255, 255, 0.75); font-weight: 600; }
127
  #stats {
@@ -137,27 +138,34 @@
137
  font-variant-numeric: tabular-nums;
138
  }
139
  #stats b { color: var(--yellow); font-weight: 600; }
 
 
 
 
 
140
  </style>
141
  </head>
142
  <body>
143
  <div id="scene"></div>
144
  <div class="halftone"></div>
145
- <header>
146
- <div class="facts"><b>Microduck</b> &middot; real trained policies &middot; MuJoCo + ONNX, live in your browser</div>
147
- <h1>This one actually learned to <span id="verb">run</span></h1>
148
- </header>
149
  <div id="stats"></div>
150
  <div id="hud" hidden>
151
- <div class="row">
152
- <button id="btn-walk" class="on">Run</button>
153
- <button id="btn-sit">Sit</button>
154
- <button id="btn-roulade">Roulade</button>
155
- <button id="btn-reset">Reset</button>
 
 
 
156
  </div>
157
- <div class="row" id="swatches"></div>
158
  <div class="keys">
159
- <b>&uarr;/&darr;</b> forward / back &nbsp; <b>&larr;/&rarr;</b> turn &nbsp;
160
- <b>A/E</b> strafe &nbsp; <b>R</b> reset &nbsp; drag to orbit
161
  </div>
162
  </div>
163
  <div id="loading">Compiling physics&hellip;</div>
 
28
  mask-image: linear-gradient(to bottom, transparent 30%, black 100%);
29
  -webkit-mask-image: linear-gradient(to bottom, transparent 30%, black 100%);
30
  }
31
+ /* Top-left corner: what this is */
32
+ #brand {
33
  position: fixed;
34
+ top: 1.4rem;
35
+ left: 1.6rem;
36
+ z-index: 10;
 
 
 
 
37
  font-size: 0.72rem;
38
  font-weight: 600;
39
  letter-spacing: 0.14em;
40
  text-transform: uppercase;
41
  color: rgba(255, 255, 255, 0.55);
42
+ line-height: 1.8;
43
  }
44
+ #brand b { color: var(--yellow); font-weight: 600; }
 
 
 
 
 
 
 
 
45
  #loading {
46
  position: fixed;
47
  inset: 0;
 
56
  padding: 0 2rem;
57
  z-index: 20;
58
  }
59
+ /* Bottom edges: controls left, key hints right */
60
  #hud {
61
  position: fixed;
62
  bottom: 1.4rem;
63
+ left: 1.6rem;
64
+ right: 1.6rem;
65
+ display: flex;
66
+ justify-content: space-between;
67
+ align-items: flex-end;
68
+ gap: 1rem;
69
+ z-index: 10;
70
+ pointer-events: none;
71
+ }
72
+ #hud .stack {
73
  display: flex;
74
  flex-direction: column;
75
+ align-items: flex-start;
76
  gap: 0.7rem;
77
+ pointer-events: auto;
78
  }
79
  #hud .row {
80
  display: flex;
 
120
  font-size: 0.7rem;
121
  letter-spacing: 0.08em;
122
  color: rgba(255, 255, 255, 0.4);
123
+ text-align: right;
124
+ line-height: 1.9;
125
+ pointer-events: auto;
126
  }
127
  #hud .keys b { color: rgba(255, 255, 255, 0.75); font-weight: 600; }
128
  #stats {
 
138
  font-variant-numeric: tabular-nums;
139
  }
140
  #stats b { color: var(--yellow); font-weight: 600; }
141
+ @media (max-width: 700px) {
142
+ #hud { flex-direction: column; align-items: flex-start; }
143
+ #hud .keys { display: none; }
144
+ #brand .sub { display: none; }
145
+ }
146
  </style>
147
  </head>
148
  <body>
149
  <div id="scene"></div>
150
  <div class="halftone"></div>
151
+ <div id="brand">
152
+ <b>Microduck</b> &middot; real trained policies<br />
153
+ <span class="sub">MuJoCo + ONNX, live in your browser</span>
154
+ </div>
155
  <div id="stats"></div>
156
  <div id="hud" hidden>
157
+ <div class="stack">
158
+ <div class="row">
159
+ <button id="btn-walk" class="on">Run</button>
160
+ <button id="btn-sit">Sit</button>
161
+ <button id="btn-roulade">Roulade</button>
162
+ <button id="btn-reset">Reset</button>
163
+ </div>
164
+ <div class="row" id="swatches"></div>
165
  </div>
 
166
  <div class="keys">
167
+ <b>&uarr;/&darr;</b> forward / back &nbsp; <b>&larr;/&rarr;</b> turn &nbsp; <b>A/E</b> strafe<br />
168
+ <b>R</b> reset &nbsp; drag to orbit &nbsp; scroll to zoom
169
  </div>
170
  </div>
171
  <div id="loading">Compiling physics&hellip;</div>
rl.js CHANGED
@@ -60,7 +60,6 @@ const mount = document.getElementById("scene");
60
  const loadingEl = document.getElementById("loading");
61
  const hudEl = document.getElementById("hud");
62
  const statsEl = document.getElementById("stats");
63
- const verbEl = document.getElementById("verb");
64
  const setLoading = (msg) => { loadingEl.textContent = msg; };
65
 
66
  // Surface boot failures on the page itself: a rejected top-level await
@@ -302,36 +301,58 @@ const rim = new THREE.DirectionalLight(0xffb366, 0.7);
302
  rim.position.set(0, 3, -2);
303
  scene.add(rim);
304
 
305
- function makeFloorTexture() {
306
- const N = 256;
307
- const c = document.createElement("canvas");
308
- c.width = c.height = N;
309
- const g = c.getContext("2d");
310
- g.fillStyle = "#131009";
311
- g.fillRect(0, 0, N, N);
312
- g.strokeStyle = "rgba(255, 179, 102, 0.05)";
313
- g.lineWidth = 2;
314
- g.strokeRect(1, 1, N - 2, N - 2);
315
- g.fillStyle = "rgba(255, 198, 30, 0.10)";
316
- for (const x of [0, N]) {
317
- for (const y of [0, N]) {
318
- g.beginPath();
319
- g.arc(x, y, 5, 0, Math.PI * 2);
320
- g.fill();
321
- }
322
- }
323
- const tex = new THREE.CanvasTexture(c);
324
- tex.colorSpace = THREE.SRGBColorSpace;
325
- tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
326
- tex.repeat.set(48, 48);
327
- return tex;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  }
329
- const floor = new THREE.Mesh(
330
- new THREE.PlaneGeometry(12, 12),
331
- new THREE.MeshStandardMaterial({ map: makeFloorTexture(), roughness: 0.9, metalness: 0.0 }),
332
- );
333
- floor.rotation.x = -Math.PI / 2;
334
- scene.add(floor);
335
 
336
  const rig = await rigPromise;
337
  scene.add(rig.placer);
@@ -370,6 +391,9 @@ function syncRig() {
370
  _follow.copy(_target).sub(controls.target).multiplyScalar(0.06);
371
  controls.target.add(_follow);
372
  camera.position.add(_follow);
 
 
 
373
  }
374
 
375
  // Quack: a quick jaw flap on every mode/colour change. The jaw isn't a
@@ -444,9 +468,6 @@ const btnSit = document.getElementById("btn-sit");
444
  const btnRoulade = document.getElementById("btn-roulade");
445
  document.getElementById("btn-reset").addEventListener("click", resetSim);
446
 
447
- // The headline verb tracks the active policy.
448
- const VERBS = { walk: "run", sitstand: "sit", roulade: "roll over" };
449
-
450
  function setMode(next) {
451
  quack();
452
  if (next !== "sit") {
@@ -477,7 +498,6 @@ function syncButtons() {
477
  btnWalk.classList.toggle("on", mode === "walk" || (mode === "sitstand" && !sitting));
478
  btnSit.classList.toggle("on", sitting);
479
  btnRoulade.classList.toggle("on", mode === "roulade");
480
- verbEl.textContent = VERBS[mode];
481
  }
482
  btnWalk.addEventListener("click", () => setMode("walk"));
483
  btnSit.addEventListener("click", () => setMode("sit"));
 
60
  const loadingEl = document.getElementById("loading");
61
  const hudEl = document.getElementById("hud");
62
  const statsEl = document.getElementById("stats");
 
63
  const setLoading = (msg) => { loadingEl.textContent = msg; };
64
 
65
  // Surface boot failures on the page itself: a rejected top-level await
 
301
  rim.position.set(0, 3, -2);
302
  scene.add(rim);
303
 
304
+ // Infinite shader grid, ported from drei's <Grid> (we're in vanilla three,
305
+ // not R3F): anti-aliased world-space lines at cell/section frequencies with
306
+ // a radial fade around the duck. Lines derive from world coordinates, so
307
+ // re-centering the mesh under the camera target every frame makes the grid
308
+ // effectively infinite without any visible swimming.
309
+ function makeInfiniteGrid() {
310
+ const material = new THREE.ShaderMaterial({
311
+ transparent: true,
312
+ depthWrite: false,
313
+ uniforms: {
314
+ uCell: { value: 0.1 },
315
+ uSection: { value: 0.5 },
316
+ uCellColor: { value: new THREE.Color(0x7d7360) },
317
+ uSectionColor: { value: new THREE.Color(0xffb366) },
318
+ uFadeDist: { value: 3.0 },
319
+ uFocus: { value: new THREE.Vector3() },
320
+ },
321
+ vertexShader: /* glsl */ `
322
+ varying vec3 vWorld;
323
+ void main() {
324
+ vec4 w = modelMatrix * vec4(position, 1.0);
325
+ vWorld = w.xyz;
326
+ gl_Position = projectionMatrix * viewMatrix * w;
327
+ }
328
+ `,
329
+ fragmentShader: /* glsl */ `
330
+ varying vec3 vWorld;
331
+ uniform float uCell, uSection, uFadeDist;
332
+ uniform vec3 uCellColor, uSectionColor, uFocus;
333
+ float gridLine(vec2 p, float size) {
334
+ vec2 r = p / size;
335
+ vec2 g = abs(fract(r - 0.5) - 0.5) / fwidth(r);
336
+ return 1.0 - min(min(g.x, g.y), 1.0);
337
+ }
338
+ void main() {
339
+ float cell = gridLine(vWorld.xz, uCell);
340
+ float section = gridLine(vWorld.xz, uSection);
341
+ float d = distance(vWorld.xz, uFocus.xz);
342
+ float fade = pow(clamp(1.0 - d / uFadeDist, 0.0, 1.0), 1.6);
343
+ vec3 col = mix(uCellColor, uSectionColor, section);
344
+ float alpha = max(section * 0.45, cell * 0.3) * fade;
345
+ if (alpha < 0.004) discard;
346
+ gl_FragColor = vec4(col, alpha);
347
+ }
348
+ `,
349
+ });
350
+ const mesh = new THREE.Mesh(new THREE.PlaneGeometry(30, 30), material);
351
+ mesh.rotation.x = -Math.PI / 2;
352
+ return mesh;
353
  }
354
+ const grid = makeInfiniteGrid();
355
+ scene.add(grid);
 
 
 
 
356
 
357
  const rig = await rigPromise;
358
  scene.add(rig.placer);
 
391
  _follow.copy(_target).sub(controls.target).multiplyScalar(0.06);
392
  controls.target.add(_follow);
393
  camera.position.add(_follow);
394
+ // Keep the grid plane (and its fade center) under the action.
395
+ grid.position.set(controls.target.x, 0, controls.target.z);
396
+ grid.material.uniforms.uFocus.value.copy(controls.target);
397
  }
398
 
399
  // Quack: a quick jaw flap on every mode/colour change. The jaw isn't a
 
468
  const btnRoulade = document.getElementById("btn-roulade");
469
  document.getElementById("btn-reset").addEventListener("click", resetSim);
470
 
 
 
 
471
  function setMode(next) {
472
  quack();
473
  if (next !== "sit") {
 
498
  btnWalk.classList.toggle("on", mode === "walk" || (mode === "sitstand" && !sitting));
499
  btnSit.classList.toggle("on", sitting);
500
  btnRoulade.classList.toggle("on", mode === "roulade");
 
501
  }
502
  btnWalk.addEventListener("click", () => setMode("walk"));
503
  btnSit.addEventListener("click", () => setMode("sit"));