Compare commits

...

2 Commits

Author SHA1 Message Date
Dayowe
99b516d087 Add range calculations for dog food amounts based on life stage
- Implement range multipliers for different life stages (e.g., 1.6-1.8x for intact adults)
- Display MER and food amounts as ranges (e.g., '2042-2298 cal/day')
- Add CSS to prevent value wrapping with white-space: nowrap
- Increase calculator max-width from 600px to 640px for better text layout
- Based on veterinary RER multiplier ranges for more accurate feeding recommendations
2025-08-18 15:33:44 +02:00
Dayowe
c4770d5ee6 Fix data-theme and data-scale widget attributes not being applied
- Remove theme/scale assignments that override widget options in build process
- Widget now properly uses data-theme and data-scale attributes from HTML
- Both light and dark themes work correctly when multiple widgets on same page
- Scale attribute properly applies to each widget independently
2025-08-18 14:54:25 +02:00
5 changed files with 434 additions and 64 deletions

View File

@ -141,8 +141,9 @@ function createWidgetJS(css, html, js) {
.replace(/document\.querySelectorAll\(/g, 'this.container.querySelectorAll(')
// Remove the DOMContentLoaded listener and class instantiation - we'll handle this in the widget wrapper
.replace(/document\.addEventListener\('DOMContentLoaded'.*?\n.*?new DogCalorieCalculator.*?\n.*?\}\);/s, '')
// Remove duplicate theme/scale assignments that override options
.replace(/this\.theme = this\.getThemeFromURL\(\) \|\| 'system';\s*\n\s*this\.scale = this\.getScaleFromURL\(\) \|\| 1\.0;/g, '')
// Remove theme/scale assignments that would override widget options
.replace(/this\.theme = this\.getThemeFromURL\(\) \|\| CALCULATOR_CONFIG\.defaultTheme;/g, '')
.replace(/this\.scale = this\.getScaleFromURL\(\) \|\| CALCULATOR_CONFIG\.defaultScale;/g, '')
// Add widget initialization methods
.replace(/constructor\(\) \{/, `constructor(container, options = {}) {
this.container = container;
@ -186,14 +187,14 @@ function createWidgetJS(css, html, js) {
// Remove existing theme classes
calculatorContainer.classList.remove('theme-light', 'theme-dark', 'theme-system');
// Add the selected theme class
if (['light', 'dark', 'system'].includes(this.options.theme)) {
calculatorContainer.classList.add('theme-' + this.options.theme);
if (['light', 'dark', 'system'].includes(this.theme)) {
calculatorContainer.classList.add('theme-' + this.theme);
}
}
}
applyScale() {
const scale = Math.max(0.5, Math.min(2.0, this.options.scale));
const scale = Math.max(0.5, Math.min(2.0, this.scale));
if (scale !== 1.0) {
this.container.style.transform = \`scale(\${scale})\`;
this.container.style.transformOrigin = 'top left';

View File

@ -32,7 +32,7 @@
}
.dog-calculator-container {
max-width: 600px;
max-width: 640px;
margin: 0 auto;
padding: 24px;
box-sizing: border-box;
@ -213,6 +213,7 @@
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
gap: 10px; /* Add gap between label and value */
}
.dog-calculator-result-item:last-child {
@ -232,6 +233,7 @@
padding: 4px 12px;
background: rgba(241, 154, 95, 0.15);
border-radius: 4px;
white-space: nowrap; /* Prevent text from wrapping to multiple lines */
}
.dog-calculator-collapsible {
@ -543,6 +545,24 @@
flex-direction: column;
align-items: flex-start;
}
/* Stack result items vertically on small screens */
.dog-calculator-result-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.dog-calculator-result-label {
margin-right: 0;
font-size: 0.9rem;
}
.dog-calculator-result-value {
font-size: 1rem;
align-self: stretch;
text-align: center;
}
}
/* Dark theme - manual override */
@ -2288,6 +2308,8 @@ const CALCULATOR_CONFIG = {
class DogCalorieCalculator {
constructor() {
this.currentMER = 0;
this.currentMERMin = 0; // For range calculations
this.currentMERMax = 0; // For range calculations
this.isImperial = false;
this.theme = this.getThemeFromURL() || CALCULATOR_CONFIG.defaultTheme;
this.scale = this.getScaleFromURL() || CALCULATOR_CONFIG.defaultScale;
@ -3335,6 +3357,25 @@ const CALCULATOR_CONFIG = {
return rer * factor;
}
// Get the range multipliers for each life stage
getLifeStageRange(factor) {
// Define ranges based on the reference image
const ranges = {
'3.0': { min: 3.0, max: 3.0 }, // Puppy 0-4 months (no range)
'2.0': { min: 2.0, max: 2.0 }, // Puppy 4m-adult OR Working light (no range for puppies)
'1.2': { min: 1.2, max: 1.4 }, // Adult inactive/obese
'1.6': { min: 1.4, max: 1.6 }, // Adult neutered/spayed
'1.8': { min: 1.6, max: 1.8 }, // Adult intact
'1.0': { min: 1.0, max: 1.0 }, // Weight loss (fixed)
'1.7': { min: 1.2, max: 1.8 }, // Weight gain (wide range)
'5.0': { min: 5.0, max: 5.0 }, // Working heavy (upper bound)
'1.1': { min: 1.1, max: 1.1 } // Senior (no range)
};
const key = factor.toFixed(1);
return ranges[key] || { min: factor, max: factor };
}
validateInput(value, min = 0, isInteger = false) {
const num = parseFloat(value);
if (isNaN(num) || num < min) return false;
@ -3466,10 +3507,21 @@ const CALCULATOR_CONFIG = {
const rer = this.calculateRER(weightKg);
const mer = this.calculateMER(rer, factor);
this.currentMER = mer;
// Calculate range for MER
const range = this.getLifeStageRange(factor);
this.currentMERMin = this.calculateMER(rer, range.min);
this.currentMERMax = this.calculateMER(rer, range.max);
this.currentMER = mer; // Keep middle/selected value for compatibility
rerValue.textContent = this.formatNumber(rer, 0) + ' cal/day';
merValue.textContent = this.formatNumber(mer, 0) + ' cal/day';
// Show MER as range if applicable
if (range.min !== range.max) {
merValue.textContent = this.formatNumber(this.currentMERMin, 0) + '-' +
this.formatNumber(this.currentMERMax, 0) + ' cal/day';
} else {
merValue.textContent = this.formatNumber(mer, 0) + ' cal/day';
}
calorieResults.style.display = 'block';
this.updateFoodCalculations();
@ -3504,6 +3556,9 @@ const CALCULATOR_CONFIG = {
updateFoodCalculations() {
if (this.currentMER === 0) return;
// Check if we have a range
const hasRange = this.currentMERMin !== this.currentMERMax;
const daysInput = document.getElementById('days');
const unitSelect = document.getElementById('unit');
const dailyFoodResults = document.getElementById('dailyFoodResults');
@ -3576,43 +3631,70 @@ const CALCULATOR_CONFIG = {
if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) {
const dailyCaloriesForThisFood = (this.currentMER * fs.percentage) / 100;
// Calculate range values if applicable
const dailyCaloriesMin = hasRange ? (this.currentMERMin * fs.percentage) / 100 : dailyCaloriesForThisFood;
const dailyCaloriesMax = hasRange ? (this.currentMERMax * fs.percentage) / 100 : dailyCaloriesForThisFood;
let dailyGramsForThisFood;
let dailyGramsMin, dailyGramsMax;
let dailyCupsForThisFood = null;
let dailyCupsMin, dailyCupsMax;
// For kcal/cup, calculate cups directly from calories
if (fs.energyUnit === 'kcalcup' && fs.energy) {
const caloriesPerCup = parseFloat(fs.energy);
dailyCupsForThisFood = dailyCaloriesForThisFood / caloriesPerCup;
dailyCupsMin = dailyCaloriesMin / caloriesPerCup;
dailyCupsMax = dailyCaloriesMax / caloriesPerCup;
// We still need grams for total calculation, use approximation
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
console.log('Cups calculation:', {
caloriesPerCup,
dailyCaloriesForThisFood,
dailyCupsForThisFood,
dailyGramsForThisFood
});
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
} else {
// For other units, calculate grams normally
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
}
// Calculate per-meal amounts if needed
const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
const displayGramsMin = this.showPerMeal ? dailyGramsMin / this.mealsPerDay : dailyGramsMin;
const displayGramsMax = this.showPerMeal ? dailyGramsMax / this.mealsPerDay : dailyGramsMax;
const displayCups = dailyCupsForThisFood !== null ?
(this.showPerMeal ? dailyCupsForThisFood / this.mealsPerDay : dailyCupsForThisFood) : null;
const displayCupsMin = dailyCupsMin !== undefined ?
(this.showPerMeal ? dailyCupsMin / this.mealsPerDay : dailyCupsMin) : null;
const displayCupsMax = dailyCupsMax !== undefined ?
(this.showPerMeal ? dailyCupsMax / this.mealsPerDay : dailyCupsMax) : null;
const displayCalories = this.showPerMeal ? dailyCaloriesForThisFood / this.mealsPerDay : dailyCaloriesForThisFood;
const displayCaloriesMin = this.showPerMeal ? dailyCaloriesMin / this.mealsPerDay : dailyCaloriesMin;
const displayCaloriesMax = this.showPerMeal ? dailyCaloriesMax / this.mealsPerDay : dailyCaloriesMax;
foodBreakdowns.push({
name: fs.name,
percentage: fs.percentage,
dailyGrams: dailyGramsForThisFood,
dailyGramsMin: dailyGramsMin,
dailyGramsMax: dailyGramsMax,
displayGrams: displayGrams,
displayGramsMin: displayGramsMin,
displayGramsMax: displayGramsMax,
dailyCups: dailyCupsForThisFood,
dailyCupsMin: dailyCupsMin,
dailyCupsMax: dailyCupsMax,
displayCups: displayCups,
displayCupsMin: displayCupsMin,
displayCupsMax: displayCupsMax,
calories: dailyCaloriesForThisFood,
displayCalories: displayCalories,
displayCaloriesMin: displayCaloriesMin,
displayCaloriesMax: displayCaloriesMax,
isLocked: fs.isLocked,
hasEnergyContent: true,
hasRange: hasRange,
foodSource: fs // Store reference for cups conversion
});
@ -3717,12 +3799,23 @@ const CALCULATOR_CONFIG = {
if (unit === 'cups') {
// For cups, use the pre-calculated cups value if available
if (breakdown.displayCups !== null) {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
if (breakdown.hasRange && breakdown.displayCupsMin !== breakdown.displayCupsMax) {
valueContent = `${this.formatNumber(breakdown.displayCupsMin, decimals)}-${this.formatNumber(breakdown.displayCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
}
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
// For other units (g, kg, oz, lb)
if (breakdown.hasRange && breakdown.displayGramsMin !== breakdown.displayGramsMax) {
const minConverted = this.convertUnits(breakdown.displayGramsMin, unit);
const maxConverted = this.convertUnits(breakdown.displayGramsMax, unit);
valueContent = `${this.formatNumber(minConverted, decimals)}-${this.formatNumber(maxConverted, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
}
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
@ -3760,23 +3853,53 @@ const CALCULATOR_CONFIG = {
if (validForCups) {
// Calculate total cups using pre-calculated values
let totalCups = 0;
let totalCupsMin = 0;
let totalCupsMax = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.displayCups !== null) {
totalCups += breakdown.displayCups;
if (breakdown.hasRange) {
totalCupsMin += breakdown.displayCupsMin || breakdown.displayCups;
totalCupsMax += breakdown.displayCupsMax || breakdown.displayCups;
} else {
totalCupsMin += breakdown.displayCups;
totalCupsMax += breakdown.displayCups;
}
}
});
console.log('Total cups display:', {
totalCups,
displayTotal,
foodBreakdowns: foodBreakdowns.map(b => ({ name: b.name, displayCups: b.displayCups }))
});
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
if (hasRange && totalCupsMin !== totalCupsMax) {
totalDisplayText = `${this.formatNumber(totalCupsMin, decimals)}-${this.formatNumber(totalCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
} else {
totalDisplayText = 'Mixed units - see breakdown';
}
} else {
convertedTotal = this.convertUnits(displayTotal, unit);
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
// Calculate totals for ranges
if (hasRange) {
let totalGramsMin = 0;
let totalGramsMax = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.hasEnergyContent) {
totalGramsMin += breakdown.displayGramsMin || breakdown.displayGrams;
totalGramsMax += breakdown.displayGramsMax || breakdown.displayGrams;
}
});
const convertedMin = this.convertUnits(totalGramsMin, unit);
const convertedMax = this.convertUnits(totalGramsMax, unit);
if (totalGramsMin !== totalGramsMax) {
totalDisplayText = `${this.formatNumber(convertedMin, decimals)}-${this.formatNumber(convertedMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
totalDisplayText = this.formatNumber(convertedMin, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
} else {
convertedTotal = this.convertUnits(displayTotal, unit);
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
}
dailyFoodValue.textContent = totalDisplayText;

View File

@ -22,7 +22,7 @@
}
.dog-calculator-container {
max-width: 600px;
max-width: 640px;
margin: 0 auto;
padding: 24px;
box-sizing: border-box;
@ -203,6 +203,7 @@
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
gap: 10px; /* Add gap between label and value */
}
.dog-calculator-result-item:last-child {
@ -222,6 +223,7 @@
padding: 4px 12px;
background: rgba(241, 154, 95, 0.15);
border-radius: 4px;
white-space: nowrap; /* Prevent text from wrapping to multiple lines */
}
.dog-calculator-collapsible {
@ -533,6 +535,24 @@
flex-direction: column;
align-items: flex-start;
}
/* Stack result items vertically on small screens */
.dog-calculator-result-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.dog-calculator-result-label {
margin-right: 0;
font-size: 0.9rem;
}
.dog-calculator-result-value {
font-size: 1rem;
align-self: stretch;
text-align: center;
}
}
/* Dark theme - manual override */

View File

@ -6,6 +6,8 @@
class DogCalorieCalculator {
constructor() {
this.currentMER = 0;
this.currentMERMin = 0; // For range calculations
this.currentMERMax = 0; // For range calculations
this.isImperial = false;
this.theme = this.getThemeFromURL() || CALCULATOR_CONFIG.defaultTheme;
this.scale = this.getScaleFromURL() || CALCULATOR_CONFIG.defaultScale;
@ -1053,6 +1055,25 @@
return rer * factor;
}
// Get the range multipliers for each life stage
getLifeStageRange(factor) {
// Define ranges based on the reference image
const ranges = {
'3.0': { min: 3.0, max: 3.0 }, // Puppy 0-4 months (no range)
'2.0': { min: 2.0, max: 2.0 }, // Puppy 4m-adult OR Working light (no range for puppies)
'1.2': { min: 1.2, max: 1.4 }, // Adult inactive/obese
'1.6': { min: 1.4, max: 1.6 }, // Adult neutered/spayed
'1.8': { min: 1.6, max: 1.8 }, // Adult intact
'1.0': { min: 1.0, max: 1.0 }, // Weight loss (fixed)
'1.7': { min: 1.2, max: 1.8 }, // Weight gain (wide range)
'5.0': { min: 5.0, max: 5.0 }, // Working heavy (upper bound)
'1.1': { min: 1.1, max: 1.1 } // Senior (no range)
};
const key = factor.toFixed(1);
return ranges[key] || { min: factor, max: factor };
}
validateInput(value, min = 0, isInteger = false) {
const num = parseFloat(value);
if (isNaN(num) || num < min) return false;
@ -1184,10 +1205,21 @@
const rer = this.calculateRER(weightKg);
const mer = this.calculateMER(rer, factor);
this.currentMER = mer;
// Calculate range for MER
const range = this.getLifeStageRange(factor);
this.currentMERMin = this.calculateMER(rer, range.min);
this.currentMERMax = this.calculateMER(rer, range.max);
this.currentMER = mer; // Keep middle/selected value for compatibility
rerValue.textContent = this.formatNumber(rer, 0) + ' cal/day';
merValue.textContent = this.formatNumber(mer, 0) + ' cal/day';
// Show MER as range if applicable
if (range.min !== range.max) {
merValue.textContent = this.formatNumber(this.currentMERMin, 0) + '-' +
this.formatNumber(this.currentMERMax, 0) + ' cal/day';
} else {
merValue.textContent = this.formatNumber(mer, 0) + ' cal/day';
}
calorieResults.style.display = 'block';
this.updateFoodCalculations();
@ -1222,6 +1254,9 @@
updateFoodCalculations() {
if (this.currentMER === 0) return;
// Check if we have a range
const hasRange = this.currentMERMin !== this.currentMERMax;
const daysInput = document.getElementById('days');
const unitSelect = document.getElementById('unit');
const dailyFoodResults = document.getElementById('dailyFoodResults');
@ -1294,43 +1329,70 @@
if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) {
const dailyCaloriesForThisFood = (this.currentMER * fs.percentage) / 100;
// Calculate range values if applicable
const dailyCaloriesMin = hasRange ? (this.currentMERMin * fs.percentage) / 100 : dailyCaloriesForThisFood;
const dailyCaloriesMax = hasRange ? (this.currentMERMax * fs.percentage) / 100 : dailyCaloriesForThisFood;
let dailyGramsForThisFood;
let dailyGramsMin, dailyGramsMax;
let dailyCupsForThisFood = null;
let dailyCupsMin, dailyCupsMax;
// For kcal/cup, calculate cups directly from calories
if (fs.energyUnit === 'kcalcup' && fs.energy) {
const caloriesPerCup = parseFloat(fs.energy);
dailyCupsForThisFood = dailyCaloriesForThisFood / caloriesPerCup;
dailyCupsMin = dailyCaloriesMin / caloriesPerCup;
dailyCupsMax = dailyCaloriesMax / caloriesPerCup;
// We still need grams for total calculation, use approximation
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
console.log('Cups calculation:', {
caloriesPerCup,
dailyCaloriesForThisFood,
dailyCupsForThisFood,
dailyGramsForThisFood
});
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
} else {
// For other units, calculate grams normally
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
}
// Calculate per-meal amounts if needed
const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
const displayGramsMin = this.showPerMeal ? dailyGramsMin / this.mealsPerDay : dailyGramsMin;
const displayGramsMax = this.showPerMeal ? dailyGramsMax / this.mealsPerDay : dailyGramsMax;
const displayCups = dailyCupsForThisFood !== null ?
(this.showPerMeal ? dailyCupsForThisFood / this.mealsPerDay : dailyCupsForThisFood) : null;
const displayCupsMin = dailyCupsMin !== undefined ?
(this.showPerMeal ? dailyCupsMin / this.mealsPerDay : dailyCupsMin) : null;
const displayCupsMax = dailyCupsMax !== undefined ?
(this.showPerMeal ? dailyCupsMax / this.mealsPerDay : dailyCupsMax) : null;
const displayCalories = this.showPerMeal ? dailyCaloriesForThisFood / this.mealsPerDay : dailyCaloriesForThisFood;
const displayCaloriesMin = this.showPerMeal ? dailyCaloriesMin / this.mealsPerDay : dailyCaloriesMin;
const displayCaloriesMax = this.showPerMeal ? dailyCaloriesMax / this.mealsPerDay : dailyCaloriesMax;
foodBreakdowns.push({
name: fs.name,
percentage: fs.percentage,
dailyGrams: dailyGramsForThisFood,
dailyGramsMin: dailyGramsMin,
dailyGramsMax: dailyGramsMax,
displayGrams: displayGrams,
displayGramsMin: displayGramsMin,
displayGramsMax: displayGramsMax,
dailyCups: dailyCupsForThisFood,
dailyCupsMin: dailyCupsMin,
dailyCupsMax: dailyCupsMax,
displayCups: displayCups,
displayCupsMin: displayCupsMin,
displayCupsMax: displayCupsMax,
calories: dailyCaloriesForThisFood,
displayCalories: displayCalories,
displayCaloriesMin: displayCaloriesMin,
displayCaloriesMax: displayCaloriesMax,
isLocked: fs.isLocked,
hasEnergyContent: true,
hasRange: hasRange,
foodSource: fs // Store reference for cups conversion
});
@ -1435,12 +1497,23 @@
if (unit === 'cups') {
// For cups, use the pre-calculated cups value if available
if (breakdown.displayCups !== null) {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
if (breakdown.hasRange && breakdown.displayCupsMin !== breakdown.displayCupsMax) {
valueContent = `${this.formatNumber(breakdown.displayCupsMin, decimals)}-${this.formatNumber(breakdown.displayCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
}
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
// For other units (g, kg, oz, lb)
if (breakdown.hasRange && breakdown.displayGramsMin !== breakdown.displayGramsMax) {
const minConverted = this.convertUnits(breakdown.displayGramsMin, unit);
const maxConverted = this.convertUnits(breakdown.displayGramsMax, unit);
valueContent = `${this.formatNumber(minConverted, decimals)}-${this.formatNumber(maxConverted, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
}
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
@ -1478,23 +1551,53 @@
if (validForCups) {
// Calculate total cups using pre-calculated values
let totalCups = 0;
let totalCupsMin = 0;
let totalCupsMax = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.displayCups !== null) {
totalCups += breakdown.displayCups;
if (breakdown.hasRange) {
totalCupsMin += breakdown.displayCupsMin || breakdown.displayCups;
totalCupsMax += breakdown.displayCupsMax || breakdown.displayCups;
} else {
totalCupsMin += breakdown.displayCups;
totalCupsMax += breakdown.displayCups;
}
}
});
console.log('Total cups display:', {
totalCups,
displayTotal,
foodBreakdowns: foodBreakdowns.map(b => ({ name: b.name, displayCups: b.displayCups }))
});
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
if (hasRange && totalCupsMin !== totalCupsMax) {
totalDisplayText = `${this.formatNumber(totalCupsMin, decimals)}-${this.formatNumber(totalCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
} else {
totalDisplayText = 'Mixed units - see breakdown';
}
} else {
convertedTotal = this.convertUnits(displayTotal, unit);
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
// Calculate totals for ranges
if (hasRange) {
let totalGramsMin = 0;
let totalGramsMax = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.hasEnergyContent) {
totalGramsMin += breakdown.displayGramsMin || breakdown.displayGrams;
totalGramsMax += breakdown.displayGramsMax || breakdown.displayGrams;
}
});
const convertedMin = this.convertUnits(totalGramsMin, unit);
const convertedMax = this.convertUnits(totalGramsMax, unit);
if (totalGramsMin !== totalGramsMax) {
totalDisplayText = `${this.formatNumber(convertedMin, decimals)}-${this.formatNumber(convertedMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
totalDisplayText = this.formatNumber(convertedMin, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
} else {
convertedTotal = this.convertUnits(displayTotal, unit);
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
}
dailyFoodValue.textContent = totalDisplayText;

View File

@ -47,7 +47,7 @@
}
.dog-calculator-container {
max-width: 600px;
max-width: 640px;
margin: 0 auto;
padding: 24px;
box-sizing: border-box;
@ -228,6 +228,7 @@
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
gap: 10px; /* Add gap between label and value */
}
.dog-calculator-result-item:last-child {
@ -247,6 +248,7 @@
padding: 4px 12px;
background: rgba(241, 154, 95, 0.15);
border-radius: 4px;
white-space: nowrap; /* Prevent text from wrapping to multiple lines */
}
.dog-calculator-collapsible {
@ -558,6 +560,24 @@
flex-direction: column;
align-items: flex-start;
}
/* Stack result items vertically on small screens */
.dog-calculator-result-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.dog-calculator-result-label {
margin-right: 0;
font-size: 0.9rem;
}
.dog-calculator-result-value {
font-size: 1rem;
align-self: stretch;
text-align: center;
}
}
/* Dark theme - manual override */
@ -2105,9 +2125,11 @@ const CALCULATOR_CONFIG = {
this.theme = this.options.theme;
this.scale = this.options.scale;
this.currentMER = 0;
this.currentMERMin = 0; // For range calculations
this.currentMERMax = 0; // For range calculations
this.isImperial = false;
this.theme = this.getThemeFromURL() || CALCULATOR_CONFIG.defaultTheme;
this.scale = this.getScaleFromURL() || CALCULATOR_CONFIG.defaultScale;
this.foodSources = [];
this.maxFoodSources = CALCULATOR_CONFIG.maxFoodSources;
this.mealsPerDay = 2;
@ -3372,6 +3394,25 @@ const CALCULATOR_CONFIG = {
return rer * factor;
}
// Get the range multipliers for each life stage
getLifeStageRange(factor) {
// Define ranges based on the reference image
const ranges = {
'3.0': { min: 3.0, max: 3.0 }, // Puppy 0-4 months (no range)
'2.0': { min: 2.0, max: 2.0 }, // Puppy 4m-adult OR Working light (no range for puppies)
'1.2': { min: 1.2, max: 1.4 }, // Adult inactive/obese
'1.6': { min: 1.4, max: 1.6 }, // Adult neutered/spayed
'1.8': { min: 1.6, max: 1.8 }, // Adult intact
'1.0': { min: 1.0, max: 1.0 }, // Weight loss (fixed)
'1.7': { min: 1.2, max: 1.8 }, // Weight gain (wide range)
'5.0': { min: 5.0, max: 5.0 }, // Working heavy (upper bound)
'1.1': { min: 1.1, max: 1.1 } // Senior (no range)
};
const key = factor.toFixed(1);
return ranges[key] || { min: factor, max: factor };
}
validateInput(value, min = 0, isInteger = false) {
const num = parseFloat(value);
if (isNaN(num) || num < min) return false;
@ -3503,10 +3544,21 @@ const CALCULATOR_CONFIG = {
const rer = this.calculateRER(weightKg);
const mer = this.calculateMER(rer, factor);
this.currentMER = mer;
// Calculate range for MER
const range = this.getLifeStageRange(factor);
this.currentMERMin = this.calculateMER(rer, range.min);
this.currentMERMax = this.calculateMER(rer, range.max);
this.currentMER = mer; // Keep middle/selected value for compatibility
rerValue.textContent = this.formatNumber(rer, 0) + ' cal/day';
merValue.textContent = this.formatNumber(mer, 0) + ' cal/day';
// Show MER as range if applicable
if (range.min !== range.max) {
merValue.textContent = this.formatNumber(this.currentMERMin, 0) + '-' +
this.formatNumber(this.currentMERMax, 0) + ' cal/day';
} else {
merValue.textContent = this.formatNumber(mer, 0) + ' cal/day';
}
calorieResults.style.display = 'block';
this.updateFoodCalculations();
@ -3541,6 +3593,9 @@ const CALCULATOR_CONFIG = {
updateFoodCalculations() {
if (this.currentMER === 0) return;
// Check if we have a range
const hasRange = this.currentMERMin !== this.currentMERMax;
const daysInput = this.container.querySelector('#days');
const unitSelect = this.container.querySelector('#unit');
const dailyFoodResults = this.container.querySelector('#dailyFoodResults');
@ -3613,43 +3668,70 @@ const CALCULATOR_CONFIG = {
if (energyPer100g && energyPer100g > 0.1 && fs.percentage > 0) {
const dailyCaloriesForThisFood = (this.currentMER * fs.percentage) / 100;
// Calculate range values if applicable
const dailyCaloriesMin = hasRange ? (this.currentMERMin * fs.percentage) / 100 : dailyCaloriesForThisFood;
const dailyCaloriesMax = hasRange ? (this.currentMERMax * fs.percentage) / 100 : dailyCaloriesForThisFood;
let dailyGramsForThisFood;
let dailyGramsMin, dailyGramsMax;
let dailyCupsForThisFood = null;
let dailyCupsMin, dailyCupsMax;
// For kcal/cup, calculate cups directly from calories
if (fs.energyUnit === 'kcalcup' && fs.energy) {
const caloriesPerCup = parseFloat(fs.energy);
dailyCupsForThisFood = dailyCaloriesForThisFood / caloriesPerCup;
dailyCupsMin = dailyCaloriesMin / caloriesPerCup;
dailyCupsMax = dailyCaloriesMax / caloriesPerCup;
// We still need grams for total calculation, use approximation
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
console.log('Cups calculation:', {
caloriesPerCup,
dailyCaloriesForThisFood,
dailyCupsForThisFood,
dailyGramsForThisFood
});
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
} else {
// For other units, calculate grams normally
dailyGramsForThisFood = (dailyCaloriesForThisFood / energyPer100g) * 100;
dailyGramsMin = (dailyCaloriesMin / energyPer100g) * 100;
dailyGramsMax = (dailyCaloriesMax / energyPer100g) * 100;
}
// Calculate per-meal amounts if needed
const displayGrams = this.showPerMeal ? dailyGramsForThisFood / this.mealsPerDay : dailyGramsForThisFood;
const displayGramsMin = this.showPerMeal ? dailyGramsMin / this.mealsPerDay : dailyGramsMin;
const displayGramsMax = this.showPerMeal ? dailyGramsMax / this.mealsPerDay : dailyGramsMax;
const displayCups = dailyCupsForThisFood !== null ?
(this.showPerMeal ? dailyCupsForThisFood / this.mealsPerDay : dailyCupsForThisFood) : null;
const displayCupsMin = dailyCupsMin !== undefined ?
(this.showPerMeal ? dailyCupsMin / this.mealsPerDay : dailyCupsMin) : null;
const displayCupsMax = dailyCupsMax !== undefined ?
(this.showPerMeal ? dailyCupsMax / this.mealsPerDay : dailyCupsMax) : null;
const displayCalories = this.showPerMeal ? dailyCaloriesForThisFood / this.mealsPerDay : dailyCaloriesForThisFood;
const displayCaloriesMin = this.showPerMeal ? dailyCaloriesMin / this.mealsPerDay : dailyCaloriesMin;
const displayCaloriesMax = this.showPerMeal ? dailyCaloriesMax / this.mealsPerDay : dailyCaloriesMax;
foodBreakdowns.push({
name: fs.name,
percentage: fs.percentage,
dailyGrams: dailyGramsForThisFood,
dailyGramsMin: dailyGramsMin,
dailyGramsMax: dailyGramsMax,
displayGrams: displayGrams,
displayGramsMin: displayGramsMin,
displayGramsMax: displayGramsMax,
dailyCups: dailyCupsForThisFood,
dailyCupsMin: dailyCupsMin,
dailyCupsMax: dailyCupsMax,
displayCups: displayCups,
displayCupsMin: displayCupsMin,
displayCupsMax: displayCupsMax,
calories: dailyCaloriesForThisFood,
displayCalories: displayCalories,
displayCaloriesMin: displayCaloriesMin,
displayCaloriesMax: displayCaloriesMax,
isLocked: fs.isLocked,
hasEnergyContent: true,
hasRange: hasRange,
foodSource: fs // Store reference for cups conversion
});
@ -3754,12 +3836,23 @@ const CALCULATOR_CONFIG = {
if (unit === 'cups') {
// For cups, use the pre-calculated cups value if available
if (breakdown.displayCups !== null) {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
if (breakdown.hasRange && breakdown.displayCupsMin !== breakdown.displayCupsMax) {
valueContent = `${this.formatNumber(breakdown.displayCupsMin, decimals)}-${this.formatNumber(breakdown.displayCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(breakdown.displayCups, decimals)} ${unitLabel}${frequencySuffix}`;
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Cups only available for foods with kcal/cup measurement">N/A</span>`;
}
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
// For other units (g, kg, oz, lb)
if (breakdown.hasRange && breakdown.displayGramsMin !== breakdown.displayGramsMax) {
const minConverted = this.convertUnits(breakdown.displayGramsMin, unit);
const maxConverted = this.convertUnits(breakdown.displayGramsMax, unit);
valueContent = `${this.formatNumber(minConverted, decimals)}-${this.formatNumber(maxConverted, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
valueContent = `${this.formatNumber(this.convertUnits(breakdown.displayGrams, unit), decimals)} ${unitLabel}${frequencySuffix}`;
}
}
} else {
valueContent = `<span class="dog-calculator-warning" title="Enter energy content to calculate amount">⚠️</span>`;
@ -3797,23 +3890,53 @@ const CALCULATOR_CONFIG = {
if (validForCups) {
// Calculate total cups using pre-calculated values
let totalCups = 0;
let totalCupsMin = 0;
let totalCupsMax = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.displayCups !== null) {
totalCups += breakdown.displayCups;
if (breakdown.hasRange) {
totalCupsMin += breakdown.displayCupsMin || breakdown.displayCups;
totalCupsMax += breakdown.displayCupsMax || breakdown.displayCups;
} else {
totalCupsMin += breakdown.displayCups;
totalCupsMax += breakdown.displayCups;
}
}
});
console.log('Total cups display:', {
totalCups,
displayTotal,
foodBreakdowns: foodBreakdowns.map(b => ({ name: b.name, displayCups: b.displayCups }))
});
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
if (hasRange && totalCupsMin !== totalCupsMax) {
totalDisplayText = `${this.formatNumber(totalCupsMin, decimals)}-${this.formatNumber(totalCupsMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
totalDisplayText = this.formatNumber(totalCups, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
} else {
totalDisplayText = 'Mixed units - see breakdown';
}
} else {
convertedTotal = this.convertUnits(displayTotal, unit);
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
// Calculate totals for ranges
if (hasRange) {
let totalGramsMin = 0;
let totalGramsMax = 0;
foodBreakdowns.forEach(breakdown => {
if (breakdown.percentage > 0 && breakdown.hasEnergyContent) {
totalGramsMin += breakdown.displayGramsMin || breakdown.displayGrams;
totalGramsMax += breakdown.displayGramsMax || breakdown.displayGrams;
}
});
const convertedMin = this.convertUnits(totalGramsMin, unit);
const convertedMax = this.convertUnits(totalGramsMax, unit);
if (totalGramsMin !== totalGramsMax) {
totalDisplayText = `${this.formatNumber(convertedMin, decimals)}-${this.formatNumber(convertedMax, decimals)} ${unitLabel}${frequencySuffix}`;
} else {
totalDisplayText = this.formatNumber(convertedMin, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
} else {
convertedTotal = this.convertUnits(displayTotal, unit);
totalDisplayText = this.formatNumber(convertedTotal, decimals) + ` ${unitLabel}${frequencySuffix}`;
}
}
dailyFoodValue.textContent = totalDisplayText;