feat(biochem): skeleton loaders for async fetches

Replace plain "Загрузка..." placeholders with shimmer-animated skeletons
matching the actual layout shape:
- library: 12 placeholder cards (canvas + 2 lines)
- reactions: 6 row skeletons (stripe + title + 2 text lines)
- properties: 10 sidebar row shimmers (thumb + 2 lines)
- biochem editor: 4-5 row skeletons for saved-molecules and challenges lists

No existing skeleton classes in ls.css; added local .bc-sk-* helpers per page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-20 19:49:54 +03:00
parent d3b1cd75a0
commit 29ef974e35
4 changed files with 139 additions and 21 deletions
+38 -4
View File
@@ -16,6 +16,26 @@
/* ── 3D mode button ── */
.tool-btn.mode-3d-active { background: rgba(6,214,224,.2) !important; border-color: #06D6E0 !important; color: #06D6E0 !important; }
/* ── Shimmer skeleton ── */
@keyframes bc-shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.bc-sk {
background: linear-gradient(90deg,
rgba(255,255,255,0.04) 0%,
rgba(255,255,255,0.10) 50%,
rgba(255,255,255,0.04) 100%);
background-size: 200% 100%;
animation: bc-shimmer 1.6s infinite;
border-radius: 8px;
}
.bc-sk-line { height: 11px; margin: 5px 0; }
.bc-sk-line.sm { width: 55%; }
.bc-sk-line.md { width: 80%; }
.bc-sk-saved { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,.05); }
.bc-sk-saved .bc-sk-fi { flex: 1; }
/* ── Toolbar ── */
.bio-toolbar {
display: flex; align-items: center; gap: 6px;
@@ -452,13 +472,13 @@
<button class="chal-type-chip" data-ctype="complete" onclick="setChalFilter(this,'complete')"><svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg> Завершить</button>
</div>
<div style="padding:0 12px 12px">
<div id="challenges-list" style="color:#666;font-size:0.82rem">Загрузка…</div>
<div id="challenges-list" style="color:#666;font-size:0.82rem"></div>
</div>
</div>
<!-- Saved pane -->
<div class="panel-pane" id="pane-saved">
<div id="saved-list" style="color:#666;font-size:0.82rem">Загрузка…</div>
<div id="saved-list" style="color:#666;font-size:0.82rem"></div>
</div>
</div>
</div>
@@ -1367,11 +1387,13 @@ function setChalFilter(btn, type) {
}
async function loadChallenges() {
const cl = document.getElementById('challenges-list');
cl.innerHTML = bcSkSaved(5);
try {
_challenges = await LS.biochemGetChallenges();
updateChalProgress();
renderChalList();
} catch { document.getElementById('challenges-list').innerHTML = '<div style="color:#666">Ошибка загрузки</div>'; }
} catch { cl.innerHTML = '<div style="color:#666">Ошибка загрузки</div>'; }
}
function updateChalProgress() {
@@ -1586,8 +1608,20 @@ async function submitChoiceAnswer(answer) {
}
// ── Saved ──
function bcSkSaved(n = 4) {
return Array.from({length: n}, () => `
<div class="bc-sk-saved">
<div class="bc-sk-fi">
<div class="bc-sk bc-sk-line md"></div>
<div class="bc-sk bc-sk-line sm"></div>
</div>
<div class="bc-sk" style="width:18px;height:18px;border-radius:4px"></div>
</div>`).join('');
}
async function loadSaved() {
const list = document.getElementById('saved-list');
list.innerHTML = bcSkSaved(4);
try {
const saved = await LS.biochemGetSaved();
if (!saved.length) { list.innerHTML = '<div style="color:#555;font-size:0.8rem">Сохранённых молекул пока нет</div>'; return; }
@@ -2007,7 +2041,7 @@ window.addEventListener('keydown', e => {
if (hoveredAtomId) { pushHistory(); removeAtom(hoveredAtomId); hoveredAtomId=null; updateInfo(); render(); }
else if (hoveredBondId) { pushHistory(); removeBond(hoveredBondId); hoveredBondId=null; updateInfo(); render(); }
}
if (e.key === 'Escape') { cancelChallenge(); bondFromId=null; closeRingMenu(); render(); }
if (e.key === 'Escape') { cancelChallenge(); bondFromId=null; closeRingMenu(); if (_is3D) toggle3D(); render(); }
if (e.ctrlKey && !e.shiftKey && e.key.toLowerCase()==='z') { e.preventDefault(); undo(); }
if ((e.ctrlKey && e.shiftKey && e.key.toLowerCase()==='z') || (e.ctrlKey && e.key.toLowerCase()==='y')) { e.preventDefault(); redo(); }
// Element shortcuts