perf: Minify JSON save keys to reduce serialized state size

Replace verbose key names with short identifiers in collectData/applyData
(e.g. name→n, zenit→z, heroicSkills→hs). Nested bond, class, and spell
objects are mapped at the boundary so in-memory state and rendering code
are unchanged. applyData uses ?? fallbacks to remain compatible with data
saved under the old key names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 02:36:13 +00:00
parent 52ee6ea26b
commit 32991843d8

View File

@@ -248,35 +248,54 @@ function addSpell() { spells.push({}); renderSpells(); }
function removeSpell(i) { spells.splice(i, 1); renderSpells(); } function removeSpell(i) { spells.splice(i, 1); renderSpells(); }
// ── SAVE / LOAD ──────────────────────────────────── // ── SAVE / LOAD ────────────────────────────────────
// Short key legend (serialized form only; in-memory objects use full names):
// n=name, pn=pronouns, id=identity, th=theme, og=origin, tr=traits
// lv=level, xp=xp, z=zenit
// im=initMod, df=defense, md=magDef
// dxb=dexBase, dxc=dexCur, inb=insBase, inc=insCur
// mgb=migBase, mgc=migCur, wpb=wlpBase, wpc=wlpCur
// hx=hpMax, hc=hpCur, mx=mpMax, mc=mpCur, ix=ipMax, ic=ipCur
// fp=fp, bp=backpack
// acn=accName, acd=accDesc, amn=armName, amd=armDesc
// mhn=mhName, mhd=mhDesc, ohn=ohName, ohd=ohDesc
// hs=heroicSkills, rn=ritualsNotes
// sa=statusesActive, ma=martialChecked, da=disciplinesChecked
// bo=bonds, pc=primaryClasses, oc=otherClasses, sp=spells
// Nested bonds: n=name, f=feelings
// Nested classes: n=name, b=benefits, s=skills
// Nested spells: n=name, nt=notes, mp=mp, tg=targets, dr=duration
function collectData() { function collectData() {
const get = id => { const el = document.getElementById(id); return el ? el.value : ''; }; 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 sa = [...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 ma = [...document.querySelectorAll('.martial-item.checked')].map(el => el.querySelector('span').textContent);
const disciplinesChecked = [...document.querySelectorAll('.disc-item.checked')].map(el => el.querySelector('span').textContent); const da = [...document.querySelectorAll('.disc-item.checked')].map(el => el.querySelector('span').textContent);
return { return {
name: get('charName'), pronouns: get('charPronouns'), n: get('charName'), pn: get('charPronouns'),
identity: get('charIdentity'), theme: get('charTheme'), origin: get('charOrigin'), id: get('charIdentity'), th: get('charTheme'), og: get('charOrigin'),
traits: get('charTraits'), tr: get('charTraits'),
level, xp: get('xpCurrent'), zenit: get('zenit'), lv: level, xp: get('xpCurrent'), z: get('zenit'),
initMod: get('initMod'), defense: get('defense'), magDef: get('magDef'), im: get('initMod'), df: get('defense'), md: get('magDef'),
dexBase: get('dex-base'), dexCur: get('dex-cur'), dxb: get('dex-base'), dxc: get('dex-cur'),
insBase: get('ins-base'), insCur: get('ins-cur'), inb: get('ins-base'), inc: get('ins-cur'),
migBase: get('mig-base'), migCur: get('mig-cur'), mgb: get('mig-base'), mgc: get('mig-cur'),
wlpBase: get('wlp-base'), wlpCur: get('wlp-cur'), wpb: get('wlp-base'), wpc: get('wlp-cur'),
hpMax: get('hpMax'), hpCur: get('hpCur'), hx: get('hpMax'), hc: get('hpCur'),
mpMax: get('mpMax'), mpCur: get('mpCur'), mx: get('mpMax'), mc: get('mpCur'),
ipMax: get('ipMax'), ipCur: get('ipCur'), ix: get('ipMax'), ic: get('ipCur'),
fp: get('fpCount'), fp: get('fpCount'),
backpack: get('backpack'), bp: get('backpack'),
accName: get('acc-name'), accDesc: get('acc-desc'), acn: get('acc-name'), acd: get('acc-desc'),
armName: get('arm-name'), armDesc: get('arm-desc'), amn: get('arm-name'), amd: get('arm-desc'),
mhName: get('mh-name'), mhDesc: get('mh-desc'), mhn: get('mh-name'), mhd: get('mh-desc'),
ohName: get('oh-name'), ohDesc: get('oh-desc'), ohn: get('oh-name'), ohd: get('oh-desc'),
heroicSkills: get('heroicSkills'), hs: get('heroicSkills'),
ritualsNotes: get('ritualsNotes'), rn: get('ritualsNotes'),
statusesActive, martialChecked, disciplinesChecked, sa, ma, da,
bonds, primaryClasses, otherClasses, spells bo: bonds.map(b => ({ n: b.name, f: b.feelings })),
pc: primaryClasses.map(c => ({ n: c.name, b: c.benefits, s: c.skills })),
oc: otherClasses.map(c => ({ n: c.name, b: c.benefits, s: c.skills })),
sp: spells.map(s => ({ n: s.name, nt: s.notes, mp: s.mp, tg: s.targets, dr: s.duration })),
}; };
} }
@@ -302,54 +321,74 @@ function loadSheet() {
function applyData(d) { function applyData(d) {
const set = (id, val) => { const el = document.getElementById(id); if (el && val !== undefined) el.value = val; }; 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('charName', d.n ?? d.name); set('charPronouns', d.pn ?? d.pronouns);
set('charIdentity', d.identity); set('charTheme', d.theme); set('charOrigin', d.origin); set('charIdentity',d.id ?? d.identity); set('charTheme', d.th ?? d.theme);
set('charTraits', d.traits); set('charOrigin', d.og ?? d.origin); set('charTraits', d.tr ?? d.traits);
level = d.level || 1; level = d.lv ?? d.level ?? 1;
document.getElementById('levelDisplay').textContent = level; document.getElementById('levelDisplay').textContent = level;
set('xpCurrent', d.xp); set('zenit', d.zenit); set('xpCurrent', d.xp); set('zenit', d.z ?? d.zenit);
set('initMod', d.initMod); set('defense', d.defense); set('magDef', d.magDef); set('initMod', d.im ?? d.initMod); set('defense', d.df ?? d.defense);
set('dex-base', d.dexBase); set('dex-cur', d.dexCur); set('magDef', d.md ?? d.magDef);
set('ins-base', d.insBase); set('ins-cur', d.insCur); set('dex-base', d.dxb ?? d.dexBase); set('dex-cur', d.dxc ?? d.dexCur);
set('mig-base', d.migBase); set('mig-cur', d.migCur); set('ins-base', d.inb ?? d.insBase); set('ins-cur', d.inc ?? d.insCur);
set('wlp-base', d.wlpBase); set('wlp-cur', d.wlpCur); set('mig-base', d.mgb ?? d.migBase); set('mig-cur', d.mgc ?? d.migCur);
set('hpMax', d.hpMax); set('hpCur', d.hpCur); set('wlp-base', d.wpb ?? d.wlpBase); set('wlp-cur', d.wpc ?? d.wlpCur);
set('mpMax', d.mpMax); set('mpCur', d.mpCur); set('hpMax', d.hx ?? d.hpMax); set('hpCur', d.hc ?? d.hpCur);
set('ipMax', d.ipMax); set('ipCur', d.ipCur); set('mpMax', d.mx ?? d.mpMax); set('mpCur', d.mc ?? d.mpCur);
set('fpCount', d.fp); set('ipMax', d.ix ?? d.ipMax); set('ipCur', d.ic ?? d.ipCur);
set('backpack', d.backpack); set('fpCount', d.fp);
set('acc-name', d.accName); set('acc-desc', d.accDesc); set('backpack', d.bp ?? d.backpack);
set('arm-name', d.armName); set('arm-desc', d.armDesc); set('acc-name', d.acn ?? d.accName); set('acc-desc', d.acd ?? d.accDesc);
set('mh-name', d.mhName); set('mh-desc', d.mhDesc); set('arm-name', d.amn ?? d.armName); set('arm-desc', d.amd ?? d.armDesc);
set('oh-name', d.ohName); set('oh-desc', d.ohDesc); set('mh-name', d.mhn ?? d.mhName); set('mh-desc', d.mhd ?? d.mhDesc);
set('heroicSkills', d.heroicSkills); set('oh-name', d.ohn ?? d.ohName); set('oh-desc', d.ohd ?? d.ohDesc);
set('ritualsNotes', d.ritualsNotes); set('heroicSkills', d.hs ?? d.heroicSkills);
set('ritualsNotes', d.rn ?? d.ritualsNotes);
// Statuses // Statuses
const sa = d.sa ?? d.statusesActive ?? [];
document.querySelectorAll('.status-item').forEach(el => { document.querySelectorAll('.status-item').forEach(el => {
const active = (d.statusesActive || []).includes(el.dataset.status); const active = sa.includes(el.dataset.status);
el.classList.toggle('active-status', active); el.classList.toggle('active-status', active);
el.querySelector('.status-check').textContent = active ? '✗' : ''; el.querySelector('.status-check').textContent = active ? '✗' : '';
}); });
// Martial // Martial
const ma = d.ma ?? d.martialChecked ?? [];
document.querySelectorAll('.martial-item').forEach(el => { document.querySelectorAll('.martial-item').forEach(el => {
const checked = (d.martialChecked || []).includes(el.querySelector('span').textContent); const checked = ma.includes(el.querySelector('span').textContent);
el.classList.toggle('checked', checked); el.classList.toggle('checked', checked);
el.querySelector('.martial-box').textContent = checked ? '✓' : ''; el.querySelector('.martial-box').textContent = checked ? '✓' : '';
}); });
// Disciplines // Disciplines
const da = d.da ?? d.disciplinesChecked ?? [];
document.querySelectorAll('.disc-item').forEach(el => { document.querySelectorAll('.disc-item').forEach(el => {
const checked = (d.disciplinesChecked || []).includes(el.querySelector('span').textContent); const checked = da.includes(el.querySelector('span').textContent);
el.classList.toggle('checked', checked); el.classList.toggle('checked', checked);
el.querySelector('.disc-box').textContent = checked ? '✓' : ''; el.querySelector('.disc-box').textContent = checked ? '✓' : '';
}); });
if (d.bonds) { bonds = d.bonds; renderBonds(); } const rawBonds = d.bo ?? d.bonds;
if (d.primaryClasses) { primaryClasses = d.primaryClasses; renderPrimaryClasses(); } if (rawBonds) {
if (d.otherClasses) { otherClasses = d.otherClasses; renderOtherClasses(); } bonds = rawBonds.map(b => ({ name: b.n ?? b.name, feelings: b.f ?? b.feelings ?? [] }));
if (d.spells) { spells = d.spells; renderSpells(); } renderBonds();
}
const rawPrimary = d.pc ?? d.primaryClasses;
if (rawPrimary) {
primaryClasses = rawPrimary.map(c => ({ name: c.n ?? c.name, benefits: c.b ?? c.benefits, skills: c.s ?? c.skills }));
renderPrimaryClasses();
}
const rawOther = d.oc ?? d.otherClasses;
if (rawOther) {
otherClasses = rawOther.map(c => ({ name: c.n ?? c.name, benefits: c.b ?? c.benefits, skills: c.s ?? c.skills }));
renderOtherClasses();
}
const rawSpells = d.sp ?? d.spells;
if (rawSpells) {
spells = rawSpells.map(s => ({ name: s.n ?? s.name, notes: s.nt ?? s.notes, mp: s.mp, targets: s.tg ?? s.targets, duration: s.dr ?? s.duration }));
renderSpells();
}
renderFP(); renderFP();
updateXPBar(); updateXPBar();
@@ -363,7 +402,7 @@ function exportSheet() {
const blob = new Blob([json], { type: 'application/json' }); const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
const charName = (data.name || 'character').replace(/[^a-z0-9_\- ]/gi, '').trim() || 'character'; const charName = (data.n || 'character').replace(/[^a-z0-9_\- ]/gi, '').trim() || 'character';
a.href = url; a.href = url;
a.download = charName + '-fabula-ultima.json'; a.download = charName + '-fabula-ultima.json';
a.click(); a.click();