107 lines
4.2 KiB
HTML
107 lines
4.2 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Тур по платформе — Morozovka{% endblock %}
|
||
{% block main_class %}container container-narrow{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="page-header">
|
||
<div>
|
||
<h1 class="page-title">👋 Тур по платформе</h1>
|
||
<p class="page-subtitle">Короткое знакомство с разделами. Займёт 2 минуты.</p>
|
||
</div>
|
||
</div>
|
||
|
||
{% if just_done %}
|
||
<div class="welcome-banner">
|
||
<div class="welcome-banner-icon">✅</div>
|
||
<div class="welcome-banner-body">
|
||
<h3 class="welcome-banner-title">Готово! Тур пройден.</h3>
|
||
<p class="welcome-banner-text">Все разделы отмечены. Можешь возвращаться сюда из футера, чтобы освежить память.</p>
|
||
</div>
|
||
<a href="/" class="btn btn-primary">На главную →</a>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="onboarding-progress">
|
||
<div class="onboarding-progress-label">
|
||
<span>Прогресс</span>
|
||
<strong>{{ completed }} / {{ total }}</strong>
|
||
</div>
|
||
<div class="onboarding-progress-bar">
|
||
<div class="onboarding-progress-fill" id="progress-fill"
|
||
style="width: {{ (completed / total * 100)|round(1) }}%"></div>
|
||
</div>
|
||
</div>
|
||
|
||
{% for step in steps %}
|
||
<div class="onboarding-step {% if ob[step.key] %}done{% endif %}" data-key="{{ step.key }}">
|
||
<div class="onboarding-step-num">{{ step.num if not ob[step.key] else '✓' }}</div>
|
||
<div class="onboarding-step-body">
|
||
<h3 class="onboarding-step-title">
|
||
<span>{{ step.icon }}</span>
|
||
{{ step.title }}
|
||
</h3>
|
||
<p class="onboarding-step-desc">{{ step.desc }}</p>
|
||
<div class="onboarding-step-actions">
|
||
<a href="{{ step.url }}" class="btn btn-outline btn-sm">Открыть →</a>
|
||
<label class="onboarding-step-check">
|
||
<input type="checkbox" data-key="{{ step.key }}"
|
||
{% if ob[step.key] %}checked{% endif %}>
|
||
Освоил(а)
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
<div style="text-align: center; margin-top: 32px;">
|
||
<form action="/onboarding/complete" method="POST" style="display: inline;">
|
||
<button type="submit" class="btn btn-primary"
|
||
onclick="return confirm('Отметить все разделы как освоенные?');">
|
||
✅ Отметить всё сразу
|
||
</button>
|
||
</form>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script>
|
||
(function() {
|
||
const checks = document.querySelectorAll('.onboarding-step-check input[data-key]');
|
||
const fill = document.getElementById('progress-fill');
|
||
|
||
function recalcProgress(completed, total) {
|
||
if (!fill) return;
|
||
fill.style.width = (completed / total * 100) + '%';
|
||
document.querySelector('.onboarding-progress-label strong').textContent = completed + ' / ' + total;
|
||
}
|
||
|
||
checks.forEach(function(cb) {
|
||
cb.addEventListener('change', async function() {
|
||
const key = cb.dataset.key;
|
||
const value = cb.checked;
|
||
const stepEl = cb.closest('.onboarding-step');
|
||
try {
|
||
const r = await fetch('/onboarding/mark', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({key: key, value: value})
|
||
});
|
||
if (!r.ok) throw new Error('bad response');
|
||
const data = await r.json();
|
||
if (stepEl) stepEl.classList.toggle('done', value);
|
||
const numEl = stepEl.querySelector('.onboarding-step-num');
|
||
if (numEl) {
|
||
if (value) numEl.textContent = '✓';
|
||
else numEl.textContent = Array.from(document.querySelectorAll('.onboarding-step')).indexOf(stepEl) + 1;
|
||
}
|
||
recalcProgress(data.completed, data.total);
|
||
} catch (e) {
|
||
cb.checked = !value; // откатить
|
||
console.error('onboarding mark failed', e);
|
||
}
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
{% endblock %}
|