3898080f04
Причина бага «из админа конструктор симуляций редиректит на дашборд»: у sim-builder.html свой пейдж-гейт, который при feature_sim_builder=false уводил на /dashboard НЕЗАВИСИМО от роли (мой прошлый admin-override был только в hideDisabledFeatures, а этот гейт его не знал). Тот же недочёт нашёлся ещё у 3 страниц с собственным фича-редиректом (на /403): collection.html, knowledge-map.html, red-book.html. Во все 4 добавил обход для админа (админ управляет модулями → видит и открывает всё, даже отключённое) — согласно правилу admin-override. Поведение для ученика/учителя не изменилось. node --check инлайна всех 4 страниц — OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
506 lines
23 KiB
HTML
506 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Коллекция — LearnSpace</title>
|
||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@400;700;800&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||
<link rel="stylesheet" href="/css/ls.css" />
|
||
<style>
|
||
.sb-content { padding: 0; }
|
||
.col-wrap {
|
||
min-height: 100vh;
|
||
padding: 28px 24px 60px;
|
||
max-width: 1100px; margin: 0 auto;
|
||
}
|
||
|
||
/* Header */
|
||
.col-header { display:flex; align-items:center; gap:14px; margin-bottom:20px; }
|
||
.col-icon {
|
||
width:52px; height:52px; border-radius:14px; flex-shrink:0;
|
||
background: linear-gradient(135deg,rgba(249,199,79,.2),rgba(155,93,229,.15));
|
||
border:1.5px solid rgba(255,255,255,.1);
|
||
display:flex; align-items:center; justify-content:center;
|
||
}
|
||
.col-icon svg { width:26px; height:26px; stroke:#F9C74F; stroke-width:1.8; fill:none; }
|
||
.col-title { font-family:'Unbounded',sans-serif; font-size:1.3rem; font-weight:800; }
|
||
.col-sub { font-size:.82rem; color:var(--text-2); margin-top:2px; }
|
||
|
||
/* Summary bar */
|
||
.col-summary {
|
||
display:flex; gap:10px; flex-wrap:wrap; margin-bottom:20px;
|
||
}
|
||
.col-sum-card {
|
||
background:var(--surface); border:1.5px solid rgba(255,255,255,.08);
|
||
border-radius:14px; padding:12px 18px;
|
||
display:flex; align-items:center; gap:10px;
|
||
min-width:110px;
|
||
}
|
||
.col-sum-icon { width:32px; height:32px; border-radius:8px; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
|
||
.col-sum-icon svg { width:16px; height:16px; stroke:currentColor; fill:none; stroke-width:2; }
|
||
.col-sum-val { font-family:'Unbounded',sans-serif; font-size:1rem; font-weight:800; }
|
||
.col-sum-label { font-size:.72rem; color:var(--text-2); margin-top:1px; }
|
||
|
||
/* Tier legend */
|
||
.tier-legend { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:16px; align-items:center; }
|
||
.tier-dot {
|
||
display:inline-flex; align-items:center; gap:5px;
|
||
font-size:.75rem; font-weight:600; color:var(--text-2); cursor:pointer;
|
||
padding:4px 10px; border-radius:99px; border:1.5px solid transparent; transition:all .15s;
|
||
}
|
||
.tier-dot.active { color:var(--text); }
|
||
.tier-dot-circle { width:10px; height:10px; border-radius:50%; flex-shrink:0; }
|
||
|
||
/* Filter */
|
||
.col-filter { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:18px; align-items:center; }
|
||
.col-pill {
|
||
padding:5px 14px; border-radius:99px; border:1.5px solid rgba(255,255,255,.12);
|
||
font-size:.78rem; font-weight:600; cursor:pointer; transition:all .15s;
|
||
background:transparent; color:var(--text-2);
|
||
}
|
||
.col-pill:hover { border-color:rgba(249,199,79,.4); color:#F9C74F; }
|
||
.col-pill.active { background:rgba(249,199,79,.12); border-color:#F9C74F; color:#F9C74F; }
|
||
|
||
/* Search */
|
||
.col-search-wrap { position:relative; margin-left:auto; }
|
||
.col-search {
|
||
padding:6px 12px 6px 34px; border-radius:99px;
|
||
border:1.5px solid rgba(255,255,255,.12);
|
||
background:rgba(255,255,255,.05); color:var(--text);
|
||
font-family:'Manrope',sans-serif; font-size:.82rem; outline:none; width:200px;
|
||
transition:border-color .2s;
|
||
}
|
||
.col-search:focus { border-color:rgba(249,199,79,.4); }
|
||
.col-search-icon {
|
||
position:absolute; left:10px; top:50%; transform:translateY(-50%);
|
||
color:var(--text-2); pointer-events:none;
|
||
}
|
||
.col-search-icon svg { width:14px; height:14px; stroke:currentColor; fill:none; stroke-width:2; }
|
||
|
||
/* Progress bar */
|
||
.col-progress-bar {
|
||
background:var(--surface); border:1.5px solid rgba(255,255,255,.08);
|
||
border-radius:14px; padding:14px 18px; margin-bottom:20px;
|
||
display:flex; align-items:center; gap:16px;
|
||
}
|
||
.col-prog-label { font-size:.82rem; color:var(--text-2); flex-shrink:0; }
|
||
.col-prog-bar { flex:1; height:8px; border-radius:99px; background:rgba(255,255,255,.08); overflow:hidden; }
|
||
.col-prog-fill { height:100%; border-radius:99px; background:linear-gradient(90deg,#9B5DE5,#F9C74F); transition:width .8s ease; }
|
||
.col-prog-pct { font-family:'Unbounded',sans-serif; font-size:.88rem; font-weight:800; color:#F9C74F; flex-shrink:0; }
|
||
|
||
/* Cards grid */
|
||
.col-grid {
|
||
display:grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||
gap:12px;
|
||
}
|
||
|
||
/* Card */
|
||
.col-card {
|
||
position:relative; cursor:pointer;
|
||
perspective:800px;
|
||
height:200px;
|
||
}
|
||
.col-card-inner {
|
||
position:absolute; inset:0;
|
||
transition:transform .5s cubic-bezier(.4,0,.2,1);
|
||
transform-style:preserve-3d;
|
||
}
|
||
.col-card:hover .col-card-inner:not(.flipped) { transform:rotateY(10deg) scale(1.03); }
|
||
.col-card-inner.flipped { transform:rotateY(180deg); }
|
||
|
||
.col-card-front, .col-card-back {
|
||
position:absolute; inset:0;
|
||
border-radius:16px; padding:14px 12px;
|
||
backface-visibility:hidden;
|
||
display:flex; flex-direction:column; align-items:center;
|
||
justify-content:center; gap:6px;
|
||
}
|
||
.col-card-back { transform:rotateY(180deg); }
|
||
|
||
/* Tier styles */
|
||
.tier-locked .col-card-front {
|
||
background:rgba(255,255,255,.04); border:1.5px solid rgba(255,255,255,.08);
|
||
filter:grayscale(1);
|
||
}
|
||
.tier-bronze .col-card-front {
|
||
background:linear-gradient(145deg,rgba(180,80,20,.3),rgba(120,50,10,.2));
|
||
border:1.5px solid rgba(205,127,50,.35);
|
||
box-shadow:0 0 20px rgba(205,127,50,.15);
|
||
}
|
||
.tier-silver .col-card-front {
|
||
background:linear-gradient(145deg,rgba(160,160,170,.25),rgba(100,100,110,.2));
|
||
border:1.5px solid rgba(192,192,192,.4);
|
||
box-shadow:0 0 20px rgba(192,192,192,.15);
|
||
}
|
||
.tier-gold .col-card-front {
|
||
background:linear-gradient(145deg,rgba(249,199,79,.2),rgba(200,150,30,.15));
|
||
border:1.5px solid rgba(249,199,79,.45);
|
||
box-shadow:0 0 24px rgba(249,199,79,.2);
|
||
}
|
||
.tier-platinum .col-card-front {
|
||
background:linear-gradient(145deg,rgba(6,214,224,.2),rgba(155,93,229,.2));
|
||
border:1.5px solid rgba(6,214,224,.45);
|
||
box-shadow:0 0 28px rgba(6,214,224,.2);
|
||
}
|
||
|
||
/* Platinum shimmer */
|
||
.tier-platinum .col-card-front::after {
|
||
content:''; position:absolute; inset:0; border-radius:16px;
|
||
background:linear-gradient(135deg,transparent 30%,rgba(255,255,255,.06) 50%,transparent 70%);
|
||
animation:shimmer 2.5s linear infinite;
|
||
background-size:200% 200%;
|
||
}
|
||
@keyframes shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
|
||
|
||
.col-card-tier-badge {
|
||
position:absolute; top:8px; right:8px;
|
||
font-size:.6rem; font-weight:800; padding:2px 7px;
|
||
border-radius:99px; text-transform:uppercase; letter-spacing:.05em;
|
||
}
|
||
.tier-locked .col-card-tier-badge { background:rgba(255,255,255,.08); color:var(--text-2); }
|
||
.tier-bronze .col-card-tier-badge { background:rgba(205,127,50,.3); color:#CD7F32; }
|
||
.tier-silver .col-card-tier-badge { background:rgba(192,192,192,.3); color:#C0C0C0; }
|
||
.tier-gold .col-card-tier-badge { background:rgba(249,199,79,.25); color:#F9C74F; }
|
||
.tier-platinum .col-card-tier-badge { background:rgba(6,214,224,.25); color:#06D6E0; }
|
||
|
||
.col-card-icon { font-size:2rem; line-height:1; }
|
||
.tier-locked .col-card-icon { opacity:.3; }
|
||
|
||
.col-card-name {
|
||
font-family:'Unbounded',sans-serif; font-size:.7rem; font-weight:800;
|
||
text-align:center; line-height:1.3; word-break:break-word;
|
||
}
|
||
.tier-locked .col-card-name { color:var(--text-2); }
|
||
|
||
.col-card-subj {
|
||
font-size:.68rem; color:var(--text-2); text-align:center;
|
||
}
|
||
|
||
/* Stars rating */
|
||
.col-card-stars { display:flex; gap:2px; }
|
||
.col-card-stars svg { width:12px; height:12px; }
|
||
|
||
/* Back face */
|
||
.col-card-back {
|
||
background:var(--surface); border:1.5px solid rgba(255,255,255,.12);
|
||
}
|
||
.col-card-back-title {
|
||
font-family:'Unbounded',sans-serif; font-size:.72rem; font-weight:800;
|
||
text-align:center; margin-bottom:6px;
|
||
}
|
||
.col-card-back-stat { font-size:.72rem; color:var(--text-2); text-align:center; }
|
||
.col-card-back-pct {
|
||
font-family:'Unbounded',sans-serif; font-size:1.3rem; font-weight:800;
|
||
color:#F9C74F;
|
||
}
|
||
.col-card-back-bar { width:80%; height:5px; border-radius:99px; background:rgba(255,255,255,.1); overflow:hidden; }
|
||
.col-card-back-fill { height:100%; border-radius:99px; }
|
||
|
||
/* Lock icon */
|
||
.col-lock-icon {
|
||
width:36px; height:36px; border-radius:50%;
|
||
background:rgba(255,255,255,.06); display:flex; align-items:center; justify-content:center;
|
||
}
|
||
.col-lock-icon svg { width:18px; height:18px; stroke:var(--text-2); fill:none; stroke-width:2; }
|
||
|
||
/* Empty state */
|
||
.col-empty {
|
||
grid-column:1/-1;
|
||
text-align:center; padding:60px; color:var(--text-2);
|
||
font-size:.88rem;
|
||
}
|
||
|
||
@media (max-width:768px) {
|
||
.col-grid { grid-template-columns:repeat(auto-fill, minmax(130px, 1fr)); }
|
||
.col-search { width:150px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app-layout">
|
||
<aside class="sidebar" id="app-sidebar"></aside>
|
||
<div class="notif-drop" id="notif-drop"></div>
|
||
|
||
<div class="sb-content">
|
||
<div class="col-wrap">
|
||
|
||
<div class="col-header">
|
||
<div class="col-icon">
|
||
<svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="col-title">Коллекция карточек</div>
|
||
<div class="col-sub">Открывай карточки, прокачивая знания по темам</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Summary -->
|
||
<div class="col-summary" id="col-summary">
|
||
<div class="col-sum-card">
|
||
<div class="col-sum-icon" style="background:rgba(249,199,79,.12);color:#F9C74F">
|
||
<svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="col-sum-val" id="sum-total">—</div>
|
||
<div class="col-sum-label">Всего тем</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sum-card">
|
||
<div class="col-sum-icon" style="background:rgba(56,217,90,.12);color:#38D95A">
|
||
<svg viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="col-sum-val" id="sum-unlocked">—</div>
|
||
<div class="col-sum-label">Открыто</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sum-card" style="cursor:pointer" onclick="setTier('platinum')">
|
||
<div class="col-sum-icon" style="background:rgba(6,214,224,.12);color:#06D6E0">
|
||
<svg viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="col-sum-val" id="sum-plat" style="color:#06D6E0">—</div>
|
||
<div class="col-sum-label">Платина</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sum-card" style="cursor:pointer" onclick="setTier('gold')">
|
||
<div class="col-sum-icon" style="background:rgba(249,199,79,.12);color:#F9C74F">
|
||
<svg viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="col-sum-val" id="sum-gold" style="color:#F9C74F">—</div>
|
||
<div class="col-sum-label">Золото</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sum-card" style="cursor:pointer" onclick="setTier('silver')">
|
||
<div class="col-sum-icon" style="background:rgba(192,192,192,.15);color:#C0C0C0">
|
||
<svg viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="col-sum-val" id="sum-silver" style="color:#C0C0C0">—</div>
|
||
<div class="col-sum-label">Серебро</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sum-card" style="cursor:pointer" onclick="setTier('bronze')">
|
||
<div class="col-sum-icon" style="background:rgba(205,127,50,.15);color:#CD7F32">
|
||
<svg viewBox="0 0 24 24"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||
</div>
|
||
<div>
|
||
<div class="col-sum-val" id="sum-bronze" style="color:#CD7F32">—</div>
|
||
<div class="col-sum-label">Бронза</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Progress -->
|
||
<div class="col-progress-bar">
|
||
<div class="col-prog-label">Прогресс коллекции</div>
|
||
<div class="col-prog-bar"><div class="col-prog-fill" id="col-prog-fill" style="width:0%"></div></div>
|
||
<div class="col-prog-pct" id="col-prog-pct">0%</div>
|
||
</div>
|
||
|
||
<!-- Filters -->
|
||
<div class="col-filter">
|
||
<button class="col-pill active" data-slug="" onclick="setSubject(this,'')">Все</button>
|
||
<button class="col-pill" data-slug="bio" onclick="setSubject(this,'bio')">Биология</button>
|
||
<button class="col-pill" data-slug="chem" onclick="setSubject(this,'chem')">Химия</button>
|
||
<button class="col-pill" data-slug="math" onclick="setSubject(this,'math')">Математика</button>
|
||
<button class="col-pill" data-slug="phys" onclick="setSubject(this,'phys')">Физика</button>
|
||
|
||
<div class="col-search-wrap" style="margin-left:auto">
|
||
<div class="col-search-icon"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
|
||
<input class="col-search" id="col-search" placeholder="Поиск темы…" oninput="renderCards()" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tier filter -->
|
||
<div class="tier-legend" id="tier-legend">
|
||
<div class="tier-dot active" data-tier="" onclick="setTier('')">
|
||
<div class="tier-dot-circle" style="background:var(--text-2)"></div>Все
|
||
</div>
|
||
<div class="tier-dot" data-tier="platinum" onclick="setTier('platinum')">
|
||
<div class="tier-dot-circle" style="background:#06D6E0"></div>Платина
|
||
</div>
|
||
<div class="tier-dot" data-tier="gold" onclick="setTier('gold')">
|
||
<div class="tier-dot-circle" style="background:#F9C74F"></div>Золото
|
||
</div>
|
||
<div class="tier-dot" data-tier="silver" onclick="setTier('silver')">
|
||
<div class="tier-dot-circle" style="background:#C0C0C0"></div>Серебро
|
||
</div>
|
||
<div class="tier-dot" data-tier="bronze" onclick="setTier('bronze')">
|
||
<div class="tier-dot-circle" style="background:#CD7F32"></div>Бронза
|
||
</div>
|
||
<div class="tier-dot" data-tier="locked" onclick="setTier('locked')">
|
||
<div class="tier-dot-circle" style="background:rgba(255,255,255,.2)"></div>Закрыто
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cards -->
|
||
<div class="col-grid" id="col-grid">
|
||
<div class="col-empty">
|
||
<div style="font-size:2rem;margin-bottom:8px"><svg class="ic" viewBox="0 0 24 24"><path d="M5 22h14M5 2h14M17 22v-4.17a2 2 0 0 0-.59-1.41L12 12l-4.41 4.42A2 2 0 0 0 7 17.83V22M7 2v4.17a2 2 0 0 0 .59 1.41L12 12l4.41-4.42A2 2 0 0 0 17 6.17V2"/></svg></div>
|
||
Загружаем коллекцию…
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/js/api.js"></script>
|
||
<script src="/js/sidebar.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/lucide@0.469.0/dist/umd/lucide.min.js"></script>
|
||
<script>
|
||
(async () => {
|
||
if (!LS.requireAuth()) return;
|
||
const user = LS.getUser();
|
||
LS.applyRoleSidebar(user);
|
||
if (user) {
|
||
LS.renderNavAvatar(document.getElementById('nav-avatar'), user);
|
||
document.getElementById('nav-user').textContent = user.name || '—';
|
||
LS.showBoardIfAllowed();
|
||
}
|
||
LS.sidebar?.init();
|
||
lucide.createIcons();
|
||
const feats = await LS.loadFeatures();
|
||
if (feats.collection === false && user?.role !== 'admin') { window.location.replace('/403'); return; }
|
||
LS.hideDisabledFeatures?.();
|
||
await loadCollection();
|
||
})();
|
||
|
||
let _cards = [];
|
||
let _filterSubject = '';
|
||
let _filterTier = '';
|
||
|
||
/* ── Subject icons ── */
|
||
const SUBJ_ICONS = { bio:'<svg class="ic" viewBox="0 0 24 24"><path d="M2 15c6.667-6 13.333 0 20-6"/><path d="M9 22c1.798-2 2.518-4 2.807-6"/><path d="M15 2c-1.798 2-2.518 4-2.807 6"/><path d="m17 6-2.5-2.5M14 8 13 7M7 18l2.5 2.5M3.5 14.5l.5.5M20 9l.5.5M6.5 12.5l1 1M16.5 10.5l1 1M10 16l1.5 1.5"/></svg>', chem:'<svg class="ic" viewBox="0 0 24 24"><path d="M9 3h6m-4.5 0v5.5l-4 7.5a1 1 0 0 0 .9 1.5h8.2a1 1 0 0 0 .9-1.5l-4-7.5V3"/></svg>️', math:'<svg class="ic" viewBox="0 0 24 24"><path d="m15 5 6.3 6.3a2.4 2.4 0 0 1 0 3.4L17 19"/><path d="M9.586 5.586A2 2 0 0 0 8.172 5H3a1 1 0 0 0-1 1v5.172a2 2 0 0 0 .586 1.414L8.29 18.29a2.426 2.426 0 0 0 3.42 0l3.58-3.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="6.5" cy="9.5" r=".5" fill="currentColor"/></svg>', phys:'<svg class="ic" viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>' };
|
||
|
||
/* ── Star SVG ── */
|
||
function starSVG(filled, color) {
|
||
return `<svg viewBox="0 0 24 24" fill="${filled ? color : 'none'}" stroke="${color}" stroke-width="2">
|
||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
||
</svg>`;
|
||
}
|
||
|
||
const TIER_COLOR = { platinum:'#06D6E0', gold:'#F9C74F', silver:'#C0C0C0', bronze:'#CD7F32', locked:'rgba(255,255,255,.2)' };
|
||
const TIER_LABEL = { platinum:'Платина', gold:'Золото', silver:'Серебро', bronze:'Бронза', locked:'Закрыто' };
|
||
const TIER_STARS = { platinum:5, gold:4, silver:3, bronze:1, locked:0 };
|
||
|
||
/* ── Load ── */
|
||
async function loadCollection() {
|
||
const data = await LS.api('/api/collection').catch(() => null);
|
||
if (!data) return;
|
||
|
||
_cards = data.cards;
|
||
|
||
document.getElementById('sum-total').textContent = data.totalTopics;
|
||
document.getElementById('sum-unlocked').textContent = data.unlockedTopics;
|
||
document.getElementById('sum-plat').textContent = data.platinumCount;
|
||
document.getElementById('sum-gold').textContent = data.goldCount;
|
||
document.getElementById('sum-silver').textContent = data.silverCount;
|
||
document.getElementById('sum-bronze').textContent = data.bronzeCount;
|
||
|
||
const pct = data.totalTopics > 0 ? Math.round(data.unlockedTopics / data.totalTopics * 100) : 0;
|
||
document.getElementById('col-prog-fill').style.width = pct + '%';
|
||
document.getElementById('col-prog-pct').textContent = pct + '%';
|
||
|
||
renderCards();
|
||
}
|
||
|
||
/* ── Filters ── */
|
||
function setSubject(el, slug) {
|
||
document.querySelectorAll('.col-pill').forEach(p => p.classList.remove('active'));
|
||
el.classList.add('active');
|
||
_filterSubject = slug;
|
||
renderCards();
|
||
}
|
||
|
||
function setTier(tier) {
|
||
document.querySelectorAll('.tier-dot').forEach(d => d.classList.remove('active'));
|
||
const el = document.querySelector(`.tier-dot[data-tier="${tier}"]`);
|
||
if (el) el.classList.add('active');
|
||
_filterTier = tier;
|
||
renderCards();
|
||
}
|
||
|
||
/* ── Render ── */
|
||
function renderCards() {
|
||
const q = document.getElementById('col-search').value.trim().toLowerCase();
|
||
let cards = _cards;
|
||
if (_filterSubject) cards = cards.filter(c => c.subjectSlug === _filterSubject);
|
||
if (_filterTier) cards = cards.filter(c => c.tier === _filterTier);
|
||
if (q) cards = cards.filter(c => c.topicName.toLowerCase().includes(q) || c.subjectName.toLowerCase().includes(q));
|
||
|
||
const grid = document.getElementById('col-grid');
|
||
if (!cards.length) {
|
||
grid.innerHTML = `<div class="col-empty"><div style="font-size:2rem;margin-bottom:8px"><svg class="ic" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>Ничего не найдено</div>`;
|
||
return;
|
||
}
|
||
|
||
// Sort: unlocked first, then by tier weight
|
||
const TIER_WEIGHT = { platinum:4, gold:3, silver:2, bronze:1, locked:0 };
|
||
cards = [...cards].sort((a,b) => (TIER_WEIGHT[b.tier]||0) - (TIER_WEIGHT[a.tier]||0));
|
||
|
||
grid.innerHTML = cards.map(card => buildCard(card)).join('');
|
||
}
|
||
|
||
function buildCard(c) {
|
||
const tierColor = TIER_COLOR[c.tier] || 'rgba(255,255,255,.2)';
|
||
const tierLbl = TIER_LABEL[c.tier] || '';
|
||
const stars = TIER_STARS[c.tier] || 0;
|
||
const icon = SUBJ_ICONS[c.subjectSlug] || '<svg class="ic" viewBox="0 0 24 24"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>';
|
||
|
||
const starsHtml = [1,2,3,4,5].map(i =>
|
||
starSVG(i <= stars, tierColor)
|
||
).join('');
|
||
|
||
const frontContent = c.tier === 'locked'
|
||
? `<div class="col-lock-icon"><svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
|
||
<div class="col-card-name">${escHtml(c.topicName)}</div>
|
||
<div class="col-card-subj">${escHtml(c.subjectName)}</div>`
|
||
: `<div class="col-card-icon">${icon}</div>
|
||
<div class="col-card-name">${escHtml(c.topicName)}</div>
|
||
<div class="col-card-subj">${escHtml(c.subjectName)}</div>
|
||
<div class="col-card-stars">${starsHtml}</div>`;
|
||
|
||
const backContent = c.tier === 'locked'
|
||
? `<div class="col-card-back-title">${escHtml(c.topicName)}</div>
|
||
<div class="col-card-back-stat">Нет правильных ответов</div>
|
||
<div class="col-card-back-stat" style="margin-top:6px">Проходи тесты,<br>чтобы открыть карточку</div>`
|
||
: `<div class="col-card-back-title">${escHtml(c.topicName)}</div>
|
||
<div class="col-card-back-pct">${c.masteryPct}%</div>
|
||
<div class="col-card-back-stat">мастерство</div>
|
||
<div class="col-card-back-bar" style="margin-top:6px">
|
||
<div class="col-card-back-fill" style="width:${c.masteryPct}%;background:${tierColor}"></div>
|
||
</div>
|
||
<div class="col-card-back-stat" style="margin-top:6px">${c.correctCount}/${c.totalAttempts} попыток</div>`;
|
||
|
||
return `<div class="col-card tier-${c.tier}" onclick="flipCard(this)">
|
||
<div class="col-card-inner">
|
||
<div class="col-card-front">
|
||
<span class="col-card-tier-badge">${tierLbl}</span>
|
||
${frontContent}
|
||
</div>
|
||
<div class="col-card-back">${backContent}</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function flipCard(el) {
|
||
el.querySelector('.col-card-inner').classList.toggle('flipped');
|
||
}
|
||
|
||
function escHtml(s) {
|
||
return (s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
}
|
||
|
||
LS.notif?.init();
|
||
</script>
|
||
<script src="/js/notifications.js"></script>
|
||
<script src="/js/search.js"></script>
|
||
<script src="/js/mobile.js"></script>
|
||
</body>
|
||
</html>
|