feat(gamification): Phase 1 — full kill-switch + textbook XP wrapping
Until now the 'gamification' feature flag did nothing: it had no row in
app_settings, the admin couldn't toggle it, awardXP/awardCoins ignored
it, and the CSS only hid three dashboard widgets — XP bars in textbooks
stayed visible regardless.
Phase 1 closes every hole.
Backend (source of truth):
• migration 029 seeds feature_gamification_enabled=1
• new isGamificationEnabled() helper in gamification/_shared.js with a
30s cache + invalidateGamificationCache() for instant admin toggles
• awardXP / awardCoins / updateStreak / unlockAchievement /
checkAchievements all bail out when the flag is off
• /api/gamification/* and /api/shop/* (user routes) return 404 when
disabled; admin routes remain open so the switch itself is reachable
• adminController.updateFeatures gains 'gamification' in the allow-list
and invalidates the cache on flip
Frontend:
• LS.isGamificationEnabled() (synchronous, populated by loadFeatures)
so xp.js + applyCosmetics can bail without a round-trip
• xp.js load/add/flush become no-ops when the flag is off
• applyCosmetics skips the round-trip when off
• CSS .no-gamification rule expanded to cover .hero-xp-badge, .po-xp,
.xp-card, .xp-bar, #frames-section, and a universal [data-gamified]
hook for future blocks
Textbooks (Variant 2 of the plan):
• backend/scripts/wrap_textbook_xp.py — idempotent script that adds
data-gamified to 167 XP tags across 63 textbook files (chapters +
hubs, all subjects/grades). Single CSS rule now hides everything.
Verified end-to-end: with the flag off, awardXP/awardCoins write nothing;
flipping back restores normal behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-1
@@ -1017,13 +1017,33 @@ body {
|
||||
/* Student without a class: hide leaderboard */
|
||||
body.no-class #lb-section { display: none !important; }
|
||||
|
||||
/* Gamification kill-switch.
|
||||
When admin turns off the feature, body.no-gamification is set by
|
||||
api.js/hideDisabledFeatures and EVERY XP / coin / streak / shop /
|
||||
achievement / frame element must vanish — across the whole app,
|
||||
not just the dashboard. The rules below cover:
|
||||
• dashboard widgets (.gam-bar, .lb-widget)
|
||||
• profile tabs (achievements, shop, frames)
|
||||
• textbook XP badges and progress cards (.hero-xp-badge, .po-xp,
|
||||
.xp-bar, .xp-card)
|
||||
• a catch-all [data-gamified] hook that wraps any future block —
|
||||
authors of new pages should wrap XP UI in a <div data-gamified>
|
||||
instead of inventing new classes. */
|
||||
body.no-gamification .gam-bar,
|
||||
body.no-gamification .lb-widget,
|
||||
body.no-gamification .achievements-section,
|
||||
body.no-gamification #tab-btn-achievements,
|
||||
body.no-gamification #tab-btn-shop,
|
||||
body.no-gamification #tab-achievements,
|
||||
body.no-gamification #tab-shop { display: none !important; }
|
||||
body.no-gamification #tab-shop,
|
||||
body.no-gamification #frames-section,
|
||||
body.no-gamification .hero-xp-badge,
|
||||
body.no-gamification .po-xp,
|
||||
body.no-gamification .xp-card,
|
||||
body.no-gamification .xp-bar,
|
||||
body.no-gamification .xp-pill,
|
||||
body.no-gamification .xp-badge,
|
||||
body.no-gamification [data-gamified] { display: none !important; }
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
RESPONSIVE — SMALL PHONES (≤ 480px)
|
||||
|
||||
@@ -233,7 +233,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -565,7 +565,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -204,7 +204,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -423,7 +423,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -208,7 +208,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -429,7 +429,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -137,7 +137,7 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -253,7 +253,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -426,7 +426,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -233,7 +233,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -406,7 +406,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -254,7 +254,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -432,7 +432,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -228,7 +228,7 @@ html.dark .final-cta-sub{color:#fcd34d}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -286,7 +286,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -541,7 +541,7 @@ function buildSidebar(id){
|
||||
const xpForLv = _xpForLevel(STATE.level), xpNext = _xpForLevel(STATE.level + 1);
|
||||
const xpInLv = STATE.xp - xpForLv, xpRange = xpNext - xpForLv;
|
||||
const xpPct = xpRange > 0 ? Math.round(xpInLv / xpRange * 100) : 100;
|
||||
html += '<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. ' + STATE.level + '</span></div><div class="xp-bar"><div class="xp-fill" style="width:' + xpPct + '%"></div></div><div class="xp-nums"><span>' + STATE.xp + ' XP</span><span>' + xpNext + ' XP</span></div></div>';
|
||||
html += '<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. ' + STATE.level + '</span></div><div class="xp-bar"><div class="xp-fill" style="width:' + xpPct + '%"></div></div><div class="xp-nums"><span>' + STATE.xp + ' XP</span><span>' + xpNext + ' XP</span></div></div>';
|
||||
html += '<div class="sidecard"><h4>' + sb.title + '</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html += '<div class="sidecard-row"><b>' + k + '</b>' + (v ? ' — ' + v : '') + '</div>'; });
|
||||
html += '</div>';
|
||||
|
||||
@@ -253,7 +253,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -528,7 +528,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -246,7 +246,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -484,7 +484,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -241,7 +241,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -462,7 +462,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -142,7 +142,7 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -788,7 +788,7 @@ input,select,textarea{font-family:inherit}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1341,8 +1341,8 @@ function buildSidebar(id){
|
||||
const xpInLevel = STATE.xp - xpForLevel;
|
||||
const xpRange = xpNext - xpForLevel;
|
||||
const xpPct = xpRange > 0 ? Math.round(xpInLevel / xpRange * 100) : 100;
|
||||
let html = `<div class="xp-card">
|
||||
<div class="xp-card-title">
|
||||
let html = `<div class="xp-card" data-gamified>
|
||||
<div class="xp-card-title" data-gamified>
|
||||
<span>XP-прогресс</span>
|
||||
<span class="xp-level">Ур. ${STATE.level}</span>
|
||||
</div>
|
||||
|
||||
@@ -373,7 +373,7 @@ input,select,textarea{font-family:inherit}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -764,8 +764,8 @@ function buildSidebar(id){
|
||||
const xpInLv = STATE.xp - xpForLv;
|
||||
const xpRange = xpNext - xpForLv;
|
||||
const xpPct = xpRange > 0 ? Math.round(xpInLv / xpRange * 100) : 100;
|
||||
html += `<div class="xp-card">
|
||||
<div class="xp-card-title">
|
||||
html += `<div class="xp-card" data-gamified>
|
||||
<div class="xp-card-title" data-gamified>
|
||||
<span>XP-прогресс</span>
|
||||
<span class="xp-level">Ур. ${STATE.level}</span>
|
||||
</div>
|
||||
|
||||
@@ -299,7 +299,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -679,8 +679,8 @@ function buildSidebar(id){
|
||||
const xpInLv = STATE.xp - xpForLv;
|
||||
const xpRange = xpNext - xpForLv;
|
||||
const xpPct = xpRange > 0 ? Math.round(xpInLv / xpRange * 100) : 100;
|
||||
html += `<div class="xp-card">
|
||||
<div class="xp-card-title">
|
||||
html += `<div class="xp-card" data-gamified>
|
||||
<div class="xp-card-title" data-gamified>
|
||||
<span>XP-прогресс</span>
|
||||
<span class="xp-level">Ур. ${STATE.level}</span>
|
||||
</div>
|
||||
|
||||
@@ -147,7 +147,7 @@ html.dark .ch-tag.indigo{color:#818cf8}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -229,7 +229,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -411,7 +411,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -228,7 +228,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -406,7 +406,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -229,7 +229,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -407,7 +407,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -224,7 +224,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -410,7 +410,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -229,7 +229,7 @@ html.dark .final-cta-sub{color:#fcd34d}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -233,7 +233,7 @@ html.dark .final-cta-sub{color:#fcd34d}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -212,7 +212,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -370,7 +370,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -206,7 +206,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -365,7 +365,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -206,7 +206,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -369,7 +369,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -209,7 +209,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -374,7 +374,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -256,7 +256,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -426,7 +426,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -256,7 +256,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -446,7 +446,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -257,7 +257,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -464,7 +464,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -258,7 +258,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -438,7 +438,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -236,7 +236,7 @@ html.dark .final-cta-sub{color:#fcd34d}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -212,7 +212,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -437,7 +437,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -207,7 +207,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -432,7 +432,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -208,7 +208,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -407,7 +407,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -196,7 +196,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -425,7 +425,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -198,7 +198,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -405,7 +405,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -159,7 +159,7 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -286,7 +286,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -558,7 +558,7 @@ function buildSidebar(id){
|
||||
const xpForLv = _xpForLevel(STATE.level), xpNext = _xpForLevel(STATE.level + 1);
|
||||
const xpInLv = STATE.xp - xpForLv, xpRange = xpNext - xpForLv;
|
||||
const xpPct = xpRange > 0 ? Math.round(xpInLv / xpRange * 100) : 100;
|
||||
html += `<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. ${STATE.level}</span></div><div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div><div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div></div>`;
|
||||
html += `<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. ${STATE.level}</span></div><div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div><div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div></div>`;
|
||||
html += `<div class="sidecard"><h4>${sb.title}</h4>`;
|
||||
sb.rows.forEach(([k,v])=>{ html += `<div class="sidecard-row"><b>${k}</b>${v ? ' — ' + v : ''}</div>`; });
|
||||
html += '</div>';
|
||||
|
||||
@@ -262,7 +262,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -432,7 +432,7 @@ function buildSidebar(id){
|
||||
const box=document.getElementById('sidebar-content'); const sb=SIDEBARS[id]||SIDEBARS.p1; let html='';
|
||||
const xpForLv=_xpForLevel(STATE.level),xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv,xpRange=xpNext-xpForLv,xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+=`<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. ${STATE.level}</span></div><div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div><div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div></div>`;
|
||||
html+=`<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. ${STATE.level}</span></div><div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div><div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div></div>`;
|
||||
html+=`<div class="sidecard"><h4>${sb.title}</h4>`; sb.rows.forEach(([k,v])=>{ html+=`<div class="sidecard-row"><b>${k}</b>${v?' — '+v:''}</div>`; }); html+='</div>';
|
||||
const tip=TIPS.find(t=>t.sec===id)||TIPS[0];
|
||||
html+=`<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#065f46;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><rect x="3" y="3" width="18" height="14" rx="1"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">${tip.html}</div></div>`;
|
||||
|
||||
@@ -254,7 +254,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -368,7 +368,7 @@ const TIPS=[
|
||||
{sec:'final3',html:'Три признака подобия — главный инструмент геометрических доказательств.'},
|
||||
];
|
||||
|
||||
function buildSidebar(id){const box=document.getElementById('sidebar-content');const sb=SIDEBARS[id]||SIDEBARS.p1;let html='';const xpForLv=_xpForLevel(STATE.level),xpNext=_xpForLevel(STATE.level+1);const xpInLv=STATE.xp-xpForLv,xpRange=xpNext-xpForLv,xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;html+=`<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. ${STATE.level}</span></div><div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div><div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div></div>`;html+=`<div class="sidecard"><h4>${sb.title}</h4>`;sb.rows.forEach(([k,v])=>{html+=`<div class="sidecard-row"><b>${k}</b>${v?' — '+v:''}</div>`;});html+='</div>';const tip=TIPS.find(t=>t.sec===id)||TIPS[0];html+=`<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#4c1d95;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">${tip.html}</div></div>`;if(STATE.achievements.size>0){html+=`<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">${STATE.achievements.size}</span></h4>`;[...STATE.achievements.values()].slice(-4).forEach(text=>{html+=`<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ ${text}</div>`;});html+='</div>';}box.innerHTML=html;if(window.renderMathInElement)try{renderMath(box);}catch(e){}}
|
||||
function buildSidebar(id){const box=document.getElementById('sidebar-content');const sb=SIDEBARS[id]||SIDEBARS.p1;let html='';const xpForLv=_xpForLevel(STATE.level),xpNext=_xpForLevel(STATE.level+1);const xpInLv=STATE.xp-xpForLv,xpRange=xpNext-xpForLv,xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;html+=`<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. ${STATE.level}</span></div><div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div><div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div></div>`;html+=`<div class="sidecard"><h4>${sb.title}</h4>`;sb.rows.forEach(([k,v])=>{html+=`<div class="sidecard-row"><b>${k}</b>${v?' — '+v:''}</div>`;});html+='</div>';const tip=TIPS.find(t=>t.sec===id)||TIPS[0];html+=`<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#4c1d95;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><polygon points="12,2 22,20 2,20"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">${tip.html}</div></div>`;if(STATE.achievements.size>0){html+=`<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">${STATE.achievements.size}</span></h4>`;[...STATE.achievements.values()].slice(-4).forEach(text=>{html+=`<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ ${text}</div>`;});html+='</div>';}box.innerHTML=html;if(window.renderMathInElement)try{renderMath(box);}catch(e){}}
|
||||
|
||||
function initTheme(){const t=localStorage.getItem('geometry8_ch3_theme')||'light';if(t==='dark')document.documentElement.classList.add('dark');document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';document.getElementById('theme-btn').addEventListener('click',()=>{document.documentElement.classList.toggle('dark');const dark=document.documentElement.classList.contains('dark');localStorage.setItem('geometry8_ch3_theme',dark?'dark':'light');document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';});}
|
||||
function renderMath(root){if(window.renderMathInElement){try{renderMathInElement(root,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false});}catch(e){}}}
|
||||
|
||||
@@ -260,7 +260,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" title="Опыт" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -402,7 +402,7 @@ const TIPS=[
|
||||
{sec:'final4',html:'Ключевая теорема: вписанный угол = ½ дуги. Из неё следуют большинство остальных.'},
|
||||
];
|
||||
|
||||
function buildSidebar(id){const box=document.getElementById('sidebar-content');const sb=SIDEBARS[id]||SIDEBARS.p1;let html='';const xpForLv=_xpForLevel(STATE.level),xpNext=_xpForLevel(STATE.level+1);const xpInLv=STATE.xp-xpForLv,xpRange=xpNext-xpForLv,xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;html+=`<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. ${STATE.level}</span></div><div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div><div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div></div>`;html+=`<div class="sidecard"><h4>${sb.title}</h4>`;sb.rows.forEach(([k,v])=>{html+=`<div class="sidecard-row"><b>${k}</b>${v?' — '+v:''}</div>`;});html+='</div>';const tip=TIPS.find(t=>t.sec===id)||TIPS[0];html+=`<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#0e4f6b;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><circle cx="12" cy="12" r="9"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">${tip.html}</div></div>`;if(STATE.achievements.size>0){html+=`<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">${STATE.achievements.size}</span></h4>`;[...STATE.achievements.values()].slice(-4).forEach(text=>{html+=`<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ ${text}</div>`;});html+='</div>';}box.innerHTML=html;if(window.renderMathInElement)try{renderMath(box);}catch(e){}}
|
||||
function buildSidebar(id){const box=document.getElementById('sidebar-content');const sb=SIDEBARS[id]||SIDEBARS.p1;let html='';const xpForLv=_xpForLevel(STATE.level),xpNext=_xpForLevel(STATE.level+1);const xpInLv=STATE.xp-xpForLv,xpRange=xpNext-xpForLv,xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;html+=`<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. ${STATE.level}</span></div><div class="xp-bar"><div class="xp-fill" style="width:${xpPct}%"></div></div><div class="xp-nums"><span>${STATE.xp} XP</span><span>${xpNext} XP</span></div></div>`;html+=`<div class="sidecard"><h4>${sb.title}</h4>`;sb.rows.forEach(([k,v])=>{html+=`<div class="sidecard-row"><b>${k}</b>${v?' — '+v:''}</div>`;});html+='</div>';const tip=TIPS.find(t=>t.sec===id)||TIPS[0];html+=`<div class="sidecard" style="background:linear-gradient(135deg,var(--warn-bg,#fef3c7),var(--pri-soft));border-color:var(--warn,#f59e0b)"><h4 style="color:#0e4f6b;display:flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><circle cx="12" cy="12" r="9"/></svg>Подсказка</h4><div class="sidecard-row" style="margin-bottom:0;font-size:.84rem;line-height:1.55">${tip.html}</div></div>`;if(STATE.achievements.size>0){html+=`<div class="sidecard"><h4>Достижения <span style="color:var(--warn);float:right">${STATE.achievements.size}</span></h4>`;[...STATE.achievements.values()].slice(-4).forEach(text=>{html+=`<div class="sidecard-row" style="font-size:.78rem;color:var(--ok)">✓ ${text}</div>`;});html+='</div>';}box.innerHTML=html;if(window.renderMathInElement)try{renderMath(box);}catch(e){}}
|
||||
|
||||
function initTheme(){const t=localStorage.getItem('geometry8_ch4_theme')||'light';if(t==='dark')document.documentElement.classList.add('dark');document.getElementById('theme-lab').textContent=t==='dark'?'Светлая':'Тёмная';document.getElementById('theme-btn').addEventListener('click',()=>{document.documentElement.classList.toggle('dark');const dark=document.documentElement.classList.contains('dark');localStorage.setItem('geometry8_ch4_theme',dark?'dark':'light');document.getElementById('theme-lab').textContent=dark?'Светлая':'Тёмная';});}
|
||||
function renderMath(root){if(window.renderMathInElement){try{renderMathInElement(root,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true},{left:'\\(',right:'\\)',display:false}],throwOnError:false});}catch(e){}}}
|
||||
|
||||
@@ -162,7 +162,7 @@ main{max-width:1100px;margin:0 auto;padding:32px 24px 60px}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -248,7 +248,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -433,7 +433,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -229,7 +229,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -402,7 +402,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -229,7 +229,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -402,7 +402,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -244,7 +244,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -421,7 +421,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -261,7 +261,7 @@ html.dark .final-cta-sub{color:#fcd34d}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -267,7 +267,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -476,7 +476,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -257,7 +257,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -441,7 +441,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -261,7 +261,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -465,7 +465,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -254,7 +254,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -423,7 +423,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -259,7 +259,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -453,7 +453,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -256,7 +256,7 @@ input[type=range]:active{box-shadow:0 0 0 4px var(--pri-soft);border-radius:8px}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -435,7 +435,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' \u2014 '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -246,7 +246,7 @@ html.dark .final-cta-sub{color:#fcd34d}
|
||||
<div id="overall-text" style="font-size:1.05rem;font-weight:700">Загрузка...</div>
|
||||
<div class="po-bar"><div id="overall-fill" class="po-fill" style="width:0%"></div></div>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none">0 XP</div>
|
||||
<div id="hero-xp-badge" class="po-xp" style="display:none" data-gamified>0 XP</div>
|
||||
</section>
|
||||
|
||||
<div class="ch-grid">
|
||||
|
||||
@@ -194,7 +194,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -367,7 +367,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -189,7 +189,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -371,7 +371,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -189,7 +189,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -388,7 +388,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -185,7 +185,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -346,7 +346,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -185,7 +185,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -346,7 +346,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -185,7 +185,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -356,7 +356,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -185,7 +185,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -367,7 +367,7 @@ function buildSidebar(id){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>'+sb.title+'</h4>';
|
||||
sb.rows.forEach(([k,v])=>{ html+='<div class="sidecard-row"><b>'+k+'</b>'+(v?' — '+v:'')+'</div>'; });
|
||||
html+='</div>';
|
||||
|
||||
@@ -155,7 +155,7 @@ a{color:inherit;text-decoration:none}
|
||||
<div class="hp-bar"><div id="hero-hp-fill" class="hp-fill"></div></div>
|
||||
<span id="hero-hp-text" class="hp-text">0%</span>
|
||||
</div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge"></div>
|
||||
<div id="hero-xp-badge" class="hero-xp-badge" data-gamified></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -240,7 +240,7 @@ function buildSidebar(){
|
||||
const xpForLv=_xpForLevel(STATE.level), xpNext=_xpForLevel(STATE.level+1);
|
||||
const xpInLv=STATE.xp-xpForLv, xpRange=xpNext-xpForLv;
|
||||
const xpPct=xpRange>0?Math.round(xpInLv/xpRange*100):100;
|
||||
html+='<div class="xp-card"><div class="xp-card-title"><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="xp-card" data-gamified><div class="xp-card-title" data-gamified><span>XP-прогресс</span><span class="xp-level">Ур. '+STATE.level+'</span></div><div class="xp-bar"><div class="xp-fill" style="width:'+xpPct+'%"></div></div><div class="xp-nums"><span>'+STATE.xp+' XP</span><span>'+xpNext+' XP</span></div></div>';
|
||||
html+='<div class="sidecard"><h4>Курс физики 11</h4>'
|
||||
+ '<div class="sidecard-row"><b>Глав</b> — 8</div>'
|
||||
+ '<div class="sidecard-row"><b>Параграфов</b> — 45</div>'
|
||||
|
||||
Reference in New Issue
Block a user