feat(algebra-8): общая система опыта для главы 1 и главы 2
Раньше: каждая глава хранила XP отдельно (algebra8_ch1_xp + algebra8_ch2_xp), формулы уровня были разные (дискретная таблица в ch1, формула sqrt в ch2), визуально XP-карты различались. Теперь: - Один ключ localStorage: 'algebra8_xp' для обеих глав. - При первой загрузке (в любой главе) — single-shot миграция: если новый ключ отсутствует, суммирует старые ch1 + ch2 и сохраняет под единый ключ. Старые ключи не удаляются (на всякий). - Единая таблица уровней XP_LEVELS = [0, 50, 120, 220, 350, 520, 740, 1000, 1300, 1700, 2200] (11 уровней, MAX = Ур. 11). - Единые функции calcLevel(xp) и _xpForLevel(lv). - XP-карта в сайдбаре главы 2 теперь идентична главе 1: градиент acc→pri-soft, .xp-card-title, .xp-bar, .xp-fill, .xp-nums. - Hero badge «★ Ур. N · NN XP» добавлен в hero обоих глав. - addXp в ch2: при повышении уровня — popup с номером уровня + confetti. - addXp в ch1: refreshProgressUI вызывается, чтобы обновлять hero badge сразу после начисления.
This commit is contained in:
@@ -65,6 +65,8 @@ input,select,textarea{font-family:inherit}
|
||||
.hero h2{font-size:1.55rem;font-weight:800;color:var(--pri2);margin-bottom:10px;letter-spacing:-.01em}
|
||||
.hero p{font-size:.95rem;color:var(--text);opacity:.88;margin-bottom:14px;max-width:640px}
|
||||
.hero-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
|
||||
.hero-xp-badge{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,var(--warn,#f59e0b),var(--pri));color:#fff;border-radius:99px;font-size:.82rem;font-weight:800;letter-spacing:.02em;box-shadow:0 4px 12px rgba(233,30,99,.22);font-family:'Unbounded',sans-serif}
|
||||
.hero-xp-badge svg{flex-shrink:0}
|
||||
.btn-primary{padding:11px 22px;background:linear-gradient(135deg,var(--pri),var(--pri2));color:#fff;border-radius:11px;font-weight:700;font-size:.92rem;display:inline-flex;align-items:center;gap:8px;box-shadow:var(--sh2);transition:transform .15s,box-shadow .15s}
|
||||
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 8px 28px rgba(233,30,99,.28)}
|
||||
.btn-secondary{padding:10px 18px;background:var(--card);color:var(--pri2);border:1.5px solid var(--pri);border-radius:11px;font-weight:700;font-size:.88rem;transition:background .15s}
|
||||
@@ -784,6 +786,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>
|
||||
</section>
|
||||
|
||||
@@ -1031,8 +1034,15 @@ function loadProgress(){
|
||||
}
|
||||
const sb = localStorage.getItem('algebra8_ch1_squaresBest');
|
||||
if(sb) STATE.squaresBest = +sb;
|
||||
const xp = localStorage.getItem('algebra8_ch1_xp');
|
||||
if(xp){ STATE.xp = +xp; STATE.level = calcLevel(STATE.xp); }
|
||||
// Общий XP для всех глав. Если ещё нет — собираем из старых ch1/ch2 ключей (single-shot миграция).
|
||||
let xp = localStorage.getItem('algebra8_xp');
|
||||
if(xp === null){
|
||||
const c1 = +(localStorage.getItem('algebra8_ch1_xp') || 0);
|
||||
const c2 = +(localStorage.getItem('algebra8_ch2_xp') || 0);
|
||||
xp = c1 + c2;
|
||||
try { localStorage.setItem('algebra8_xp', String(xp)); } catch(e){}
|
||||
}
|
||||
STATE.xp = +xp || 0; STATE.level = calcLevel(STATE.xp);
|
||||
const sk = localStorage.getItem('algebra8_ch1_streak');
|
||||
if(sk){ const o = JSON.parse(sk); STATE.streak = o.streak||0; STATE.maxStreak = o.max||0; }
|
||||
const dc = localStorage.getItem('algebra8_ch1_daily');
|
||||
@@ -1046,7 +1056,7 @@ function saveProgress(){
|
||||
localStorage.setItem('algebra8_ch1_progress', JSON.stringify(STATE.progress));
|
||||
localStorage.setItem('algebra8_ch1_achievements', JSON.stringify(Object.fromEntries(STATE.achievements)));
|
||||
if(isFinite(STATE.squaresBest)) localStorage.setItem('algebra8_ch1_squaresBest', String(STATE.squaresBest));
|
||||
localStorage.setItem('algebra8_ch1_xp', String(STATE.xp));
|
||||
localStorage.setItem('algebra8_xp', String(STATE.xp));
|
||||
localStorage.setItem('algebra8_ch1_streak', JSON.stringify({streak:STATE.streak, max:STATE.maxStreak}));
|
||||
localStorage.setItem('algebra8_ch1_daily', JSON.stringify(STATE.dailyChallenge));
|
||||
localStorage.setItem('algebra8_ch1_bossResults', JSON.stringify(STATE.bossResults));
|
||||
@@ -1082,6 +1092,11 @@ function refreshProgressUI(){
|
||||
});
|
||||
// check 95% for final chapter modal
|
||||
if(t >= 95) _maybeShowFinalChapter();
|
||||
// XP badge in hero — единый стиль с главой 2
|
||||
const xpBadge = document.getElementById('hero-xp-badge');
|
||||
if(xpBadge){
|
||||
xpBadge.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px;height:13px"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg> Ур. ' + STATE.level + ' · ' + (STATE.xp || 0) + ' XP';
|
||||
}
|
||||
}
|
||||
let _finalShown = false;
|
||||
function _maybeShowFinalChapter(){
|
||||
@@ -5911,6 +5926,7 @@ function addXp(amount, source){
|
||||
STATE.xp += amount;
|
||||
STATE.level = calcLevel(STATE.xp);
|
||||
saveProgress();
|
||||
refreshProgressUI(); // обновляет XP-бейдж в hero
|
||||
|
||||
if(STATE.level > prevLevel){
|
||||
// Level up!
|
||||
|
||||
Reference in New Issue
Block a user