-
-
✦ Arcana & Spells
-
-
-
- | Name / Notes |
- MP Cost |
- Targets |
- Duration |
- |
-
-
-
-
-
-
-
-
-
⊕ Rituals
-
-
-
-
-
-
-
+
+
✦ Arcana & Spells
+
+
+
+ | Name / Notes |
+ MP Cost |
+ Targets |
+ Duration |
+ |
+
+
+
+
+
-
+
+
+
-
+
+ // ── SAVE / LOAD ────────────────────────────────────
+ function collectData() {
+ const get = id => { const el = document.getElementById(id); return el ? el.value : ''; };
+ const statusesActive = [...document.querySelectorAll('.status-item.active-status')].map(el => el.dataset.status);
+ const martialChecked = [...document.querySelectorAll('.martial-item.checked')].map(el => el.querySelector('span').textContent);
+ const disciplinesChecked = [...document.querySelectorAll('.disc-item.checked')].map(el => el.querySelector('span').textContent);
+
+ return {
+ name: get('charName'), pronouns: get('charPronouns'),
+ identity: get('charIdentity'), theme: get('charTheme'), origin: get('charOrigin'),
+ traits: get('charTraits'),
+ level, xp: get('xpCurrent'), zenit: get('zenit'),
+ initMod: get('initMod'), defense: get('defense'), magDef: get('magDef'),
+ dexBase: get('dex-base'), dexCur: get('dex-cur'),
+ insBase: get('ins-base'), insCur: get('ins-cur'),
+ migBase: get('mig-base'), migCur: get('mig-cur'),
+ wlpBase: get('wlp-base'), wlpCur: get('wlp-cur'),
+ hpMax: get('hpMax'), hpCur: get('hpCur'),
+ mpMax: get('mpMax'), mpCur: get('mpCur'),
+ ipMax: get('ipMax'), ipCur: get('ipCur'),
+ fp: get('fpCount'),
+ backpack: get('backpack'),
+ accName: get('acc-name'), accDesc: get('acc-desc'),
+ armName: get('arm-name'), armDesc: get('arm-desc'),
+ mhName: get('mh-name'), mhDesc: get('mh-desc'),
+ ohName: get('oh-name'), ohDesc: get('oh-desc'),
+ heroicSkills: get('heroicSkills'),
+ ritualsNotes: get('ritualsNotes'),
+ statusesActive, martialChecked, disciplinesChecked,
+ bonds, primaryClasses, otherClasses, spells
+ };
+ }
+
+ function saveSheet() {
+ const data = collectData();
+ localStorage.setItem('fabulaUltimaSheet', JSON.stringify(data));
+ const st = document.getElementById('saveStatus');
+ st.classList.add('show');
+ setTimeout(() => st.classList.remove('show'), 2000);
+ }
+
+ function tryAutoLoad() {
+ const raw = localStorage.getItem('fabulaUltimaSheet');
+ if (raw) try { applyData(JSON.parse(raw)); } catch (e) { }
+ }
+
+ function loadSheet() {
+ const raw = localStorage.getItem('fabulaUltimaSheet');
+ if (!raw) return alert('No saved sheet found.');
+ try { applyData(JSON.parse(raw)); } catch (e) { alert('Could not load sheet.'); }
+ }
+
+ function applyData(d) {
+ const set = (id, val) => { const el = document.getElementById(id); if (el && val !== undefined) el.value = val; };
+ set('charName', d.name); set('charPronouns', d.pronouns);
+ set('charIdentity', d.identity); set('charTheme', d.theme); set('charOrigin', d.origin);
+ set('charTraits', d.traits);
+ level = d.level || 1;
+ document.getElementById('levelDisplay').textContent = level;
+ set('xpCurrent', d.xp); set('zenit', d.zenit);
+ set('initMod', d.initMod); set('defense', d.defense); set('magDef', d.magDef);
+ set('dex-base', d.dexBase); set('dex-cur', d.dexCur);
+ set('ins-base', d.insBase); set('ins-cur', d.insCur);
+ set('mig-base', d.migBase); set('mig-cur', d.migCur);
+ set('wlp-base', d.wlpBase); set('wlp-cur', d.wlpCur);
+ set('hpMax', d.hpMax); set('hpCur', d.hpCur);
+ set('mpMax', d.mpMax); set('mpCur', d.mpCur);
+ set('ipMax', d.ipMax); set('ipCur', d.ipCur);
+ set('fpCount', d.fp);
+ set('backpack', d.backpack);
+ set('acc-name', d.accName); set('acc-desc', d.accDesc);
+ set('arm-name', d.armName); set('arm-desc', d.armDesc);
+ set('mh-name', d.mhName); set('mh-desc', d.mhDesc);
+ set('oh-name', d.ohName); set('oh-desc', d.ohDesc);
+ set('heroicSkills', d.heroicSkills);
+ set('ritualsNotes', d.ritualsNotes);
+
+ // Statuses
+ document.querySelectorAll('.status-item').forEach(el => {
+ const active = (d.statusesActive || []).includes(el.dataset.status);
+ el.classList.toggle('active-status', active);
+ el.querySelector('.status-check').textContent = active ? '✗' : '';
+ });
+
+ // Martial
+ document.querySelectorAll('.martial-item').forEach(el => {
+ const checked = (d.martialChecked || []).includes(el.querySelector('span').textContent);
+ el.classList.toggle('checked', checked);
+ el.querySelector('.martial-box').textContent = checked ? '✓' : '';
+ });
+
+ // Disciplines
+ document.querySelectorAll('.disc-item').forEach(el => {
+ const checked = (d.disciplinesChecked || []).includes(el.querySelector('span').textContent);
+ el.classList.toggle('checked', checked);
+ el.querySelector('.disc-box').textContent = checked ? '✓' : '';
+ });
+
+ if (d.bonds) { bonds = d.bonds; renderBonds(); }
+ if (d.primaryClasses) { primaryClasses = d.primaryClasses; renderPrimaryClasses(); }
+ if (d.otherClasses) { otherClasses = d.otherClasses; renderOtherClasses(); }
+ if (d.spells) { spells = d.spells; renderSpells(); }
+
+ renderFP();
+ updateXPBar();
+ updateCrisis();
+ }
+
+ // ── EXPORT / IMPORT JSON ───────────────────────────
+ function exportSheet() {
+ const data = collectData();
+ const json = JSON.stringify(data, null, 2);
+ const blob = new Blob([json], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ const charName = (data.name || 'character').replace(/[^a-z0-9_\- ]/gi, '').trim() || 'character';
+ a.href = url;
+ a.download = charName + '-fabula-ultima.json';
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+
+ function importSheet() {
+ document.getElementById('importFileInput').value = '';
+ document.getElementById('importFileInput').click();
+ }
+
+ function handleImportFile(input) {
+ const file = input.files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = e => {
+ try {
+ const data = JSON.parse(e.target.result);
+ applyData(data);
+ saveSheet();
+ const st = document.getElementById('saveStatus');
+ st.textContent = 'Imported!';
+ st.classList.add('show');
+ setTimeout(() => { st.classList.remove('show'); st.textContent = 'Saved!'; }, 2500);
+ } catch (err) {
+ alert('Could not import: invalid JSON file.');
+ }
+ };
+ reader.readAsText(file);
+ }
+
+ // Auto-save every 30s
+ setInterval(saveSheet, 30000);
+
+ init();
+
-
+
+