Fix spinner: CSS rotate on compositor thread, not setInterval
Browse files- index.html +15 -24
index.html
CHANGED
|
@@ -317,7 +317,7 @@
|
|
| 317 |
.doc-card.ranked-3 .doc-score-num { color: #60A5FA; }
|
| 318 |
.doc-card.ranked-3 .doc-score-bar { background: #60A5FA; }
|
| 319 |
|
| 320 |
-
/* Active card being scored — amber left-border
|
| 321 |
.doc-card.computing {
|
| 322 |
border-color: var(--amber-dim);
|
| 323 |
box-shadow: inset 3px 0 0 var(--amber), 0 0 12px rgba(245,158,11,0.08);
|
|
@@ -329,21 +329,20 @@
|
|
| 329 |
50% { box-shadow: inset 3px 0 0 var(--amber), 0 0 18px rgba(245,158,11,0.18); }
|
| 330 |
}
|
| 331 |
|
|
|
|
| 332 |
.doc-card.computing .doc-score-num {
|
|
|
|
| 333 |
color: var(--amber);
|
| 334 |
-
font-size:
|
| 335 |
-
|
| 336 |
-
|
| 337 |
}
|
| 338 |
|
| 339 |
-
@keyframes
|
| 340 |
-
|
| 341 |
-
/* CSS content animation doesn't work on non-pseudo-elements — use JS instead */
|
| 342 |
-
0%, 100% { opacity: 1; }
|
| 343 |
-
50% { opacity: 0.35; }
|
| 344 |
}
|
| 345 |
|
| 346 |
-
/*
|
| 347 |
.doc-card.computing .doc-score-bar {
|
| 348 |
background: var(--amber);
|
| 349 |
width: 100% !important;
|
|
@@ -352,9 +351,9 @@
|
|
| 352 |
}
|
| 353 |
|
| 354 |
@keyframes bar-indeterminate {
|
| 355 |
-
0% { transform: scaleX(0);
|
| 356 |
-
50% { transform: scaleX(1);
|
| 357 |
-
100% { transform: scaleX(0);
|
| 358 |
}
|
| 359 |
|
| 360 |
/* ── Unscored doc (has text but wasn't in last rank run) ── */
|
|
@@ -761,17 +760,11 @@
|
|
| 761 |
rankBtn.classList.add("running");
|
| 762 |
rankBtn.textContent = "⟳ Ranking…";
|
| 763 |
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
toScore.forEach(({ card }) => {
|
| 768 |
card.classList.add("computing");
|
| 769 |
-
|
| 770 |
-
numEl.textContent = SPINNER[0];
|
| 771 |
-
let i = 0;
|
| 772 |
-
spinners.set(card, setInterval(() => {
|
| 773 |
-
numEl.textContent = SPINNER[++i % SPINNER.length];
|
| 774 |
-
}, 80));
|
| 775 |
});
|
| 776 |
|
| 777 |
const scored = [];
|
|
@@ -797,7 +790,6 @@
|
|
| 797 |
const en = Math.exp(ln - m);
|
| 798 |
const score = ey / (ey + en);
|
| 799 |
|
| 800 |
-
clearInterval(spinners.get(card));
|
| 801 |
card.classList.remove("computing");
|
| 802 |
animateScore(
|
| 803 |
card.querySelector(".doc-score-num"),
|
|
@@ -808,7 +800,6 @@
|
|
| 808 |
|
| 809 |
} catch (err) {
|
| 810 |
console.error("Inference error on doc", id, err);
|
| 811 |
-
clearInterval(spinners.get(card));
|
| 812 |
card.classList.remove("computing");
|
| 813 |
card.querySelector(".doc-score-num").textContent = "err";
|
| 814 |
setStatus("error", `Error: ${err?.message ?? String(err)}`);
|
|
|
|
| 317 |
.doc-card.ranked-3 .doc-score-num { color: #60A5FA; }
|
| 318 |
.doc-card.ranked-3 .doc-score-bar { background: #60A5FA; }
|
| 319 |
|
| 320 |
+
/* Active card being scored — amber left-border + glow pulse */
|
| 321 |
.doc-card.computing {
|
| 322 |
border-color: var(--amber-dim);
|
| 323 |
box-shadow: inset 3px 0 0 var(--amber), 0 0 12px rgba(245,158,11,0.08);
|
|
|
|
| 329 |
50% { box-shadow: inset 3px 0 0 var(--amber), 0 0 18px rgba(245,158,11,0.18); }
|
| 330 |
}
|
| 331 |
|
| 332 |
+
/* CSS-only spinner — works even when WASM blocks the JS event loop */
|
| 333 |
.doc-card.computing .doc-score-num {
|
| 334 |
+
display: inline-block; /* required for transform */
|
| 335 |
color: var(--amber);
|
| 336 |
+
font-size: 1.2rem;
|
| 337 |
+
animation: spin 0.7s linear infinite;
|
| 338 |
+
transform-origin: center;
|
| 339 |
}
|
| 340 |
|
| 341 |
+
@keyframes spin {
|
| 342 |
+
to { transform: rotate(360deg); }
|
|
|
|
|
|
|
|
|
|
| 343 |
}
|
| 344 |
|
| 345 |
+
/* Indeterminate progress bar (also CSS-only) */
|
| 346 |
.doc-card.computing .doc-score-bar {
|
| 347 |
background: var(--amber);
|
| 348 |
width: 100% !important;
|
|
|
|
| 351 |
}
|
| 352 |
|
| 353 |
@keyframes bar-indeterminate {
|
| 354 |
+
0% { transform: scaleX(0); opacity: 0.6; }
|
| 355 |
+
50% { transform: scaleX(1); opacity: 1; }
|
| 356 |
+
100% { transform: scaleX(0); opacity: 0.6; }
|
| 357 |
}
|
| 358 |
|
| 359 |
/* ── Unscored doc (has text but wasn't in last rank run) ── */
|
|
|
|
| 760 |
rankBtn.classList.add("running");
|
| 761 |
rankBtn.textContent = "⟳ Ranking…";
|
| 762 |
|
| 763 |
+
// Set ⟳ once — CSS rotates it on the compositor thread,
|
| 764 |
+
// so it spins even while WASM inference blocks the JS event loop.
|
|
|
|
| 765 |
toScore.forEach(({ card }) => {
|
| 766 |
card.classList.add("computing");
|
| 767 |
+
card.querySelector(".doc-score-num").textContent = "⟳";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 768 |
});
|
| 769 |
|
| 770 |
const scored = [];
|
|
|
|
| 790 |
const en = Math.exp(ln - m);
|
| 791 |
const score = ey / (ey + en);
|
| 792 |
|
|
|
|
| 793 |
card.classList.remove("computing");
|
| 794 |
animateScore(
|
| 795 |
card.querySelector(".doc-score-num"),
|
|
|
|
| 800 |
|
| 801 |
} catch (err) {
|
| 802 |
console.error("Inference error on doc", id, err);
|
|
|
|
| 803 |
card.classList.remove("computing");
|
| 804 |
card.querySelector(".doc-score-num").textContent = "err";
|
| 805 |
setStatus("error", `Error: ${err?.message ?? String(err)}`);
|