exptech commited on
Commit
6cea1df
·
verified ·
1 Parent(s): 82512f0

Remove stage buttons, add refresh button, load all previews on desktop

Browse files
Files changed (4) hide show
  1. app.js +36 -164
  2. g1-moves/space/CLAUDE.md +7 -0
  3. index.html +0 -7
  4. style.css +14 -27
app.js CHANGED
@@ -11,7 +11,6 @@
11
  let allClips = [];
12
  let baseUrl = '';
13
  let activeCategory = 'all';
14
- let activeStage = 'policy';
15
  const isMobile = window.matchMedia('(max-width: 480px)').matches;
16
 
17
  // ------------------------------------
@@ -33,7 +32,6 @@
33
  updateStats(data.stats);
34
  filterAndRender();
35
  setupCategoryButtons();
36
- setupStageButtons();
37
  setupModal();
38
  setupScrollReveal();
39
  } catch (err) {
@@ -181,26 +179,25 @@
181
  card.className = 'clip-card';
182
  card.dataset.clipId = clip.id;
183
 
184
- const stageData = clip.stages[activeStage];
185
- const mediaSrc = stageData
186
- ? mediaUrl(stageData)
187
- : null;
188
- const isVideo = mediaSrc && mediaSrc.endsWith('.mp4');
189
- const useViewer = activeStage === 'policy' && clip.has_onnx;
190
-
191
- const stages = ['capture', 'retarget', 'training', 'policy'];
192
 
193
  var mediaHtml;
194
  if (useViewer) {
195
- mediaHtml = '<iframe data-src="viewer.html?clip=' + encodeURIComponent(clip.id) +
196
- '&category=' + encodeURIComponent(clip.category) +
197
  '&embed=1" class="lazy-iframe" allowtransparency="true" style="width:100%;height:100%;border:none;"></iframe>';
198
- } else if (mediaSrc) {
199
- mediaHtml = isVideo
200
- ? '<video data-src="' + mediaSrc + '" autoplay muted loop playsinline preload="none" class="lazy-video"></video>'
201
- : '<img data-src="' + mediaSrc + '" alt="' + escHtml(clip.name) + '" loading="lazy">';
202
  } else {
203
- mediaHtml = '<div class="card-placeholder">Not available</div>';
 
 
 
 
 
 
 
 
 
204
  }
205
 
206
  card.innerHTML =
@@ -216,21 +213,14 @@
216
  '<span class="card-separator"></span>' +
217
  '<span>' + clip.performer + '</span>' +
218
  '</div>' +
219
- '<div class="card-stages">' +
220
- stages.map(function (s) {
221
- var has = !!clip.stages[s];
222
- var active = s === activeStage;
223
- var cls = 'card-stage-btn' +
224
- (active ? ' active' : '') +
225
- (has ? '' : ' disabled');
226
- return '<button class="' + cls + '" data-stage="' + s + '"' +
227
- (has ? '' : ' disabled') +
228
- ' title="' + capitalize(s) + '">' +
229
- s.charAt(0).toUpperCase() +
230
- '</button>';
231
- }).join('') +
232
  (clip.has_policy
233
- ? '<a class="card-view-btn" href="viewer.html?clip=' + encodeURIComponent(clip.id) + '&category=' + encodeURIComponent(clip.category) + '" title="Run policy in browser">' +
234
  '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>' +
235
  'View' +
236
  '</a>'
@@ -243,71 +233,29 @@
243
  openModal(clip);
244
  });
245
 
246
- // Per-card stage buttons
247
- var stageBtns = card.querySelectorAll('.card-stage-btn:not(.disabled)');
248
- for (var i = 0; i < stageBtns.length; i++) {
249
- stageBtns[i].addEventListener('click', (function (btn) {
250
- return function (e) {
251
- e.stopPropagation();
252
- switchCardStage(card, clip, btn.dataset.stage);
253
- };
254
- })(stageBtns[i]));
 
 
 
255
  }
256
 
257
  return card;
258
  }
259
 
