Spaces:
Running
Running
Remove stage buttons, add refresh button, load all previews on desktop
Browse files- app.js +36 -164
- g1-moves/space/CLAUDE.md +7 -0
- index.html +0 -7
- 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
|
| 185 |
-
|
| 186 |
-
|
| 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="
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 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="
|
| 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 |
-
//
|
| 247 |
-
var
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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:
|
| 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-
|
| 483 |
display: flex;
|
| 484 |
-
|
|
|
|
| 485 |
}
|
| 486 |
|
| 487 |
-
.card-
|
| 488 |
-
width:
|
| 489 |
-
height:
|
| 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-
|
| 505 |
-
border-color:
|
| 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-
|
| 516 |
-
|
| 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; }
|