feat(dashboard): hero-карточки главной — чтение, лаборатория дня, питомец
Пересборка верхней зоны дашборда по скриншоту (редизайн был утерян): - 3 hero-карточки вместо action-cards: «Начать чтение» (продолжение курса через /api/courses/continue), «Лаборатория дня» (детерминир. выбор по дню + SVG-превью из lab-previews.js), «Питомец» (синхрон с модулем /pet через /api/pet + единый PetSprite.render). - Подключены восстановленные ассеты pet-sprite.js и lab-previews.js. - Убран weak-topics из hero; питомец показывает уровень/XP/стрик/ цель дня/настроение, синхронно со страницей /pet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+93
-42
@@ -1440,23 +1440,64 @@
|
||||
<div class="action-banner" id="action-banner" style="display:none">
|
||||
<!-- populated by JS -->
|
||||
</div>
|
||||
<div class="action-cards" id="action-cards" style="display:none">
|
||||
<a class="ac-card" id="ac-continue" style="display:none" href="#">
|
||||
<span class="ac-emoji"><svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2V3zm20 0h-6a4 4 0 00-4 4v14a3 3 0 013-3h7V3z"/></svg></span>
|
||||
<div class="ac-body">
|
||||
<div class="ac-title" id="ac-cont-title">—</div>
|
||||
<div class="ac-sub" id="ac-cont-sub">—</div>
|
||||
<div class="hero-row" id="hero-row" style="display:none">
|
||||
|
||||
<!-- Card 1 — Continue / start reading -->
|
||||
<a class="hero-card hc-read" id="hc-read" href="/textbooks">
|
||||
<span class="hc-tag">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5a2 2 0 0 1 2-2h6v17H6a2 2 0 0 0-2 2z"/><path d="M20 5a2 2 0 0 0-2-2h-6v17h6a2 2 0 0 1 2 2z"/></svg>
|
||||
<span id="hc-read-tag">Начать чтение</span>
|
||||
</span>
|
||||
<div class="hc-h" id="hc-read-title">Учебники</div>
|
||||
<div class="hc-p" id="hc-read-sub">Открой учебник и продолжи курс с того места, где остановился.</div>
|
||||
<div class="hc-progress" id="hc-read-prog-wrap" style="display:none"><i id="hc-read-prog" style="width:0%"></i></div>
|
||||
<div class="hc-foot">
|
||||
<span class="hc-meta" id="hc-read-meta">новый учебник</span>
|
||||
<span class="hc-pct" id="hc-read-pct" style="display:none">0%</span>
|
||||
<span class="hc-btn">Начать <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
|
||||
</div>
|
||||
<span class="ac-badge" id="ac-cont-pct"></span>
|
||||
</a>
|
||||
<a class="ac-card" id="ac-weak" style="display:none" href="#">
|
||||
<span class="ac-emoji"><svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block;vertical-align:middle"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg></span>
|
||||
<div class="ac-body">
|
||||
<div class="ac-title" id="ac-weak-title">—</div>
|
||||
<div class="ac-sub" id="ac-weak-sub">—</div>
|
||||
|
||||
<!-- Card 2 — Lab of the day -->
|
||||
<a class="hero-card hc-lab" id="hc-lab" href="/lab">
|
||||
<div class="hc-bg" id="hc-lab-bg"></div>
|
||||
<span class="hc-tag">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6M10 3v6l-5.4 9.3A1.5 1.5 0 0 0 5.9 21h12.2a1.5 1.5 0 0 0 1.3-2.3L14 9V3"/><path d="M7.5 15h9"/></svg>
|
||||
Лаборатория дня
|
||||
</span>
|
||||
<div class="hc-h" id="hc-lab-title">Газовые законы</div>
|
||||
<div class="hc-p" id="hc-lab-sub">Давление, объём и температура газа.</div>
|
||||
<div class="hc-chips" id="hc-lab-chips">
|
||||
<span class="hc-chip subj" id="hc-lab-subj">Физика</span>
|
||||
<span class="hc-chip" id="hc-lab-time">~10 мин</span>
|
||||
<span class="hc-chip" id="hc-lab-level">средне</span>
|
||||
</div>
|
||||
<div class="hc-foot">
|
||||
<span class="hc-meta" id="hc-lab-meta">Освой: уравнение состояния газа</span>
|
||||
<span class="hc-btn">Открыть <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
|
||||
</div>
|
||||
<span class="ac-badge" id="ac-weak-pct" style="color:#E0335E"></span>
|
||||
</a>
|
||||
|
||||
<!-- Card 3 — Pet (synced with /pet module) -->
|
||||
<a class="hero-card hc-pet" id="hc-pet" href="/pet">
|
||||
<span class="hc-tag">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="5.5" cy="11" r="2"/><circle cx="9.5" cy="6.5" r="2"/><circle cx="14.5" cy="6.5" r="2"/><circle cx="18.5" cy="11" r="2"/><path d="M8.2 16.4C8.2 14.3 9.9 13 12 13s3.8 1.3 3.8 3.4c0 1.7-1.3 2.8-2.6 3.2-.8.2-1.6.2-2.4 0-1.3-.4-2.6-1.5-2.6-3.2z"/></svg>
|
||||
Питомец
|
||||
</span>
|
||||
<div class="hc-pet-top">
|
||||
<div class="hc-pet-name" id="hc-pet-name">Квантик</div>
|
||||
<div class="hc-pet-art" id="hc-pet-art"></div>
|
||||
</div>
|
||||
<div class="hc-xp-row"><span>Ур. <b id="hc-pet-lvl">1</b></span><span><b id="hc-pet-xp">0</b> / <span id="hc-pet-xpmax">500</span> XP</span></div>
|
||||
<div class="hc-progress"><i id="hc-pet-prog" style="width:0%"></i></div>
|
||||
<div class="hc-pet-chips">
|
||||
<div class="hc-pchip"><b id="hc-pet-streak">0</b><span>стрик</span></div>
|
||||
<div class="hc-pchip"><b id="hc-pet-goal">0/2</b><span>цель дня</span></div>
|
||||
<div class="hc-pchip"><b id="hc-pet-mood">бодр</b><span>настроение</span></div>
|
||||
</div>
|
||||
<span class="hc-btn">Ухаживать <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg></span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3177,41 +3218,49 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ══ WIDGET: Continue reading (action card) ══════════════════════ */
|
||||
/* ══ HERO: Reading card (continue or start) ══════════════════════ */
|
||||
async function loadContinueWidget() {
|
||||
const card = document.getElementById('ac-continue');
|
||||
const cardsWrap = document.getElementById('action-cards');
|
||||
const card = document.getElementById('hc-read');
|
||||
if (!card) return;
|
||||
try {
|
||||
const data = await LS.api('/api/courses/continue');
|
||||
if (!data || !data.courseId) { card.style.display = 'none'; return; }
|
||||
if (!data || !data.courseId) return; // оставляем дефолт «Начать чтение → Учебники»
|
||||
const pct = data.lessonCount > 0 ? Math.round((data.doneCount || 0) / data.lessonCount * 100) : 0;
|
||||
const href = `/lesson?course=${data.courseId}&lesson=${data.lessonId}`;
|
||||
card.href = href;
|
||||
card.style.display = '';
|
||||
if (cardsWrap) cardsWrap.style.display = '';
|
||||
document.getElementById('ac-cont-title').textContent = data.courseTitle || 'Продолжить';
|
||||
document.getElementById('ac-cont-sub').textContent = `${data.lessonTitle || ''} · ${data.doneCount || 0}/${data.lessonCount || 0} уроков`;
|
||||
document.getElementById('ac-cont-pct').textContent = pct + '%';
|
||||
} catch { card.style.display = 'none'; }
|
||||
card.href = `/lesson?course=${data.courseId}&lesson=${data.lessonId}`;
|
||||
document.getElementById('hc-read-tag').textContent = 'Продолжить чтение';
|
||||
document.getElementById('hc-read-title').textContent = data.courseTitle || 'Продолжить';
|
||||
document.getElementById('hc-read-sub').textContent = data.lessonTitle || '';
|
||||
document.getElementById('hc-read-meta').textContent = `${data.doneCount || 0} / ${data.lessonCount || 0} уроков`;
|
||||
const progWrap = document.getElementById('hc-read-prog-wrap');
|
||||
if (progWrap) { progWrap.style.display = ''; document.getElementById('hc-read-prog').style.width = pct + '%'; }
|
||||
const pctEl = document.getElementById('hc-read-pct');
|
||||
if (pctEl) { pctEl.style.display = ''; pctEl.textContent = pct + '%'; }
|
||||
} catch { /* нет курса в процессе — карточка остаётся в дефолте */ }
|
||||
}
|
||||
|
||||
/* ══ WIDGET: Weak topics (action card) ════════════════════════════ */
|
||||
async function loadWeakWidget() {
|
||||
const card = document.getElementById('ac-weak');
|
||||
const cardsWrap = document.getElementById('action-cards');
|
||||
/* ══ HERO: Lab of the day (deterministic daily pick) ═════════════ */
|
||||
const LAB_OF_DAY = [
|
||||
{ key:'isoprocess', href:'/lab?sim=molphys', title:'Газовые законы', sub:'Давление, объём и температура газа.', subj:'Физика', time:'~10 мин', level:'средне', goal:'уравнение состояния газа' },
|
||||
{ key:'opticsbench', href:'/lab?sim=opticsbench', title:'Оптическая скамья', sub:'Собери систему линз и проследи ход лучей.', subj:'Физика', time:'~12 мин', level:'средне', goal:'построение изображения в линзе' },
|
||||
{ key:'circuit', href:'/lab?sim=circuit', title:'Электрическая цепь', sub:'Закон Ома: ток, напряжение и сопротивление.', subj:'Физика', time:'~8 мин', level:'легко', goal:'расчёт цепи по закону Ома' },
|
||||
{ key:'pendulum', href:'/lab?sim=pendulum', title:'Математический маятник', sub:'Период колебаний и зависимость от длины.', subj:'Физика', time:'~9 мин', level:'легко', goal:'формула периода маятника' },
|
||||
{ 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() {
|
||||
const card = document.getElementById('hc-lab');
|
||||
if (!card) return;
|
||||
try {
|
||||
const topics = await LS.getWeakTopics();
|
||||
if (!topics.length) { card.style.display = 'none'; return; }
|
||||
const t = topics[0];
|
||||
card.href = `/test-run?subject=${t.subject_slug}&mode=exam&count=15&topic=${t.topic_id}`;
|
||||
card.style.display = '';
|
||||
if (cardsWrap) cardsWrap.style.display = '';
|
||||
document.getElementById('ac-weak-title').textContent = t.topic;
|
||||
document.getElementById('ac-weak-sub').textContent = `${t.subject_name} · ${t.wrong} из ${t.total} неверно`;
|
||||
document.getElementById('ac-weak-pct').textContent = t.error_pct + '%';
|
||||
} catch { card.style.display = 'none'; }
|
||||
const dayIdx = Math.floor(Date.now() / 86400000) % LAB_OF_DAY.length;
|
||||
const lab = LAB_OF_DAY[dayIdx];
|
||||
card.href = lab.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;
|
||||
}
|
||||
|
||||
/* ══ ACTIVITY: data structure ══════════════════════════════════════ */
|
||||
@@ -3999,9 +4048,11 @@
|
||||
loadSubjProgressWidget(rows);
|
||||
renderStreakCalendar(rows);
|
||||
} catch {}
|
||||
const heroRow = document.getElementById('hero-row');
|
||||
if (heroRow) heroRow.style.display = '';
|
||||
loadContinueWidget();
|
||||
loadWeakWidget();
|
||||
loadTheoryWidget();
|
||||
loadLabOfDay();
|
||||
loadPetHero();
|
||||
loadFlashcardWidget();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user