Kaya transition v1
This commit is contained in:
parent
90657f9aa4
commit
7fd139b321
247
iframe.html
247
iframe.html
@ -200,6 +200,12 @@
|
|||||||
color: var(--text-label);
|
color: var(--text-label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kaya end-weight readonly field: compact, non-editable */
|
||||||
|
#kayaEndWeight {
|
||||||
|
width: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.dog-calculator-results {
|
.dog-calculator-results {
|
||||||
background: linear-gradient(135deg, rgba(241, 154, 95, 0.08) 0%, rgba(241, 154, 95, 0.04) 100%);
|
background: linear-gradient(135deg, rgba(241, 154, 95, 0.08) 0%, rgba(241, 154, 95, 0.04) 100%);
|
||||||
border: 1px solid rgba(241, 154, 95, 0.2);
|
border: 1px solid rgba(241, 154, 95, 0.2);
|
||||||
@ -1964,50 +1970,18 @@
|
|||||||
<div class="dog-calculator-container" id="dogCalculator">
|
<div class="dog-calculator-container" id="dogCalculator">
|
||||||
<div class="dog-calculator-section">
|
<div class="dog-calculator-section">
|
||||||
<div class="dog-calculator-section-header">
|
<div class="dog-calculator-section-header">
|
||||||
<h2>Dog's Characteristics</h2>
|
<h2>Kaya’s Transition</h2>
|
||||||
<div class="dog-calculator-unit-switch">
|
|
||||||
<span class="dog-calculator-unit-label active" id="metricLabel">Metric</span>
|
|
||||||
<label class="dog-calculator-switch">
|
|
||||||
<input type="checkbox" id="unitToggle">
|
|
||||||
<span class="dog-calculator-slider"></span>
|
|
||||||
</label>
|
|
||||||
<span class="dog-calculator-unit-label" id="imperialLabel">Imperial</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dog-calculator-form-group">
|
<div class="dog-calculator-form-group">
|
||||||
<label for="dogType">Dog Type / Activity Level:</label>
|
<label for="ageMonths">Kaya’s age (months):</label>
|
||||||
<select id="dogType" aria-describedby="dogTypeHelp">
|
<input type="number" id="ageMonths" min="2" max="12" step="0.1" placeholder="Enter age in months" aria-describedby="ageHelp">
|
||||||
<option value="">Select dog type...</option>
|
<div id="ageClampNote" class="dog-calculator-error dog-calculator-hidden">Age adjusted to the supported 2–12 month range.</div>
|
||||||
<option value="3.0">Puppy (0-4 months)</option>
|
|
||||||
<option value="2.0">Puppy (4 months - adult)</option>
|
|
||||||
<option value="1.2">Adult - inactive/obese</option>
|
|
||||||
<option value="1.6">Adult (neutered/spayed) - average activity</option>
|
|
||||||
<option value="1.8">Adult (intact) - average activity</option>
|
|
||||||
<option value="1.0">Adult - weight loss</option>
|
|
||||||
<option value="1.7">Adult - weight gain</option>
|
|
||||||
<option value="2.0">Working dog - light work</option>
|
|
||||||
<option value="3.0">Working dog - moderate work</option>
|
|
||||||
<option value="5.0">Working dog - heavy work</option>
|
|
||||||
<option value="1.1">Senior dog</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dog-calculator-form-group">
|
<div class="dog-calculator-form-group">
|
||||||
<label for="weight" id="weightLabel">Dog's Weight (kg):</label>
|
<label for="kayaEndWeight">Kaya’s end‑weight:</label>
|
||||||
<input type="number" id="weight" min="0.1" step="0.1" placeholder="Enter weight in kg" aria-describedby="weightHelp">
|
<input type="text" id="kayaEndWeight" value="30 kg" readonly>
|
||||||
<div id="weightError" class="dog-calculator-error dog-calculator-hidden">Please enter a valid weight (minimum 0.1 kg)</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dog-calculator-results" id="calorieResults" style="display: none;">
|
|
||||||
<div class="dog-calculator-result-item">
|
|
||||||
<span class="dog-calculator-result-label">Resting Energy Requirement (RER):</span>
|
|
||||||
<span class="dog-calculator-result-value" id="rerValue">- cal/day</span>
|
|
||||||
</div>
|
|
||||||
<div class="dog-calculator-result-item">
|
|
||||||
<span class="dog-calculator-result-label">Maintenance Energy Requirement (MER):</span>
|
|
||||||
<span class="dog-calculator-result-value" id="merValue">- cal/day</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -2102,45 +2076,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dog-calculator-action-buttons">
|
|
||||||
<button class="dog-calculator-btn dog-calculator-btn-share" id="shareBtn">
|
|
||||||
Share
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dog-calculator-footer">
|
<div class="dog-calculator-footer">
|
||||||
<a href="https://caninenutritionandwellness.com" target="_blank" rel="noopener noreferrer">
|
<a href="https://caninenutritionandwellness.com" target="_blank" rel="noopener noreferrer">
|
||||||
by caninenutritionandwellness.com
|
by caninenutritionandwellness.com
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Share Modal -->
|
|
||||||
<div id="shareModal" class="dog-calculator-modal" style="display: none;">
|
|
||||||
<div class="dog-calculator-modal-content">
|
|
||||||
<span class="dog-calculator-modal-close" id="shareModalClose">×</span>
|
|
||||||
<h3>Share Calculator</h3>
|
|
||||||
<div class="dog-calculator-share-buttons">
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-facebook plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-facebook" id="shareFacebook">
|
|
||||||
Facebook
|
|
||||||
</button>
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-twitter plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-twitter" id="shareTwitter">
|
|
||||||
Twitter
|
|
||||||
</button>
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-linkedin plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-linkedin" id="shareLinkedIn">
|
|
||||||
LinkedIn
|
|
||||||
</button>
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-email plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-email" id="shareEmail">
|
|
||||||
Email
|
|
||||||
</button>
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-copy plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-copy-link" id="shareCopy">
|
|
||||||
Copy Link
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dog-calculator-share-url">
|
|
||||||
<input type="text" id="shareUrl" readonly>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
@ -2173,6 +2113,9 @@ const CALCULATOR_CONFIG = {
|
|||||||
this.maxFoodSources = CALCULATOR_CONFIG.maxFoodSources;
|
this.maxFoodSources = CALCULATOR_CONFIG.maxFoodSources;
|
||||||
this.mealsPerDay = 2;
|
this.mealsPerDay = 2;
|
||||||
this.showPerMeal = false;
|
this.showPerMeal = false;
|
||||||
|
// Kayafied reference source tracking
|
||||||
|
this.kibbleRefId = null;
|
||||||
|
this.gcRefId = null;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2227,8 +2170,45 @@ const CALCULATOR_CONFIG = {
|
|||||||
|
|
||||||
// Food Source Management Methods
|
// Food Source Management Methods
|
||||||
initializeFoodSources() {
|
initializeFoodSources() {
|
||||||
this.addFoodSource();
|
// Seed three sources for Kaya's transition
|
||||||
|
const gc = {
|
||||||
|
id: this.generateFoodSourceId(),
|
||||||
|
name: 'Fred & Felia, gently cooked',
|
||||||
|
energy: '115',
|
||||||
|
energyUnit: 'kcal100g',
|
||||||
|
percentage: 5,
|
||||||
|
isLocked: false
|
||||||
|
};
|
||||||
|
this.foodSources.push(gc);
|
||||||
|
this.renderFoodSource(gc);
|
||||||
|
this.gcRefId = gc.id;
|
||||||
|
|
||||||
|
const kibble = {
|
||||||
|
id: this.generateFoodSourceId(),
|
||||||
|
name: 'Eukanuba, kibble',
|
||||||
|
energy: '372',
|
||||||
|
energyUnit: 'kcal100g',
|
||||||
|
percentage: 95,
|
||||||
|
isLocked: false
|
||||||
|
};
|
||||||
|
this.foodSources.push(kibble);
|
||||||
|
this.renderFoodSource(kibble);
|
||||||
|
this.kibbleRefId = kibble.id;
|
||||||
|
|
||||||
|
const treats = {
|
||||||
|
id: this.generateFoodSourceId(),
|
||||||
|
name: 'Treats',
|
||||||
|
energy: '',
|
||||||
|
energyUnit: 'kcal100g',
|
||||||
|
percentage: 0,
|
||||||
|
isLocked: false
|
||||||
|
};
|
||||||
|
this.foodSources.push(treats);
|
||||||
|
this.renderFoodSource(treats);
|
||||||
|
|
||||||
this.updateAddButton();
|
this.updateAddButton();
|
||||||
|
this.updateRemoveButtons();
|
||||||
|
this.refreshAllPercentageUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
addFoodSource() {
|
addFoodSource() {
|
||||||
@ -2263,6 +2243,9 @@ const CALCULATOR_CONFIG = {
|
|||||||
if (index === -1) return;
|
if (index === -1) return;
|
||||||
|
|
||||||
this.foodSources.splice(index, 1);
|
this.foodSources.splice(index, 1);
|
||||||
|
// If reference IDs were removed, clear them
|
||||||
|
if (this.kibbleRefId === id) this.kibbleRefId = null;
|
||||||
|
if (this.gcRefId === id) this.gcRefId = null;
|
||||||
|
|
||||||
// Remove the DOM element
|
// Remove the DOM element
|
||||||
const element = document.getElementById(`foodSource-${id}`);
|
const element = document.getElementById(`foodSource-${id}`);
|
||||||
@ -2276,6 +2259,7 @@ const CALCULATOR_CONFIG = {
|
|||||||
this.updateAddButton();
|
this.updateAddButton();
|
||||||
this.updateRemoveButtons();
|
this.updateRemoveButtons();
|
||||||
this.refreshAllPercentageUI();
|
this.refreshAllPercentageUI();
|
||||||
|
this.updateCalorieCalculations();
|
||||||
}
|
}
|
||||||
|
|
||||||
generateFoodSourceId() {
|
generateFoodSourceId() {
|
||||||
@ -2747,6 +2731,10 @@ const CALCULATOR_CONFIG = {
|
|||||||
if (energyInput) {
|
if (energyInput) {
|
||||||
energyInput.addEventListener('input', () => {
|
energyInput.addEventListener('input', () => {
|
||||||
this.updateFoodSourceData(id, 'energy', energyInput.value);
|
this.updateFoodSourceData(id, 'energy', energyInput.value);
|
||||||
|
// If kibble reference changed, recompute daily target
|
||||||
|
if (id === this.kibbleRefId) {
|
||||||
|
this.updateCalorieCalculations();
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-select cups when entering energy for kcal/cup
|
// Auto-select cups when entering energy for kcal/cup
|
||||||
const foodSource = this.foodSources.find(fs => fs.id === id);
|
const foodSource = this.foodSources.find(fs => fs.id === id);
|
||||||
@ -2784,6 +2772,9 @@ const CALCULATOR_CONFIG = {
|
|||||||
if (energyUnitSelect) {
|
if (energyUnitSelect) {
|
||||||
energyUnitSelect.addEventListener('change', () => {
|
energyUnitSelect.addEventListener('change', () => {
|
||||||
this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value);
|
this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value);
|
||||||
|
if (id === this.kibbleRefId) {
|
||||||
|
this.updateCalorieCalculations();
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-select the most appropriate unit based on energy unit
|
// Auto-select the most appropriate unit based on energy unit
|
||||||
const unitSelect = document.getElementById('unit');
|
const unitSelect = document.getElementById('unit');
|
||||||
@ -2961,6 +2952,7 @@ const CALCULATOR_CONFIG = {
|
|||||||
bindEvents() {
|
bindEvents() {
|
||||||
const weightInput = document.getElementById('weight');
|
const weightInput = document.getElementById('weight');
|
||||||
const dogTypeSelect = document.getElementById('dogType');
|
const dogTypeSelect = document.getElementById('dogType');
|
||||||
|
const ageInput = document.getElementById('ageMonths');
|
||||||
const daysInput = document.getElementById('days');
|
const daysInput = document.getElementById('days');
|
||||||
const unitSelect = document.getElementById('unit');
|
const unitSelect = document.getElementById('unit');
|
||||||
const unitToggle = document.getElementById('unitToggle');
|
const unitToggle = document.getElementById('unitToggle');
|
||||||
@ -2973,6 +2965,12 @@ const CALCULATOR_CONFIG = {
|
|||||||
|
|
||||||
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations());
|
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations());
|
||||||
|
|
||||||
|
// Kayafied: age input drives energy target
|
||||||
|
if (ageInput) {
|
||||||
|
ageInput.addEventListener('input', () => this.updateCalorieCalculations());
|
||||||
|
ageInput.addEventListener('blur', () => this.updateCalorieCalculations());
|
||||||
|
}
|
||||||
|
|
||||||
if (daysInput) {
|
if (daysInput) {
|
||||||
daysInput.addEventListener('input', () => {
|
daysInput.addEventListener('input', () => {
|
||||||
this.updateDayLabel();
|
this.updateDayLabel();
|
||||||
@ -3316,57 +3314,86 @@ const CALCULATOR_CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateCalorieCalculations() {
|
updateCalorieCalculations() {
|
||||||
const dogTypeSelect = document.getElementById('dogType');
|
// Kaya-specific: compute daily kcal target from age + kibble energy
|
||||||
const calorieResults = document.getElementById('calorieResults');
|
const ageInput = document.getElementById('ageMonths');
|
||||||
const rerValue = document.getElementById('rerValue');
|
const ageClampNote = document.getElementById('ageClampNote');
|
||||||
const merValue = document.getElementById('merValue');
|
|
||||||
|
|
||||||
if (!dogTypeSelect || !calorieResults || !rerValue || !merValue) {
|
// Default: hide clamp note
|
||||||
|
if (ageClampNote) ageClampNote.classList.add('dog-calculator-hidden');
|
||||||
|
|
||||||
|
if (!ageInput || !ageInput.value) {
|
||||||
|
this.currentMER = 0;
|
||||||
|
this.currentMERMin = 0;
|
||||||
|
this.currentMERMax = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const weightKg = this.getWeightInKg();
|
let age = parseFloat(ageInput.value);
|
||||||
const dogTypeFactor = dogTypeSelect.value;
|
if (isNaN(age)) {
|
||||||
|
this.currentMER = 0;
|
||||||
this.showError('weightError', false);
|
this.currentMERMin = 0;
|
||||||
|
this.currentMERMax = 0;
|
||||||
if (!weightKg || weightKg < 0.1) {
|
|
||||||
const weightInput = document.getElementById('weight');
|
|
||||||
if (weightInput && weightInput.value) this.showError('weightError', true);
|
|
||||||
calorieResults.style.display = 'none';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dogTypeFactor) {
|
// Clamp age to [2, 12]
|
||||||
calorieResults.style.display = 'none';
|
if (age < 2) { age = 2; if (ageClampNote) ageClampNote.classList.remove('dog-calculator-hidden'); }
|
||||||
|
if (age > 12) { age = 12; if (ageClampNote) ageClampNote.classList.remove('dog-calculator-hidden'); }
|
||||||
|
|
||||||
|
// Calculate interpolated kibble grams/day for 30 kg at this age
|
||||||
|
const kibbleGrams = this.getKayaKibbleGramsForAge(age);
|
||||||
|
|
||||||
|
// Get kibble reference energy density in kcal/100g
|
||||||
|
let kibbleEnergyPer100g = null;
|
||||||
|
if (this.kibbleRefId) {
|
||||||
|
const kibbleRef = this.foodSources.find(fs => fs.id === this.kibbleRefId);
|
||||||
|
kibbleEnergyPer100g = kibbleRef ? this.getFoodSourceEnergyPer100g(kibbleRef) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kibbleEnergyPer100g || kibbleEnergyPer100g <= 0) {
|
||||||
|
// Cannot compute without kibble energy density
|
||||||
|
this.currentMER = 0;
|
||||||
|
this.currentMERMin = 0;
|
||||||
|
this.currentMERMax = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const factor = parseFloat(dogTypeFactor);
|
const kcalPerGram = kibbleEnergyPer100g / 100.0;
|
||||||
|
const dailyKcal = kibbleGrams * kcalPerGram;
|
||||||
const rer = this.calculateRER(weightKg);
|
this.currentMER = dailyKcal;
|
||||||
const mer = this.calculateMER(rer, factor);
|
this.currentMERMin = dailyKcal;
|
||||||
|
this.currentMERMax = dailyKcal;
|
||||||
// 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';
|
|
||||||
|
|
||||||
// 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();
|
this.updateFoodCalculations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kaya: monthly table with interpolation between months
|
||||||
|
getKayaKibbleGramsForAge(ageMonths) {
|
||||||
|
// Precomputed monthly values for 30 kg (g/day)
|
||||||
|
const table = {
|
||||||
|
2: 250,
|
||||||
|
3: 330,
|
||||||
|
4: 365,
|
||||||
|
5: 382,
|
||||||
|
6: 400,
|
||||||
|
7: 405,
|
||||||
|
8: 410,
|
||||||
|
9: 410,
|
||||||
|
10: 410,
|
||||||
|
11: 408,
|
||||||
|
12: 405
|
||||||
|
};
|
||||||
|
// Exact integer month
|
||||||
|
const lower = Math.floor(ageMonths);
|
||||||
|
const upper = Math.ceil(ageMonths);
|
||||||
|
if (lower === upper) return table[lower] || 0;
|
||||||
|
// Linear interpolation between bounds
|
||||||
|
const lowerVal = table[lower] || 0;
|
||||||
|
const upperVal = table[upper] || 0;
|
||||||
|
const t = (ageMonths - lower) / (upper - lower);
|
||||||
|
return lowerVal + (upperVal - lowerVal) * t;
|
||||||
|
}
|
||||||
|
|
||||||
updateCupsButtonState() {
|
updateCupsButtonState() {
|
||||||
const cupsButton = document.getElementById('cupsButton');
|
const cupsButton = document.getElementById('cupsButton');
|
||||||
if (!cupsButton) return;
|
if (!cupsButton) return;
|
||||||
|
|||||||
@ -190,6 +190,12 @@
|
|||||||
color: var(--text-label);
|
color: var(--text-label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kaya end-weight readonly field: compact, non-editable */
|
||||||
|
#kayaEndWeight {
|
||||||
|
width: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.dog-calculator-results {
|
.dog-calculator-results {
|
||||||
background: linear-gradient(135deg, rgba(241, 154, 95, 0.08) 0%, rgba(241, 154, 95, 0.04) 100%);
|
background: linear-gradient(135deg, rgba(241, 154, 95, 0.08) 0%, rgba(241, 154, 95, 0.04) 100%);
|
||||||
border: 1px solid rgba(241, 154, 95, 0.2);
|
border: 1px solid rgba(241, 154, 95, 0.2);
|
||||||
|
|||||||
@ -1,50 +1,18 @@
|
|||||||
<div class="dog-calculator-container" id="dogCalculator">
|
<div class="dog-calculator-container" id="dogCalculator">
|
||||||
<div class="dog-calculator-section">
|
<div class="dog-calculator-section">
|
||||||
<div class="dog-calculator-section-header">
|
<div class="dog-calculator-section-header">
|
||||||
<h2>Dog's Characteristics</h2>
|
<h2>Kaya’s Transition</h2>
|
||||||
<div class="dog-calculator-unit-switch">
|
|
||||||
<span class="dog-calculator-unit-label active" id="metricLabel">Metric</span>
|
|
||||||
<label class="dog-calculator-switch">
|
|
||||||
<input type="checkbox" id="unitToggle">
|
|
||||||
<span class="dog-calculator-slider"></span>
|
|
||||||
</label>
|
|
||||||
<span class="dog-calculator-unit-label" id="imperialLabel">Imperial</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dog-calculator-form-group">
|
<div class="dog-calculator-form-group">
|
||||||
<label for="dogType">Dog Type / Activity Level:</label>
|
<label for="ageMonths">Kaya’s age (months):</label>
|
||||||
<select id="dogType" aria-describedby="dogTypeHelp">
|
<input type="number" id="ageMonths" min="2" max="12" step="0.1" placeholder="Enter age in months" aria-describedby="ageHelp">
|
||||||
<option value="">Select dog type...</option>
|
<div id="ageClampNote" class="dog-calculator-error dog-calculator-hidden">Age adjusted to the supported 2–12 month range.</div>
|
||||||
<option value="3.0">Puppy (0-4 months)</option>
|
|
||||||
<option value="2.0">Puppy (4 months - adult)</option>
|
|
||||||
<option value="1.2">Adult - inactive/obese</option>
|
|
||||||
<option value="1.6">Adult (neutered/spayed) - average activity</option>
|
|
||||||
<option value="1.8">Adult (intact) - average activity</option>
|
|
||||||
<option value="1.0">Adult - weight loss</option>
|
|
||||||
<option value="1.7">Adult - weight gain</option>
|
|
||||||
<option value="2.0">Working dog - light work</option>
|
|
||||||
<option value="3.0">Working dog - moderate work</option>
|
|
||||||
<option value="5.0">Working dog - heavy work</option>
|
|
||||||
<option value="1.1">Senior dog</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dog-calculator-form-group">
|
<div class="dog-calculator-form-group">
|
||||||
<label for="weight" id="weightLabel">Dog's Weight (kg):</label>
|
<label for="kayaEndWeight">Kaya’s end‑weight:</label>
|
||||||
<input type="number" id="weight" min="0.1" step="0.1" placeholder="Enter weight in kg" aria-describedby="weightHelp">
|
<input type="text" id="kayaEndWeight" value="30 kg" readonly>
|
||||||
<div id="weightError" class="dog-calculator-error dog-calculator-hidden">Please enter a valid weight (minimum 0.1 kg)</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dog-calculator-results" id="calorieResults" style="display: none;">
|
|
||||||
<div class="dog-calculator-result-item">
|
|
||||||
<span class="dog-calculator-result-label">Resting Energy Requirement (RER):</span>
|
|
||||||
<span class="dog-calculator-result-value" id="rerValue">- cal/day</span>
|
|
||||||
</div>
|
|
||||||
<div class="dog-calculator-result-item">
|
|
||||||
<span class="dog-calculator-result-label">Maintenance Energy Requirement (MER):</span>
|
|
||||||
<span class="dog-calculator-result-value" id="merValue">- cal/day</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -139,44 +107,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dog-calculator-action-buttons">
|
|
||||||
<button class="dog-calculator-btn dog-calculator-btn-share" id="shareBtn">
|
|
||||||
Share
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dog-calculator-footer">
|
<div class="dog-calculator-footer">
|
||||||
<a href="https://caninenutritionandwellness.com" target="_blank" rel="noopener noreferrer">
|
<a href="https://caninenutritionandwellness.com" target="_blank" rel="noopener noreferrer">
|
||||||
by caninenutritionandwellness.com
|
by caninenutritionandwellness.com
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Share Modal -->
|
|
||||||
<div id="shareModal" class="dog-calculator-modal" style="display: none;">
|
|
||||||
<div class="dog-calculator-modal-content">
|
|
||||||
<span class="dog-calculator-modal-close" id="shareModalClose">×</span>
|
|
||||||
<h3>Share Calculator</h3>
|
|
||||||
<div class="dog-calculator-share-buttons">
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-facebook plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-facebook" id="shareFacebook">
|
|
||||||
Facebook
|
|
||||||
</button>
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-twitter plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-twitter" id="shareTwitter">
|
|
||||||
Twitter
|
|
||||||
</button>
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-linkedin plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-linkedin" id="shareLinkedIn">
|
|
||||||
LinkedIn
|
|
||||||
</button>
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-email plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-email" id="shareEmail">
|
|
||||||
Email
|
|
||||||
</button>
|
|
||||||
<button class="dog-calculator-share-btn dog-calculator-share-copy plausible-event-name=Calculator+Usage plausible-event-action=calculator-share-copy-link" id="shareCopy">
|
|
||||||
Copy Link
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dog-calculator-share-url">
|
|
||||||
<input type="text" id="shareUrl" readonly>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,6 +15,9 @@
|
|||||||
this.maxFoodSources = CALCULATOR_CONFIG.maxFoodSources;
|
this.maxFoodSources = CALCULATOR_CONFIG.maxFoodSources;
|
||||||
this.mealsPerDay = 2;
|
this.mealsPerDay = 2;
|
||||||
this.showPerMeal = false;
|
this.showPerMeal = false;
|
||||||
|
// Kayafied reference source tracking
|
||||||
|
this.kibbleRefId = null;
|
||||||
|
this.gcRefId = null;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,8 +72,45 @@
|
|||||||
|
|
||||||
// Food Source Management Methods
|
// Food Source Management Methods
|
||||||
initializeFoodSources() {
|
initializeFoodSources() {
|
||||||
this.addFoodSource();
|
// Seed three sources for Kaya's transition
|
||||||
|
const gc = {
|
||||||
|
id: this.generateFoodSourceId(),
|
||||||
|
name: 'Fred & Felia, gently cooked',
|
||||||
|
energy: '115',
|
||||||
|
energyUnit: 'kcal100g',
|
||||||
|
percentage: 5,
|
||||||
|
isLocked: false
|
||||||
|
};
|
||||||
|
this.foodSources.push(gc);
|
||||||
|
this.renderFoodSource(gc);
|
||||||
|
this.gcRefId = gc.id;
|
||||||
|
|
||||||
|
const kibble = {
|
||||||
|
id: this.generateFoodSourceId(),
|
||||||
|
name: 'Eukanuba, kibble',
|
||||||
|
energy: '372',
|
||||||
|
energyUnit: 'kcal100g',
|
||||||
|
percentage: 95,
|
||||||
|
isLocked: false
|
||||||
|
};
|
||||||
|
this.foodSources.push(kibble);
|
||||||
|
this.renderFoodSource(kibble);
|
||||||
|
this.kibbleRefId = kibble.id;
|
||||||
|
|
||||||
|
const treats = {
|
||||||
|
id: this.generateFoodSourceId(),
|
||||||
|
name: 'Treats',
|
||||||
|
energy: '',
|
||||||
|
energyUnit: 'kcal100g',
|
||||||
|
percentage: 0,
|
||||||
|
isLocked: false
|
||||||
|
};
|
||||||
|
this.foodSources.push(treats);
|
||||||
|
this.renderFoodSource(treats);
|
||||||
|
|
||||||
this.updateAddButton();
|
this.updateAddButton();
|
||||||
|
this.updateRemoveButtons();
|
||||||
|
this.refreshAllPercentageUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
addFoodSource() {
|
addFoodSource() {
|
||||||
@ -105,6 +145,9 @@
|
|||||||
if (index === -1) return;
|
if (index === -1) return;
|
||||||
|
|
||||||
this.foodSources.splice(index, 1);
|
this.foodSources.splice(index, 1);
|
||||||
|
// If reference IDs were removed, clear them
|
||||||
|
if (this.kibbleRefId === id) this.kibbleRefId = null;
|
||||||
|
if (this.gcRefId === id) this.gcRefId = null;
|
||||||
|
|
||||||
// Remove the DOM element
|
// Remove the DOM element
|
||||||
const element = document.getElementById(`foodSource-${id}`);
|
const element = document.getElementById(`foodSource-${id}`);
|
||||||
@ -118,6 +161,7 @@
|
|||||||
this.updateAddButton();
|
this.updateAddButton();
|
||||||
this.updateRemoveButtons();
|
this.updateRemoveButtons();
|
||||||
this.refreshAllPercentageUI();
|
this.refreshAllPercentageUI();
|
||||||
|
this.updateCalorieCalculations();
|
||||||
}
|
}
|
||||||
|
|
||||||
generateFoodSourceId() {
|
generateFoodSourceId() {
|
||||||
@ -589,6 +633,10 @@
|
|||||||
if (energyInput) {
|
if (energyInput) {
|
||||||
energyInput.addEventListener('input', () => {
|
energyInput.addEventListener('input', () => {
|
||||||
this.updateFoodSourceData(id, 'energy', energyInput.value);
|
this.updateFoodSourceData(id, 'energy', energyInput.value);
|
||||||
|
// If kibble reference changed, recompute daily target
|
||||||
|
if (id === this.kibbleRefId) {
|
||||||
|
this.updateCalorieCalculations();
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-select cups when entering energy for kcal/cup
|
// Auto-select cups when entering energy for kcal/cup
|
||||||
const foodSource = this.foodSources.find(fs => fs.id === id);
|
const foodSource = this.foodSources.find(fs => fs.id === id);
|
||||||
@ -626,6 +674,9 @@
|
|||||||
if (energyUnitSelect) {
|
if (energyUnitSelect) {
|
||||||
energyUnitSelect.addEventListener('change', () => {
|
energyUnitSelect.addEventListener('change', () => {
|
||||||
this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value);
|
this.updateFoodSourceData(id, 'energyUnit', energyUnitSelect.value);
|
||||||
|
if (id === this.kibbleRefId) {
|
||||||
|
this.updateCalorieCalculations();
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-select the most appropriate unit based on energy unit
|
// Auto-select the most appropriate unit based on energy unit
|
||||||
const unitSelect = document.getElementById('unit');
|
const unitSelect = document.getElementById('unit');
|
||||||
@ -803,6 +854,7 @@
|
|||||||
bindEvents() {
|
bindEvents() {
|
||||||
const weightInput = document.getElementById('weight');
|
const weightInput = document.getElementById('weight');
|
||||||
const dogTypeSelect = document.getElementById('dogType');
|
const dogTypeSelect = document.getElementById('dogType');
|
||||||
|
const ageInput = document.getElementById('ageMonths');
|
||||||
const daysInput = document.getElementById('days');
|
const daysInput = document.getElementById('days');
|
||||||
const unitSelect = document.getElementById('unit');
|
const unitSelect = document.getElementById('unit');
|
||||||
const unitToggle = document.getElementById('unitToggle');
|
const unitToggle = document.getElementById('unitToggle');
|
||||||
@ -815,6 +867,12 @@
|
|||||||
|
|
||||||
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations());
|
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations());
|
||||||
|
|
||||||
|
// Kayafied: age input drives energy target
|
||||||
|
if (ageInput) {
|
||||||
|
ageInput.addEventListener('input', () => this.updateCalorieCalculations());
|
||||||
|
ageInput.addEventListener('blur', () => this.updateCalorieCalculations());
|
||||||
|
}
|
||||||
|
|
||||||
if (daysInput) {
|
if (daysInput) {
|
||||||
daysInput.addEventListener('input', () => {
|
daysInput.addEventListener('input', () => {
|
||||||
this.updateDayLabel();
|
this.updateDayLabel();
|
||||||
@ -1158,57 +1216,86 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateCalorieCalculations() {
|
updateCalorieCalculations() {
|
||||||
const dogTypeSelect = document.getElementById('dogType');
|
// Kaya-specific: compute daily kcal target from age + kibble energy
|
||||||
const calorieResults = document.getElementById('calorieResults');
|
const ageInput = document.getElementById('ageMonths');
|
||||||
const rerValue = document.getElementById('rerValue');
|
const ageClampNote = document.getElementById('ageClampNote');
|
||||||
const merValue = document.getElementById('merValue');
|
|
||||||
|
|
||||||
if (!dogTypeSelect || !calorieResults || !rerValue || !merValue) {
|
// Default: hide clamp note
|
||||||
|
if (ageClampNote) ageClampNote.classList.add('dog-calculator-hidden');
|
||||||
|
|
||||||
|
if (!ageInput || !ageInput.value) {
|
||||||
|
this.currentMER = 0;
|
||||||
|
this.currentMERMin = 0;
|
||||||
|
this.currentMERMax = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const weightKg = this.getWeightInKg();
|
let age = parseFloat(ageInput.value);
|
||||||
const dogTypeFactor = dogTypeSelect.value;
|
if (isNaN(age)) {
|
||||||
|
this.currentMER = 0;
|
||||||
this.showError('weightError', false);
|
this.currentMERMin = 0;
|
||||||
|
this.currentMERMax = 0;
|
||||||
if (!weightKg || weightKg < 0.1) {
|
|
||||||
const weightInput = document.getElementById('weight');
|
|
||||||
if (weightInput && weightInput.value) this.showError('weightError', true);
|
|
||||||
calorieResults.style.display = 'none';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dogTypeFactor) {
|
// Clamp age to [2, 12]
|
||||||
calorieResults.style.display = 'none';
|
if (age < 2) { age = 2; if (ageClampNote) ageClampNote.classList.remove('dog-calculator-hidden'); }
|
||||||
|
if (age > 12) { age = 12; if (ageClampNote) ageClampNote.classList.remove('dog-calculator-hidden'); }
|
||||||
|
|
||||||
|
// Calculate interpolated kibble grams/day for 30 kg at this age
|
||||||
|
const kibbleGrams = this.getKayaKibbleGramsForAge(age);
|
||||||
|
|
||||||
|
// Get kibble reference energy density in kcal/100g
|
||||||
|
let kibbleEnergyPer100g = null;
|
||||||
|
if (this.kibbleRefId) {
|
||||||
|
const kibbleRef = this.foodSources.find(fs => fs.id === this.kibbleRefId);
|
||||||
|
kibbleEnergyPer100g = kibbleRef ? this.getFoodSourceEnergyPer100g(kibbleRef) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kibbleEnergyPer100g || kibbleEnergyPer100g <= 0) {
|
||||||
|
// Cannot compute without kibble energy density
|
||||||
|
this.currentMER = 0;
|
||||||
|
this.currentMERMin = 0;
|
||||||
|
this.currentMERMax = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const factor = parseFloat(dogTypeFactor);
|
const kcalPerGram = kibbleEnergyPer100g / 100.0;
|
||||||
|
const dailyKcal = kibbleGrams * kcalPerGram;
|
||||||
const rer = this.calculateRER(weightKg);
|
this.currentMER = dailyKcal;
|
||||||
const mer = this.calculateMER(rer, factor);
|
this.currentMERMin = dailyKcal;
|
||||||
|
this.currentMERMax = dailyKcal;
|
||||||
// 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';
|
|
||||||
|
|
||||||
// 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();
|
this.updateFoodCalculations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kaya: monthly table with interpolation between months
|
||||||
|
getKayaKibbleGramsForAge(ageMonths) {
|
||||||
|
// Precomputed monthly values for 30 kg (g/day)
|
||||||
|
const table = {
|
||||||
|
2: 250,
|
||||||
|
3: 330,
|
||||||
|
4: 365,
|
||||||
|
5: 382,
|
||||||
|
6: 400,
|
||||||
|
7: 405,
|
||||||
|
8: 410,
|
||||||
|
9: 410,
|
||||||
|
10: 410,
|
||||||
|
11: 408,
|
||||||
|
12: 405
|
||||||
|
};
|
||||||
|
// Exact integer month
|
||||||
|
const lower = Math.floor(ageMonths);
|
||||||
|
const upper = Math.ceil(ageMonths);
|
||||||
|
if (lower === upper) return table[lower] || 0;
|
||||||
|
// Linear interpolation between bounds
|
||||||
|
const lowerVal = table[lower] || 0;
|
||||||
|
const upperVal = table[upper] || 0;
|
||||||
|
const t = (ageMonths - lower) / (upper - lower);
|
||||||
|
return lowerVal + (upperVal - lowerVal) * t;
|
||||||
|
}
|
||||||
|
|
||||||
updateCupsButtonState() {
|
updateCupsButtonState() {
|
||||||
const cupsButton = document.getElementById('cupsButton');
|
const cupsButton = document.getElementById('cupsButton');
|
||||||
if (!cupsButton) return;
|
if (!cupsButton) return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user