260
- function switchCardStage(card, clip, stage) {
261
- var stageData = clip.stages[stage];
262
- if (!stageData) return;
263
-
264
- var useViewer = stage === 'policy' && clip.has_onnx;
265
- var src = mediaUrl(stageData);
266
- var isVideo = src && src.endsWith('.mp4');
267
- var existing = card.querySelector('.card-media img, .card-media video, .card-media iframe');
268
- var placeholder = card.querySelector('.card-placeholder');
269
- var target = existing || placeholder;
270
-
271
- if (target) {
272
- var newEl;
273
- if (useViewer) {
274
- newEl = document.createElement('iframe');
275
- newEl.src = 'viewer.html?clip=' + encodeURIComponent(clip.id) +
276
- '&category=' + encodeURIComponent(clip.category) + '&embed=1';
277
- newEl.style.cssText = 'width:100%;height:100%;border:none;';
278
- newEl.setAttribute('allowtransparency', 'true');
279
- } else if (isVideo) {
280
- newEl = document.createElement('video');
281
- newEl.src = src;
282
- newEl.autoplay = true;
283
- newEl.muted = true;
284
- newEl.loop = true;
285
- newEl.playsInline = true;
286
- } else {
287
- newEl = document.createElement('img');
288
- newEl.src = src;
289
- newEl.alt = clip.name;
290
- }
291
- target.replaceWith(newEl);
292
- }
293
-
294
- // Update active state on buttons
295
- var btns = card.querySelectorAll('.card-stage-btn');
296
- for (var i = 0; i < btns.length; i++) {
297
- btns[i].classList.toggle('active', btns[i].dataset.stage === stage);
298
- }
299
- }
300
-
301
  // ------------------------------------
302
- // Lazy loading (with iframe cap)
303
  // ------------------------------------
304
  var lazyObserver = null;
305
- var iframeObserver = null;
306
- var MAX_IFRAMES = 5;
307
- var activeIframes = new Set();
308
 
309
  function observeLazyImages() {
310
- // Standard lazy loading for images and videos
311
  if (!lazyObserver) {
312
  lazyObserver = new IntersectionObserver(function (entries) {
313
  entries.forEach(function (entry) {
@@ -315,75 +263,17 @@
315
  var el = entry.target;
316
  if (el.dataset.src) {
317
  el.src = el.dataset.src;
318
- el.removeAttribute('data-src');
319
  }
320
  lazyObserver.unobserve(el);
321
  }
322
  });
323
- }, { rootMargin: isMobile ? '100px' : '300px' });
324
  }
325
 
326
- var lazyMedia = grid.querySelectorAll('img[data-src], video[data-src]');
327
  for (var i = 0; i < lazyMedia.length; i++) {
328
  lazyObserver.observe(lazyMedia[i]);
329
  }
330
-
331
- // Capped iframe loading: max MAX_IFRAMES active at once
332
- // When an iframe leaves the viewport, unload it to free memory
333
- if (!iframeObserver) {
334
- iframeObserver = new IntersectionObserver(function (entries) {
335
- entries.forEach(function (entry) {
336
- var iframe = entry.target;
337
- if (entry.isIntersecting) {
338
- // Want to load — check if we have capacity
339
- if (activeIframes.size < MAX_IFRAMES) {
340
- loadIframe(iframe);
341
- }
342
- // If at capacity, this iframe stays queued (data-src set, no src)
343
- } else {
344
- // Left viewport — unload to free memory
345
- unloadIframe(iframe);
346
- }
347
- });
348
- // After processing exits, try to fill any freed slots
349
- fillIframeSlots();
350
- }, { rootMargin: isMobile ? '100px' : '300px' });
351
- }
352
-
353
- var iframes = grid.querySelectorAll('iframe.lazy-iframe');
354
- for (var i = 0; i < iframes.length; i++) {
355
- iframeObserver.observe(iframes[i]);
356
- }
357
- }
358
-
359
- function loadIframe(iframe) {
360
- if (activeIframes.has(iframe)) return;
361
- if (iframe.dataset.src) {
362
- iframe.src = iframe.dataset.src;
363
- activeIframes.add(iframe);
364
- }
365
- }
366
-
367
- function unloadIframe(iframe) {
368
- if (!activeIframes.has(iframe)) return;
369
- iframe.src = 'about:blank';
370
- activeIframes.delete(iframe);
371
- }
372
-
373
- function fillIframeSlots() {
374
- if (activeIframes.size >= MAX_IFRAMES) return;
375
- // Find visible iframes that aren't loaded yet
376
- var iframes = grid.querySelectorAll('iframe.lazy-iframe[data-src]');
377
- for (var i = 0; i < iframes.length; i++) {
378
- if (activeIframes.size >= MAX_IFRAMES) break;
379
- if (activeIframes.has(iframes[i])) continue;
380
- // Check if it's currently in the viewport
381
- var rect = iframes[i].getBoundingClientRect();
382
- var margin = isMobile ? 100 : 300;
383
- if (rect.bottom > -margin && rect.top < window.innerHeight + margin) {
384
- loadIframe(iframes[i]);
385
- }
386
- }
387
  }
