diff --git a/frontend/dashboard.html b/frontend/dashboard.html
index d80fa3d..2e24024 100644
--- a/frontend/dashboard.html
+++ b/frontend/dashboard.html
@@ -3289,20 +3289,57 @@
{ key:'waves', href:'/lab?sim=waves', title:'Волны и колебания', sub:'Длина волны, частота и стоячие волны.', subj:'Физика', time:'~11 мин', level:'средне', goal:'связь v = λf' },
{ key:'stereo', href:'/lab?sim=stereo', title:'Стереометрия 3D', sub:'Сечения и объёмы пространственных фигур.', subj:'Геометрия', time:'~10 мин', level:'сложно', goal:'построение сечений' },
];
- function loadLabOfDay() {
+ // Метаданные карточки (время/уровень/цель/подпись) — их нет в каталоге БД,
+ // поэтому держим curated-карту по id (источник — LAB_OF_DAY выше).
+ const LAB_META = {};
+ LAB_OF_DAY.forEach(l => { LAB_META[l.key] = l; });
+ const CAT_LABEL = { math: 'Математика', phys: 'Физика', chem: 'Химия', bio: 'Биология', game: 'Игра' };
+
+ // Синхронизировано с каталогом лаборатории (/api/lab/sims): крутим только
+ // среди ВКЛЮЧЁННЫХ симуляций, у которых есть превью; title/категория — из БД,
+ // поэтому переименования/выключения в админке отражаются здесь автоматически.
+ async function loadLabOfDay() {
const card = document.getElementById('hc-lab');
if (!card) return;
- const dayIdx = Math.floor(Date.now() / 86400000) % LAB_OF_DAY.length;
- const lab = LAB_OF_DAY[dayIdx];
- card.href = lab.href;
+
+ let pick = null;
+ try {
+ const r = await LS.api('/api/lab/sims');
+ const sims = (r && r.sims) || [];
+ const hasPreview = id => window.LabPreviews && LabPreviews[id];
+ let pool = sims.filter(s => s.enabled && hasPreview(s.id));
+ const feat = pool.filter(s => s.featured);
+ if (feat.length >= 2) pool = feat; // приоритет «рекомендуемых», если их достаточно
+ pool.sort((a, b) => (a.sort - b.sort) || (a.id < b.id ? -1 : 1)); // стабильный порядок для детерминизма
+ if (pool.length) {
+ const s = pool[Math.floor(Date.now() / 86400000) % pool.length];
+ const m = LAB_META[s.id] || {};
+ pick = {
+ href: '/lab?sim=' + s.id,
+ key: s.id,
+ title: s.title || m.title || s.id, // title из каталога (источник истины)
+ sub: m.sub || 'Открой симуляцию и поэкспериментируй.',
+ subj: m.subj || CAT_LABEL[s.cat] || 'Лаборатория',
+ time: m.time || '~10 мин',
+ level: m.level || 'средне',
+ goal: m.goal || (s.title || ''),
+ };
+ }
+ } catch (_) { /* каталог недоступен — упадём на статичный список ниже */ }
+
+ if (!pick) { // фолбэк: прежний детерминированный выбор из захардкоженного списка
+ pick = LAB_OF_DAY[Math.floor(Date.now() / 86400000) % LAB_OF_DAY.length];
+ }
+
+ card.href = pick.href;
const bg = document.getElementById('hc-lab-bg');
- if (bg && window.LabPreviews && LabPreviews[lab.key]) bg.innerHTML = LabPreviews[lab.key];
- document.getElementById('hc-lab-title').textContent = lab.title;
- document.getElementById('hc-lab-sub').textContent = lab.sub;
- document.getElementById('hc-lab-subj').textContent = lab.subj;
- document.getElementById('hc-lab-time').textContent = lab.time;
- document.getElementById('hc-lab-level').textContent = lab.level;
- document.getElementById('hc-lab-meta').textContent = 'Освой: ' + lab.goal;
+ if (bg && window.LabPreviews && LabPreviews[pick.key]) bg.innerHTML = LabPreviews[pick.key];
+ document.getElementById('hc-lab-title').textContent = pick.title;
+ document.getElementById('hc-lab-sub').textContent = pick.sub;
+ document.getElementById('hc-lab-subj').textContent = pick.subj;
+ document.getElementById('hc-lab-time').textContent = pick.time;
+ document.getElementById('hc-lab-level').textContent = pick.level;
+ document.getElementById('hc-lab-meta').textContent = 'Освой: ' + pick.goal;
}
/* ══ HERO: Pet (synced with /pet module via /api/pet + PetSprite) ═ */