diff --git a/frontend/classes.html b/frontend/classes.html index 3673f5c..4e9cbfc 100644 --- a/frontend/classes.html +++ b/frontend/classes.html @@ -717,7 +717,7 @@ - 🃏 Коллекция + Коллекция diff --git a/frontend/collection-rb.html b/frontend/collection-rb.html index 4be0713..a277c0b 100644 --- a/frontend/collection-rb.html +++ b/frontend/collection-rb.html @@ -299,7 +299,7 @@ const METHOD_LABELS = { explore: ' Исследование', quest: ' Квест', sighting:' Наблюдение', - daily: '⭐ Вид дня', + daily: ' Вид дня', }; const ACHIEVEMENTS = [ diff --git a/frontend/js/assistant.js b/frontend/js/assistant.js index 0c84e86..6118e5c 100644 --- a/frontend/js/assistant.js +++ b/frontend/js/assistant.js @@ -19,6 +19,8 @@ /* ── helpers ─────────────────────────────────────────────────────────── */ function esc(s) { return (window.LS && LS.escapeHtml) ? LS.escapeHtml(String(s == null ? '' : s)) : String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]; }); } + // Безопасный href: только внутренний путь /… или https://… — блокирует javascript:/data:. + function safeUrl(u) { u = String(u == null ? '' : u).trim(); return (/^\//.test(u) || /^https?:\/\//i.test(u)) ? u : '#'; } function lsGet(k) { try { return localStorage.getItem(k); } catch (e) { return null; } } function lsSet(k, v) { try { localStorage.setItem(k, v); } catch (e) {} } function todayKey() { return new Date().toISOString().slice(0, 10); } @@ -372,7 +374,7 @@ function hintHtml(rule) { var act = null; try { act = rule.action(); } catch (e) {} - var actHtml = act && act.url ? '' + esc(act.label || 'Открыть') + '' : ''; + var actHtml = act && act.url ? '' + esc(act.label || 'Открыть') + '' : ''; var dismiss = (rule.scope !== 'celebration') ? 'Не показывать' : ''; return '' + esc(PET && PET.petName ? PET.petName : 'Квантик') + '' + '' + esc(rule.text ? rule.text() : rule.text) + '' + @@ -633,13 +635,13 @@ // источники (RAG) if (model && sources.length) { var sc = document.createElement('div'); sc.className = 'asst-src'; - sc.innerHTML = 'Источник: ' + sources.map(function (s) { return '' + esc(s.title) + (s.section ? ', ' + esc(s.section) : '') + ''; }).join('; '); + sc.innerHTML = 'Источник: ' + sources.map(function (s) { return '' + esc(s.title) + (s.section ? ', ' + esc(s.section) : '') + ''; }).join('; '); chatEl.appendChild(sc); } // ссылки FAQ/платформа var links = ''; - if (!model && ans.length) links += ans.slice(0, 2).filter(function (a) { return a.url; }).map(function (a) { return '' + esc(a.q) + ''; }).join(' · '); - if (found.length) links += (links ? '' : '') + 'На платформе: ' + found.slice(0, 3).map(function (f) { return '' + esc(f.title || '…') + ''; }).join(' · '); + if (!model && ans.length) links += ans.slice(0, 2).filter(function (a) { return a.url; }).map(function (a) { return '' + esc(a.q) + ''; }).join(' · '); + if (found.length) links += (links ? '' : '') + 'На платформе: ' + found.slice(0, 3).map(function (f) { return '' + esc(f.title || '…') + ''; }).join(' · '); if (links) { var l = document.createElement('div'); l.className = 'asst-msg-links'; l.innerHTML = links; chatEl.appendChild(l); } // оценка ответа if (model) { diff --git a/frontend/pet.html b/frontend/pet.html index f73b33c..a7b4637 100644 --- a/frontend/pet.html +++ b/frontend/pet.html @@ -1776,11 +1776,11 @@ function showFeedResult(correct, xp, customMsg) { el.style.cssText = 'display:block;padding:12px 14px;border-radius:12px;background:rgba(56,217,90,.1);border:1.5px solid rgba(56,217,90,.25);font-size:.87rem;font-weight:700;text-align:center;color:#38D95A'; el.textContent = `Правильно! +${xp} XP — питомец доволен!`; floatLabel(`+${xp} XP`, '#38D95A'); - document.getElementById('pet-bubble').textContent = '😋 Вкуснятина!'; + document.getElementById('pet-bubble').textContent = 'Вкуснятина!'; setTimeout(() => { closeFeedGame(); startFeedCooldown(1800); }, 1800); } else { el.style.cssText = 'display:block;padding:12px 14px;border-radius:12px;background:rgba(249,65,68,.08);border:1.5px solid rgba(249,65,68,.2);font-size:.87rem;font-weight:700;text-align:center;color:#F94144'; - el.textContent = 'Неверно — питомец остался голодным 😢'; + el.textContent = 'Неверно — питомец остался голодным'; setTimeout(closeFeedGame, 1800); } } diff --git a/js/api.js b/js/api.js index 373340b..fefaa25 100644 --- a/js/api.js +++ b/js/api.js @@ -960,28 +960,17 @@ async function biochemSavePathwayProgress(pathway,step,completed){ return req('P const _prefsCache = {}; let _prefsDirty = false; let _prefsTimer = null; +const _PREFS_LS_KEY = 'ls_prefs'; -// SYNC DISABLED (debug mode) — раскомментировать для включения синхронизации -async function _prefsLoad() { /* disabled */ } -function _prefsFlush() { /* disabled */ } - -// async function _prefsLoad() { -// if (!isLoggedIn()) return; -// try { -// const data = await apiFetch('/api/preferences', { method: 'GET' }); -// Object.assign(_prefsCache, data); -// } catch (e) {} -// } -// -// function _prefsFlush() { -// if (!_prefsDirty) return; -// _prefsDirty = false; -// if (!isLoggedIn()) return; -// apiFetch('/api/preferences', { -// method: 'PATCH', -// body: JSON.stringify(_prefsCache), -// }).catch(() => {}); -// } +// Персистентность настроек — в localStorage (per-device). Раньше sync был отключён, +// и настройки молча терялись при перезагрузке. Грузим синхронно сразу при загрузке api.js. +try { const raw = localStorage.getItem(_PREFS_LS_KEY); if (raw) Object.assign(_prefsCache, JSON.parse(raw)); } catch (e) {} +async function _prefsLoad() { try { const raw = localStorage.getItem(_PREFS_LS_KEY); if (raw) Object.assign(_prefsCache, JSON.parse(raw)); } catch (e) {} } +function _prefsFlush() { + if (!_prefsDirty) return; + _prefsDirty = false; + try { localStorage.setItem(_PREFS_LS_KEY, JSON.stringify(_prefsCache)); } catch (e) {} +} const lsPrefs = { get(key, def) { @@ -1442,6 +1431,7 @@ function connectSSE(onEvent) { window.addEventListener('pagehide', () => { if (_sseShared) { _sseShared.close(); _sseShared = null; } _sseListeners.clear(); + try { lsPrefs.flush(); } catch (e) {} }); /* ── assignment templates ─────────────────────────────────────────────────── */