388
 
389
  // ------------------------------------
@@ -425,20 +315,6 @@
425
  }
426
  }
427
 
428
- // ------------------------------------
429
- // Global stage buttons
430
- // ------------------------------------
431
- function setupStageButtons() {
432
- var btns = document.querySelectorAll('.global-stage-btn');
433
- for (var i = 0; i < btns.length; i++) {
434
- btns[i].addEventListener('click', function () {
435
- setActiveBtn('.global-stage-btn', this);
436
- activeStage = this.dataset.stage;
437
- filterAndRender();
438
- });
439
- }
440
- }
441
-
442
  function setActiveBtn(selector, activeEl) {
443
  var btns = document.querySelectorAll(selector);
444
  for (var i = 0; i < btns.length; i++) {
@@ -554,10 +430,6 @@
554
  return m + ':' + (s < 10 ? '0' : '') + s;
555
  }
556
 
557
- function capitalize(str) {
558
- return str.charAt(0).toUpperCase() + str.slice(1);
559
- }
560
-
561
  function escHtml(str) {
562
  var div = document.createElement('div');
563
  div.textContent = str;
 
11
  let allClips = [];
12
  let baseUrl = '';
13
  let activeCategory = 'all';
 
14
  const isMobile = window.matchMedia('(max-width: 480px)').matches;
15
 
16
  // ------------------------------------
 
32
  updateStats(data.stats);
33
  filterAndRender();
34
  setupCategoryButtons();
 
35
  setupModal();
36
  setupScrollReveal();
37
  } catch (err) {
 
179
  card.className = 'clip-card';
180
  card.dataset.clipId = clip.id;
181
 
182
+ const useViewer = clip.has_onnx;
183
+ var viewerUrl = 'viewer.html?clip=' + encodeURIComponent(clip.id) +
184
+ '&category=' + encodeURIComponent(clip.category);
 
 
 
 
 
185
 
186
  var mediaHtml;
187
  if (useViewer) {
188
+ mediaHtml = '<iframe data-src="' + viewerUrl +
 
189
  '&embed=1" class="lazy-iframe" allowtransparency="true" style="width:100%;height:100%;border:none;"></iframe>';
 
 
 
 
190
  } else {
191
+ var stageData = clip.stages.policy || clip.stages.training || clip.stages.retarget || clip.stages.capture;
192
+ var mediaSrc = stageData ? mediaUrl(stageData) : null;
193
+ var isVideo = mediaSrc && mediaSrc.endsWith('.mp4');
194
+ if (mediaSrc) {
195
+ mediaHtml = isVideo
196
+ ? '<video data-src="' + mediaSrc + '" autoplay muted loop playsinline preload="none" class="lazy-video"></video>'
197
+ : '<img data-src="' + mediaSrc + '" alt="' + escHtml(clip.name) + '" loading="lazy">';
198
+ } else {
199
+ mediaHtml = '<div class="card-placeholder">Not available</div>';
200
+ }
201
  }
202
 
203
  card.innerHTML =
 
213
  '<span class="card-separator"></span>' +
214
  '<span>' + clip.performer + '</span>' +
215
  '</div>' +
216
+ '<div class="card-actions">' +
217
+ (useViewer
218
+ ? '<button class="card-refresh-btn" title="Reload viewer">' +
219
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 4v6h6"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>' +
220
+ '</button>'
221
+ : '') +
 
 
 
 
 
 
 
222
  (clip.has_policy
223
+ ? '<a class="card-view-btn" href="' + viewerUrl + '" title="Run policy in browser">' +
224
  '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>' +
225
  'View' +
226
  '</a>'
 
233
  openModal(clip);
234
  });
235
 
236
+ // Refresh button
237
+ var refreshBtn = card.querySelector('.card-refresh-btn');
238
+ if (refreshBtn) {
239
+ refreshBtn.addEventListener('click', function (e) {
240
+ e.stopPropagation();
241
+ var iframe = card.querySelector('iframe');
242
+ if (iframe) {
243
+ var src = iframe.dataset.src || iframe.src;
244
+ iframe.src = 'about:blank';
245
+ setTimeout(function () { iframe.src = src; }, 50);
246
+ }
247
+ });
248
  }
249
 
250
  return card;
251
  }
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  // ------------------------------------
254
+ // Lazy loading
255
  // ------------------------------------
