From 29ef974e35ecd6d7fa17a7f6c5f0e923d0159d33 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 20 May 2026 19:49:54 +0300 Subject: [PATCH] feat(biochem): skeleton loaders for async fetches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/biochem-library.html | 39 ++++++++++++++++++++++------ frontend/biochem-properties.html | 35 ++++++++++++++++++++++++- frontend/biochem-reactions.html | 44 ++++++++++++++++++++++++++------ frontend/biochem.html | 42 +++++++++++++++++++++++++++--- 4 files changed, 139 insertions(+), 21 deletions(-) diff --git a/frontend/biochem-library.html b/frontend/biochem-library.html index 5afa4c8..088f20f 100644 --- a/frontend/biochem-library.html +++ b/frontend/biochem-library.html @@ -209,6 +209,26 @@ } .detail-open-btn:hover { background: linear-gradient(135deg, rgba(155,93,229,.4), rgba(6,214,224,.25)); border-color: rgba(6,214,224,.5); color: #fff; } + /* ── 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-square { aspect-ratio: 1; } + .bc-sk-line { height: 12px; margin: 6px 0; } + .bc-sk-line.sm { width: 60%; } + .bc-sk-line.md { width: 80%; } + .bc-sk-card { padding: 12px; border: 1px solid rgba(255,255,255,.06); border-radius: 10px; } + /* Empty state */ .lib-empty { grid-column: 1/-1; @@ -294,14 +314,7 @@
-
-
- - - -

Загрузка молекул…

-
-
+
@@ -470,7 +483,17 @@ let selectedId = null; const CAT_LABELS = { inorganic:'Неорганика', organic:'Органика', biomolecule:'Биомолекулы' }; const DIFF_STARS = ['', '', '', '']; +function bcSkLibrary(n = 12) { + return Array.from({length: n}, () => ` +
+
+
+
+
`).join(''); +} + async function init() { + document.getElementById('lib-grid').innerHTML = bcSkLibrary(12); try { [allMols, allReactions] = await Promise.all([ LS.biochemGetMolecules(), diff --git a/frontend/biochem-properties.html b/frontend/biochem-properties.html index 6a1479a..fd832f6 100644 --- a/frontend/biochem-properties.html +++ b/frontend/biochem-properties.html @@ -155,6 +155,27 @@ .add-card:hover { border-color: rgba(155,93,229,.5); color: #9B5DE5; background: rgba(155,93,229,.05); } .add-card-icon { font-size: 2rem; } + /* ── 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: 10px; margin: 5px 0; } + .bc-sk-line.sm { width: 55%; } + .bc-sk-line.md { width: 80%; } + .bc-sk-molrow { display: flex; align-items: center; gap: 8px; padding: 7px 8px; margin-bottom: 2px; } + .bc-sk-molrow .bc-sk-thumb { width: 34px; height: 34px; border-radius: 8px; flex-shrink: 0; } + .bc-sk-molrow .bc-sk-info { flex: 1; } + /* ── Mobile ── */ @media (max-width: 768px) { html, body { overflow: auto; } @@ -193,7 +214,7 @@
-
Загрузка…
+
@@ -294,7 +315,19 @@ let _catFilter = ''; let _searchQ = ''; let _compare = []; // array of mol objects, max 4 +function bcSkMolList(n = 10) { + return Array.from({length: n}, () => ` +
+
+
+
+
+
+
`).join(''); +} + async function init() { + document.getElementById('mol-list').innerHTML = bcSkMolList(10); try { _allMols = await LS.biochemGetMolecules(); applyFilter(); diff --git a/frontend/biochem-reactions.html b/frontend/biochem-reactions.html index b7f08bb..b726be7 100644 --- a/frontend/biochem-reactions.html +++ b/frontend/biochem-reactions.html @@ -63,6 +63,28 @@ .type-chip.active { background: rgba(155,93,229,.18); border-color: rgba(155,93,229,.6); color: #c084fc; } .filter-count { font-size: 0.76rem; color: #444; margin-left: auto; white-space: nowrap; font-weight: 600; } + /* ── 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: 12px; margin: 5px 0; } + .bc-sk-line.sm { width: 60%; } + .bc-sk-line.md { width: 80%; } + .bc-sk-row { display: flex; gap: 12px; padding: 14px 16px; border-bottom: 1px solid rgba(255,255,255,.05); + border-radius: 12px; margin-bottom: 10px; border: 1px solid rgba(255,255,255,.06); } + .bc-sk-row .bc-sk-avatar { width: 4px; height: auto; border-radius: 4px; flex-shrink: 0; } + .bc-sk-row .bc-sk-text { flex: 1; } + /* ── Scroll area ── */ .rxn-scroll { flex: 1; overflow-y: auto; padding: 18px 20px 40px; } .rxn-scroll::-webkit-scrollbar { width: 5px; } @@ -330,14 +352,7 @@
-
-
- - - -

Загрузка реакций…

-
-
+
@@ -464,7 +479,20 @@ let molCache = {}; let filterType = ''; let filterTopic = ''; +function bcSkReactions(n = 6) { + return Array.from({length: n}, () => ` +
+
+
+
+
+
+
+
`).join(''); +} + async function init() { + document.getElementById('rxn-list').innerHTML = bcSkReactions(6); try { allRxns = await LS.biochemGetReactions(); document.getElementById('subtitle').textContent = `${allRxns.length} реакций в базе`; diff --git a/frontend/biochem.html b/frontend/biochem.html index 6a6b9cf..f4dc606 100644 --- a/frontend/biochem.html +++ b/frontend/biochem.html @@ -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 @@
-
Загрузка…
+
-
Загрузка…
+
@@ -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 = '
Ошибка загрузки
'; } + } catch { cl.innerHTML = '
Ошибка загрузки
'; } } function updateChalProgress() { @@ -1586,8 +1608,20 @@ async function submitChoiceAnswer(answer) { } // ── Saved ── +function bcSkSaved(n = 4) { + return Array.from({length: n}, () => ` +
+
+
+
+
+
+
`).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 = '
Сохранённых молекул пока нет
'; 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