// ═══════════════════════════════════════════════════════════════════════ // STATE — global runtime state // ═══════════════════════════════════════════════════════════════════════ const SP = 'hvunified_'; const STATE = { page: null, level: 1, tier: 'novice', isekai: false, battleMode: '', inBattle: false, battleInitialized: false, hoverTarget: -1, interruptHover: false, interruptAlert: false, monsters: [], hp: 1, mp: 1, sp: 1, oc: 0, spiritStance: false, buffs: {}, spellsKnown: [], skillsKnown: [], itemsKnown: {}, cooldowns: {}, battleMonsterNames: [], channeling: false, monsterData: {}, difficulty: 'Normal', battleStats: { damageDealt: 0, damageTaken: 0, turns: 0, drops: 0, credits: 0, }, round: 0, settingsVisible: false, }; // Load monster data from localStorage try { STATE.monsterData = JSON.parse(localStorage[SP + 'monsters'] || '{}'); } catch (e) { STATE.monsterData = {}; } // ── helpers ── function loadConfig() { try { const saved = JSON.parse(localStorage[SP + 'cfg'] || '{}'); for (const [k, v] of Object.entries(saved)) { if (CFG[k] !== undefined) CFG[k] = v; } } catch (e) {} } function saveConfig() { try { localStorage[SP + 'cfg'] = JSON.stringify(CFG); } catch (e) {} } function setConfig(key, value) { if (CFG[key] !== undefined) { CFG[key] = value; saveConfig(); } } function readCSSDigits(container) { if (!container) return null; const digits = []; container.querySelectorAll('div').forEach(c => { const m = (c.className || '').match(/c4(\d)\b/); if (m) digits.push(m[1]); }); if (digits.length === 0) return null; const parsed = parseInt(digits.join('')); return isNaN(parsed) ? null : parsed; } // CSS font character map: c5a=a, c5b=b, ..., c5z=z // c59 = space, c4a = period, c40-c49 = 0-9 // Note: no \\b word boundary — JS handles boundaries differently in class names function readCSSText(container) { if (!container) return ''; let text = ''; container.querySelectorAll('div').forEach(c => { const cls = c.className || ''; // c5a-c5z → letters a-z const letterMatch = cls.match(/c5([a-z])\b/); if (letterMatch) { text += letterMatch[1]; return; } // c59 → space if (/c59\b/.test(cls)) { text += ' '; return; } // c4a → period if (/c4a\b/.test(cls)) { text += '.'; return; } // c40-c49 → digits 0-9 const digitMatch = cls.match(/c4([0-9])\b/); if (digitMatch) { text += digitMatch[1]; return; } }); return text; } // Save difficulty from last overworld page read function cacheDifficulty() { if (STATE.difficulty && STATE.difficulty !== 'Normal') { try { localStorage[SP + 'difficulty'] = STATE.difficulty; } catch (e) {} } } // Load cached difficulty (for battle pages where difficulty isn't in DOM) function restoreCachedDifficulty() { if (STATE.difficulty === 'Normal' || !STATE.difficulty) { try { const cached = localStorage[SP + 'difficulty']; if (cached) STATE.difficulty = cached; } catch (e) {} } }