diff --git a/iframe.html b/iframe.html
index 877ee92..22bb23a 100644
--- a/iframe.html
+++ b/iframe.html
@@ -2114,15 +2114,19 @@ const CALCULATOR_CONFIG = {
// Kayafied reference source tracking
this.kibbleRefId = null;
this.gcRefId = null;
+ this.storageKey = 'kaya_calculator_state_v1';
this.init();
}
init() {
this.applyTheme();
this.applyScale();
- this.initializeFoodSources();
- this.bindEvents();
this.updateUnitLabels();
+ const restored = this.loadStateFromStorage();
+ if (!restored) {
+ this.initializeFoodSources();
+ }
+ this.bindEvents();
this.setupIframeResize();
// Show the calculator with fade-in
@@ -2130,6 +2134,117 @@ const CALCULATOR_CONFIG = {
container.classList.add('loaded');
}
+ // Persistence helpers
+ saveStateToStorage() {
+ try {
+ const ageInput = document.getElementById('ageMonths');
+ const unitSelect = document.getElementById('unit');
+ const daysInput = document.getElementById('days');
+ const state = {
+ version: 1,
+ age: ageInput && ageInput.value !== '' ? parseFloat(ageInput.value) : null,
+ unit: unitSelect ? unitSelect.value : 'g',
+ days: daysInput && daysInput.value ? parseInt(daysInput.value) : 1,
+ showPerMeal: !!this.showPerMeal,
+ mealsPerDay: this.mealsPerDay,
+ foodSources: this.foodSources.map(fs => ({
+ id: fs.id,
+ name: fs.name,
+ energy: fs.energy,
+ energyUnit: fs.energyUnit,
+ percentage: fs.percentage,
+ isLocked: fs.isLocked,
+ chartType: fs.chartType || null,
+ splitByMeals: (fs.splitByMeals === undefined ? true : fs.splitByMeals)
+ }))
+ };
+ localStorage.setItem(this.storageKey, JSON.stringify(state));
+ } catch (e) {
+ // Ignore storage errors (private mode, etc.)
+ }
+ }
+
+ loadStateFromStorage() {
+ try {
+ const raw = localStorage.getItem(this.storageKey);
+ if (!raw) return false;
+ const state = JSON.parse(raw);
+ if (!state || typeof state !== 'object') return false;
+
+ // Restore unit first
+ const unitSelect = document.getElementById('unit');
+ if (unitSelect && state.unit && (state.unit === 'g' || state.unit === 'kg')) {
+ unitSelect.value = state.unit;
+ this.setActiveUnitButton(state.unit);
+ }
+
+ // Restore days
+ const daysInput = document.getElementById('days');
+ if (daysInput && state.days) {
+ daysInput.value = state.days;
+ this.updateDayLabel();
+ }
+
+ // Restore meal settings
+ this.showPerMeal = !!state.showPerMeal;
+ this.mealsPerDay = state.mealsPerDay || 2;
+ const showDaily = document.getElementById('showDaily');
+ const showPerMeal = document.getElementById('showPerMeal');
+ const mealsPerDayInput = document.getElementById('mealsPerDay');
+ const mealInputGroup = document.getElementById('mealInputGroup');
+ if (showDaily && showPerMeal) {
+ if (this.showPerMeal) {
+ showPerMeal.checked = true;
+ if (mealInputGroup) mealInputGroup.style.display = 'inline-flex';
+ } else {
+ showDaily.checked = true;
+ if (mealInputGroup) mealInputGroup.style.display = 'none';
+ }
+ }
+ if (mealsPerDayInput) {
+ mealsPerDayInput.value = this.mealsPerDay;
+ }
+
+ // Restore age
+ const ageInput = document.getElementById('ageMonths');
+ if (ageInput && (state.age || state.age === 0)) {
+ ageInput.value = state.age;
+ }
+
+ // Restore food sources
+ if (Array.isArray(state.foodSources) && state.foodSources.length) {
+ this.foodSources = [];
+ this.kibbleRefId = null;
+ this.gcRefId = null;
+ state.foodSources.forEach(saved => {
+ const fs = {
+ id: saved.id || this.generateFoodSourceId(),
+ name: saved.name || 'Food Source',
+ energy: saved.energy || '',
+ energyUnit: saved.energyUnit || 'kcal100g',
+ percentage: typeof saved.percentage === 'number' ? saved.percentage : 0,
+ isLocked: !!saved.isLocked,
+ chartType: saved.chartType || null,
+ splitByMeals: (saved.splitByMeals === undefined ? true : saved.splitByMeals)
+ };
+ this.foodSources.push(fs);
+ this.renderFoodSource(fs);
+ if (fs.chartType === 'kibble' && !this.kibbleRefId) this.kibbleRefId = fs.id;
+ if (fs.chartType === 'gc' && !this.gcRefId) this.gcRefId = fs.id;
+ });
+ this.updateAddButton();
+ this.updateRemoveButtons();
+ this.refreshAllPercentageUI();
+ }
+
+ // Trigger calculations
+ this.updateCalorieCalculations();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
getThemeFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const theme = urlParams.get('theme');
@@ -2234,6 +2349,7 @@ const CALCULATOR_CONFIG = {
this.updateAddButton();
this.updateRemoveButtons();
this.refreshAllPercentageUI();
+ this.saveStateToStorage();
}
removeFoodSource(id) {
@@ -2262,6 +2378,7 @@ const CALCULATOR_CONFIG = {
this.updateRemoveButtons();
this.refreshAllPercentageUI();
this.updateCalorieCalculations();
+ this.saveStateToStorage();
}
generateFoodSourceId() {
@@ -2543,6 +2660,7 @@ const CALCULATOR_CONFIG = {
}
});
+ this.saveStateToStorage();
return true;
}
@@ -2563,6 +2681,7 @@ const CALCULATOR_CONFIG = {
// Update food calculations
this.updateFoodCalculations();
+ this.saveStateToStorage();
}
updateSliderConstraints(foodSource) {
@@ -2722,6 +2841,7 @@ const CALCULATOR_CONFIG = {
const newName = nameInput.value.trim() || `Food Source ${this.foodSources.findIndex(fs => fs.id === id) + 1}`;
this.updateFoodSourceData(id, 'name', newName);
this.updateFoodCalculations(); // This will refresh the food amount breakdown with new names
+ this.saveStateToStorage();
});
nameInput.addEventListener('blur', () => {
@@ -2731,6 +2851,7 @@ const CALCULATOR_CONFIG = {
nameInput.value = defaultName;
this.updateFoodSourceData(id, 'name', defaultName);
this.updateFoodCalculations();
+ this.saveStateToStorage();
}
});
}
@@ -2755,6 +2876,7 @@ const CALCULATOR_CONFIG = {
}
}
this.updateFoodCalculations();
+ this.saveStateToStorage();
});
energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id));
}
@@ -2776,32 +2898,37 @@ const CALCULATOR_CONFIG = {
// Cups display not available; default to grams
unitSelect.value = 'g';
this.setActiveUnitButton('g');
- this.updateFoodCalculations();
- break;
- case 'kcal100g':
- // For kcal/100g, select grams
- unitSelect.value = 'g';
- this.setActiveUnitButton('g');
- this.updateFoodCalculations();
- break;
- case 'kcalkg':
- // For kcal/kg, also select grams (or could be kg)
- unitSelect.value = 'g';
- this.setActiveUnitButton('g');
- this.updateFoodCalculations();
- break;
- case 'kcalcan':
- // For kcal/can, use grams as default
- unitSelect.value = 'g';
- this.setActiveUnitButton('g');
- this.updateFoodCalculations();
- break;
- }
- } else {
- // No unit select, just update calculations
this.updateFoodCalculations();
+ this.saveStateToStorage();
+ break;
+ case 'kcal100g':
+ // For kcal/100g, select grams
+ unitSelect.value = 'g';
+ this.setActiveUnitButton('g');
+ this.updateFoodCalculations();
+ this.saveStateToStorage();
+ break;
+ case 'kcalkg':
+ // For kcal/kg, also select grams (or could be kg)
+ unitSelect.value = 'g';
+ this.setActiveUnitButton('g');
+ this.updateFoodCalculations();
+ this.saveStateToStorage();
+ break;
+ case 'kcalcan':
+ // For kcal/can, use grams as default
+ unitSelect.value = 'g';
+ this.setActiveUnitButton('g');
+ this.updateFoodCalculations();
+ this.saveStateToStorage();
+ break;
}
- });
+ } else {
+ // No unit select, just update calculations
+ this.updateFoodCalculations();
+ this.saveStateToStorage();
+ }
+ });
}
if (percentageSlider) {
@@ -2855,6 +2982,7 @@ const CALCULATOR_CONFIG = {
this.updateLockIcon(id);
this.updateLockStates();
this.refreshAllPercentageUI();
+ this.saveStateToStorage();
}
updateLockIcon(id) {
@@ -2945,19 +3073,20 @@ const CALCULATOR_CONFIG = {
// Kayafied: age input drives energy target
if (ageInput) {
- ageInput.addEventListener('input', () => this.updateCalorieCalculations());
- ageInput.addEventListener('blur', () => this.updateCalorieCalculations());
+ ageInput.addEventListener('input', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); });
+ ageInput.addEventListener('blur', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); });
}
if (daysInput) {
daysInput.addEventListener('input', () => {
this.updateDayLabel();
this.updateFoodCalculations();
+ this.saveStateToStorage();
});
daysInput.addEventListener('blur', () => this.validateDays());
}
- if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations());
+ if (unitSelect) unitSelect.addEventListener('change', () => { this.updateFoodCalculations(); this.saveStateToStorage(); });
// Unit button event listeners
const unitButtons = document.querySelectorAll('.dog-calculator-unit-btn');
@@ -2969,6 +3098,7 @@ const CALCULATOR_CONFIG = {
if (unitSelect) {
unitSelect.value = selectedUnit;
this.updateFoodCalculations();
+ this.saveStateToStorage();
}
});
});
diff --git a/src/js/calculator.js b/src/js/calculator.js
index a7b52f2..36f5b51 100644
--- a/src/js/calculator.js
+++ b/src/js/calculator.js
@@ -18,15 +18,19 @@
// Kayafied reference source tracking
this.kibbleRefId = null;
this.gcRefId = null;
+ this.storageKey = 'kaya_calculator_state_v1';
this.init();
}
init() {
this.applyTheme();
this.applyScale();
- this.initializeFoodSources();
- this.bindEvents();
this.updateUnitLabels();
+ const restored = this.loadStateFromStorage();
+ if (!restored) {
+ this.initializeFoodSources();
+ }
+ this.bindEvents();
this.setupIframeResize();
// Show the calculator with fade-in
@@ -34,6 +38,117 @@
container.classList.add('loaded');
}
+ // Persistence helpers
+ saveStateToStorage() {
+ try {
+ const ageInput = document.getElementById('ageMonths');
+ const unitSelect = document.getElementById('unit');
+ const daysInput = document.getElementById('days');
+ const state = {
+ version: 1,
+ age: ageInput && ageInput.value !== '' ? parseFloat(ageInput.value) : null,
+ unit: unitSelect ? unitSelect.value : 'g',
+ days: daysInput && daysInput.value ? parseInt(daysInput.value) : 1,
+ showPerMeal: !!this.showPerMeal,
+ mealsPerDay: this.mealsPerDay,
+ foodSources: this.foodSources.map(fs => ({
+ id: fs.id,
+ name: fs.name,
+ energy: fs.energy,
+ energyUnit: fs.energyUnit,
+ percentage: fs.percentage,
+ isLocked: fs.isLocked,
+ chartType: fs.chartType || null,
+ splitByMeals: (fs.splitByMeals === undefined ? true : fs.splitByMeals)
+ }))
+ };
+ localStorage.setItem(this.storageKey, JSON.stringify(state));
+ } catch (e) {
+ // Ignore storage errors (private mode, etc.)
+ }
+ }
+
+ loadStateFromStorage() {
+ try {
+ const raw = localStorage.getItem(this.storageKey);
+ if (!raw) return false;
+ const state = JSON.parse(raw);
+ if (!state || typeof state !== 'object') return false;
+
+ // Restore unit first
+ const unitSelect = document.getElementById('unit');
+ if (unitSelect && state.unit && (state.unit === 'g' || state.unit === 'kg')) {
+ unitSelect.value = state.unit;
+ this.setActiveUnitButton(state.unit);
+ }
+
+ // Restore days
+ const daysInput = document.getElementById('days');
+ if (daysInput && state.days) {
+ daysInput.value = state.days;
+ this.updateDayLabel();
+ }
+
+ // Restore meal settings
+ this.showPerMeal = !!state.showPerMeal;
+ this.mealsPerDay = state.mealsPerDay || 2;
+ const showDaily = document.getElementById('showDaily');
+ const showPerMeal = document.getElementById('showPerMeal');
+ const mealsPerDayInput = document.getElementById('mealsPerDay');
+ const mealInputGroup = document.getElementById('mealInputGroup');
+ if (showDaily && showPerMeal) {
+ if (this.showPerMeal) {
+ showPerMeal.checked = true;
+ if (mealInputGroup) mealInputGroup.style.display = 'inline-flex';
+ } else {
+ showDaily.checked = true;
+ if (mealInputGroup) mealInputGroup.style.display = 'none';
+ }
+ }
+ if (mealsPerDayInput) {
+ mealsPerDayInput.value = this.mealsPerDay;
+ }
+
+ // Restore age
+ const ageInput = document.getElementById('ageMonths');
+ if (ageInput && (state.age || state.age === 0)) {
+ ageInput.value = state.age;
+ }
+
+ // Restore food sources
+ if (Array.isArray(state.foodSources) && state.foodSources.length) {
+ this.foodSources = [];
+ this.kibbleRefId = null;
+ this.gcRefId = null;
+ state.foodSources.forEach(saved => {
+ const fs = {
+ id: saved.id || this.generateFoodSourceId(),
+ name: saved.name || 'Food Source',
+ energy: saved.energy || '',
+ energyUnit: saved.energyUnit || 'kcal100g',
+ percentage: typeof saved.percentage === 'number' ? saved.percentage : 0,
+ isLocked: !!saved.isLocked,
+ chartType: saved.chartType || null,
+ splitByMeals: (saved.splitByMeals === undefined ? true : saved.splitByMeals)
+ };
+ this.foodSources.push(fs);
+ this.renderFoodSource(fs);
+ if (fs.chartType === 'kibble' && !this.kibbleRefId) this.kibbleRefId = fs.id;
+ if (fs.chartType === 'gc' && !this.gcRefId) this.gcRefId = fs.id;
+ });
+ this.updateAddButton();
+ this.updateRemoveButtons();
+ this.refreshAllPercentageUI();
+ }
+
+ // Trigger calculations
+ this.updateCalorieCalculations();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
getThemeFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const theme = urlParams.get('theme');
@@ -138,6 +253,7 @@
this.updateAddButton();
this.updateRemoveButtons();
this.refreshAllPercentageUI();
+ this.saveStateToStorage();
}
removeFoodSource(id) {
@@ -166,6 +282,7 @@
this.updateRemoveButtons();
this.refreshAllPercentageUI();
this.updateCalorieCalculations();
+ this.saveStateToStorage();
}
generateFoodSourceId() {
@@ -447,6 +564,7 @@
}
});
+ this.saveStateToStorage();
return true;
}
@@ -467,6 +585,7 @@
// Update food calculations
this.updateFoodCalculations();
+ this.saveStateToStorage();
}
updateSliderConstraints(foodSource) {
@@ -626,6 +745,7 @@
const newName = nameInput.value.trim() || `Food Source ${this.foodSources.findIndex(fs => fs.id === id) + 1}`;
this.updateFoodSourceData(id, 'name', newName);
this.updateFoodCalculations(); // This will refresh the food amount breakdown with new names
+ this.saveStateToStorage();
});
nameInput.addEventListener('blur', () => {
@@ -635,6 +755,7 @@
nameInput.value = defaultName;
this.updateFoodSourceData(id, 'name', defaultName);
this.updateFoodCalculations();
+ this.saveStateToStorage();
}
});
}
@@ -659,6 +780,7 @@
}
}
this.updateFoodCalculations();
+ this.saveStateToStorage();
});
energyInput.addEventListener('blur', () => this.validateFoodSourceEnergy(id));
}
@@ -680,32 +802,37 @@
// Cups display not available; default to grams
unitSelect.value = 'g';
this.setActiveUnitButton('g');
- this.updateFoodCalculations();
- break;
- case 'kcal100g':
- // For kcal/100g, select grams
- unitSelect.value = 'g';
- this.setActiveUnitButton('g');
- this.updateFoodCalculations();
- break;
- case 'kcalkg':
- // For kcal/kg, also select grams (or could be kg)
- unitSelect.value = 'g';
- this.setActiveUnitButton('g');
- this.updateFoodCalculations();
- break;
- case 'kcalcan':
- // For kcal/can, use grams as default
- unitSelect.value = 'g';
- this.setActiveUnitButton('g');
- this.updateFoodCalculations();
- break;
- }
- } else {
- // No unit select, just update calculations
this.updateFoodCalculations();
+ this.saveStateToStorage();
+ break;
+ case 'kcal100g':
+ // For kcal/100g, select grams
+ unitSelect.value = 'g';
+ this.setActiveUnitButton('g');
+ this.updateFoodCalculations();
+ this.saveStateToStorage();
+ break;
+ case 'kcalkg':
+ // For kcal/kg, also select grams (or could be kg)
+ unitSelect.value = 'g';
+ this.setActiveUnitButton('g');
+ this.updateFoodCalculations();
+ this.saveStateToStorage();
+ break;
+ case 'kcalcan':
+ // For kcal/can, use grams as default
+ unitSelect.value = 'g';
+ this.setActiveUnitButton('g');
+ this.updateFoodCalculations();
+ this.saveStateToStorage();
+ break;
}
- });
+ } else {
+ // No unit select, just update calculations
+ this.updateFoodCalculations();
+ this.saveStateToStorage();
+ }
+ });
}
if (percentageSlider) {
@@ -759,6 +886,7 @@
this.updateLockIcon(id);
this.updateLockStates();
this.refreshAllPercentageUI();
+ this.saveStateToStorage();
}
updateLockIcon(id) {
@@ -849,19 +977,20 @@
// Kayafied: age input drives energy target
if (ageInput) {
- ageInput.addEventListener('input', () => this.updateCalorieCalculations());
- ageInput.addEventListener('blur', () => this.updateCalorieCalculations());
+ ageInput.addEventListener('input', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); });
+ ageInput.addEventListener('blur', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); });
}
if (daysInput) {
daysInput.addEventListener('input', () => {
this.updateDayLabel();
this.updateFoodCalculations();
+ this.saveStateToStorage();
});
daysInput.addEventListener('blur', () => this.validateDays());
}
- if (unitSelect) unitSelect.addEventListener('change', () => this.updateFoodCalculations());
+ if (unitSelect) unitSelect.addEventListener('change', () => { this.updateFoodCalculations(); this.saveStateToStorage(); });
// Unit button event listeners
const unitButtons = document.querySelectorAll('.dog-calculator-unit-btn');
@@ -873,6 +1002,7 @@
if (unitSelect) {
unitSelect.value = selectedUnit;
this.updateFoodCalculations();
+ this.saveStateToStorage();
}
});
});