256
  var lazyObserver = null;
 
 
 
257
 
258
  function observeLazyImages() {
 
259
  if (!lazyObserver) {
260
  lazyObserver = new IntersectionObserver(function (entries) {
261
  entries.forEach(function (entry) {
 
263
  var el = entry.target;
264
  if (el.dataset.src) {
265
  el.src = el.dataset.src;
 
266
  }
267
  lazyObserver.unobserve(el);
268
  }
269
  });
270
+ }, { rootMargin: '300px' });
271
  }
272
 
273
+ var lazyMedia = grid.querySelectorAll('img[data-src], video[data-src], iframe.lazy-iframe[data-src]');
274
  for (var i = 0; i < lazyMedia.length; i++) {
275
  lazyObserver.observe(lazyMedia[i]);
276
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  }
278
 
279
  // ------------------------------------
 
315
  }
316
  }
317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  function setActiveBtn(selector, activeEl) {
319
  var btns = document.querySelectorAll(selector);
320
  for (var i = 0; i < btns.length; i++) {
 
430
  return m + ':' + (s < 10 ? '0' : '') + s;
431
  }
432
 
 
 
 
 
433
  function escHtml(str) {
434
  var div = document.createElement('div');
435
  div.textContent = str;
g1-moves/space/CLAUDE.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <claude-mem-context>
2
+ # Recent Activity
3
+
4
+ <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
5
+
6
+ *No recent activity*
7
+ </claude-mem-context>
index.html CHANGED
@@ -79,13 +79,6 @@
79
  <button class="category-btn" data-category="karate">Karate</button>
80
  <button class="category-btn" data-category="bonus">Bonus</button>
81
  </div>
82
- <div class="filter-group">
83
- <span class="filter-label">Stage</span>
84
- <button class="global-stage-btn" data-stage="capture">Capture</button>
85
- <button class="global-stage-btn" data-stage="retarget">Retarget</button>
86
- <button class="global-stage-btn" data-stage="training">Training</button>
87
- <button class="global-stage-btn active" data-stage="policy">Policy</button>
88
- </div>
89
  </section>
90
 
91
  <!-- Gallery -->
 
79
  <button class="category-btn" data-category="karate">Karate</button>
80
  <button class="category-btn" data-category="bonus">Bonus</button>
81
  </div>
 
 
 
 
 
 
 
82
  </section>
83
 
84
  <!-- Gallery -->
style.css CHANGED
@@ -279,8 +279,7 @@ a:hover { opacity: 0.8; }
279
  margin-right: 0.4rem;
280
  }
281
 
282
- .category-btn,
283
- .global-stage-btn {
284
  font-family: var(--font-mono);
285
  font-size: 0.7rem;
286
  letter-spacing: 0.04em;
@@ -293,14 +292,12 @@ a:hover { opacity: 0.8; }
293
  transition: all 0.2s;
294
  }
295
 
296
- .category-btn:hover,
297
- .global-stage-btn:hover {
298
  border-color: var(--border-hover);
299
  color: var(--text-primary);
300
  }
301
 
302
- .category-btn.active,
303
- .global-stage-btn.active {
304
  background: var(--accent-dim);
305
  border-color: rgba(0, 212, 255, 0.35);
306
  color: var(--accent);
@@ -479,42 +476,34 @@ a:hover { opacity: 0.8; }
479
  background: var(--text-tertiary);
480
  }
481
 
482
- .card-stages {
483
  display: flex;
484
- gap: 0.3rem;
 
485
  }
486
 
487
- .card-stage-btn {
488
- width: 26px;
489
- height: 26px;
490
  border-radius: 4px;
491
  border: 1px solid var(--border);
492
  background: transparent;
493
  color: var(--text-secondary);
494
- font-family: var(--font-mono);
495
- font-size: 0.6rem;
496
- font-weight: 600;
497
  cursor: pointer;
498
  transition: all 0.15s;
499
  display: flex;
500
  align-items: center;
501
  justify-content: center;
 
502
  }
503
 
504
- .card-stage-btn:hover:not(.disabled) {
505
- border-color: rgba(0, 212, 255, 0.4);
506
- color: var(--accent);
507
- }
508
-
509
- .card-stage-btn.active {
510
- background: var(--accent-dim);
511
- border-color: rgba(0, 212, 255, 0.35);
512
  color: var(--accent);
513
  }
514
 
515
- .card-stage-btn.disabled {
516
- opacity: 0.2;
517
- cursor: default;
518
  }
519
 
520
  .card-view-btn {
@@ -1076,8 +1065,6 @@ footer {
1076
  .card-info { padding: 0.6rem 0.75rem 0.75rem; }
1077
  .card-title { font-size: 0.85rem; }
1078
  .card-meta { font-size: 0.65rem; }
1079
- .card-stage-btn { width: 24px; height: 24px; font-size: 0.55rem; }
1080
-
1081
  .pipeline-strip { transform: scale(0.7); }
1082
 
1083
  .stats-bar { gap: 1rem 2rem; }
 
279
  margin-right: 0.4rem;
280
  }
281
 
282
+ .category-btn {
 
283
  font-family: var(--font-mono);
284
  font-size: 0.7rem;
285
  letter-spacing: 0.04em;
 
292
  transition: all 0.2s;
293
  }
294
 
295
+ .category-btn:hover {
 
296
  border-color: var(--border-hover);
297
  color: var(--text-primary);
298
  }
299
 
300
+ .category-btn.active {
 
301
  background: var(--accent-dim);
302
  border-color: rgba(0, 212, 255, 0.35);
303
  color: var(--accent);
 
476
  background: var(--text-tertiary);
477
  }
478
 
479
+ .card-actions {
480
  display: flex;
481
+ align-items: center;
482
+ gap: 0.4rem;
483
  }
484
 
485
+ .card-refresh-btn {
486
+ width: 28px;
487
+ height: 28px;
488
  border-radius: 4px;
489
  border: 1px solid var(--border);
490
  background: transparent;
491
  color: var(--text-secondary);
 
 
 
492
  cursor: pointer;
493
  transition: all 0.15s;
494
  display: flex;
495
  align-items: center;
496
  justify-content: center;
497
+ padding: 0;
498
  }
499
 
500
+ .card-refresh-btn:hover {
501
+ border-color: var(--accent);
 
 
 
 
 
 
502
  color: var(--accent);
503
  }
504
 
505
+ .card-refresh-btn svg {
506
+ flex-shrink: 0;
 
507
  }
508
 
509
  .card-view-btn {
 
1065
  .card-info { padding: 0.6rem 0.75rem 0.75rem; }
1066
  .card-title { font-size: 0.85rem; }
1067
  .card-meta { font-size: 0.65rem; }
 
 
1068
  .pipeline-strip { transform: scale(0.7); }
1069
 
1070
  .stats-bar { gap: 1rem 2rem; }